diff --git a/Lombiq.HelpfulLibraries.OrchardCore/Docs/Security.md b/Lombiq.HelpfulLibraries.OrchardCore/Docs/Security.md index 327780bf..a6ff1f09 100644 --- a/Lombiq.HelpfulLibraries.OrchardCore/Docs/Security.md +++ b/Lombiq.HelpfulLibraries.OrchardCore/Docs/Security.md @@ -14,5 +14,7 @@ These extensions provide additional security and can resolve issues reported by ## Services +- `GoogleAnalyticsContentSecurityPolicyProvider`: Provides various directives for the `Content-Security-Policy` header, allowing using Google Analytics tracking. Is automatically enabled when the `OrchardCore.Google.Analytics` feature is enabled or the provider is explicitly enabled for the current request via is `static` method. +- `ReCaptchaContentSecurityPolicyProvider`: Provides various directives for the `Content-Security-Policy` header, allowing using ReCaptcha captchas. Is automatically enabled when the `OrchardCore.ReCaptcha` feature is enabled. - `ResourceManagerContentSecurityPolicyProvider`: An abstract base class for implementing content security policy providers that trigger when the specified resource is included. - `VueContentSecurityPolicyProvider`: An implementation of `ResourceManagerContentSecurityPolicyProvider` that adds `script-src: unsafe-eval` permission to the page if it uses the `vuejs` resource. This includes any Vue.js app in stock Orchard Core, apps you create in your view files, and SFCs created with the Lombiq.VueJs module. This is necessary, because without `unsafe-eval` Vue.js only supports templates that are pre-compiled into JS code. diff --git a/Lombiq.HelpfulLibraries.OrchardCore/Security/GoogleAnalyticsContentSecurityPolicyProvider.cs b/Lombiq.HelpfulLibraries.OrchardCore/Security/GoogleAnalyticsContentSecurityPolicyProvider.cs new file mode 100644 index 00000000..056242f4 --- /dev/null +++ b/Lombiq.HelpfulLibraries.OrchardCore/Security/GoogleAnalyticsContentSecurityPolicyProvider.cs @@ -0,0 +1,35 @@ +using Lombiq.HelpfulLibraries.AspNetCore.Security; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using OrchardCore.Environment.Shell; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +using static Lombiq.HelpfulLibraries.AspNetCore.Security.ContentSecurityPolicyDirectives; + +namespace Lombiq.HelpfulLibraries.OrchardCore.Security; + +public class GoogleAnalyticsContentSecurityPolicyProvider : IContentSecurityPolicyProvider +{ + private const string HttpContextItemKey = nameof(GoogleAnalyticsContentSecurityPolicyProvider); + + public async ValueTask UpdateAsync(IDictionary securityPolicies, HttpContext context) + { + var googleAnalyticsIsEnabled = context.Items.ContainsKey(HttpContextItemKey); + + if (!googleAnalyticsIsEnabled) + { + var shellFeaturesManager = context.RequestServices.GetRequiredService(); + googleAnalyticsIsEnabled = (await shellFeaturesManager.GetEnabledFeaturesAsync()) + .Any(feature => feature.Id == "OrchardCore.Google.Analytics"); + } + + if (googleAnalyticsIsEnabled) + { + CspHelper.MergeValues(securityPolicies, ScriptSrc, "www.googletagmanager.com"); + } + } + + public static void EnableForCurrentRequest(HttpContext context) => context.Items[HttpContextItemKey] = "enabled"; +} diff --git a/Lombiq.HelpfulLibraries.OrchardCore/Security/SecurityOrchardCoreBuilderExtensions.cs b/Lombiq.HelpfulLibraries.OrchardCore/Security/SecurityOrchardCoreBuilderExtensions.cs index af5bf07d..0dd0c554 100644 --- a/Lombiq.HelpfulLibraries.OrchardCore/Security/SecurityOrchardCoreBuilderExtensions.cs +++ b/Lombiq.HelpfulLibraries.OrchardCore/Security/SecurityOrchardCoreBuilderExtensions.cs @@ -90,7 +90,13 @@ public static OrchardCoreBuilder ConfigureAntiForgeryAlwaysSecure(this OrchardCo /// /// /// Adds that provides various directives for the - /// Content-Security-Policy header, allowing using a ReCaptcha captcha. + /// Content-Security-Policy header, allowing using ReCaptcha captchas. + /// + /// + /// + /// + /// Adds that provides various directives for + /// the Content-Security-Policy header, allowing using Google Analytics tracking. /// /// /// @@ -144,6 +150,7 @@ private static OrchardCoreBuilder ConfigureSecurityDefaultsInner( .AddContentSecurityPolicyProvider() .AddContentSecurityPolicyProvider() .AddContentSecurityPolicyProvider() + .AddContentSecurityPolicyProvider() .ConfigureSessionCookieAlwaysSecure(), (app, _, serviceProvider) => {