From aa4d72bcebffb5fdba129471c20dd406904aa2a4 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Mon, 2 Dec 2024 15:39:15 +0000 Subject: [PATCH 01/11] Optimised builders for header values; moved some usings out to global usings; BREAKING CHANGE: Removed the ability to remove the "X-Powered-By" header; --- .../Controllers/HomeController.cs | 10 +- OwaspHeaders.Core.Example/GlobalUsings.cs | 1 + ...ealisticContentSecurityPolicyGenerators.cs | 222 ++++++++---------- .../OwaspHeaders.Core.Example.csproj | 8 + OwaspHeaders.Core.Example/Program.cs | 4 +- .../CacheControlHeaderOptionsTests.cs | 9 +- .../ContentSecurityPolicyOptionsTests.cs | 7 +- .../CustomHeaders/CrossOriginOptionsTests.cs | 7 +- .../CustomHeaders/ExpectCtOptionsTests.cs | 6 +- ...ermittedCrossDomainPoliciesOptionsTests.cs | 6 +- .../ReferrerPolicyOptionsTests.cs | 6 +- .../CustomHeaders/SecureHeadersTests.cs | 9 +- .../StrictTransportSecurityOptionsTests.cs | 6 +- .../CustomHeaders/XContextTypeOptionsTests.cs | 6 +- .../CustomHeaders/XFrameOptionsTests.cs | 6 +- .../CustomHeaders/XRemovePoweredByOptions.cs | 45 ---- .../CustomHeaders/XssProtectionOptionTests.cs | 6 +- OwaspHeaders.Core.Tests/GlobalUsings.cs | 9 + .../HeaderValueGuardClausesStringValues.cs | 19 ++ .../GuardClauses/ObjectGuardClauses.cs | 21 ++ .../HttpContextExtensionsTests/TryAdd.cs | 7 +- .../HttpContextExtensionsTests/TryRemove.cs | 7 +- .../RegressionTests/CspRegressionTests.cs | 11 +- .../TrimEndTests.cs | 2 - changelog.md | 8 +- src/Constants.cs | 28 +-- src/Enums/CspSandboxType.cs | 5 + src/Enums/ReferrerPolicyOptions.cs | 5 + src/Enums/XFrameOptions.cs | 6 + src/Enums/XPermittedCrossDomainOptionValue.cs | 6 + .../ContentSecurityPolicyExtensions.cs | 14 +- src/Extensions/GlobalUsings.cs | 5 + src/Extensions/HttpContextExtensions.cs | 22 +- .../SecureHeadersMiddlewareBuilder.cs | 29 +-- .../SecureHeadersMiddlewareExtensions.cs | 24 +- src/Extensions/StringBuilderExtensions.cs | 34 ++- src/Extensions/StringExtensions.cs | 20 -- src/GlobalUsings.cs | 2 + src/Guards/HeaderValueGuardClauses.cs | 12 + src/Guards/ObjectGuardClauses.cs | 13 + src/Helpers/ArgumentExceptionHelper.cs | 7 +- src/Helpers/ContentSecurityPolicyHelpers.cs | 5 +- src/Models/CacheControl.cs | 55 +++-- .../ContentSecurityPolicyConfiguration.cs | 96 ++++---- src/Models/ContentSecurityPolicyElement.cs | 8 +- src/Models/ContentSecurityPolicyExtensions.cs | 6 +- ...ntSecurityPolicyReportOnlyConfiguration.cs | 26 +- src/Models/ContentSecurityPolicySandBox.cs | 15 +- src/Models/CrossOriginResourcePolicy.cs | 20 +- src/Models/ExpectCt.cs | 17 +- src/Models/GlobalUsings.cs | 3 + src/Models/HstsConfiguration.cs | 20 +- ...PermittedCrossDomainPolicyConfiguration.cs | 20 +- src/Models/ReferrerPolicy.cs | 14 +- .../SecureHeadersMiddlewareConfiguration.cs | 15 +- src/Models/XFrameOptionsConfiguration.cs | 40 ++-- src/OwaspHeaders.Core.csproj | 2 +- src/SecureHeadersMiddleware.cs | 74 +++--- 58 files changed, 526 insertions(+), 590 deletions(-) create mode 100644 OwaspHeaders.Core.Example/GlobalUsings.cs delete mode 100644 OwaspHeaders.Core.Tests/CustomHeaders/XRemovePoweredByOptions.cs create mode 100644 OwaspHeaders.Core.Tests/GlobalUsings.cs create mode 100644 OwaspHeaders.Core.Tests/GuardClauses/HeaderValueGuardClausesStringValues.cs create mode 100644 OwaspHeaders.Core.Tests/GuardClauses/ObjectGuardClauses.cs create mode 100644 src/Extensions/GlobalUsings.cs delete mode 100644 src/Extensions/StringExtensions.cs create mode 100644 src/GlobalUsings.cs create mode 100644 src/Guards/HeaderValueGuardClauses.cs create mode 100644 src/Guards/ObjectGuardClauses.cs create mode 100644 src/Models/GlobalUsings.cs diff --git a/OwaspHeaders.Core.Example/Controllers/HomeController.cs b/OwaspHeaders.Core.Example/Controllers/HomeController.cs index b31e755..3f06d69 100644 --- a/OwaspHeaders.Core.Example/Controllers/HomeController.cs +++ b/OwaspHeaders.Core.Example/Controllers/HomeController.cs @@ -8,9 +8,9 @@ public class HomeController(ILogger logger) : ControllerBase { private readonly ILogger _logger = logger; -[HttpGet(Name = "/")] -public IEnumerable Get() -{ - return HttpContext.Response.Headers.Select(h => h.ToString()).ToArray(); -} + [HttpGet(Name = "/")] + public IEnumerable Get() + { + return HttpContext.Response.Headers.Select(h => h.ToString()).ToArray(); + } } diff --git a/OwaspHeaders.Core.Example/GlobalUsings.cs b/OwaspHeaders.Core.Example/GlobalUsings.cs new file mode 100644 index 0000000..a09121d --- /dev/null +++ b/OwaspHeaders.Core.Example/GlobalUsings.cs @@ -0,0 +1 @@ +global using OwaspHeaders.Core.Extensions; diff --git a/OwaspHeaders.Core.Example/Helpers/RealisticContentSecurityPolicyGenerators.cs b/OwaspHeaders.Core.Example/Helpers/RealisticContentSecurityPolicyGenerators.cs index c863dc1..b29dd31 100644 --- a/OwaspHeaders.Core.Example/Helpers/RealisticContentSecurityPolicyGenerators.cs +++ b/OwaspHeaders.Core.Example/Helpers/RealisticContentSecurityPolicyGenerators.cs @@ -1,5 +1,4 @@ using OwaspHeaders.Core.Enums; -using OwaspHeaders.Core.Extensions; using OwaspHeaders.Core.Models; namespace OwaspHeaders.Core.Example.Helpers; @@ -33,128 +32,115 @@ public static SecureHeadersMiddlewareConfiguration GenerateOwaspHomePageCsp() => SecureHeadersMiddlewareExtensions.BuildDefaultConfiguration() .UseContentSecurityPolicy() .SetCspUris( - new List - { - new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://api.github.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.githubusercontent.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google-analytics.com" }, - new() - { - CommandType = CspCommandType.Uri, - DirectiveOrUri = "https://owaspadmin.azurewebsites.net" - }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://platform.twitter.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.youtube.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.doubleclick.net" } - }, CspUriType.DefaultUri) + [ + new ContentSecurityPolicyElement { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://api.github.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.githubusercontent.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google-analytics.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://owaspadmin.azurewebsites.net" }, + + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://platform.twitter.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.youtube.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.doubleclick.net" } + ], CspUriType.DefaultUri) .SetCspUris( - new List - { - new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://api.github.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.githubusercontent.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google-analytics.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://owaspadmin.azurewebsites.net" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://platform.twitter.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.youtube.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.doubleclick.net" }, - }, CspUriType.FrameAncestors) + [ + new ContentSecurityPolicyElement { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://api.github.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.githubusercontent.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google-analytics.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://owaspadmin.azurewebsites.net" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://platform.twitter.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.youtube.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.doubleclick.net" } + ], CspUriType.FrameAncestors) .SetCspUris( - new List - { - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.vuejs.org" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.stripe.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.wufoo.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.sched.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twitter.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.youtube.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://w.soundcloud.com" }, - }, CspUriType.Frame) + [ + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.vuejs.org" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.stripe.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.wufoo.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.sched.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twitter.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.youtube.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://w.soundcloud.com" } + ], CspUriType.Frame) .SetCspUris( - new List - { - new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, - new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "unsafe-inline" }, - new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "unsafe-eval" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://fonts.googleapis.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://app.diagrams.net" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://cdnjs.cloudflare.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://cse.google.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.vuejs.org" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.stripe.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.wufoo.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.youtube.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.meetup.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.sched.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google-analytics.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://unpkg.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://buttons.github.io" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.google.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.gstatic.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twitter.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" }, - }, CspUriType.Script) + [ + new ContentSecurityPolicyElement { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Directive, DirectiveOrUri = "unsafe-inline" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Directive, DirectiveOrUri = "unsafe-eval" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://fonts.googleapis.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://app.diagrams.net" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://cdnjs.cloudflare.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://cse.google.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.vuejs.org" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.stripe.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.wufoo.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.youtube.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.meetup.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.sched.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google-analytics.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://unpkg.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://buttons.github.io" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.google.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.gstatic.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twitter.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" } + ], CspUriType.Script) .SetCspUris( - new List - { - new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, - new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "unsafe-inline" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.gstatic.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://cdnjs.cloudflare.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.google.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://fonts.googleapis.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://platform.twitter.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "data:" }, - }, CspUriType.Style) + [ + new ContentSecurityPolicyElement { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Directive, DirectiveOrUri = "unsafe-inline" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.gstatic.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://cdnjs.cloudflare.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.google.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://fonts.googleapis.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://platform.twitter.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "data:" } + ], CspUriType.Style) .SetCspUris( - new List - { - new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "fonts.gstatic.com" } - }, CspUriType.Font) + [ + new ContentSecurityPolicyElement { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "fonts.gstatic.com" } + ], CspUriType.Font) .SetCspUris( - new List - { - new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://pay.google.com" } - }, CspUriType.Manifest) + [ + new ContentSecurityPolicyElement { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://pay.google.com" } + ], CspUriType.Manifest) .SetCspUris( - new List - { - new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.globalappsec.org" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "data:" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "www.w3.org" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://licensebuttons.net" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://img.shields.io" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twitter.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://github.githubassets.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://platform.twitter.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.githubusercontent.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.vercel.app" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.cloudfront.net" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.coreinfrastructure.org" }, - new() - { - CommandType = CspCommandType.Uri, - DirectiveOrUri = "https://*.securityknowledgeframework.org" - }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://badges.gitter.im" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://travis-ci.org" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://api.travis-ci.org" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://s3.amazonaws.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://snyk.io" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://coveralls.io" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://requires.io" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://github.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.googleapis.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google.com" }, - new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.gstatic.com" }, - }, CspUriType.Img); + [ + new ContentSecurityPolicyElement { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.globalappsec.org" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "data:" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "www.w3.org" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://licensebuttons.net" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://img.shields.io" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twitter.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://github.githubassets.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://platform.twitter.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.githubusercontent.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.vercel.app" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.cloudfront.net" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.coreinfrastructure.org" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.securityknowledgeframework.org" }, + + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://badges.gitter.im" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://travis-ci.org" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://api.travis-ci.org" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://s3.amazonaws.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://snyk.io" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://coveralls.io" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://requires.io" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://github.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.googleapis.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google.com" }, + new ContentSecurityPolicyElement { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.gstatic.com" } + + ], CspUriType.Img); } diff --git a/OwaspHeaders.Core.Example/OwaspHeaders.Core.Example.csproj b/OwaspHeaders.Core.Example/OwaspHeaders.Core.Example.csproj index b64b584..2524cec 100644 --- a/OwaspHeaders.Core.Example/OwaspHeaders.Core.Example.csproj +++ b/OwaspHeaders.Core.Example/OwaspHeaders.Core.Example.csproj @@ -17,4 +17,12 @@ + + + + + diff --git a/OwaspHeaders.Core.Example/Program.cs b/OwaspHeaders.Core.Example/Program.cs index f484384..4f4416d 100644 --- a/OwaspHeaders.Core.Example/Program.cs +++ b/OwaspHeaders.Core.Example/Program.cs @@ -1,6 +1,4 @@ -using OwaspHeaders.Core.Extensions; - -var builder = WebApplication.CreateBuilder(args); +var builder = WebApplication.CreateBuilder(args); // Add services to the container. diff --git a/OwaspHeaders.Core.Tests/CustomHeaders/CacheControlHeaderOptionsTests.cs b/OwaspHeaders.Core.Tests/CustomHeaders/CacheControlHeaderOptionsTests.cs index 4fbb1ae..4ce5603 100644 --- a/OwaspHeaders.Core.Tests/CustomHeaders/CacheControlHeaderOptionsTests.cs +++ b/OwaspHeaders.Core.Tests/CustomHeaders/CacheControlHeaderOptionsTests.cs @@ -1,11 +1,4 @@ -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using OwaspHeaders.Core.Extensions; -using Xunit; - -namespace OwaspHeaders.Core.Tests.CustomHeaders +namespace OwaspHeaders.Core.Tests.CustomHeaders { public class CacheControlHeaderOptionsTests { diff --git a/OwaspHeaders.Core.Tests/CustomHeaders/ContentSecurityPolicyOptionsTests.cs b/OwaspHeaders.Core.Tests/CustomHeaders/ContentSecurityPolicyOptionsTests.cs index 36a3450..a57c1d8 100644 --- a/OwaspHeaders.Core.Tests/CustomHeaders/ContentSecurityPolicyOptionsTests.cs +++ b/OwaspHeaders.Core.Tests/CustomHeaders/ContentSecurityPolicyOptionsTests.cs @@ -1,9 +1,4 @@ -using System.Threading.Tasks; -using OwaspHeaders.Core.Enums; -using OwaspHeaders.Core.Extensions; -using Xunit; - -namespace OwaspHeaders.Core.Tests.CustomHeaders +namespace OwaspHeaders.Core.Tests.CustomHeaders { public class ContentSecurityPolicyOptionsTests : SecureHeadersTests { diff --git a/OwaspHeaders.Core.Tests/CustomHeaders/CrossOriginOptionsTests.cs b/OwaspHeaders.Core.Tests/CustomHeaders/CrossOriginOptionsTests.cs index b622af2..d4033ac 100644 --- a/OwaspHeaders.Core.Tests/CustomHeaders/CrossOriginOptionsTests.cs +++ b/OwaspHeaders.Core.Tests/CustomHeaders/CrossOriginOptionsTests.cs @@ -1,9 +1,4 @@ -using System.Threading.Tasks; -using OwaspHeaders.Core.Extensions; -using OwaspHeaders.Core.Models; -using Xunit; - -namespace OwaspHeaders.Core.Tests.CustomHeaders +namespace OwaspHeaders.Core.Tests.CustomHeaders { public class CrossOriginOptionsTests : SecureHeadersTests { diff --git a/OwaspHeaders.Core.Tests/CustomHeaders/ExpectCtOptionsTests.cs b/OwaspHeaders.Core.Tests/CustomHeaders/ExpectCtOptionsTests.cs index a459fed..f4faa0d 100644 --- a/OwaspHeaders.Core.Tests/CustomHeaders/ExpectCtOptionsTests.cs +++ b/OwaspHeaders.Core.Tests/CustomHeaders/ExpectCtOptionsTests.cs @@ -1,8 +1,4 @@ -using System.Threading.Tasks; -using OwaspHeaders.Core.Extensions; -using Xunit; - -namespace OwaspHeaders.Core.Tests.CustomHeaders +namespace OwaspHeaders.Core.Tests.CustomHeaders { public class ExpectCtOptionsTests : SecureHeadersTests { diff --git a/OwaspHeaders.Core.Tests/CustomHeaders/PermittedCrossDomainPoliciesOptionsTests.cs b/OwaspHeaders.Core.Tests/CustomHeaders/PermittedCrossDomainPoliciesOptionsTests.cs index a817c08..4d96107 100644 --- a/OwaspHeaders.Core.Tests/CustomHeaders/PermittedCrossDomainPoliciesOptionsTests.cs +++ b/OwaspHeaders.Core.Tests/CustomHeaders/PermittedCrossDomainPoliciesOptionsTests.cs @@ -1,8 +1,4 @@ -using System.Threading.Tasks; -using OwaspHeaders.Core.Extensions; -using Xunit; - -namespace OwaspHeaders.Core.Tests.CustomHeaders +namespace OwaspHeaders.Core.Tests.CustomHeaders { public class PermittedCrossDomainPoliciesOptionsTests : SecureHeadersTests { diff --git a/OwaspHeaders.Core.Tests/CustomHeaders/ReferrerPolicyOptionsTests.cs b/OwaspHeaders.Core.Tests/CustomHeaders/ReferrerPolicyOptionsTests.cs index 9e246e7..b3cb7b3 100644 --- a/OwaspHeaders.Core.Tests/CustomHeaders/ReferrerPolicyOptionsTests.cs +++ b/OwaspHeaders.Core.Tests/CustomHeaders/ReferrerPolicyOptionsTests.cs @@ -1,8 +1,4 @@ -using System.Threading.Tasks; -using OwaspHeaders.Core.Extensions; -using Xunit; - -namespace OwaspHeaders.Core.Tests.CustomHeaders +namespace OwaspHeaders.Core.Tests.CustomHeaders { public class ReferrerPolicyOptionsTests : SecureHeadersTests { diff --git a/OwaspHeaders.Core.Tests/CustomHeaders/SecureHeadersTests.cs b/OwaspHeaders.Core.Tests/CustomHeaders/SecureHeadersTests.cs index 79af584..8e07a08 100644 --- a/OwaspHeaders.Core.Tests/CustomHeaders/SecureHeadersTests.cs +++ b/OwaspHeaders.Core.Tests/CustomHeaders/SecureHeadersTests.cs @@ -1,11 +1,4 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using OwaspHeaders.Core.Models; -using Xunit; - -namespace OwaspHeaders.Core.Tests.CustomHeaders +namespace OwaspHeaders.Core.Tests.CustomHeaders { public abstract class SecureHeadersTests { diff --git a/OwaspHeaders.Core.Tests/CustomHeaders/StrictTransportSecurityOptionsTests.cs b/OwaspHeaders.Core.Tests/CustomHeaders/StrictTransportSecurityOptionsTests.cs index a980679..2107300 100644 --- a/OwaspHeaders.Core.Tests/CustomHeaders/StrictTransportSecurityOptionsTests.cs +++ b/OwaspHeaders.Core.Tests/CustomHeaders/StrictTransportSecurityOptionsTests.cs @@ -1,8 +1,4 @@ -using System.Threading.Tasks; -using OwaspHeaders.Core.Extensions; -using Xunit; - -namespace OwaspHeaders.Core.Tests.CustomHeaders +namespace OwaspHeaders.Core.Tests.CustomHeaders { public class StrictTransportSecurityOptionsTests : SecureHeadersTests { diff --git a/OwaspHeaders.Core.Tests/CustomHeaders/XContextTypeOptionsTests.cs b/OwaspHeaders.Core.Tests/CustomHeaders/XContextTypeOptionsTests.cs index 4d25c0c..690817d 100644 --- a/OwaspHeaders.Core.Tests/CustomHeaders/XContextTypeOptionsTests.cs +++ b/OwaspHeaders.Core.Tests/CustomHeaders/XContextTypeOptionsTests.cs @@ -1,8 +1,4 @@ -using System.Threading.Tasks; -using OwaspHeaders.Core.Extensions; -using Xunit; - -namespace OwaspHeaders.Core.Tests.CustomHeaders +namespace OwaspHeaders.Core.Tests.CustomHeaders { public class XContextTypeOptionsTests : SecureHeadersTests { diff --git a/OwaspHeaders.Core.Tests/CustomHeaders/XFrameOptionsTests.cs b/OwaspHeaders.Core.Tests/CustomHeaders/XFrameOptionsTests.cs index c44e47e..972fd9d 100644 --- a/OwaspHeaders.Core.Tests/CustomHeaders/XFrameOptionsTests.cs +++ b/OwaspHeaders.Core.Tests/CustomHeaders/XFrameOptionsTests.cs @@ -1,8 +1,4 @@ -using System.Threading.Tasks; -using OwaspHeaders.Core.Extensions; -using Xunit; - -namespace OwaspHeaders.Core.Tests.CustomHeaders +namespace OwaspHeaders.Core.Tests.CustomHeaders { public class XFrameOptionsTests : SecureHeadersTests { diff --git a/OwaspHeaders.Core.Tests/CustomHeaders/XRemovePoweredByOptions.cs b/OwaspHeaders.Core.Tests/CustomHeaders/XRemovePoweredByOptions.cs deleted file mode 100644 index b637135..0000000 --- a/OwaspHeaders.Core.Tests/CustomHeaders/XRemovePoweredByOptions.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Threading.Tasks; -using OwaspHeaders.Core.Extensions; -using Xunit; - -namespace OwaspHeaders.Core.Tests.CustomHeaders -{ - public class XRemovePoweredByOptions : SecureHeadersTests - { - [Fact] - public async Task When_RemovePoweredByHeaderCalled_Header_Is_Present() - { - // arrange - var headerPresentConfig = SecureHeadersMiddlewareBuilder.CreateBuilder() - .RemovePoweredByHeader().Build(); - var secureHeadersMiddleware = new SecureHeadersMiddleware(_onNext, headerPresentConfig); - - // act - await secureHeadersMiddleware.InvokeAsync(_context); - - // assert - Assert.True(headerPresentConfig.RemoveXPoweredByHeader); - Assert.False(_context.Response.Headers.ContainsKey(Constants.PoweredByHeaderName)); - Assert.False(_context.Response.Headers.ContainsKey(Constants.ServerHeaderName)); - } - - [Fact] - public async Task When_RemovePoweredByHeaderNotCalled_Header_Not_Present() - { - // arrange - var headerPresentConfig = SecureHeadersMiddlewareBuilder.CreateBuilder() - .Build(); - var secureHeadersMiddleware = new SecureHeadersMiddleware(_onNext, headerPresentConfig); - - // act - await secureHeadersMiddleware.InvokeAsync(_context); - - // assert - Assert.False(headerPresentConfig.RemoveXPoweredByHeader); - // Am currently running the 2.1.300 Preview 1 build of the SDK - // and the server doesn't seem to add this header. - // Therefore this assert is commented out, as it will always fail - // Assert.True(_context.Response.Headers.ContainsKey(Constants.PoweredByHeaderName)); - } - } -} diff --git a/OwaspHeaders.Core.Tests/CustomHeaders/XssProtectionOptionTests.cs b/OwaspHeaders.Core.Tests/CustomHeaders/XssProtectionOptionTests.cs index 376e08d..be1c3f8 100644 --- a/OwaspHeaders.Core.Tests/CustomHeaders/XssProtectionOptionTests.cs +++ b/OwaspHeaders.Core.Tests/CustomHeaders/XssProtectionOptionTests.cs @@ -1,8 +1,4 @@ -using System.Threading.Tasks; -using OwaspHeaders.Core.Extensions; -using Xunit; - -namespace OwaspHeaders.Core.Tests.CustomHeaders +namespace OwaspHeaders.Core.Tests.CustomHeaders { public class XssProtectionOptionTests : SecureHeadersTests { diff --git a/OwaspHeaders.Core.Tests/GlobalUsings.cs b/OwaspHeaders.Core.Tests/GlobalUsings.cs new file mode 100644 index 0000000..a83d0fe --- /dev/null +++ b/OwaspHeaders.Core.Tests/GlobalUsings.cs @@ -0,0 +1,9 @@ +global using System; +global using System.Linq; +global using System.Threading; +global using System.Threading.Tasks; +global using Microsoft.AspNetCore.Http; +global using OwaspHeaders.Core.Enums; +global using OwaspHeaders.Core.Extensions; +global using OwaspHeaders.Core.Models; +global using Xunit; diff --git a/OwaspHeaders.Core.Tests/GuardClauses/HeaderValueGuardClausesStringValues.cs b/OwaspHeaders.Core.Tests/GuardClauses/HeaderValueGuardClausesStringValues.cs new file mode 100644 index 0000000..58e5151 --- /dev/null +++ b/OwaspHeaders.Core.Tests/GuardClauses/HeaderValueGuardClausesStringValues.cs @@ -0,0 +1,19 @@ +namespace OwaspHeaders.Core.Tests.GuardClauses; + +public class HeaderValueGuardClauses +{ + [Theory] + [InlineData("")] + [InlineData(" ")] + public void NullOrWhitespaceValue_Raises_ArgumentException(string inputValue) + { + // Arrange + + // Act + var exception = Record.Exception(() => Guards.HeaderValueGuardClauses.StringCannotBeNullOrWhitsSpace(inputValue, nameof(inputValue))); + + // Assert + Assert.IsType(exception); + Assert.Equal($"No value for {nameof(inputValue)} was supplied", exception.Message); + } +} diff --git a/OwaspHeaders.Core.Tests/GuardClauses/ObjectGuardClauses.cs b/OwaspHeaders.Core.Tests/GuardClauses/ObjectGuardClauses.cs new file mode 100644 index 0000000..ddbcfa1 --- /dev/null +++ b/OwaspHeaders.Core.Tests/GuardClauses/ObjectGuardClauses.cs @@ -0,0 +1,21 @@ +namespace OwaspHeaders.Core.Tests.GuardClauses; + +public class ObjectGuardClauses +{ + [Fact] + public void NullObject_Raises_NullArgumentException() + { + // Arrange + Object inputObject = null; + var expectedOutputMessage = Guid.NewGuid().ToString(); + + // Act + var exception = Record.Exception(() => Guards.ObjectGuardClauses.ObjectCannotBeNull(inputObject, + nameof(inputObject), expectedOutputMessage)); + + // Assert + Assert.IsType(exception); + Assert.Contains(expectedOutputMessage, exception.Message); + Assert.Contains(nameof(inputObject), exception.Message); + } +} diff --git a/OwaspHeaders.Core.Tests/HttpContextExtensionsTests/TryAdd.cs b/OwaspHeaders.Core.Tests/HttpContextExtensionsTests/TryAdd.cs index 0efd616..f6aaf7a 100644 --- a/OwaspHeaders.Core.Tests/HttpContextExtensionsTests/TryAdd.cs +++ b/OwaspHeaders.Core.Tests/HttpContextExtensionsTests/TryAdd.cs @@ -1,9 +1,4 @@ -using System; -using Microsoft.AspNetCore.Http; -using OwaspHeaders.Core.Extensions; -using Xunit; - -namespace OwaspHeaders.Core.Tests.HttpContextExtensionsTests +namespace OwaspHeaders.Core.Tests.HttpContextExtensionsTests { public class TryAdd { diff --git a/OwaspHeaders.Core.Tests/HttpContextExtensionsTests/TryRemove.cs b/OwaspHeaders.Core.Tests/HttpContextExtensionsTests/TryRemove.cs index 028221a..e764102 100644 --- a/OwaspHeaders.Core.Tests/HttpContextExtensionsTests/TryRemove.cs +++ b/OwaspHeaders.Core.Tests/HttpContextExtensionsTests/TryRemove.cs @@ -1,9 +1,4 @@ -using System; -using Microsoft.AspNetCore.Http; -using OwaspHeaders.Core.Extensions; -using Xunit; - -namespace OwaspHeaders.Core.Tests.HttpContextExtensionsTests +namespace OwaspHeaders.Core.Tests.HttpContextExtensionsTests { public class TryRemove { diff --git a/OwaspHeaders.Core.Tests/RegressionTests/CspRegressionTests.cs b/OwaspHeaders.Core.Tests/RegressionTests/CspRegressionTests.cs index a507a5d..b9b75c1 100644 --- a/OwaspHeaders.Core.Tests/RegressionTests/CspRegressionTests.cs +++ b/OwaspHeaders.Core.Tests/RegressionTests/CspRegressionTests.cs @@ -1,13 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using OwaspHeaders.Core.Enums; -using OwaspHeaders.Core.Extensions; -using OwaspHeaders.Core.Models; -using Xunit; +using System.Collections.Generic; namespace OwaspHeaders.Core.Tests.RegressionTests { diff --git a/OwaspHeaders.Core.Tests/StringBuilderExtensionsTests/TrimEndTests.cs b/OwaspHeaders.Core.Tests/StringBuilderExtensionsTests/TrimEndTests.cs index 4338eda..82fb3a2 100644 --- a/OwaspHeaders.Core.Tests/StringBuilderExtensionsTests/TrimEndTests.cs +++ b/OwaspHeaders.Core.Tests/StringBuilderExtensionsTests/TrimEndTests.cs @@ -1,6 +1,4 @@ using System.Text; -using OwaspHeaders.Core.Extensions; -using Xunit; namespace OwaspHeaders.Core.Tests.StringBuilderExtensionsTests { diff --git a/changelog.md b/changelog.md index 4726bbf..c334c00 100644 --- a/changelog.md +++ b/changelog.md @@ -22,7 +22,13 @@ This version dropped support for .NET 6 and .NET 7, as they are no longer suppor All projects in the [GitHub repo](https://github.com/GaProgMan/OwaspHeaders.Core) now build and run with either .NET 8 or .NET 9, whichever is present (deferring to the highest version number if both are present). As of November 19th, 2024 there are no new features in Version 9, so if you still need to use the NuGet package with .NET 6 or 7 please use Version 8 of the package. -#### Version 9.1 +#### Version 9.2.x + +A number of small optimisations for generating HTTP header values have been made. There are also new Guard clauses in place to protect from a number of null or null/whitespace issues. All using statements have been cleaned up, with a large number placed in relevant global usings files. + +**BREAKING CHANGE**: Removal of the X-Powered-By header has been completely removed in this version. The reason for this is that the X-Powered-By header is included by the reverse proxy, which ASP .NET Core has no control over. See the section in the Readme labelled "Server Header: A Warning" for details on how to remove this header. + +#### Version 9.1.x The `max-age` value for the Strict-Transport-Security (HSTS) header was updated to the OWASP recommended value of 31536000 (365 days). diff --git a/src/Constants.cs b/src/Constants.cs index d8d5314..f7bc2ef 100644 --- a/src/Constants.cs +++ b/src/Constants.cs @@ -2,32 +2,28 @@ { public static class Constants { - public static readonly string StrictTransportSecurityHeaderName = "Strict-Transport-Security"; + public const string StrictTransportSecurityHeaderName = "Strict-Transport-Security"; - public static readonly string XFrameOptionsHeaderName = "X-Frame-Options"; + public const string XFrameOptionsHeaderName = "X-Frame-Options"; - public static readonly string XssProtectionHeaderName = "X-XSS-Protection"; + public const string XssProtectionHeaderName = "X-XSS-Protection"; - public static readonly string XContentTypeOptionsHeaderName = "X-Content-Type-Options"; + public const string XContentTypeOptionsHeaderName = "X-Content-Type-Options"; - public static readonly string ContentSecurityPolicyHeaderName = "Content-Security-Policy"; + public const string ContentSecurityPolicyHeaderName = "Content-Security-Policy"; - public static readonly string ContentSecurityPolicyReportOnlyHeaderName = "Content-Security-Policy-Report-Only"; + public const string ContentSecurityPolicyReportOnlyHeaderName = "Content-Security-Policy-Report-Only"; - public static readonly string XContentSecurityPolicyHeaderName = "X-Content-Security-Policy"; + public const string XContentSecurityPolicyHeaderName = "X-Content-Security-Policy"; - public static readonly string PermittedCrossDomainPoliciesHeaderName = "X-Permitted-Cross-Domain-Policies"; + public const string PermittedCrossDomainPoliciesHeaderName = "X-Permitted-Cross-Domain-Policies"; - public static readonly string ReferrerPolicyHeaderName = "Referrer-Policy"; + public const string ReferrerPolicyHeaderName = "Referrer-Policy"; - public static readonly string CacheControlHeaderName = "Cache-Control"; + public const string CacheControlHeaderName = "Cache-Control"; - public static readonly string ExpectCtHeaderName = "Expect-CT"; + public const string ExpectCtHeaderName = "Expect-CT"; - public static readonly string PoweredByHeaderName = "X-Powered-By"; - - public static readonly string ServerHeaderName = "Server"; - - public static readonly string CrossOriginResourcePolicyHeaderName = "Cross-Origin-Resource-Policy"; + public const string CrossOriginResourcePolicyHeaderName = "Cross-Origin-Resource-Policy"; } } diff --git a/src/Enums/CspSandboxType.cs b/src/Enums/CspSandboxType.cs index 1716d9b..9009875 100644 --- a/src/Enums/CspSandboxType.cs +++ b/src/Enums/CspSandboxType.cs @@ -1,5 +1,10 @@ namespace OwaspHeaders.Core.Enums { + /// Please note: these enum values are named after the CSP Sandbox Types + /// exactly. This is so that we can use the value as a string, without having to + /// do any C# string magic (and waste cycles doing so) to get the right names. + /// This does mean that Rider (et al.) will tell you that the naming convention + /// here is non-standard. public enum CspSandboxType { allowForms, diff --git a/src/Enums/ReferrerPolicyOptions.cs b/src/Enums/ReferrerPolicyOptions.cs index 70916f1..3f98454 100644 --- a/src/Enums/ReferrerPolicyOptions.cs +++ b/src/Enums/ReferrerPolicyOptions.cs @@ -1,5 +1,10 @@ namespace OwaspHeaders.Core.Enums { + /// Please note: these enum values are named after Referrer Policy Options + /// exactly. This is so that we can use the value as a string, without having to + /// do any C# string magic (and waste cycles doing so) to get the right names. + /// This does mean that Rider (et al.) will tell you that the naming convention + /// here is non-standard. public enum ReferrerPolicyOptions { noReferrer, diff --git a/src/Enums/XFrameOptions.cs b/src/Enums/XFrameOptions.cs index 4f6c304..fe28308 100644 --- a/src/Enums/XFrameOptions.cs +++ b/src/Enums/XFrameOptions.cs @@ -1,5 +1,11 @@ namespace OwaspHeaders.Core.Enums { + /// Please note: these enum values are named after the X-Frame-Options + /// values exactly. This is so that we can use the value as a string, without + /// having to do any C# string magic (and waste cycles doing so) to get the right + /// names. + /// This does mean that Rider (et al.) will tell you that the naming convention + /// here is non-standard. public enum XFrameOptions { Deny, diff --git a/src/Enums/XPermittedCrossDomainOptionValue.cs b/src/Enums/XPermittedCrossDomainOptionValue.cs index b8989df..89d543b 100644 --- a/src/Enums/XPermittedCrossDomainOptionValue.cs +++ b/src/Enums/XPermittedCrossDomainOptionValue.cs @@ -1,5 +1,11 @@ namespace OwaspHeaders.Core.Enums { + /// + /// Please note: these enum values are named after the X-Permitted-Cross-Domain-Options + /// values exactly. This is so that we can use the value as a string, without having to + /// do any C# string magic (and waste cycles doing so) to get the right names. + /// This does mean that Rider (et al.) will tell you that the naming convention + /// here is non-standard. public enum XPermittedCrossDomainOptionValue { none, diff --git a/src/Extensions/ContentSecurityPolicyExtensions.cs b/src/Extensions/ContentSecurityPolicyExtensions.cs index 4c9b33d..f0c0523 100644 --- a/src/Extensions/ContentSecurityPolicyExtensions.cs +++ b/src/Extensions/ContentSecurityPolicyExtensions.cs @@ -1,16 +1,13 @@ -using System.Collections.Generic; -using OwaspHeaders.Core.Enums; -using OwaspHeaders.Core.Models; - -namespace OwaspHeaders.Core.Extensions +namespace OwaspHeaders.Core.Extensions { public static class ContentSecurityPolicyExtensions { /// /// Used to set the Content Security Policy URIs for a given /// - public static SecureHeadersMiddlewareConfiguration SetCspUris - (this SecureHeadersMiddlewareConfiguration config, List baseUri, + public static SecureHeadersMiddlewareConfiguration SetCspUris( + this SecureHeadersMiddlewareConfiguration config, + List baseUri, CspUriType cspUriType) { if (config.UseContentSecurityPolicy) @@ -22,7 +19,8 @@ public static SecureHeadersMiddlewareConfiguration SetCspUris } /// - /// Used to set up the Content Security Policy Sandbox for a given or multiple s + /// Used to set up the Content Security Policy Sandbox for a given or multiple + /// s /// public static SecureHeadersMiddlewareConfiguration SetCspSandBox (this SecureHeadersMiddlewareConfiguration config, params CspSandboxType[] sandboxType) diff --git a/src/Extensions/GlobalUsings.cs b/src/Extensions/GlobalUsings.cs new file mode 100644 index 0000000..3b5f2b7 --- /dev/null +++ b/src/Extensions/GlobalUsings.cs @@ -0,0 +1,5 @@ +global using System.Collections.Generic; +global using OwaspHeaders.Core.Enums; +global using OwaspHeaders.Core.Guards; +global using OwaspHeaders.Core.Helpers; +global using OwaspHeaders.Core.Models; diff --git a/src/Extensions/HttpContextExtensions.cs b/src/Extensions/HttpContextExtensions.cs index 6f31347..1ed44b2 100644 --- a/src/Extensions/HttpContextExtensions.cs +++ b/src/Extensions/HttpContextExtensions.cs @@ -1,16 +1,15 @@ -using System; -using Microsoft.AspNetCore.Http; - -namespace OwaspHeaders.Core.Extensions +namespace OwaspHeaders.Core.Extensions { public static class HttpContextExtensions { - public static bool ResponseContainsHeader(this HttpContext httpContext, string header) + private static bool ResponseContainsHeader(this HttpContext httpContext, + string header) { return httpContext.Response.Headers.ContainsKey(header); } - public static bool TryAddHeader(this HttpContext httpContext, string headerName, string headerValue) + public static bool TryAddHeader(this HttpContext httpContext, + string headerName, string headerValue) { if (httpContext.ResponseContainsHeader(headerName)) { @@ -19,12 +18,13 @@ public static bool TryAddHeader(this HttpContext httpContext, string headerName, try { // ASP0019 states that: - // "IDictionary.Add will throw an ArgumentException when attempting to add a duplicate key." + // "IDictionary.Add will throw an ArgumentException when attempting + // to add a duplicate key." // However, we've already done a check to see whether the - // Response.Headers object - // already contains a header with this name (in the above if statement). - // So we'll disable the warning here then immediately restore it - // after we've done what we need to. + // Response.Headers object contains a header with this name (in the + // above if statement). + // So we'll disable the warning here then immediately restore it after + // we've done what we need to. #pragma warning disable ASP0019 httpContext.Response.Headers.Append(headerName, headerValue); #pragma warning restore ASP0019 diff --git a/src/Extensions/SecureHeadersMiddlewareBuilder.cs b/src/Extensions/SecureHeadersMiddlewareBuilder.cs index f1816c0..8f301c1 100644 --- a/src/Extensions/SecureHeadersMiddlewareBuilder.cs +++ b/src/Extensions/SecureHeadersMiddlewareBuilder.cs @@ -7,11 +7,6 @@ // at the following url: // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect-CT -using System; -using System.Collections.Generic; -using OwaspHeaders.Core.Enums; -using OwaspHeaders.Core.Helpers; -using OwaspHeaders.Core.Models; using static OwaspHeaders.Core.Models.CrossOriginResourcePolicy; namespace OwaspHeaders.Core.Extensions @@ -192,10 +187,7 @@ public static SecureHeadersMiddlewareConfiguration UseContentSecurityPolicyRepor bool upgradeInsecureRequests = true, string referrer = null, bool useXContentSecurityPolicy = false) { // Check whether the URI is valid before continuing - if (!reportUri.IsValidHttpsUri()) - { - ArgumentExceptionHelper.RaiseException(nameof(reportUri)); - } + HeaderValueGuardClauses.StringCannotBeNullOrWhitsSpace(reportUri, nameof(reportUri)); config.UseContentSecurityPolicyReportOnly = true; config.UseXContentSecurityPolicy = useXContentSecurityPolicy; @@ -305,24 +297,7 @@ public static SecureHeadersMiddlewareConfiguration UseExpectCt config.ExpectCt = new ExpectCt(reportUri, maxAge, enforce); return config; } - - /// - /// Thanks to Stack Overflow userrawb for this description: - /// This is a common non-standard HTTP response header. It's often included by default in responses constructed via a particular scripting technology. - /// Source: https://stackoverflow.com/a/33580769/1143474 - /// - /// - /// A lot of web security experts recommend removing this header as it exposes the version - /// of the server software. Malicious actors can target your application with attacks relevant - /// to the version of the server software you are using. - /// - public static SecureHeadersMiddlewareConfiguration RemovePoweredByHeader - (this SecureHeadersMiddlewareConfiguration config) - { - config.RemoveXPoweredByHeader = true; - return config; - } - + /// /// The HTTP Cross-Origin-Resource-Policy response header conveys a desire that the browser /// blocks no-cors cross-origin/cross-site requests to the given resource. diff --git a/src/Extensions/SecureHeadersMiddlewareExtensions.cs b/src/Extensions/SecureHeadersMiddlewareExtensions.cs index 2699f77..eada97a 100644 --- a/src/Extensions/SecureHeadersMiddlewareExtensions.cs +++ b/src/Extensions/SecureHeadersMiddlewareExtensions.cs @@ -1,6 +1,4 @@ -using System; -using Microsoft.AspNetCore.Builder; -using OwaspHeaders.Core.Models; +using Microsoft.AspNetCore.Builder; namespace OwaspHeaders.Core.Extensions { @@ -16,7 +14,7 @@ public static class SecureHeadersMiddlewareExtensions /// current default values that they supply. /// /// - /// This method sets up all of the headers which are recommended by OWASP, using + /// This method sets up all the headers which are recommended by OWASP, using /// default values that they recommend in their best practises. Please see the following /// url for the current best practises: /// https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#tab=Best_Practices @@ -32,7 +30,6 @@ public static SecureHeadersMiddlewareConfiguration BuildDefaultConfiguration() .UsePermittedCrossDomainPolicies() .UseReferrerPolicy() .UseCacheControl() - .RemovePoweredByHeader() .UseXssProtection() .UseCrossOriginResourcePolicy() .Build(); @@ -47,22 +44,21 @@ public static SecureHeadersMiddlewareConfiguration BuildDefaultConfiguration() /// The instance of the to use /// /// - /// [OPTIONAL] An instance of the containing all of the config for each request + /// [OPTIONAL] An instance of the + /// containing all the config for each request /// /// /// The with the added /// /// - /// If an instance of is not provided, then the default value - /// from will be provided. + /// If an instance of is not provided, + /// then the default value from will be provided. /// - public static IApplicationBuilder UseSecureHeadersMiddleware(this IApplicationBuilder builder, SecureHeadersMiddlewareConfiguration config = null) + public static IApplicationBuilder UseSecureHeadersMiddleware(this IApplicationBuilder builder, + SecureHeadersMiddlewareConfiguration config = null) { - if (builder == null) - { - throw new ArgumentNullException( - $"{nameof(builder)} cannot be null when setting up OWASP Secure Headers in OwaspHeaders.Core"); - } + ObjectGuardClauses.ObjectCannotBeNull(builder, nameof(builder), + "cannot be null when setting up OWASP Secure Headers in OwaspHeaders.Core"); return builder.UseMiddleware(config ?? BuildDefaultConfiguration()); } } diff --git a/src/Extensions/StringBuilderExtensions.cs b/src/Extensions/StringBuilderExtensions.cs index 82839b7..40f5882 100644 --- a/src/Extensions/StringBuilderExtensions.cs +++ b/src/Extensions/StringBuilderExtensions.cs @@ -1,14 +1,10 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text; -using OwaspHeaders.Core.Enums; -using OwaspHeaders.Core.Models; +using System.Linq; namespace OwaspHeaders.Core.Extensions { public static class StringBuilderExtensions { - private static string EmptySpace = " "; + private const char EmptySpace = ' '; /// /// This method is adapted from the following Stack Overflow answer: @@ -19,15 +15,24 @@ public static class StringBuilderExtensions /// public static StringBuilder TrimEnd(this StringBuilder sb) { - if (sb == null || sb.Length == 0) return sb; + if (sb == null || sb.Length == 0) + { + return sb; + } int i = sb.Length - 1; for (; i >= 0; i--) + { if (!char.IsWhiteSpace(sb[i])) + { break; + } + } if (i < sb.Length - 1) + { sb.Length = i + 1; + } return sb; } @@ -42,18 +47,23 @@ public static StringBuilder TrimEnd(this StringBuilder sb) public static StringBuilder BuildValuesForDirective(this StringBuilder stringBuilder, string directiveName, List directiveValues) { - if (!directiveValues.Any()) return stringBuilder; + if (directiveValues.Count == 0) + { + return stringBuilder; + } stringBuilder.Append(directiveName); if (directiveValues.Any(d => d.CommandType == CspCommandType.Directive)) { - var directives = directiveValues.Where(command => command.CommandType == CspCommandType.Directive); + var directives = directiveValues. + Where(command => command.CommandType == CspCommandType.Directive); stringBuilder.Append(EmptySpace); - if (directives.Any()) + var contentSecurityPolicyElements = directives.ToList(); + if (contentSecurityPolicyElements.Count != 0) { stringBuilder.Append(string.Join(EmptySpace, - directives.Select(directive => $"'{directive.DirectiveOrUri}'"))); + contentSecurityPolicyElements.Select(directive => $"'{directive.DirectiveOrUri}'"))); } } @@ -66,7 +76,7 @@ public static StringBuilder BuildValuesForDirective(this StringBuilder stringBui } stringBuilder.TrimEnd(); - stringBuilder.Append(";"); + stringBuilder.Append(';'); return stringBuilder; } } diff --git a/src/Extensions/StringExtensions.cs b/src/Extensions/StringExtensions.cs deleted file mode 100644 index a87da81..0000000 --- a/src/Extensions/StringExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace OwaspHeaders.Core.Extensions -{ - public static class StringExtensions - { - /// - /// Used to ensure that a passed URI (see ) is a valid URI - /// - /// The URI to check - /// Whether the supplied URI is valid or not (true if so, false otherwise - /// Enforces HTTPS for URI - public static bool IsValidHttpsUri(this string uri) - { - Uri uriResult; - return Uri.TryCreate(uri, UriKind.Absolute, out uriResult) - && uriResult.Scheme == Uri.UriSchemeHttps; - } - } -} diff --git a/src/GlobalUsings.cs b/src/GlobalUsings.cs new file mode 100644 index 0000000..5124301 --- /dev/null +++ b/src/GlobalUsings.cs @@ -0,0 +1,2 @@ +global using System; +global using Microsoft.AspNetCore.Http; diff --git a/src/Guards/HeaderValueGuardClauses.cs b/src/Guards/HeaderValueGuardClauses.cs new file mode 100644 index 0000000..4e97fee --- /dev/null +++ b/src/Guards/HeaderValueGuardClauses.cs @@ -0,0 +1,12 @@ +namespace OwaspHeaders.Core.Guards; + +public static class HeaderValueGuardClauses +{ + public static void StringCannotBeNullOrWhitsSpace(string value, string parameterName) + { + if (string.IsNullOrWhiteSpace(value)) + { + ArgumentExceptionHelper.RaiseException(parameterName); + } + } +} diff --git a/src/Guards/ObjectGuardClauses.cs b/src/Guards/ObjectGuardClauses.cs new file mode 100644 index 0000000..789a70b --- /dev/null +++ b/src/Guards/ObjectGuardClauses.cs @@ -0,0 +1,13 @@ +namespace OwaspHeaders.Core.Guards; + +public static class ObjectGuardClauses +{ + public static void ObjectCannotBeNull(Object obj, string parameterName, string message) + { + if (obj is null) + { + ArgumentExceptionHelper.RaiseArgumentNullException(parameterName, message); + } + } + +} diff --git a/src/Helpers/ArgumentExceptionHelper.cs b/src/Helpers/ArgumentExceptionHelper.cs index 5152cc7..9243219 100644 --- a/src/Helpers/ArgumentExceptionHelper.cs +++ b/src/Helpers/ArgumentExceptionHelper.cs @@ -7,7 +7,12 @@ public static class ArgumentExceptionHelper /// public static void RaiseException(string argumentName) { - throw new System.ArgumentException($"No value for {argumentName} was supplied"); + throw new ArgumentException($"No value for {argumentName} was supplied"); + } + + public static void RaiseArgumentNullException(string argumentName, string message) + { + throw new ArgumentNullException(argumentName, message); } } } diff --git a/src/Helpers/ContentSecurityPolicyHelpers.cs b/src/Helpers/ContentSecurityPolicyHelpers.cs index ad6468b..ee32477 100644 --- a/src/Helpers/ContentSecurityPolicyHelpers.cs +++ b/src/Helpers/ContentSecurityPolicyHelpers.cs @@ -1,7 +1,4 @@ -using OwaspHeaders.Core.Enums; -using OwaspHeaders.Core.Models; - -namespace OwaspHeaders.Core.Helpers +namespace OwaspHeaders.Core.Helpers { public static class ContentSecurityPolicyHelpers { diff --git a/src/Models/CacheControl.cs b/src/Models/CacheControl.cs index 3b63308..fd7debb 100644 --- a/src/Models/CacheControl.cs +++ b/src/Models/CacheControl.cs @@ -1,65 +1,70 @@ -using System.Diagnostics.CodeAnalysis; -using System.Text; - -namespace OwaspHeaders.Core.Models +namespace OwaspHeaders.Core.Models { /// - /// This class represents some of the most commonly used Cache Control directives. For more information on this - /// header, please see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control + /// This class represents some of the most commonly used Cache Control + /// directives. For more information on this header, please see: + /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control /// public class CacheControl : IConfigurationBase { /// - /// Whether all or part of the HTTP response message is intended for a single user and must - /// not be cached by a shared cache. + /// Whether all or part of the HTTP response message is intended for a + /// single user and must not be cached by a shared cache. /// /// The following is taken from the MDN article for cache-control - /// If you forget to add private to a response with personalized content, then that response can be stored in - /// a shared cache and end up being reused for multiple users, which can cause personal information to leak. + /// If you forget to add private to a response with personalized content, + /// then that response can be stored in a shared cache and end up being + /// reused for multiple users, which can cause personal information to + /// leak. /// Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#private /// - public bool Private { get; set; } + private bool Private { get; } /// - /// The maximum age, specified in seconds, that the HTTP client is willing to accept a response. + /// The maximum age, specified in seconds, that the HTTP client is willing + /// to accept a response. /// - public int MaxAge { get; set; } + private int MaxAge { get; } /// - /// Represents whether the response can be cached. If the response cannot be cached, then the origin server + /// Represents whether the response can be cached. If the response cannot be + /// cached, then the origin server /// must be contacted for every request. /// /// /// The following is taken from the MDN article for cache-control - /// Note that no-cache does not mean "don't cache". no-cache allows caches to store a response but requires - /// them to revalidate it before reuse. If the sense of "don't cache" that you want is actually "don't store", + /// Note that no-cache does not mean "don't cache". no-cache allows caches + /// to store a response but requires them to revalidate it before reuse. + /// If the sense of "don't cache" that you want is actually "don't store", /// then no-store is the directive to use. /// Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#no-cache /// - public bool NoCache { get; set; } + private bool NoCache { get; } /// - /// Represents whether the response can be stored in caches and used whilst still fresh + /// Represents whether the response can be stored in caches and used whilst still + /// fresh /// /// /// This is used alongside the MaxAge directive /// - public bool MustRevalidate { get; set; } + private bool MustRevalidate { get; } /// - /// Represents whether the response can be stored anywhere (i.e. either public or private caches) + /// Represents whether the response can be stored anywhere (i.e. either public + /// or private caches) /// - public bool NoStore { get; set; } + private bool NoStore { get; } /// - /// Protected constructor, we can no longer create instances of this - /// class without using the public constructor + /// Protected constructor, we can no longer create instances of this class without + /// using the public constructor /// [ExcludeFromCodeCoverage] protected CacheControl() { } - public CacheControl(bool @private, int maxAge = 86400, bool noCache = false, bool noStore = false, - bool mustRevalidate = false) + public CacheControl(bool @private, int maxAge = 86400, bool noCache = false, + bool noStore = false, bool mustRevalidate = false) { Private = @private; MaxAge = maxAge; diff --git a/src/Models/ContentSecurityPolicyConfiguration.cs b/src/Models/ContentSecurityPolicyConfiguration.cs index 5fb7909..5027415 100644 --- a/src/Models/ContentSecurityPolicyConfiguration.cs +++ b/src/Models/ContentSecurityPolicyConfiguration.cs @@ -1,9 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text; -using OwaspHeaders.Core.Extensions; - -namespace OwaspHeaders.Core.Models +namespace OwaspHeaders.Core.Models { public class ContentSecurityPolicyConfiguration : IConfigurationBase { @@ -18,12 +13,13 @@ public class ContentSecurityPolicyConfiguration : IConfigurationBase public List DefaultSrc { get; set; } /// - /// The script-src values to use (valid sources for sources for JavaScript) + /// The script-src values to use (valid sources for JavaScript) /// public List ScriptSrc { get; set; } /// - /// The object-src values to use (valid sources for the object, embed, and applet elements) + /// The object-src values to use (valid sources for the object, embed, and applet + /// elements) /// public List ObjectSrc { get; set; } @@ -38,22 +34,26 @@ public class ContentSecurityPolicyConfiguration : IConfigurationBase public List ImgSrc { get; set; } /// - /// The media-src values to use (valid sources for loading media using the audio and video elements) + /// The media-src values to use (valid sources for loading media using the audio + /// and video elements) /// public List MediaSrc { get; set; } /// - /// The frame-src values to use (valid sources for nested browsing contexts loading using elements such as frame and iframe) + /// The frame-src values to use (valid sources for nested browsing contexts loading + /// using elements such as frame and iframe) /// public List FrameSrc { get; set; } /// - /// The child-src values to use (valid sources for web workers and nested browsing contexts loaded using elements such as frame and iframe) + /// The child-src values to use (valid sources for web workers and nested browsing + /// contexts loaded using elements such as frame and iframe) /// public List ChildSrc { get; set; } /// - /// The frame-ancestors values to use (valid parents that may embed a page using frame, iframe, object, embed, or applet) + /// The frame-ancestors values to use (valid parents that may embed a page using frame, + /// iframe, object, embed, or applet) /// public List FrameAncestors { get; set; } @@ -63,7 +63,8 @@ public class ContentSecurityPolicyConfiguration : IConfigurationBase public List FontSrc { get; set; } /// - /// The connect-src values to use (restricts the URLs which can be loaded using script interfaces) + /// The connect-src values to use (restricts the URLs which can be loaded using script + /// interfaces) /// public List ConnectSrc { get; set; } @@ -73,7 +74,8 @@ public class ContentSecurityPolicyConfiguration : IConfigurationBase public List ManifestSrc { get; set; } /// - /// The form-action values to use (restricts the URLs which can be used as the target of a form submissions from a given context) + /// The form-action values to use (restricts the URLs which can be used as the target of + /// a form submissions from a given context) /// public List FormAction { get; set; } @@ -83,49 +85,58 @@ public class ContentSecurityPolicyConfiguration : IConfigurationBase public ContentSecurityPolicySandBox Sandbox { get; set; } /// - /// Define the set of plugins that can be invoked by the protected resource by limiting the types of resources that can be embedded + /// Define the set of plugins that can be invoked by the protected resource by limiting + /// the types of resources that can be embedded /// - public string PluginTypes { get; set; } + private string PluginTypes { get; } /// - /// Whether to include the block-all-mixed-content directive (prevents loading any assets using HTTP when the page is loaded using HTTPS) + /// Whether to include the block-all-mixed-content directive (prevents loading any assets + /// using HTTP when the page is loaded using HTTPS) /// - public bool BlockAllMixedContent { get; set; } + private bool BlockAllMixedContent { get; } /// - /// Whether to include the upgrade-insecure-requests directive (instructs user agents to treat all of a site's insecure URLs as though they have been replaced with secure URLs) + /// Whether to include the upgrade-insecure-requests directive (instructs user agents to + /// treat all of a site's insecure URLs as though they have been replaced with secure URLs) /// - public bool UpgradeInsecureRequests { get; set; } + private bool UpgradeInsecureRequests { get; } /// /// Define information user agent must send in Referer header /// - public string Referrer { get; set; } + private string Referrer { get; } /// - /// Whether to instruct the user agent to report attempts to violate the Content Security Policy + /// Whether to instruct the user agent to report attempts to violate the Content-Security + /// Policy /// - public string ReportUri { get; set; } + public string ReportUri { get; init; } + /// + /// Protected constructor, we can no longer create instances of this class without + /// using the public constructor + /// + [ExcludeFromCodeCoverage] protected ContentSecurityPolicyConfiguration() { } public ContentSecurityPolicyConfiguration(string pluginTypes, bool blockAllMixedContent, bool upgradeInsecureRequests, string referrer, string reportUri) { - BaseUri = new List(); - DefaultSrc = new List(); - ScriptSrc = new List(); - ObjectSrc = new List(); - StyleSrc = new List(); - ImgSrc = new List(); - MediaSrc = new List(); - FrameSrc = new List(); - ChildSrc = new List(); - FrameAncestors = new List(); - FontSrc = new List(); - ConnectSrc = new List(); - ManifestSrc = new List(); - FormAction = new List(); + BaseUri = []; + DefaultSrc = []; + ScriptSrc = []; + ObjectSrc = []; + StyleSrc = []; + ImgSrc = []; + MediaSrc = []; + FrameSrc = []; + ChildSrc = []; + FrameAncestors = []; + FontSrc = []; + ConnectSrc = []; + ManifestSrc = []; + FormAction = []; PluginTypes = pluginTypes; BlockAllMixedContent = blockAllMixedContent; @@ -195,10 +206,13 @@ public string BuildHeaderValue() private bool AnyValues() { - return BaseUri.Any() || DefaultSrc.Any() || ScriptSrc.Any() || ObjectSrc.Any() - || StyleSrc.Any() || ImgSrc.Any() || MediaSrc.Any() || FrameSrc.Any() - || ChildSrc.Any() || FrameAncestors.Any() || FontSrc.Any() || ConnectSrc.Any() - || ManifestSrc.Any() || FormAction.Any(); + return BaseUri.Count != 0 || DefaultSrc.Count != 0 || + ScriptSrc.Count != 0 || ObjectSrc.Count != 0 || + StyleSrc.Count != 0 || ImgSrc.Count != 0 || + MediaSrc.Count != 0 || FrameSrc.Count != 0 || + ChildSrc.Count != 0 || FrameAncestors.Count != 0 || + FontSrc.Count != 0 || ConnectSrc.Count != 0 || + ManifestSrc.Count != 0 || FormAction.Count != 0; } } } diff --git a/src/Models/ContentSecurityPolicyElement.cs b/src/Models/ContentSecurityPolicyElement.cs index 15c344d..c42c221 100644 --- a/src/Models/ContentSecurityPolicyElement.cs +++ b/src/Models/ContentSecurityPolicyElement.cs @@ -1,10 +1,8 @@ -using OwaspHeaders.Core.Enums; - -namespace OwaspHeaders.Core.Models +namespace OwaspHeaders.Core.Models { public class ContentSecurityPolicyElement { - public CspCommandType CommandType { get; set; } - public string DirectiveOrUri { get; set; } + public CspCommandType CommandType { get; init; } + public string DirectiveOrUri { get; init; } } } diff --git a/src/Models/ContentSecurityPolicyExtensions.cs b/src/Models/ContentSecurityPolicyExtensions.cs index c997c59..90c91fd 100644 --- a/src/Models/ContentSecurityPolicyExtensions.cs +++ b/src/Models/ContentSecurityPolicyExtensions.cs @@ -1,8 +1,4 @@ -using System.Collections.Generic; -using OwaspHeaders.Core.Enums; -using OwaspHeaders.Core.Helpers; - -namespace OwaspHeaders.Core.Models +namespace OwaspHeaders.Core.Models { public static class ContentSecurityPolicyExtensions { diff --git a/src/Models/ContentSecurityPolicyReportOnlyConfiguration.cs b/src/Models/ContentSecurityPolicyReportOnlyConfiguration.cs index af156f7..82eeb19 100644 --- a/src/Models/ContentSecurityPolicyReportOnlyConfiguration.cs +++ b/src/Models/ContentSecurityPolicyReportOnlyConfiguration.cs @@ -1,6 +1,4 @@ -using OwaspHeaders.Core.Helpers; - -namespace OwaspHeaders.Core.Models +namespace OwaspHeaders.Core.Models { /// /// Represents a Report-Only CSP rule set. See the following link for more information: @@ -16,19 +14,15 @@ public ContentSecurityPolicyReportOnlyConfiguration(string pluginTypes, bool blo public new string BuildHeaderValue() { - if (string.IsNullOrWhiteSpace(ReportUri)) - { - // We cannot have an empty ReportUri in a Report-Uri only CSP - // Whilst this doesn't break the spec, as it states: - // "The CSP report-uri directive should be used with this header, - // otherwise this header will be an expensive no-op machine" - // the decision has been taken to raise an exception here when - // an empty one has been supplied. - // This decision was taken to ensure that the Report-Uri will - // actually be called - otherwise this response header is useless - ArgumentExceptionHelper.RaiseException(nameof(ReportUri)); - } - + // We cannot have an empty ReportUri in a Report-Uri only CSP + // Whilst this doesn't break the spec, as it states: + // "The CSP report-uri directive should be used with this header, + // otherwise this header will be an expensive no-op machine" + // the decision has been taken to raise an exception here when + // an empty one has been supplied. + // This decision was taken to ensure that the Report-Uri will + // actually be called - otherwise this response header is useless + HeaderValueGuardClauses.StringCannotBeNullOrWhitsSpace(ReportUri, nameof(ReportUri)); return base.BuildHeaderValue(); } } diff --git a/src/Models/ContentSecurityPolicySandBox.cs b/src/Models/ContentSecurityPolicySandBox.cs index ce73dce..76ea5a3 100644 --- a/src/Models/ContentSecurityPolicySandBox.cs +++ b/src/Models/ContentSecurityPolicySandBox.cs @@ -1,14 +1,15 @@ -using System.Collections.Generic; -using System.Text; -using OwaspHeaders.Core.Enums; - -namespace OwaspHeaders.Core.Models +namespace OwaspHeaders.Core.Models { public class ContentSecurityPolicySandBox : IConfigurationBase { + /// + /// Protected constructor, we can no longer create instances of this class without + /// using the public constructor + /// + [ExcludeFromCodeCoverage] protected ContentSecurityPolicySandBox() { } - public IList SandboxTypes { get; set; } + private IList SandboxTypes { get; } public ContentSecurityPolicySandBox(params CspSandboxType[] sandboxType) { @@ -55,7 +56,7 @@ public string BuildHeaderValue() } } - returnStr.Append(";"); + returnStr.Append(';'); return returnStr.ToString(); } } diff --git a/src/Models/CrossOriginResourcePolicy.cs b/src/Models/CrossOriginResourcePolicy.cs index 0b88930..286f6ef 100644 --- a/src/Models/CrossOriginResourcePolicy.cs +++ b/src/Models/CrossOriginResourcePolicy.cs @@ -2,11 +2,16 @@ { /// /// Cross-Origin-Resource-Policy - /// This response header(also named CORP) allows to define a policy that lets web sites and applications opt in to protection - /// against certain requests from other origins(such as those issued with elements like