diff --git a/Lombiq.HelpfulLibraries.AspNetCore/Docs/Security.md b/Lombiq.HelpfulLibraries.AspNetCore/Docs/Security.md index 6bf831d8..f48e316b 100644 --- a/Lombiq.HelpfulLibraries.AspNetCore/Docs/Security.md +++ b/Lombiq.HelpfulLibraries.AspNetCore/Docs/Security.md @@ -8,6 +8,7 @@ - `EmbeddedMediaContentSecurityPolicyProvider`: An optional policy provider that permits additional host names used by usual media embedding sources (like YouTube) for the `frame-scr` directive. - `IContentSecurityPolicyProvider`: Interface for services that update the dictionary that will be turned into the `Content-Security-Policy` header value. - `ServiceCollectionExtensions`: Extensions methods for `IServiceCollection`, e.g. `AddContentSecurityPolicyProvider()` is a shortcut to register `IContentSecurityPolicyProvider` in dependency injection. +- `XWidgetsContentSecurityPolicyProvider`: An optional content security policy provider that provides configuration to allow the usage of X (Twitter) social widgets. There is a similar section for security extensions related to Orchard Core [here](../../Lombiq.HelpfulLibraries.OrchardCore/Docs/Security.md). diff --git a/Lombiq.HelpfulLibraries.AspNetCore/Security/CdnContentSecurityPolicyProvider.cs b/Lombiq.HelpfulLibraries.AspNetCore/Security/CdnContentSecurityPolicyProvider.cs index 543f7ddc..bd9b7a8a 100644 --- a/Lombiq.HelpfulLibraries.AspNetCore/Security/CdnContentSecurityPolicyProvider.cs +++ b/Lombiq.HelpfulLibraries.AspNetCore/Security/CdnContentSecurityPolicyProvider.cs @@ -17,43 +17,53 @@ public class CdnContentSecurityPolicyProvider : IContentSecurityPolicyProvider /// /// Gets the sources that will be added to the directive. /// - public static ConcurrentBag PermittedStyleSources { get; } = new( + public static ConcurrentBag PermittedStyleSources { get; } = [ - "fonts.googleapis.com", - "fonts.gstatic.com", // #spell-check-ignore-line "cdn.jsdelivr.net", // #spell-check-ignore-line - "fastly.jsdelivr.net", // #spell-check-ignore-line "cdnjs.cloudflare.com", // #spell-check-ignore-line + "fastly.jsdelivr.net", // #spell-check-ignore-line + "fonts.cdnfonts.com", // #spell-check-ignore-line + "fonts.googleapis.com", + "fonts.gstatic.com", // #spell-check-ignore-line "maxcdn.bootstrapcdn.com", // #spell-check-ignore-line - ]); + "unpkg.com", // #spell-check-ignore-line + ]; /// /// Gets the sources that will be added to the directive. /// - public static ConcurrentBag PermittedScriptSources { get; } = new( + public static ConcurrentBag PermittedScriptSources { get; } = [ "cdn.jsdelivr.net", // #spell-check-ignore-line "cdnjs.cloudflare.com", // #spell-check-ignore-line "code.jquery.com", "fastly.jsdelivr.net", // #spell-check-ignore-line "maxcdn.bootstrapcdn.com", // #spell-check-ignore-line - ]); + "unpkg.com", // #spell-check-ignore-line + ]; /// /// Gets the sources that will be added to the directive. /// - public static ConcurrentBag PermittedFontSources { get; } = new( + public static ConcurrentBag PermittedFontSources { get; } = [ "cdn.jsdelivr.net", // #spell-check-ignore-line + "cdnjs.cloudflare.com", // #spell-check-ignore-line + "fonts.cdnfonts.com", // #spell-check-ignore-line "fonts.googleapis.com", "fonts.gstatic.com", // #spell-check-ignore-line - ]); + ]; /// /// Gets the sources that will be added to the directive. /// public static ConcurrentBag PermittedFrameSources { get; } = []; + /// + /// Gets the sources that will be added to the directive. + /// + public static ConcurrentBag PermittedImgSources { get; } = []; + public ValueTask UpdateAsync(IDictionary securityPolicies, HttpContext context) { var any = false; @@ -82,6 +92,12 @@ public ValueTask UpdateAsync(IDictionary securityPolicies, HttpC CspHelper.MergeValues(securityPolicies, FrameSrc, PermittedFrameSources); } + if (!PermittedImgSources.IsEmpty) + { + any = true; + CspHelper.MergeValues(securityPolicies, ImgSrc, PermittedImgSources); + } + if (any) { var allPermittedSources = PermittedStyleSources.Concat(PermittedScriptSources).Concat(PermittedFontSources); diff --git a/Lombiq.HelpfulLibraries.AspNetCore/Security/ContentSecurityPolicyDirectives.cs b/Lombiq.HelpfulLibraries.AspNetCore/Security/ContentSecurityPolicyDirectives.cs index 81c0c321..8ae1cd92 100644 --- a/Lombiq.HelpfulLibraries.AspNetCore/Security/ContentSecurityPolicyDirectives.cs +++ b/Lombiq.HelpfulLibraries.AspNetCore/Security/ContentSecurityPolicyDirectives.cs @@ -1,8 +1,9 @@ -namespace Lombiq.HelpfulLibraries.AspNetCore.Security; +namespace Lombiq.HelpfulLibraries.AspNetCore.Security; /// -/// The Content-Security-Policy directives defined in the W3C -/// Recommendation. +/// The Content-Security-Policy directives defined in the W3C +/// Recommendation (also see the MDN page). /// public static class ContentSecurityPolicyDirectives { @@ -15,13 +16,20 @@ public static class ContentSecurityPolicyDirectives public const string FrameAncestors = "frame-ancestors"; public const string FrameSrc = "frame-src"; public const string ImgSrc = "img-src"; + public const string ManifestSrc = "manifest-src"; public const string MediaSrc = "media-src"; public const string ObjectSrc = "object-src"; public const string PluginTypes = "plugin-types"; + public const string ReportTo = "report-to"; public const string ReportUri = "report-uri"; public const string Sandbox = "sandbox"; public const string ScriptSrc = "script-src"; + public const string ScriptSrcAttr = "script-src-attr"; + public const string ScriptSrcElem = "script-src-elem"; public const string StyleSrc = "style-src"; + public const string StyleSrcAttr = "style-src-attr"; + public const string UpgradeInsecureRequests = "upgrade-insecure-requests"; + public const string StyleSrcElem = "style-src-elem"; public const string WorkerSrc = "worker-src"; public static class CommonValues diff --git a/Lombiq.HelpfulLibraries.AspNetCore/Security/XWidgetsContentSecurityPolicyProvider.cs b/Lombiq.HelpfulLibraries.AspNetCore/Security/XWidgetsContentSecurityPolicyProvider.cs new file mode 100644 index 00000000..02a12cf9 --- /dev/null +++ b/Lombiq.HelpfulLibraries.AspNetCore/Security/XWidgetsContentSecurityPolicyProvider.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Http; +using System.Collections.Generic; +using System.Threading.Tasks; +using static Lombiq.HelpfulLibraries.AspNetCore.Security.ContentSecurityPolicyDirectives; + +namespace Lombiq.HelpfulLibraries.AspNetCore.Security; + +/// +/// An optional content security policy provider that provides configuration to allow the usage of X (Twitter) social +/// widgets. +/// +public class XWidgetsContentSecurityPolicyProvider : IContentSecurityPolicyProvider +{ + private const string PlatformDotTwitter = "platform.twitter.com"; + + public ValueTask UpdateAsync(IDictionary securityPolicies, HttpContext context) + { + CspHelper.MergeValues(securityPolicies, FrameSrc, PlatformDotTwitter); + CspHelper.MergeValues(securityPolicies, ImgSrc, PlatformDotTwitter, "syndication.twitter.com"); + CspHelper.MergeValues(securityPolicies, StyleSrc, PlatformDotTwitter); + CspHelper.MergeValues(securityPolicies, ScriptSrc, PlatformDotTwitter); + + return ValueTask.CompletedTask; + } +} diff --git a/Lombiq.HelpfulLibraries.OrchardCore/Docs/Security.md b/Lombiq.HelpfulLibraries.OrchardCore/Docs/Security.md index 56e3e66d..62d20897 100644 --- a/Lombiq.HelpfulLibraries.OrchardCore/Docs/Security.md +++ b/Lombiq.HelpfulLibraries.OrchardCore/Docs/Security.md @@ -1,13 +1,13 @@ # Lombiq Helpful Libraries - Orchard Core Libraries - Security -## Extensions - -- `SecurityOrchardCoreBuilderExtensions`: Adds `BuilderExtensions` extensions. For example, the `ConfigureSecurityDefaultsWithStaticFiles()` that provides some default security configuration for Orchard Core. - There is a similar section for security extensions related to ASP.NET Core [here](../../Lombiq.HelpfulLibraries.AspNetCore/Docs/Security.md). All of the services mentioned in both documents are included in the `ConfigureSecurityDefaults()` and `ConfigureSecurityDefaultsWithStaticFiles()` extensions. These extensions provide additional security and can resolve issues reported by the [ZAP security scanner](https://github.com/Lombiq/UI-Testing-Toolbox/blob/dev/Lombiq.Tests.UI/Docs/SecurityScanning.md). +## Extensions + +- `SecurityOrchardCoreBuilderExtensions`: Adds `BuilderExtensions` extensions. For example, the `ConfigureSecurityDefaultsWithStaticFiles()` that provides some default security configuration for Orchard Core. + ## Attributes - `ContentSecurityPolicyAttribute`: You can add the `[ContentSecurityPolicy(value, name)]` attribute to any MVC action's method. This way you can grant per-action content security policy permissions, right there in the controller. These attributes are handled by the `ContentSecurityPolicyAttributeContentSecurityPolicyProvider`. @@ -19,3 +19,5 @@ These extensions provide additional security and can resolve issues reported by - `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. + +You can configure optional or custom content security policy providers by implementing the `IContentSecurityPolicyProvider` interface and registering them in the DI container with `AddContentSecurityPolicyProvider()`, e.g. `services.AddContentSecurityPolicyProvider();` in a `Startup` class. You can also register providers for the whole app (i.e. all tenants) from the root `Program` class via `OrchardCoreBuilder.ApplicationServices`. diff --git a/Lombiq.HelpfulLibraries.OrchardCore/Security/GoogleAnalyticsContentSecurityPolicyProvider.cs b/Lombiq.HelpfulLibraries.OrchardCore/Security/GoogleAnalyticsContentSecurityPolicyProvider.cs index ed4e96b7..c29ca7b3 100644 --- a/Lombiq.HelpfulLibraries.OrchardCore/Security/GoogleAnalyticsContentSecurityPolicyProvider.cs +++ b/Lombiq.HelpfulLibraries.OrchardCore/Security/GoogleAnalyticsContentSecurityPolicyProvider.cs @@ -13,9 +13,11 @@ public class GoogleAnalyticsContentSecurityPolicyProvider : IContentSecurityPoli { private const string HttpContextItemKey = nameof(GoogleAnalyticsContentSecurityPolicyProvider); + public static bool AlwaysEnabled { get; set; } + public async ValueTask UpdateAsync(IDictionary securityPolicies, HttpContext context) { - var googleAnalyticsIsEnabled = context.Items.ContainsKey(HttpContextItemKey); + var googleAnalyticsIsEnabled = AlwaysEnabled || context.Items.ContainsKey(HttpContextItemKey); if (!googleAnalyticsIsEnabled) { diff --git a/Lombiq.HelpfulLibraries.Tests/Lombiq.HelpfulLibraries.Tests.csproj b/Lombiq.HelpfulLibraries.Tests/Lombiq.HelpfulLibraries.Tests.csproj index a13da007..834c188c 100644 --- a/Lombiq.HelpfulLibraries.Tests/Lombiq.HelpfulLibraries.Tests.csproj +++ b/Lombiq.HelpfulLibraries.Tests/Lombiq.HelpfulLibraries.Tests.csproj @@ -17,7 +17,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -31,7 +31,7 @@ - +