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"))
+{
@@ -32,9 +42,9 @@
}
-@if (Context.Request.Cookies.ContainsKey(CookieSettings.CookieName) && Context.Request.Query.ContainsKey(CookieSettings.CookiesAcceptedQueryString))
+@if (Context.Request.Cookies.ContainsKey(CookieSettings.CookieName) && Context.Request.Query.ContainsKey(CookieSettings.CookieBannerInteractionQueryString))
{
- var acceptedState = (Context.Request.Cookies.ContainsKey(CookieSettings.CookieName) && Context.Request.Cookies[CookieSettings.CookieName] == ((int)CookieAcceptanceValues.Accept).ToString()) ? "accepted" : "rejected";
+ var acceptedState = (Context.Request.Cookies.ContainsKey(CookieSettings.CookieName) && Context.Request.Cookies[CookieSettings.CookieName] == (CookieAcceptanceValues.Accept).ToString()) ? "accepted" : "rejected";
@@ -47,7 +57,7 @@
}
-@if (Context.Request.Cookies.ContainsKey(CookieSettings.CookieName) && Context.Request.Query.ContainsKey(CookieSettings.CookieBannerInteractionQueryString))
+@if (!cookiePreferencesService.IsUnknown() && Context.Request.Query.ContainsKey(CookieSettings.CookieBannerInteractionQueryString))
{
- var acceptedState = (Context.Request.Cookies.ContainsKey(CookieSettings.CookieName) && Context.Request.Cookies[CookieSettings.CookieName] == (CookieAcceptanceValues.Accept).ToString()) ? "accepted" : "rejected";
+ var acceptedState = cookiePreferencesService.IsAccepted() ? "accepted" : "rejected";
From 4c4f0d64aee5f005f462ffda6fbb4dcb71e484e0 Mon Sep 17 00:00:00 2001
From: Andy Mantell <134642+andymantell@users.noreply.github.com>
Date: Fri, 6 Dec 2024 13:47:35 +0000
Subject: [PATCH 4/4] code review updates
---
.../CookiePreferencesServiceTests.cs | 2 +-
.../CookieAcceptanceMiddleware.cs | 20 +++++++++----------
.../CookiePreferencesService.cs | 2 +-
.../ICookiePreferencesService.cs | 1 -
.../Pages/ContactUs.cshtml.cs | 1 -
5 files changed, 12 insertions(+), 14 deletions(-)
diff --git a/Frontend/CO.CDP.OrganisationApp.Tests/CookiePreferencesServiceTests.cs b/Frontend/CO.CDP.OrganisationApp.Tests/CookiePreferencesServiceTests.cs
index 3ff0a560f..76f1d074c 100644
--- a/Frontend/CO.CDP.OrganisationApp.Tests/CookiePreferencesServiceTests.cs
+++ b/Frontend/CO.CDP.OrganisationApp.Tests/CookiePreferencesServiceTests.cs
@@ -78,7 +78,7 @@ public void Reset_ShouldDeleteCookie_AndSetPendingValueToUnknown()
[Fact]
public void GetValue_ShouldReturnPendingValue_WhenSet()
{
- _cookiePreferencesService.SetCookie(CookieAcceptanceValues.Accept);
+ _cookiePreferencesService.Accept();
_cookiePreferencesService.GetValue().Should().Be(CookieAcceptanceValues.Accept);
}
diff --git a/Frontend/CO.CDP.OrganisationApp/CookieAcceptanceMiddleware.cs b/Frontend/CO.CDP.OrganisationApp/CookieAcceptanceMiddleware.cs
index e873e2238..7546418e5 100644
--- a/Frontend/CO.CDP.OrganisationApp/CookieAcceptanceMiddleware.cs
+++ b/Frontend/CO.CDP.OrganisationApp/CookieAcceptanceMiddleware.cs
@@ -8,17 +8,17 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
string cookiesAccepted = cookiesAcceptedValue.ToString().ToLower();
- if (cookiesAccepted == "true")
+ switch (cookiesAccepted)
{
- cookiePreferencesService.Accept();
- }
- else if (cookiesAccepted == "false")
- {
- cookiePreferencesService.Reject();
- }
- else if (cookiesAccepted == "unknown")
- {
- cookiePreferencesService.Reset();
+ case "true":
+ cookiePreferencesService.Accept();
+ break;
+ case "false":
+ cookiePreferencesService.Reject();
+ break;
+ case "unknown":
+ cookiePreferencesService.Reset();
+ break;
}
}
diff --git a/Frontend/CO.CDP.OrganisationApp/CookiePreferencesService.cs b/Frontend/CO.CDP.OrganisationApp/CookiePreferencesService.cs
index 469d4f0fe..840a10750 100644
--- a/Frontend/CO.CDP.OrganisationApp/CookiePreferencesService.cs
+++ b/Frontend/CO.CDP.OrganisationApp/CookiePreferencesService.cs
@@ -28,7 +28,7 @@ public void Reset()
pendingAcceptanceValue = CookieAcceptanceValues.Unknown;
}
- public void SetCookie(CookieAcceptanceValues value)
+ private void SetCookie(CookieAcceptanceValues value)
{
_context.Response.Cookies.Append(CookieSettings.CookieName, value.ToString(), new CookieOptions
{
diff --git a/Frontend/CO.CDP.OrganisationApp/ICookiePreferencesService.cs b/Frontend/CO.CDP.OrganisationApp/ICookiePreferencesService.cs
index 11e6c5c8a..fce705800 100644
--- a/Frontend/CO.CDP.OrganisationApp/ICookiePreferencesService.cs
+++ b/Frontend/CO.CDP.OrganisationApp/ICookiePreferencesService.cs
@@ -7,7 +7,6 @@ public interface ICookiePreferencesService
void Accept();
void Reject();
void Reset();
- void SetCookie(CookieAcceptanceValues value);
bool IsUnknown();
CookieAcceptanceValues GetValue();
}
\ No newline at end of file
diff --git a/Frontend/CO.CDP.OrganisationApp/Pages/ContactUs.cshtml.cs b/Frontend/CO.CDP.OrganisationApp/Pages/ContactUs.cshtml.cs
index f74bf4530..e4b08d9e7 100644
--- a/Frontend/CO.CDP.OrganisationApp/Pages/ContactUs.cshtml.cs
+++ b/Frontend/CO.CDP.OrganisationApp/Pages/ContactUs.cshtml.cs
@@ -8,7 +8,6 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
-using CO.CDP.OrganisationApp.Models;
namespace CO.CDP.OrganisationApp.Pages;