Adds Https Redirection and Hsts Middlewares (#264)

This commit is contained in:
Justin Kotalik 2017-10-13 12:15:35 -07:00 committed by GitHub
parent 83d1396330
commit 758f9fcea5
17 changed files with 770 additions and 0 deletions

View File

@ -57,6 +57,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
version.xml = version.xml
EndProjectSection
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
GlobalSection(SolutionConfigurationPlatforms) = preSolution
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}.Release|Any CPU.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -128,6 +146,9 @@ Global
{45308A9D-F4C6-46A8-A24F-E73D995CC223} = {A5076D28-FA7E-4606-9410-FEDD0D603527}
{3360A5D1-70C0-49EE-9051-04A6A6B836DC} = {8437B0F3-3894-4828-A945-A9187F37631D}
{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
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4518E9CE-3680-4E05-9259-B64EA7807158}

View File

@ -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>

View File

@ -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/"
}
}
}

View File

@ -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.

View File

@ -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>();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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; }
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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; }
}
}

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -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());
}
}
}

View File

@ -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());
}
}
}

View File

@ -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());
}
}
}

View File

@ -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>