diff --git a/src/Constants.cs b/src/Constants.cs index 47e1f7e..814a154 100644 --- a/src/Constants.cs +++ b/src/Constants.cs @@ -9,6 +9,7 @@ public static class Constants public const string XssProtectionHeaderName = "X-XSS-Protection"; public const string XContentTypeOptionsHeaderName = "X-Content-Type-Options"; + public const string XContentTypeOptionsValue = "nosniff"; public const string ContentSecurityPolicyHeaderName = "Content-Security-Policy"; diff --git a/src/OwaspHeaders.Core.csproj b/src/OwaspHeaders.Core.csproj index f91b1aa..2f13e2a 100644 --- a/src/OwaspHeaders.Core.csproj +++ b/src/OwaspHeaders.Core.csproj @@ -8,7 +8,7 @@ OwaspHeaders.Core - 9.5.0 + 9.6.0 Jamie Taylor RJJ Software Ltd MIT diff --git a/src/SecureHeadersMiddleware.cs b/src/SecureHeadersMiddleware.cs index 9e8a5f5..e251312 100644 --- a/src/SecureHeadersMiddleware.cs +++ b/src/SecureHeadersMiddleware.cs @@ -7,9 +7,19 @@ namespace OwaspHeaders.Core; /// A middleware for injecting OWASP recommended headers into a /// HTTP Request /// -public class SecureHeadersMiddleware(RequestDelegate next, SecureHeadersMiddlewareConfiguration config) +public class SecureHeadersMiddleware() { - private string _calculatedContentSecurityPolicy; +private string _calculatedContentSecurityPolicy; +private readonly Dictionary _headers; +private readonly RequestDelegate _next; +private readonly SecureHeadersMiddlewareConfiguration _config; + +public SecureHeadersMiddleware(RequestDelegate next, SecureHeadersMiddlewareConfiguration config) : this() +{ + _config = config; + _next = next; + _headers = new Dictionary(); +} /// /// The main task of the middleware. This will be invoked whenever @@ -19,106 +29,121 @@ public class SecureHeadersMiddleware(RequestDelegate next, SecureHeadersMiddlewa /// public async Task InvokeAsync(HttpContext httpContext) { - if (config == null) + if (_config == null) { - throw new ArgumentException($"Expected an instance of the {nameof(SecureHeadersMiddlewareConfiguration)} object."); + throw new ArgumentException( + $"Expected an instance of the {nameof(SecureHeadersMiddlewareConfiguration)} object."); } if (!RequestShouldBeIgnored(httpContext.Request.Path)) { - if (config.UseHsts) + if (_headers.Count == 0) { - httpContext.TryAddHeader(Constants.StrictTransportSecurityHeaderName, - config.HstsConfiguration.BuildHeaderValue()); + GenerateRelevantHeaders(); } - if (config.UseXFrameOptions) + foreach (var (key, value) in _headers) { - httpContext.TryAddHeader(Constants.XFrameOptionsHeaderName, - config.XFrameOptionsConfiguration.BuildHeaderValue()); + httpContext.TryAddHeader(key, value); } + } - if (config.UseXssProtection) - { - httpContext.TryAddHeader(Constants.XssProtectionHeaderName, - config.XssConfiguration.BuildHeaderValue()); - } + // Call the next middleware in the chain + await _next(httpContext); +} - if (config.UseXContentTypeOptions) - { - httpContext.TryAddHeader(Constants.XContentTypeOptionsHeaderName, "nosniff"); - } +private void GenerateRelevantHeaders() +{ + if (_config.UseHsts) + { + _headers.TryAdd(Constants.StrictTransportSecurityHeaderName, + _config.HstsConfiguration.BuildHeaderValue()); + } - if (config.UseContentSecurityPolicyReportOnly) - { - if (string.IsNullOrWhiteSpace(_calculatedContentSecurityPolicy)) - { - _calculatedContentSecurityPolicy = - config.ContentSecurityPolicyReportOnlyConfiguration.BuildHeaderValue(); - } - - httpContext.TryAddHeader(Constants.ContentSecurityPolicyReportOnlyHeaderName, - _calculatedContentSecurityPolicy); - } - else if (config.UseContentSecurityPolicy) - { - if (string.IsNullOrWhiteSpace(_calculatedContentSecurityPolicy)) - { - _calculatedContentSecurityPolicy = config.ContentSecurityPolicyConfiguration.BuildHeaderValue(); - } + if (_config.UseXFrameOptions) + { + _headers.TryAdd(Constants.XFrameOptionsHeaderName, + _config.XFrameOptionsConfiguration.BuildHeaderValue()); + } - httpContext.TryAddHeader(Constants.ContentSecurityPolicyHeaderName, - _calculatedContentSecurityPolicy); - } + if (_config.UseXssProtection) + { + _headers.TryAdd(Constants.XssProtectionHeaderName, + _config.XssConfiguration.BuildHeaderValue()); + } - if (config.UseXContentSecurityPolicy) - { - httpContext.TryAddHeader(Constants.XContentSecurityPolicyHeaderName, - config.ContentSecurityPolicyConfiguration.BuildHeaderValue()); - } + if (_config.UseXContentTypeOptions) + { + _headers.TryAdd(Constants.XContentTypeOptionsHeaderName, Constants.XContentTypeOptionsValue); + } - if (config.UsePermittedCrossDomainPolicy) + if (_config.UseContentSecurityPolicyReportOnly) + { + if (string.IsNullOrWhiteSpace(_calculatedContentSecurityPolicy)) { - httpContext.TryAddHeader(Constants.PermittedCrossDomainPoliciesHeaderName, - config.PermittedCrossDomainPolicyConfiguration.BuildHeaderValue()); + _calculatedContentSecurityPolicy = + _config.ContentSecurityPolicyReportOnlyConfiguration.BuildHeaderValue(); } - if (config.UseReferrerPolicy) + _headers.TryAdd(Constants.ContentSecurityPolicyReportOnlyHeaderName, + _calculatedContentSecurityPolicy); + } + else if (_config.UseContentSecurityPolicy) + { + if (string.IsNullOrWhiteSpace(_calculatedContentSecurityPolicy)) { - httpContext.TryAddHeader(Constants.ReferrerPolicyHeaderName, - config.ReferrerPolicy.BuildHeaderValue()); + _calculatedContentSecurityPolicy = _config.ContentSecurityPolicyConfiguration.BuildHeaderValue(); } - if (config.UseExpectCt) - { - httpContext.TryAddHeader(Constants.ExpectCtHeaderName, - config.ExpectCt.BuildHeaderValue()); - } + _headers.TryAdd(Constants.ContentSecurityPolicyHeaderName, + _calculatedContentSecurityPolicy); + } - if (config.UseCacheControl) - { - httpContext.TryAddHeader(Constants.CacheControlHeaderName, - config.CacheControl.BuildHeaderValue()); - } + if (_config.UseXContentSecurityPolicy) + { + _headers.TryAdd(Constants.XContentSecurityPolicyHeaderName, + _config.ContentSecurityPolicyConfiguration.BuildHeaderValue()); + } - if (config.UseCrossOriginResourcePolicy) - { - httpContext.TryAddHeader(Constants.CrossOriginResourcePolicyHeaderName, - config.CrossOriginResourcePolicy.BuildHeaderValue()); - } + if (_config.UsePermittedCrossDomainPolicy) + { + _headers.TryAdd(Constants.PermittedCrossDomainPoliciesHeaderName, + _config.PermittedCrossDomainPolicyConfiguration.BuildHeaderValue()); } - // Call the next middleware in the chain - await next(httpContext); + if (_config.UseReferrerPolicy) + { + _headers.TryAdd(Constants.ReferrerPolicyHeaderName, + _config.ReferrerPolicy.BuildHeaderValue()); + } + + if (_config.UseExpectCt) + { + _headers.TryAdd(Constants.ExpectCtHeaderName, + _config.ExpectCt.BuildHeaderValue()); + } + + if (_config.UseCacheControl) + { + _headers.TryAdd(Constants.CacheControlHeaderName, + _config.CacheControl.BuildHeaderValue()); + } + + if (_config.UseCrossOriginResourcePolicy) + { + _headers.TryAdd(Constants.CrossOriginResourcePolicyHeaderName, + _config.CrossOriginResourcePolicy.BuildHeaderValue()); + } } private bool RequestShouldBeIgnored(PathString requestedPath) { - if (config.UrlsToIgnore.Count == 0) + if (_config.UrlsToIgnore.Count == 0) { return false; } - return requestedPath.HasValue && config.UrlsToIgnore.Any(url => url.Equals(requestedPath.Value!, StringComparison.InvariantCulture)); + return requestedPath.HasValue && + _config.UrlsToIgnore.Any(url => url.Equals(requestedPath.Value!, StringComparison.InvariantCulture)); } }