diff --git a/build/dependencies.props b/build/dependencies.props
index 8b795c771b..620c2bd49d 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -14,6 +14,7 @@
2.1.0-preview3-32110
2.1.0-preview3-32110
2.1.0-preview3-32110
+ 2.1.0-preview3-32110
2.1.0-preview3-32110
2.1.0-preview3-32110
2.1.0-preview3-32110
diff --git a/samples/HostFilteringSample/Program.cs b/samples/HostFilteringSample/Program.cs
index 0d4ffa9324..0ccc7cdb2e 100644
--- a/samples/HostFilteringSample/Program.cs
+++ b/samples/HostFilteringSample/Program.cs
@@ -25,8 +25,8 @@ namespace HostFilteringSample
.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
- config.AddJsonFile("appsettings.json", optional: true)
- .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
+ config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
+ .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
})
.UseKestrel()
.UseStartup();
diff --git a/samples/HostFilteringSample/Startup.cs b/samples/HostFilteringSample/Startup.cs
index 93c217b71c..283d8ee7f9 100644
--- a/samples/HostFilteringSample/Startup.cs
+++ b/samples/HostFilteringSample/Startup.cs
@@ -1,12 +1,15 @@
// 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.Collections.Generic;
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.HostFiltering;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
namespace HostFilteringSample
{
@@ -23,9 +26,22 @@ namespace HostFilteringSample
{
services.AddHostFiltering(options =>
{
- // If this is excluded then it will fall back to the server's addresses
- options.AllowedHosts = Config.GetSection("AllowedHosts").Get>();
+
});
+
+ // Fallback
+ services.PostConfigure(options =>
+ {
+ if (options.AllowedHosts == null || options.AllowedHosts.Count == 0)
+ {
+ // "AllowedHosts": "localhost;127.0.0.1;[::1]"
+ var hosts = Config["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
+ // Fall back to "*" to disable.
+ options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" });
+ }
+ });
+ // Change notification
+ services.AddSingleton>(new ConfigurationChangeTokenSource(Config));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
diff --git a/samples/HostFilteringSample/appsettings.Development.json b/samples/HostFilteringSample/appsettings.Development.json
index 7c2d8e26dc..b8d74ac7a2 100644
--- a/samples/HostFilteringSample/appsettings.Development.json
+++ b/samples/HostFilteringSample/appsettings.Development.json
@@ -1,3 +1,3 @@
{
- "AllowedHosts": [ "localhost", "127.0.0.1", "[::1]" ]
+ "AllowedHosts": "localhost;127.0.0.1;[::1]"
}
diff --git a/samples/HostFilteringSample/appsettings.Production.json b/samples/HostFilteringSample/appsettings.Production.json
index f2fc90c390..0c6fabda95 100644
--- a/samples/HostFilteringSample/appsettings.Production.json
+++ b/samples/HostFilteringSample/appsettings.Production.json
@@ -1,3 +1,3 @@
{
- "AllowedHosts": [ "example.com", "localhost" ]
+ "AllowedHosts": "example.com;localhost"
}
diff --git a/src/Microsoft.AspNetCore.HostFiltering/HostFilteringMiddleware.cs b/src/Microsoft.AspNetCore.HostFiltering/HostFilteringMiddleware.cs
index e1e38eca37..d355edd0fc 100644
--- a/src/Microsoft.AspNetCore.HostFiltering/HostFilteringMiddleware.cs
+++ b/src/Microsoft.AspNetCore.HostFiltering/HostFilteringMiddleware.cs
@@ -30,7 +30,8 @@ namespace Microsoft.AspNetCore.HostFiltering
private readonly RequestDelegate _next;
private readonly ILogger _logger;
- private readonly HostFilteringOptions _options;
+ private readonly IOptionsMonitor _optionsMonitor;
+ private HostFilteringOptions _options;
private IList _allowedHosts;
private bool? _allowAnyNonEmptyHost;
@@ -39,13 +40,21 @@ namespace Microsoft.AspNetCore.HostFiltering
///
///
///
- ///
+ ///
public HostFilteringMiddleware(RequestDelegate next, ILogger logger,
- IOptions options)
+ IOptionsMonitor optionsMonitor)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
- _options = options?.Value ?? throw new ArgumentNullException(nameof(options));
+ _optionsMonitor = optionsMonitor ?? throw new ArgumentNullException(nameof(optionsMonitor));
+ _options = _optionsMonitor.CurrentValue;
+ _optionsMonitor.OnChange(options =>
+ {
+ // Clear the cached settings so the next EnsureConfigured will re-evaluate.
+ _options = options;
+ _allowedHosts = new List();
+ _allowAnyNonEmptyHost = null;
+ });
}
///
@@ -55,9 +64,9 @@ namespace Microsoft.AspNetCore.HostFiltering
///
public Task Invoke(HttpContext context)
{
- EnsureConfigured();
+ var allowedHosts = EnsureConfigured();
- if (!CheckHost(context))
+ if (!CheckHost(context, allowedHosts))
{
context.Response.StatusCode = 400;
if (_options.IncludeFailureMessage)
@@ -72,19 +81,20 @@ namespace Microsoft.AspNetCore.HostFiltering
return _next(context);
}
- private void EnsureConfigured()
+ private IList EnsureConfigured()
{
if (_allowAnyNonEmptyHost == true || _allowedHosts?.Count > 0)
{
- return;
+ return _allowedHosts;
}
var allowedHosts = new List();
if (_options.AllowedHosts?.Count > 0 && !TryProcessHosts(_options.AllowedHosts, allowedHosts))
{
_logger.LogDebug("Wildcard detected, all requests with hosts will be allowed.");
+ _allowedHosts = allowedHosts;
_allowAnyNonEmptyHost = true;
- return;
+ return _allowedHosts;
}
if (allowedHosts.Count == 0)
@@ -94,6 +104,7 @@ namespace Microsoft.AspNetCore.HostFiltering
_logger.LogDebug("Allowed hosts: " + string.Join("; ", allowedHosts));
_allowedHosts = allowedHosts;
+ return _allowedHosts;
}
// returns false if any wildcards were found
@@ -127,7 +138,7 @@ namespace Microsoft.AspNetCore.HostFiltering
}
// This does not duplicate format validations that are expected to be performed by the host.
- private bool CheckHost(HttpContext context)
+ private bool CheckHost(HttpContext context, IList allowedHosts)
{
var host = new StringSegment(context.Request.Headers[HeaderNames.Host].ToString()).Trim();
@@ -153,7 +164,7 @@ namespace Microsoft.AspNetCore.HostFiltering
return true;
}
- if (HostString.MatchesAny(host, _allowedHosts))
+ if (HostString.MatchesAny(host, allowedHosts))
{
_logger.LogTrace($"The host '{host}' matches an allowed host.");
return true;
diff --git a/test/Microsoft.AspNetCore.HostFiltering.Tests/HostFilteringMiddlewareTests.cs b/test/Microsoft.AspNetCore.HostFiltering.Tests/HostFilteringMiddlewareTests.cs
index 2fc7b8c713..ab45bc9554 100644
--- a/test/Microsoft.AspNetCore.HostFiltering.Tests/HostFilteringMiddlewareTests.cs
+++ b/test/Microsoft.AspNetCore.HostFiltering.Tests/HostFilteringMiddlewareTests.cs
@@ -2,10 +2,14 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using Xunit;
@@ -179,5 +183,58 @@ namespace Microsoft.AspNetCore.HostFiltering
var response = await server.CreateRequest("/").GetAsync();
Assert.Equal(400, (int)response.StatusCode);
}
+
+ [Fact]
+ public async Task SupportsDynamicOptionsReload()
+ {
+ var config = new ConfigurationBuilder().Add(new ReloadableMemorySource()).Build();
+ config["AllowedHosts"] = "localhost";
+ var currentHost = "otherHost";
+
+ var builder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddHostFiltering(options =>
+ {
+ options.AllowedHosts = new[] { config["AllowedHosts"] };
+ });
+ services.AddSingleton>(new ConfigurationChangeTokenSource(config));
+ })
+ .Configure(app =>
+ {
+ app.Use((ctx, next) =>
+ {
+ ctx.Request.Headers[HeaderNames.Host] = currentHost;
+ return next();
+ });
+ app.UseHostFiltering();
+ app.Run(c => Task.CompletedTask);
+ });
+ var server = new TestServer(builder);
+ var response = await server.CreateRequest("/").GetAsync();
+ Assert.Equal(400, (int)response.StatusCode);
+
+ config["AllowedHosts"] = "otherHost";
+
+ response = await server.CreateRequest("/").GetAsync();
+ Assert.Equal(200, (int)response.StatusCode);
+ }
+
+ private class ReloadableMemorySource : IConfigurationSource
+ {
+ public IConfigurationProvider Build(IConfigurationBuilder builder)
+ {
+ return new ReloadableMemoryProvider();
+ }
+ }
+
+ internal class ReloadableMemoryProvider : ConfigurationProvider
+ {
+ public override void Set(string key, string value)
+ {
+ base.Set(key, value);
+ OnReload();
+ }
+ }
}
}
diff --git a/test/Microsoft.AspNetCore.HostFiltering.Tests/Microsoft.AspNetCore.HostFiltering.Tests.csproj b/test/Microsoft.AspNetCore.HostFiltering.Tests/Microsoft.AspNetCore.HostFiltering.Tests.csproj
index d71805c949..94262065fa 100644
--- a/test/Microsoft.AspNetCore.HostFiltering.Tests/Microsoft.AspNetCore.HostFiltering.Tests.csproj
+++ b/test/Microsoft.AspNetCore.HostFiltering.Tests/Microsoft.AspNetCore.HostFiltering.Tests.csproj
@@ -6,6 +6,7 @@
+