update the cookie consent control.

This commit is contained in:
Ben
2025-02-28 23:32:17 +08:00
parent 6dddd8eff8
commit 1ced5c0425
16 changed files with 322 additions and 134 deletions

View File

@ -31,6 +31,8 @@
@inject IUrlMappingRepository UrlMappingRepository
@inject IVisitorRepository VisitorRepository
@inject IJwtManager JwtManager
@inject ICookieConsentService CookieConsentService
@inject ISettingService SettingService
@if (_initialized)
{
@ -107,6 +109,7 @@
private string _styleSheets = "";
private string _scripts = "";
private string _message = "";
private bool _allowCookies;
private PageState _pageState;
// CascadingParameter is required to access HttpContext
@ -140,6 +143,9 @@
_prerender = site.Prerender;
_fingerprint = site.Fingerprint;
var cookieConsentSettings = SettingService.GetSetting(site.Settings, "CookieConsent", string.Empty);
_allowCookies = string.IsNullOrEmpty(cookieConsentSettings) || await CookieConsentService.CanTrackAsync(cookieConsentSettings == "optout");
var modules = new List<Module>();
Route route = new Route(url, alias.Path);
@ -170,7 +176,7 @@
modules = await SiteService.GetModulesAsync(site.SiteId, page.PageId);
}
if (site.VisitorTracking)
if (site.VisitorTracking && _allowCookies)
{
TrackVisitor(site.SiteId);
}
@ -245,7 +251,8 @@
ReturnUrl = "",
IsInternalNavigation = false,
RenderId = Guid.NewGuid(),
Refresh = true
Refresh = true,
AllowCookies = _allowCookies
};
}
else

View File

@ -19,10 +19,22 @@ namespace Oqtane.Controllers
_cookieConsentService = cookieConsentService;
}
[HttpGet("CanTrack")]
public async Task<bool> CanTrack()
[HttpGet("IsActioned")]
public async Task<bool> IsActioned()
{
return await _cookieConsentService.CanTrackAsync();
return await _cookieConsentService.IsActionedAsync();
}
[HttpGet("CanTrack")]
public async Task<bool> CanTrack(string optout)
{
return await _cookieConsentService.CanTrackAsync(bool.Parse(optout));
}
[HttpGet("CreateActionedCookie")]
public async Task<string> CreateActionedCookie()
{
return await _cookieConsentService.CreateActionedCookieAsync();
}
[HttpGet("CreateConsentCookie")]
@ -30,5 +42,11 @@ namespace Oqtane.Controllers
{
return await _cookieConsentService.CreateConsentCookieAsync();
}
[HttpGet("WithdrawConsentCookie")]
public async Task<string> WithdrawConsentCookie()
{
return await _cookieConsentService.WithdrawConsentCookieAsync();
}
}
}

View File

@ -55,8 +55,5 @@ namespace Oqtane.Extensions
public static IApplicationBuilder UseExceptionMiddleWare(this IApplicationBuilder builder)
=> builder.UseMiddleware<ExceptionMiddleware>();
public static IApplicationBuilder UseCookieConsent(this IApplicationBuilder builder)
=> builder.UseMiddleware<CookieConsentMiddleware>();
}
}

View File

@ -1,71 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Oqtane.Services;
using Oqtane.Shared;
namespace Oqtane.Infrastructure
{
internal class CookieConsentMiddleware
{
private readonly IList<string> _defaultEssentialCookies = new List<string>
{
".AspNetCore.Culture",
"X-XSRF-TOKEN-COOKIE",
".AspNetCore.Identity.Application"
};
private readonly RequestDelegate _next;
public CookieConsentMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
// check if framework is installed
var config = context.RequestServices.GetService(typeof(IConfigManager)) as IConfigManager;
var settingService = context.RequestServices.GetService(typeof(ISettingService)) as ISettingService;
var cookieConsentService = context.RequestServices.GetService(typeof(ICookieConsentService)) as ICookieConsentService;
string path = context.Request.Path.ToString();
if (config.IsInstalled())
{
try
{
var settings = (Dictionary<string, string>)context.Items[Constants.HttpContextSiteSettingsKey];
if (settings != null)
{
var cookieConsentEnabled = bool.Parse(settingService.GetSetting(settings, "CookieConsent", "False"));
if (cookieConsentEnabled && !await cookieConsentService.CanTrackAsync())
{
//only allow essential cookies when consent is not granted
var loginCookieName = settingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
var cookiesSetting = settingService.GetSetting(settings, "EssentialCookies", string.Empty);
var essentialCookies = !string.IsNullOrEmpty(cookiesSetting) ? cookiesSetting.Split(",").ToList() : _defaultEssentialCookies;
foreach (var cookie in context.Request.Cookies)
{
if (cookie.Key != loginCookieName && !essentialCookies.Contains(cookie.Key))
{
context.Response.Cookies.Delete(cookie.Key);
}
}
}
}
}
catch(Exception ex)
{
}
}
// continue processing
if (_next != null) await _next(context);
}
}
}

View File

@ -1,10 +1,13 @@
using System;
using System.Diagnostics.Contracts;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.Options;
using Oqtane.Documentation;
using Oqtane.Shared;
namespace Oqtane.Services
{
@ -12,27 +15,106 @@ namespace Oqtane.Services
public class ServerCookieConsentService : ICookieConsentService
{
private readonly IHttpContextAccessor _accessor;
private readonly CookiePolicyOptions _cookiePolicyOptions;
public ServerCookieConsentService(IHttpContextAccessor accessor)
public ServerCookieConsentService(IHttpContextAccessor accessor, IOptions<CookiePolicyOptions> cookiePolicyOptions)
{
_accessor = accessor;
_cookiePolicyOptions = cookiePolicyOptions.Value;
}
public Task<bool> CanTrackAsync()
public Task<bool> IsActionedAsync()
{
var consentFeature = _accessor.HttpContext?.Features.Get<ITrackingConsentFeature>();
var canTrack = consentFeature?.CanTrack ?? true;
var actioned = false;
if (_accessor.HttpContext != null)
{
var cookieValue = GetCookieValue("actioned");
actioned = cookieValue == Constants.CookieConsentActionCookieValue;
}
return Task.FromResult(actioned);
}
public Task<bool> CanTrackAsync(bool optOut)
{
var canTrack = true;
if (_accessor.HttpContext != null)
{
var cookieValue = GetCookieValue("consent");
var saved = cookieValue == Constants.CookieConsentCookieValue;
if (optOut)
{
canTrack = string.IsNullOrEmpty(cookieValue) || !saved;
}
else
{
canTrack = cookieValue == Constants.CookieConsentCookieValue;
}
}
return Task.FromResult(canTrack);
}
public Task<string> CreateActionedCookieAsync()
{
var cookieString = CreateCookieString(false, string.Empty);
return Task.FromResult(cookieString);
}
public Task<string> CreateConsentCookieAsync()
{
var consentFeature = _accessor.HttpContext?.Features.Get<ITrackingConsentFeature>();
consentFeature?.GrantConsent();
var cookie = consentFeature?.CreateConsentCookie() ?? string.Empty;
var cookieString = CreateCookieString(true, Constants.CookieConsentCookieValue);
return Task.FromResult(cookieString);
}
return Task.FromResult(cookie);
public Task<string> WithdrawConsentCookieAsync()
{
var cookieString = CreateCookieString(true, string.Empty);
return Task.FromResult(cookieString);
}
private string GetCookieValue(string type)
{
var cookieValue = string.Empty;
if (_accessor.HttpContext != null)
{
var value = _accessor.HttpContext.Request.Cookies[Constants.CookieConsentCookieName];
var index = type == "actioned" ? 1 : 0;
cookieValue = !string.IsNullOrEmpty(value) && value.Contains("|") ? value.Split('|')[index] : string.Empty;
}
return cookieValue;
}
private string CreateCookieString(bool saved, string savedValue)
{
var cookieString = string.Empty;
if (_accessor.HttpContext != null)
{
var savedCookie = saved ? savedValue : GetCookieValue("consent");
var actionedCookie = Constants.CookieConsentActionCookieValue;
var cookieValue = $"{savedCookie}|{actionedCookie}";
var options = _cookiePolicyOptions.ConsentCookie.Build(_accessor.HttpContext);
if (!_accessor.HttpContext.Response.HasStarted)
{
_accessor.HttpContext.Response.Cookies.Append(
Constants.CookieConsentCookieName,
cookieValue,
new CookieOptions()
{
Expires = options.Expires,
IsEssential = true,
SameSite = options.SameSite,
Secure = options.Secure
}
);
}
//get the cookie string from response header
cookieString = options.CreateCookieHeader(Constants.CookieConsentCookieName, Uri.EscapeDataString(cookieValue)).ToString();
}
return cookieString;
}
}
}

View File

@ -169,13 +169,6 @@ namespace Oqtane
options.CustomSchemaIds(type => type.ToString()); // Handle SchemaId already used for different type
});
services.TryAddSwagger(_useSwagger);
//add cookie consent policy
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.ConsentCookieValue = Constants.CookieConsentCookieValue;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -232,8 +225,6 @@ namespace Oqtane
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();
app.UseCookiePolicy();
app.UseCookieConsent();
if (_useSwagger)
{

View File

@ -121,6 +121,16 @@
color: white;
}
/* cookie consent */
.gdpr-consent-bar .btn-show{
bottom: 0;
right: 5px;
}
.gdpr-consent-bar .btn-hide{
top: 0;
right: 5px;
}
@media (max-width: 767.98px) {
.main .top-row {
display: none;

View File

@ -100,6 +100,16 @@ div.app-moduleactions a.dropdown-toggle, div.app-moduleactions div.dropdown-menu
z-index: 1000;
}
/* cookie consent */
.gdpr-consent-bar .btn-show{
bottom: 0;
right: 5px;
}
.gdpr-consent-bar .btn-hide{
top: 0;
right: 5px;
}
@media (max-width: 767.98px) {
.app-menu {