From a437643a1494e59d07ca27b536a545c7dbf36b4b Mon Sep 17 00:00:00 2001 From: Andy Mantell <134642+andymantell@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:58:31 +0000 Subject: [PATCH 1/4] Initial work receiving cookie preferences from FTS --- .../CookieAcceptanceMiddleware.cs | 27 ++++++ .../CookiePreferencesService.cs | 95 +++++++++++++++++++ .../Pages/Cookies.cshtml | 6 +- .../Pages/Cookies.cshtml.cs | 47 +++------ .../Pages/Shared/_CookieBanner.cshtml | 20 +++- .../Pages/Shared/_Layout.cshtml | 4 +- Frontend/CO.CDP.OrganisationApp/Program.cs | 4 + 7 files changed, 161 insertions(+), 42 deletions(-) create mode 100644 Frontend/CO.CDP.OrganisationApp/CookieAcceptanceMiddleware.cs create mode 100644 Frontend/CO.CDP.OrganisationApp/CookiePreferencesService.cs diff --git a/Frontend/CO.CDP.OrganisationApp/CookieAcceptanceMiddleware.cs b/Frontend/CO.CDP.OrganisationApp/CookieAcceptanceMiddleware.cs new file mode 100644 index 000000000..e873e2238 --- /dev/null +++ b/Frontend/CO.CDP.OrganisationApp/CookieAcceptanceMiddleware.cs @@ -0,0 +1,27 @@ +namespace CO.CDP.OrganisationApp; + +public class CookieAcceptanceMiddleware(ICookiePreferencesService cookiePreferencesService) : IMiddleware +{ + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + if (context.Request.Query.TryGetValue(CookieSettings.FtsHandoverParameter, out var cookiesAcceptedValue)) + { + string cookiesAccepted = cookiesAcceptedValue.ToString().ToLower(); + + if (cookiesAccepted == "true") + { + cookiePreferencesService.Accept(); + } + else if (cookiesAccepted == "false") + { + cookiePreferencesService.Reject(); + } + else if (cookiesAccepted == "unknown") + { + cookiePreferencesService.Reset(); + } + } + + await next(context); + } +} diff --git a/Frontend/CO.CDP.OrganisationApp/CookiePreferencesService.cs b/Frontend/CO.CDP.OrganisationApp/CookiePreferencesService.cs new file mode 100644 index 000000000..b5726d0a7 --- /dev/null +++ b/Frontend/CO.CDP.OrganisationApp/CookiePreferencesService.cs @@ -0,0 +1,95 @@ +namespace CO.CDP.OrganisationApp; + +public interface ICookiePreferencesService +{ + bool IsAccepted(); + bool IsRejected(); + void Accept(); + void Reject(); + void Reset(); + void SetCookie(CookieAcceptanceValues value); + bool ValueIs(CookieAcceptanceValues value); + bool IsUnknown(); +} + +public class CookiePreferencesService : ICookiePreferencesService +{ + private readonly HttpContext _context; + + private CookieAcceptanceValues pendingAcceptanceValue; + + public CookiePreferencesService(IHttpContextAccessor httpContextAccessor) + { + _context = httpContextAccessor.HttpContext + ?? throw new InvalidOperationException("No active HTTP context."); + } + + public void Accept() + { + SetCookie(CookieAcceptanceValues.Accept); + } + + public void Reject() + { + SetCookie(CookieAcceptanceValues.Reject); + } + + public void Reset() + { + _context.Response.Cookies.Delete(CookieSettings.CookieName); + pendingAcceptanceValue = CookieAcceptanceValues.Unknown; + } + + public void SetCookie(CookieAcceptanceValues value) + { + _context.Response.Cookies.Append(CookieSettings.CookieName, value.ToString(), new CookieOptions + { + Expires = DateTimeOffset.UtcNow.AddDays(365), + IsEssential = true, + HttpOnly = true, + Secure = _context.Request.IsHttps + }); + pendingAcceptanceValue = value; + } + + public bool ValueIs(CookieAcceptanceValues value) + { + if(pendingAcceptanceValue == value) + { + return true; + } + + return _context.Request.Cookies.ContainsKey(CookieSettings.CookieName) + && _context.Request.Cookies[CookieSettings.CookieName] == value.ToString(); + } + + public bool IsAccepted() + { + return ValueIs(CookieAcceptanceValues.Accept); + } + + public bool IsRejected() + { + return ValueIs(CookieAcceptanceValues.Reject); + } + + public bool IsUnknown() + { + return ValueIs(CookieAcceptanceValues.Unknown); + } +} +public enum CookieAcceptanceValues +{ + Unknown, + Accept, + Reject +} + +public static class CookieSettings +{ + public const string CookieAcceptanceFieldName = "CookieAcceptance"; + public const string CookieSettingsPageReturnUrlFieldName = "ReturnUrl"; + public const string CookieBannerInteractionQueryString = "cookieBannerInteraction"; + public const string CookieName = "SIRSI_COOKIES_PREFERENCES_SET"; + public const string FtsHandoverParameter = "cookies_accepted"; +} \ No newline at end of file diff --git a/Frontend/CO.CDP.OrganisationApp/Pages/Cookies.cshtml b/Frontend/CO.CDP.OrganisationApp/Pages/Cookies.cshtml index fd1a24641..f6630a0b2 100644 --- a/Frontend/CO.CDP.OrganisationApp/Pages/Cookies.cshtml +++ b/Frontend/CO.CDP.OrganisationApp/Pages/Cookies.cshtml @@ -4,6 +4,8 @@ @using CO.CDP.Localization @using Microsoft.AspNetCore.Mvc.Localization +@inject ICookiePreferencesService cookiePreferencesService; + @{ ViewData["Title"] = StaticTextResource.Supplementary_Cookies_Title; var hasError = ((TagBuilder)Html.ValidationMessageFor(m => m.CookieAcceptance)).HasInnerHtml; @@ -45,13 +47,13 @@
- +
- + diff --git a/Frontend/CO.CDP.OrganisationApp/Pages/Cookies.cshtml.cs b/Frontend/CO.CDP.OrganisationApp/Pages/Cookies.cshtml.cs index 0c8d8ede7..3e5520105 100644 --- a/Frontend/CO.CDP.OrganisationApp/Pages/Cookies.cshtml.cs +++ b/Frontend/CO.CDP.OrganisationApp/Pages/Cookies.cshtml.cs @@ -9,8 +9,8 @@ namespace CO.CDP.OrganisationApp.Pages; [AuthenticatedSessionNotRequired] public class CookiesModel( - IWebHostEnvironment env, - ITempDataService tempDataService) : PageModel + ITempDataService tempDataService, + ICookiePreferencesService cookiePreferencesService) : PageModel { [BindProperty] [Required(ErrorMessage="Choose whether you accept cookies that measure website use")] @@ -38,45 +38,26 @@ public IActionResult OnPost() return Page(); } - Response.Cookies.Append(CookieSettings.CookieName, ((int)CookieAcceptance).ToString(), new CookieOptions{ - Expires = DateTimeOffset.UtcNow.AddDays(365), - IsEssential = true, - HttpOnly = false, - Secure = !env.IsDevelopment() - }); + switch(CookieAcceptance) + { + case CookieAcceptanceValues.Accept: + cookiePreferencesService.Accept(); + break; + + case CookieAcceptanceValues.Reject: + cookiePreferencesService.Reject(); + break; + } if (!string.IsNullOrEmpty(ReturnUrl) && Url.IsLocalUrl(ReturnUrl)) { - return LocalRedirect(QueryHelpers.AddQueryString(ReturnUrl, CookieSettings.CookiesAcceptedQueryString, "true")); + return LocalRedirect(QueryHelpers.AddQueryString(ReturnUrl, CookieSettings.CookieBannerInteractionQueryString, "true")); } + tempDataService.Put(FlashMessageTypes.Success, new FlashMessage( "You’ve set your cookie preferences." )); return RedirectToPage("/Cookies"); } - - public bool RadioIsChecked(CookieAcceptanceValues value) - { - return Request.Cookies.ContainsKey(CookieSettings.CookieName) && Request.Cookies[CookieSettings.CookieName] == ((int)value).ToString(); - } -} - -public enum CookieAcceptanceValues -{ - Accept=1, - Reject=2 -} - -public static class CookieSettings -{ - public const string CookieAcceptanceFieldName = "CookieAcceptance"; - public const string CookieSettingsPageReturnUrlFieldName = "ReturnUrl"; - public const string CookiesAcceptedQueryString = "cookiesAccepted"; - - // Cookie name in FTS is FT_COOKIES_PREFERENCES_SET - // Cookie values have been configured to match (See the 1 and 2 in the CookieAcceptanceValues enum). - // So if we're sharing a domain in production,we could switch to using the same cookie name and remove the frontend page - // (But keep the post handler for setting it - otherwise our cookie banner would need to post across to their subdomain) - public const string CookieName = "SIRSI_COOKIES_PREFERENCES_SET"; } \ No newline at end of file diff --git a/Frontend/CO.CDP.OrganisationApp/Pages/Shared/_CookieBanner.cshtml b/Frontend/CO.CDP.OrganisationApp/Pages/Shared/_CookieBanner.cshtml index 2ed94f1e9..ed34a74d8 100644 --- a/Frontend/CO.CDP.OrganisationApp/Pages/Shared/_CookieBanner.cshtml +++ b/Frontend/CO.CDP.OrganisationApp/Pages/Shared/_CookieBanner.cshtml @@ -1,8 +1,18 @@ +@using Microsoft.AspNetCore.Http.Extensions; +@using Microsoft.AspNetCore.Http; +@using Microsoft.AspNetCore.WebUtilities; +@inject ICookiePreferencesService cookiePreferencesService; + @{ - string currentUrl = Context.Request.PathBase + Context.Request.Path + Context.Request.QueryString; + var queryDict = QueryHelpers.ParseQuery(Context.Request.QueryString.Value); + queryDict.Remove(CookieSettings.FtsHandoverParameter); + var queryBuilder = new QueryBuilder(queryDict); + QueryString updatedQueryString = queryBuilder.ToQueryString(); + string currentUrl = Context.Request.PathBase + Context.Request.Path + updatedQueryString; } -@if (!Context.Request.Cookies.ContainsKey(CookieSettings.CookieName) && !Context.Request.Path.StartsWithSegments("/cookies")) { +@if (cookiePreferencesService.IsUnknown() && !Context.Request.Query.ContainsKey(CookieSettings.CookieBannerInteractionQueryString) && !Context.Request.Path.StartsWithSegments("/cookies")) +{