Read from IServerAddressesFeature for HTTPS port (#276)

This commit is contained in:
Justin Kotalik 2017-12-19 22:44:24 -08:00 committed by GitHub
parent 4144306359
commit fd4cd6f58b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 294 additions and 54 deletions

View File

@ -3,23 +3,25 @@
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
</PropertyGroup>
<PropertyGroup Label="Package Versions">
<InternalAspNetCoreSdkPackageVersion>2.1.0-preview1-15626</InternalAspNetCoreSdkPackageVersion>
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.1.0-preview1-27849</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
<MicrosoftAspNetCoreHttpAbstractionsPackageVersion>2.1.0-preview1-27849</MicrosoftAspNetCoreHttpAbstractionsPackageVersion>
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.1.0-preview1-27849</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
<MicrosoftAspNetCoreHttpPackageVersion>2.1.0-preview1-27849</MicrosoftAspNetCoreHttpPackageVersion>
<MicrosoftAspNetCoreServerKestrelHttpsPackageVersion>2.1.0-preview1-27849</MicrosoftAspNetCoreServerKestrelHttpsPackageVersion>
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.1.0-preview1-27849</MicrosoftAspNetCoreServerKestrelPackageVersion>
<MicrosoftAspNetCoreTestHostPackageVersion>2.1.0-preview1-27849</MicrosoftAspNetCoreTestHostPackageVersion>
<MicrosoftExtensionsConfigurationAbstractionsPackageVersion>2.1.0-preview1-27849</MicrosoftExtensionsConfigurationAbstractionsPackageVersion>
<MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>2.1.0-preview1-27849</MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.1.0-preview1-27849</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingConsolePackageVersion>2.1.0-preview1-27849</MicrosoftExtensionsLoggingConsolePackageVersion>
<MicrosoftExtensionsLoggingTestingPackageVersion>2.1.0-preview1-27849</MicrosoftExtensionsLoggingTestingPackageVersion>
<MicrosoftExtensionsOptionsPackageVersion>2.1.0-preview1-27849</MicrosoftExtensionsOptionsPackageVersion>
<InternalAspNetCoreSdkPackageVersion>2.1.0-preview1-15638</InternalAspNetCoreSdkPackageVersion>
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.1.0-preview1-27855</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
<MicrosoftAspNetCoreHttpAbstractionsPackageVersion>2.1.0-preview1-27855</MicrosoftAspNetCoreHttpAbstractionsPackageVersion>
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.1.0-preview1-27855</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
<MicrosoftAspNetCoreHttpPackageVersion>2.1.0-preview1-27855</MicrosoftAspNetCoreHttpPackageVersion>
<MicrosoftAspNetCoreServerKestrelCorePackageVersion>2.1.0-preview1-27855</MicrosoftAspNetCoreServerKestrelCorePackageVersion>
<MicrosoftAspNetCoreServerKestrelHttpsPackageVersion>2.1.0-preview1-27855</MicrosoftAspNetCoreServerKestrelHttpsPackageVersion>
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.1.0-preview1-27855</MicrosoftAspNetCoreServerKestrelPackageVersion>
<MicrosoftAspNetCoreTestHostPackageVersion>2.1.0-preview1-27855</MicrosoftAspNetCoreTestHostPackageVersion>
<MicrosoftExtensionsConfigurationAbstractionsPackageVersion>2.1.0-preview1-27855</MicrosoftExtensionsConfigurationAbstractionsPackageVersion>
<MicrosoftExtensionsConfigurationBinderPackageVersion>2.1.0-preview1-27855</MicrosoftExtensionsConfigurationBinderPackageVersion>
<MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>2.1.0-preview1-27855</MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.1.0-preview1-27855</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingConsolePackageVersion>2.1.0-preview1-27855</MicrosoftExtensionsLoggingConsolePackageVersion>
<MicrosoftExtensionsLoggingTestingPackageVersion>2.1.0-preview1-27855</MicrosoftExtensionsLoggingTestingPackageVersion>
<MicrosoftExtensionsOptionsPackageVersion>2.1.0-preview1-27855</MicrosoftExtensionsOptionsPackageVersion>
<MicrosoftNETCoreApp20PackageVersion>2.0.0</MicrosoftNETCoreApp20PackageVersion>
<MicrosoftNETCoreApp21PackageVersion>2.1.0-preview1-26016-05</MicrosoftNETCoreApp21PackageVersion>
<MicrosoftNetHttpHeadersPackageVersion>2.1.0-preview1-27849</MicrosoftNetHttpHeadersPackageVersion>
<MicrosoftNetHttpHeadersPackageVersion>2.1.0-preview1-27855</MicrosoftNetHttpHeadersPackageVersion>
<MicrosoftNETTestSdkPackageVersion>15.3.0</MicrosoftNETTestSdkPackageVersion>
<MoqPackageVersion>4.7.49</MoqPackageVersion>
<XunitAnalyzersPackageVersion>0.8.0</XunitAnalyzersPackageVersion>

View File

@ -1,2 +1,2 @@
version:2.1.0-preview1-15626
commithash:fd6410e9c90c428bc01238372303ad09cb9ec889
version:2.1.0-preview1-15638
commithash:1d3a0c725dc6b8ae6b0e47800fd6b4d8f8b8d545

View File

@ -2,8 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Rewrite;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@ -32,27 +32,7 @@ namespace Microsoft.AspNetCore.Builder
var options = app.ApplicationServices.GetRequiredService<IOptions<HttpsRedirectionOptions>>().Value;
// The tls port set in options will have priority over the one in configuration.
var httpsPort = options.HttpsPort;
if (httpsPort == null)
{
// Only read configuration if there is no httpsPort
var config = app.ApplicationServices.GetRequiredService<IConfiguration>();
var configHttpsPort = config["HTTPS_PORT"];
// If the string isn't empty, try to parse it.
if (!string.IsNullOrEmpty(configHttpsPort)
&& int.TryParse(configHttpsPort, out var intHttpsPort))
{
httpsPort = intHttpsPort;
}
}
var rewriteOptions = new RewriteOptions();
rewriteOptions.AddRedirectToHttps(
options.RedirectStatusCode,
httpsPort);
app.UseRewriter(rewriteOptions);
app.UseMiddleware<HttpsRedirectionMiddleware>(app.ServerFeatures.Get<IServerAddressesFeature>());
return app;
}

View File

@ -0,0 +1,124 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.HttpsPolicy
{
public class HttpsRedirectionMiddleware
{
private readonly RequestDelegate _next;
private int? _httpsPort;
private readonly int _statusCode;
private readonly IServerAddressesFeature _serverAddressesFeature;
private readonly IConfiguration _config;
/// <summary>
/// Initializes the HttpsRedirectionMiddleware
/// </summary>
/// <param name="next"></param>
/// <param name="serverAddressesFeature">The</param>
/// <param name="options"></param>
/// <param name="config"></param>
public HttpsRedirectionMiddleware(RequestDelegate next, IServerAddressesFeature serverAddressesFeature, IOptions<HttpsRedirectionOptions> options, IConfiguration config)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
_config = config ?? throw new ArgumentException(nameof(config));
_next = next ?? throw new ArgumentNullException(nameof(next));
_serverAddressesFeature = serverAddressesFeature ?? throw new ArgumentNullException(nameof(serverAddressesFeature));
var httpsRedirectionOptions = options.Value;
_httpsPort = httpsRedirectionOptions.HttpsPort;
_statusCode = httpsRedirectionOptions.RedirectStatusCode;
}
/// <summary>
/// Invokes the HttpsRedirectionMiddleware
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public Task Invoke(HttpContext context)
{
if (context.Request.IsHttps)
{
return _next(context);
}
if (!_httpsPort.HasValue)
{
CheckForHttpsPorts();
}
var host = context.Request.Host;
if (_httpsPort != 443)
{
host = new HostString(host.Host, _httpsPort.Value);
}
else
{
host = new HostString(host.Host);
}
var request = context.Request;
var redirectUrl = UriHelper.BuildAbsolute(
"https",
host,
request.PathBase,
request.Path,
request.QueryString);
context.Response.StatusCode = _statusCode;
context.Response.Headers[HeaderNames.Location] = redirectUrl;
return Task.CompletedTask;
}
private void CheckForHttpsPorts()
{
// The IServerAddressesFeature will not be ready until the middleware is Invoked,
// Order for finding the HTTPS port:
// 1. Set in the HttpsRedirectionOptions
// 2. HTTPS_PORT environment variable
// 3. IServerAddressesFeature
// 4. 443 (or not set)
_httpsPort = _config.GetValue<int?>("HTTPS_PORT");
if (_httpsPort.HasValue)
{
return;
}
int? httpsPort = null;
foreach (var address in _serverAddressesFeature.Addresses)
{
var bindingAddress = BindingAddress.Parse(address);
if (bindingAddress.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
{
// If we find multiple different https ports specified, throw
if (httpsPort.HasValue && httpsPort != bindingAddress.Port)
{
throw new ArgumentException("Cannot determine the https port from IServerAddressesFeature, multiple values were found. " +
"Please set the desired port explicitly on HttpsRedirectionOptions.HttpsPort.");
}
else
{
httpsPort = bindingAddress.Port;
}
}
}
_httpsPort = httpsPort ?? 443;
}
}
}

View File

@ -16,10 +16,13 @@ namespace Microsoft.AspNetCore.HttpsPolicy
public int RedirectStatusCode { get; set; } = StatusCodes.Status302Found;
/// <summary>
/// The TLS port to be added to the redirected URL.
/// The HTTPS port to be added to the redirected URL.
/// </summary>
/// <remarks>
/// Defaults to 443 if not provided.
/// If the HttpsPort is not set, we will try to get the HttpsPort from the following:
/// 1. HTTPS_PORT environment variable
/// 2. IServerAddressesFeature
/// 3. 443 (or not set)
/// </remarks>
public int? HttpsPort { get; set; }
}

View File

@ -11,6 +11,10 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Rewrite\Microsoft.AspNetCore.Rewrite.csproj" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpAbstractionsPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="$(MicrosoftAspNetCoreHttpExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="$(MicrosoftExtensionsConfigurationBinderPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(MicrosoftAspNetCoreHostingAbstractionsPackageVersion)" />
</ItemGroup>
</Project>

View File

@ -8,7 +8,9 @@ using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
@ -55,7 +57,9 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
});
});
var server = new TestServer(builder);
var featureCollection = new FeatureCollection();
featureCollection.Set<IServerAddressesFeature>(new ServerAddressesFeature());
var server = new TestServer(builder, featureCollection);
var client = server.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "");

View File

@ -8,7 +8,9 @@ using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
@ -34,7 +36,9 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
});
});
var server = new TestServer(builder);
var featureCollection = new FeatureCollection();
featureCollection.Set<IServerAddressesFeature>(new ServerAddressesFeature());
var server = new TestServer(builder, featureCollection);
var client = server.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "");
@ -73,7 +77,9 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
});
});
var server = new TestServer(builder);
var featureCollection = new FeatureCollection();
featureCollection.Set<IServerAddressesFeature>(new ServerAddressesFeature());
var server = new TestServer(builder, featureCollection);
var client = server.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "");
@ -111,7 +117,9 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
});
});
var server = new TestServer(builder);
var featureCollection = new FeatureCollection();
featureCollection.Set<IServerAddressesFeature>(new ServerAddressesFeature());
var server = new TestServer(builder, featureCollection);
var client = server.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "");
@ -123,13 +131,17 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
}
[Theory]
[InlineData(null, null, "https://localhost/")]
[InlineData(null, "5000", "https://localhost:5000/")]
[InlineData(null, "443", "https://localhost/")]
[InlineData(443, "5000", "https://localhost/")]
[InlineData(4000, "5000", "https://localhost:4000/")]
[InlineData(5000, null, "https://localhost:5000/")]
public async Task SetHttpsPortEnvironmentVariable_ReturnsCorrectStatusCodeOnResponse(int? optionsHttpsPort, string configHttpsPort, string expectedUrl)
[InlineData(null, null, null, "https://localhost/")]
[InlineData(null, null, "https://localhost:4444/", "https://localhost:4444/")]
[InlineData(null, null, "https://localhost:443/", "https://localhost/")]
[InlineData(null, null, "http://localhost:5044/", "https://localhost/")]
[InlineData(null, null, "https://localhost/", "https://localhost/")]
[InlineData(null, "5000", "https://localhost:4444/", "https://localhost:5000/")]
[InlineData(null, "443", "https://localhost:4444/", "https://localhost/")]
[InlineData(443, "5000", "https://localhost:4444/", "https://localhost/")]
[InlineData(4000, "5000", "https://localhost:4444/", "https://localhost:4000/")]
[InlineData(5000, null, "https://localhost:4444/", "https://localhost:5000/")]
public async Task SetHttpsPortEnvironmentVariableAndServerFeature_ReturnsCorrectStatusCodeOnResponse(int? optionsHttpsPort, string configHttpsPort, string serverAddressFeatureUrl, string expectedUrl)
{
var builder = new WebHostBuilder()
.ConfigureServices(services =>
@ -147,8 +159,18 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
return context.Response.WriteAsync("Hello world");
});
});
builder.UseSetting("HTTPS_PORT", configHttpsPort);
var server = new TestServer(builder);
var featureCollection = new FeatureCollection();
featureCollection.Set<IServerAddressesFeature>(new ServerAddressesFeature());
var server = new TestServer(builder, featureCollection);
if (serverAddressFeatureUrl != null)
{
server.Features.Get<IServerAddressesFeature>().Addresses.Add(serverAddressFeatureUrl);
}
var client = server.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "");
@ -157,5 +179,106 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
Assert.Equal(expectedUrl, response.Headers.Location.ToString());
}
[Fact]
public async Task SetServerAddressesFeature_SingleHttpsAddress_Success()
{
var builder = new WebHostBuilder()
.ConfigureServices(services =>
{
services.AddHttpsRedirection(options =>
{
});
})
.Configure(app =>
{
app.UseHttpsRedirection();
app.Run(context =>
{
return context.Response.WriteAsync("Hello world");
});
});
var featureCollection = new FeatureCollection();
featureCollection.Set<IServerAddressesFeature>(new ServerAddressesFeature());
var server = new TestServer(builder, featureCollection);
server.Features.Get<IServerAddressesFeature>().Addresses.Add("https://localhost:5050");
var client = server.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "");
var response = await client.SendAsync(request);
Assert.Equal("https://localhost:5050/", response.Headers.Location.ToString());
}
[Fact]
public async Task SetServerAddressesFeature_MultipleHttpsAddresses_ThrowInMiddleware()
{
var builder = new WebHostBuilder()
.ConfigureServices(services =>
{
services.AddHttpsRedirection(options =>
{
});
})
.Configure(app =>
{
app.UseHttpsRedirection();
app.Run(context =>
{
return context.Response.WriteAsync("Hello world");
});
});
var featureCollection = new FeatureCollection();
featureCollection.Set<IServerAddressesFeature>(new ServerAddressesFeature());
var server = new TestServer(builder, featureCollection);
server.Features.Get<IServerAddressesFeature>().Addresses.Add("https://localhost:5050");
server.Features.Get<IServerAddressesFeature>().Addresses.Add("https://localhost:5051");
var client = server.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "");
await Assert.ThrowsAsync<ArgumentException>(async () => await client.SendAsync(request));
}
[Fact]
public async Task SetServerAddressesFeature_MultipleHttpsAddressesWithSamePort_Success()
{
var builder = new WebHostBuilder()
.ConfigureServices(services =>
{
services.AddHttpsRedirection(options =>
{
});
})
.Configure(app =>
{
app.UseHttpsRedirection();
app.Run(context =>
{
return context.Response.WriteAsync("Hello world");
});
});
var featureCollection = new FeatureCollection();
featureCollection.Set<IServerAddressesFeature>(new ServerAddressesFeature());
var server = new TestServer(builder, featureCollection);
server.Features.Get<IServerAddressesFeature>().Addresses.Add("https://localhost:5050");
server.Features.Get<IServerAddressesFeature>().Addresses.Add("https://localhost:5050");
var client = server.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "");
var response = await client.SendAsync(request);
Assert.Equal("https://localhost:5050/", response.Headers.Location.ToString());
}
}
}