Adds Https Redirection and Hsts Middlewares (#264)
This commit is contained in:
parent
83d1396330
commit
758f9fcea5
|
|
@ -57,6 +57,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||||
version.xml = version.xml
|
version.xml = version.xml
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpsPolicy", "src\Microsoft.AspNetCore.HttpsPolicy\Microsoft.AspNetCore.HttpsPolicy.csproj", "{4D39C29B-4EC8-497C-B411-922DA494D71B}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpsPolicySample", "samples\HttpsPolicySample\HttpsPolicySample.csproj", "{AC424AEE-4883-49C6-945F-2FC916B8CA1C}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpsPolicy.Tests", "test\Microsoft.AspNetCore.HttpsEnforcement.Tests\Microsoft.AspNetCore.HttpsPolicy.Tests.csproj", "{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -111,6 +117,18 @@ Global
|
||||||
{B2A3CE38-51B2-4486-982C-98C380AF140E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{B2A3CE38-51B2-4486-982C-98C380AF140E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{B2A3CE38-51B2-4486-982C-98C380AF140E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{B2A3CE38-51B2-4486-982C-98C380AF140E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{B2A3CE38-51B2-4486-982C-98C380AF140E}.Release|Any CPU.Build.0 = Release|Any CPU
|
{B2A3CE38-51B2-4486-982C-98C380AF140E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4D39C29B-4EC8-497C-B411-922DA494D71B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4D39C29B-4EC8-497C-B411-922DA494D71B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4D39C29B-4EC8-497C-B411-922DA494D71B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4D39C29B-4EC8-497C-B411-922DA494D71B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{AC424AEE-4883-49C6-945F-2FC916B8CA1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{AC424AEE-4883-49C6-945F-2FC916B8CA1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{AC424AEE-4883-49C6-945F-2FC916B8CA1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{AC424AEE-4883-49C6-945F-2FC916B8CA1C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -128,6 +146,9 @@ Global
|
||||||
{45308A9D-F4C6-46A8-A24F-E73D995CC223} = {A5076D28-FA7E-4606-9410-FEDD0D603527}
|
{45308A9D-F4C6-46A8-A24F-E73D995CC223} = {A5076D28-FA7E-4606-9410-FEDD0D603527}
|
||||||
{3360A5D1-70C0-49EE-9051-04A6A6B836DC} = {8437B0F3-3894-4828-A945-A9187F37631D}
|
{3360A5D1-70C0-49EE-9051-04A6A6B836DC} = {8437B0F3-3894-4828-A945-A9187F37631D}
|
||||||
{B2A3CE38-51B2-4486-982C-98C380AF140E} = {9587FE9F-5A17-42C4-8021-E87F59CECB98}
|
{B2A3CE38-51B2-4486-982C-98C380AF140E} = {9587FE9F-5A17-42C4-8021-E87F59CECB98}
|
||||||
|
{4D39C29B-4EC8-497C-B411-922DA494D71B} = {A5076D28-FA7E-4606-9410-FEDD0D603527}
|
||||||
|
{AC424AEE-4883-49C6-945F-2FC916B8CA1C} = {9587FE9F-5A17-42C4-8021-E87F59CECB98}
|
||||||
|
{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE} = {8437B0F3-3894-4828-A945-A9187F37631D}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {4518E9CE-3680-4E05-9259-B64EA7807158}
|
SolutionGuid = {4518E9CE-3680-4E05-9259-B64EA7807158}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
|
||||||
|
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp2.0</TargetFrameworks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Https" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.HttpsPolicy\Microsoft.AspNetCore.HttpsPolicy.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:31894/",
|
||||||
|
"sslPort": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"HttpsSample": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"applicationUrl": "http://localhost:31895/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.HttpsPolicy;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace HttpsSample
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
|
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddHttpsRedirection(options =>
|
||||||
|
{
|
||||||
|
options.RedirectStatusCode = StatusCodes.Status301MovedPermanently;
|
||||||
|
options.TlsPort = 5001;
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddHsts(options =>
|
||||||
|
{
|
||||||
|
options.MaxAge = TimeSpan.FromDays(30);
|
||||||
|
options.Preload = true;
|
||||||
|
options.IncludeSubDomains = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app, IHostingEnvironment environment)
|
||||||
|
{
|
||||||
|
if (!environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseHsts();
|
||||||
|
}
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync("Hello world!");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry point for the application.
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var host = new WebHostBuilder()
|
||||||
|
.UseKestrel(
|
||||||
|
options =>
|
||||||
|
{
|
||||||
|
options.Listen(new IPEndPoint(IPAddress.Loopback, 5001), listenOptions =>
|
||||||
|
{
|
||||||
|
listenOptions.UseHttps("testCert.pfx", "testPassword");
|
||||||
|
});
|
||||||
|
options.Listen(new IPEndPoint(IPAddress.Loopback, 5000), listenOptions =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory()) // for the cert file
|
||||||
|
.UseStartup<Startup>()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
host.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.HttpsPolicy;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Builder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for the HSTS middleware.
|
||||||
|
/// </summary>
|
||||||
|
public static class HstsBuilderExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds middleware for using HSTS, which adds the Strict-Transport-Security header.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="app">The <see cref="IApplicationBuilder"/> instance this method extends.</param>
|
||||||
|
public static IApplicationBuilder UseHsts(this IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
if (app == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(app));
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.UseMiddleware<HstsMiddleware>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.HttpsPolicy
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enables HTTP Strict Transport Security (HSTS)
|
||||||
|
/// See https://tools.ietf.org/html/rfc6797.
|
||||||
|
/// </summary>
|
||||||
|
public class HstsMiddleware
|
||||||
|
{
|
||||||
|
private const string IncludeSubDomains = "; includeSubDomains";
|
||||||
|
private const string Preload = "; preload";
|
||||||
|
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly StringValues _strictTransportSecurityValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize the HSTS middleware.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="next"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
|
public HstsMiddleware(RequestDelegate next, IOptions<HstsOptions> options)
|
||||||
|
{
|
||||||
|
if (next == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(next));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
_next = next;
|
||||||
|
|
||||||
|
var hstsOptions = options.Value;
|
||||||
|
var maxAge = Convert.ToInt64(Math.Floor(hstsOptions.MaxAge.TotalSeconds))
|
||||||
|
.ToString(CultureInfo.InvariantCulture);
|
||||||
|
var includeSubdomains = hstsOptions.IncludeSubDomains ? IncludeSubDomains : StringSegment.Empty;
|
||||||
|
var preload = hstsOptions.Preload ? Preload : StringSegment.Empty;
|
||||||
|
_strictTransportSecurityValue = new StringValues($"max-age={maxAge}{includeSubdomains}{preload}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoke the middleware.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The <see cref="HttpContext"/>.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task Invoke(HttpContext context)
|
||||||
|
{
|
||||||
|
if (context.Request.IsHttps)
|
||||||
|
{
|
||||||
|
context.Response.Headers[HeaderNames.StrictTransportSecurity] = _strictTransportSecurityValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _next(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.HttpsPolicy
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Options for the Hsts Middleware
|
||||||
|
/// </summary>
|
||||||
|
public class HstsOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the max-age parameter of the Strict-Transport-Security header.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Max-age is required; defaults to 30 days.
|
||||||
|
/// See: https://tools.ietf.org/html/rfc6797#section-6.1.1
|
||||||
|
/// </remarks>
|
||||||
|
public TimeSpan MaxAge { get; set; } = TimeSpan.FromDays(30);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables includeSubDomain parameter of the Strict-Transport-Security header.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// See: https://tools.ietf.org/html/rfc6797#section-6.1.2
|
||||||
|
/// </remarks>
|
||||||
|
public bool IncludeSubDomains { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the preload parameter of the Strict-Transport-Security header.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Preload is not part of the RFC specification, but is supported by web browsers
|
||||||
|
/// to preload HSTS sites on fresh install. See https://hstspreload.org/.
|
||||||
|
/// </remarks>
|
||||||
|
public bool Preload { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.HttpsPolicy;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Builder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for the HSTS middleware.
|
||||||
|
/// </summary>
|
||||||
|
public static class HstsServicesExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds HSTS services.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="services">The <see cref="IServiceCollection"/> for adding services.</param>
|
||||||
|
/// <param name="configureOptions">A delegate to configure the <see cref="HstsOptions"/>.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IServiceCollection AddHsts(this IServiceCollection services, Action<HstsOptions> configureOptions)
|
||||||
|
{
|
||||||
|
if (services == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(services));
|
||||||
|
}
|
||||||
|
if (configureOptions == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(configureOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
services.Configure(configureOptions);
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.HttpsPolicy;
|
||||||
|
using Microsoft.AspNetCore.Rewrite;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Builder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for the HttpsRedirection middleware.
|
||||||
|
/// </summary>
|
||||||
|
public static class HttpsPolicyBuilderExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds middleware for redirecting HTTP Requests to HTTPS.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="app">The <see cref="IApplicationBuilder"/> instance this method extends.</param>
|
||||||
|
/// <returns>The <see cref="IApplicationBuilder"/> for HttpsRedirection.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// HTTPS Enforcement interanlly uses the UrlRewrite middleware to redirect HTTP requests to HTTPS.
|
||||||
|
/// </remarks>
|
||||||
|
public static IApplicationBuilder UseHttpsRedirection(this IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
if (app == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(app));
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = app.ApplicationServices.GetRequiredService<IOptions<HttpsRedirectionOptions>>().Value;
|
||||||
|
|
||||||
|
var rewriteOptions = new RewriteOptions();
|
||||||
|
rewriteOptions.AddRedirectToHttps(
|
||||||
|
options.RedirectStatusCode,
|
||||||
|
options.TlsPort);
|
||||||
|
|
||||||
|
app.UseRewriter(rewriteOptions);
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.HttpsPolicy
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Options for the HttpsRedirection middleware
|
||||||
|
/// </summary>
|
||||||
|
public class HttpsRedirectionOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The status code to redirect the response to.
|
||||||
|
/// </summary>
|
||||||
|
public int RedirectStatusCode { get; set; } = StatusCodes.Status301MovedPermanently;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The TLS port to be added to the redirected URL.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Defaults to 443 if not provided.
|
||||||
|
/// </remarks>
|
||||||
|
public int? TlsPort { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.HttpsPolicy;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Builder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for the HttpsRedirection middleware.
|
||||||
|
/// </summary>
|
||||||
|
public static class HttpsRedirectionServicesExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds HTTPS redirection services.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="services">The <see cref="IServiceCollection"/> for adding services.</param>
|
||||||
|
/// <param name="configureOptions">A delegate to configure the <see cref="HttpsRedirectionOptions"/>.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IServiceCollection AddHttpsRedirection(this IServiceCollection services, Action<HttpsRedirectionOptions> configureOptions)
|
||||||
|
{
|
||||||
|
if (services == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(services));
|
||||||
|
}
|
||||||
|
if (configureOptions == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(configureOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
services.Configure(configureOptions);
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Description>
|
||||||
|
ASP.NET Core basic middleware for supporting HTTPS Redirection and HTTP Strict-Transport-Security.
|
||||||
|
</Description>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<PackageTags>aspnetcore;https;hsts</PackageTags>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Rewrite\Microsoft.AspNetCore.Rewrite.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.TestHost;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.HttpsPolicy.Tests
|
||||||
|
{
|
||||||
|
public class HstsMiddlewareTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task SetOptionsWithDefault_SetsMaxAgeToCorrectValue()
|
||||||
|
{
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.UseUrls("https://*:5050")
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
})
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UseHsts();
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
return context.Response.WriteAsync("Hello world");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
client.BaseAddress = new Uri("https://localhost:5050");
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||||
|
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Equal("max-age=2592000", response.Headers.GetValues(HeaderNames.StrictTransportSecurity).FirstOrDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(0, false, false, "max-age=0")]
|
||||||
|
[InlineData(-1, false, false, "max-age=-1")]
|
||||||
|
[InlineData(0, true, false, "max-age=0; includeSubDomains")]
|
||||||
|
[InlineData(50000, false, true, "max-age=50000; preload")]
|
||||||
|
[InlineData(0, true, true, "max-age=0; includeSubDomains; preload")]
|
||||||
|
[InlineData(50000, true, true, "max-age=50000; includeSubDomains; preload")]
|
||||||
|
public async Task SetOptionsThroughConfigure_SetsHeaderCorrectly(int maxAge, bool includeSubDomains, bool preload, string expected)
|
||||||
|
{
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.UseUrls("https://*:5050")
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.Configure<HstsOptions>(options => {
|
||||||
|
options.Preload = preload;
|
||||||
|
options.IncludeSubDomains = includeSubDomains;
|
||||||
|
options.MaxAge = TimeSpan.FromSeconds(maxAge);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UseHsts();
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
return context.Response.WriteAsync("Hello world");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
client.BaseAddress = new Uri("https://localhost:5050");
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||||
|
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Equal(expected, response.Headers.GetValues(HeaderNames.StrictTransportSecurity).FirstOrDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(0, false, false, "max-age=0")]
|
||||||
|
[InlineData(-1, false, false, "max-age=-1")]
|
||||||
|
[InlineData(0, true, false, "max-age=0; includeSubDomains")]
|
||||||
|
[InlineData(50000, false, true, "max-age=50000; preload")]
|
||||||
|
[InlineData(0, true, true, "max-age=0; includeSubDomains; preload")]
|
||||||
|
[InlineData(50000, true, true, "max-age=50000; includeSubDomains; preload")]
|
||||||
|
public async Task SetOptionsThroughHelper_SetsHeaderCorrectly(int maxAge, bool includeSubDomains, bool preload, string expected)
|
||||||
|
{
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.UseUrls("https://*:5050")
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddHsts(options => {
|
||||||
|
options.Preload = preload;
|
||||||
|
options.IncludeSubDomains = includeSubDomains;
|
||||||
|
options.MaxAge = TimeSpan.FromSeconds(maxAge);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UseHsts();
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
return context.Response.WriteAsync("Hello world");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
client.BaseAddress = new Uri("https://localhost:5050");
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||||
|
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Equal(expected, response.Headers.GetValues(HeaderNames.StrictTransportSecurity).FirstOrDefault());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.TestHost;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.HttpsPolicy.Tests
|
||||||
|
{
|
||||||
|
public class HttpsPolicyTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData(302, null, 2592000, false, false, "max-age=2592000", "https://localhost/")]
|
||||||
|
[InlineData(301, 5050, 2592000, false, false, "max-age=2592000", "https://localhost:5050/")]
|
||||||
|
[InlineData(301, 443, 2592000, false, false, "max-age=2592000", "https://localhost/")]
|
||||||
|
[InlineData(301, 443, 2592000, true, false, "max-age=2592000; includeSubDomains", "https://localhost/")]
|
||||||
|
[InlineData(301, 443, 2592000, false, true, "max-age=2592000; preload", "https://localhost/")]
|
||||||
|
[InlineData(301, null, 2592000, true, true, "max-age=2592000; includeSubDomains; preload", "https://localhost/")]
|
||||||
|
[InlineData(302, 5050, 2592000, true, true, "max-age=2592000; includeSubDomains; preload", "https://localhost:5050/")]
|
||||||
|
public async Task SetsBothHstsAndHttpsRedirection_RedirectOnFirstRequest_HstsOnSecondRequest(int statusCode, int? tlsPort, int maxAge, bool includeSubDomains, bool preload, string expectedHstsHeader, string expectedUrl)
|
||||||
|
{
|
||||||
|
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.UseUrls("https://*:5050", "http://*:5050")
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.Configure<HttpsRedirectionOptions>(options =>
|
||||||
|
{
|
||||||
|
options.RedirectStatusCode = statusCode;
|
||||||
|
options.TlsPort = tlsPort;
|
||||||
|
});
|
||||||
|
services.Configure<HstsOptions>(options =>
|
||||||
|
{
|
||||||
|
options.IncludeSubDomains = includeSubDomains;
|
||||||
|
options.MaxAge = TimeSpan.FromSeconds(maxAge);
|
||||||
|
options.Preload = preload;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
app.UseHsts();
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
return context.Response.WriteAsync("Hello world");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||||
|
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
Assert.Equal(statusCode, (int)response.StatusCode);
|
||||||
|
Assert.Equal(expectedUrl, response.Headers.Location.ToString());
|
||||||
|
|
||||||
|
client = server.CreateClient();
|
||||||
|
client.BaseAddress = new Uri(response.Headers.Location.ToString());
|
||||||
|
request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||||
|
response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
Assert.Equal(expectedHstsHeader, response.Headers.GetValues(HeaderNames.StrictTransportSecurity).FirstOrDefault());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.TestHost;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.HttpsPolicy.Tests
|
||||||
|
{
|
||||||
|
public class HttpsRedirectionMiddlewareTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task SetOptions_DefaultsSetCorrectly()
|
||||||
|
{
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
})
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
return context.Response.WriteAsync("Hello world");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||||
|
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.MovedPermanently, response.StatusCode);
|
||||||
|
Assert.Equal("https://localhost/", response.Headers.Location.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(301, null, "https://localhost/")]
|
||||||
|
[InlineData(302, null, "https://localhost/")]
|
||||||
|
[InlineData(307, null, "https://localhost/")]
|
||||||
|
[InlineData(308, null, "https://localhost/")]
|
||||||
|
[InlineData(301, 5050, "https://localhost:5050/")]
|
||||||
|
[InlineData(301, 443, "https://localhost/")]
|
||||||
|
public async Task SetOptions_SetStatusCodeTlsPort(int statusCode, int? tlsPort, string expected)
|
||||||
|
{
|
||||||
|
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.Configure<HttpsRedirectionOptions>(options =>
|
||||||
|
{
|
||||||
|
options.RedirectStatusCode = statusCode;
|
||||||
|
options.TlsPort = tlsPort;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
return context.Response.WriteAsync("Hello world");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||||
|
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
Assert.Equal(statusCode, (int)response.StatusCode);
|
||||||
|
Assert.Equal(expected, response.Headers.Location.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(301, null, "https://localhost/")]
|
||||||
|
[InlineData(302, null, "https://localhost/")]
|
||||||
|
[InlineData(307, null, "https://localhost/")]
|
||||||
|
[InlineData(308, null, "https://localhost/")]
|
||||||
|
[InlineData(301, 5050, "https://localhost:5050/")]
|
||||||
|
[InlineData(301, 443, "https://localhost/")]
|
||||||
|
public async Task SetOptionsThroughHelperMethod_SetStatusCodeTlsPort(int statusCode, int? tlsPort, string expectedUrl)
|
||||||
|
{
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddHttpsRedirection(options =>
|
||||||
|
{
|
||||||
|
options.RedirectStatusCode = statusCode;
|
||||||
|
options.TlsPort = tlsPort;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
return context.Response.WriteAsync("Hello world");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "");
|
||||||
|
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
Assert.Equal(statusCode, (int)response.StatusCode);
|
||||||
|
Assert.Equal(expectedUrl, response.Headers.Location.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.HttpsPolicy\Microsoft.AspNetCore.HttpsPolicy.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
Loading…
Reference in New Issue