Skip to content

Commit

Permalink
Header Caching (#152)
Browse files Browse the repository at this point in the history
* Calculated header values are now cached for the next time the middleware is processed.

* Minor version bump

* Strange formatting requirement that all level 1 indentation within SecureHeadersMiddleware be removed

---------

Co-authored-by: Jamie Taylor <jamie.taylor@rjj-software.co.uk>
  • Loading branch information
GaProgMan and jamie-taylor-rjj authored Dec 26, 2024
1 parent 84080ad commit 43fadbd
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 70 deletions.
1 change: 1 addition & 0 deletions src/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
2 changes: 1 addition & 1 deletion src/OwaspHeaders.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<!-- NuGet metadata -->
<PackageId>OwaspHeaders.Core</PackageId>
<Version>9.5.0</Version>
<Version>9.6.0</Version>
<Authors>Jamie Taylor</Authors>
<Company>RJJ Software Ltd</Company>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand Down
163 changes: 94 additions & 69 deletions src/SecureHeadersMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,19 @@ namespace OwaspHeaders.Core;
/// A middleware for injecting OWASP recommended headers into a
/// HTTP Request
/// </summary>
public class SecureHeadersMiddleware(RequestDelegate next, SecureHeadersMiddlewareConfiguration config)
public class SecureHeadersMiddleware()
{
private string _calculatedContentSecurityPolicy;
private string _calculatedContentSecurityPolicy;
private readonly Dictionary<string, string> _headers;
private readonly RequestDelegate _next;
private readonly SecureHeadersMiddlewareConfiguration _config;

public SecureHeadersMiddleware(RequestDelegate next, SecureHeadersMiddlewareConfiguration config) : this()
{
_config = config;
_next = next;
_headers = new Dictionary<string, string>();
}

/// <summary>
/// The main task of the middleware. This will be invoked whenever
Expand All @@ -19,106 +29,121 @@ public class SecureHeadersMiddleware(RequestDelegate next, SecureHeadersMiddlewa
/// <returns></returns>
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));
}
}

0 comments on commit 43fadbd

Please sign in to comment.