From 9e6dc5b2dab4e4efc6e2fc331a65cd768ae59627 Mon Sep 17 00:00:00 2001 From: Chris R Date: Mon, 14 Mar 2016 09:59:49 -0700 Subject: [PATCH] #95 Add the IISMiddleware via a IStartupFilter. --- samples/IISSample/Startup.cs | 4 +- .../IISAddressExtensions.cs | 38 ----------- .../IISMiddleware.cs | 21 +++--- .../IISMiddlewareExtensions.cs | 49 ------------- .../IISSetupFilter.cs | 28 ++++++++ .../IISWebHostExtensions.cs | 47 +++++++++++++ .../IISMiddlewareTests.cs | 68 ++++++++++++++----- test/TestSites/Program.cs | 2 +- test/TestSites/StartupHelloWorld.cs | 2 +- test/TestSites/StartupHttpsHelloWorld.cs | 2 +- test/TestSites/StartupNtlmAuthentication.cs | 2 - 11 files changed, 139 insertions(+), 124 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.Server.IISIntegration/IISAddressExtensions.cs delete mode 100644 src/Microsoft.AspNetCore.Server.IISIntegration/IISMiddlewareExtensions.cs create mode 100644 src/Microsoft.AspNetCore.Server.IISIntegration/IISSetupFilter.cs create mode 100644 src/Microsoft.AspNetCore.Server.IISIntegration/IISWebHostExtensions.cs diff --git a/samples/IISSample/Startup.cs b/samples/IISSample/Startup.cs index 9f6e1aa745..238f9c1f96 100644 --- a/samples/IISSample/Startup.cs +++ b/samples/IISSample/Startup.cs @@ -14,8 +14,6 @@ namespace IISSample loggerfactory.AddConsole(LogLevel.Debug); var logger = loggerfactory.CreateLogger("Requests"); - - app.UseIIS(); app.Run(async (context) => { @@ -49,7 +47,7 @@ namespace IISSample var host = new WebHostBuilder() .UseDefaultConfiguration(args) .UseServer("Microsoft.AspNetCore.Server.Kestrel") - .UseIISUrl() + .UseIIS() .UseStartup() .Build(); diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/IISAddressExtensions.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/IISAddressExtensions.cs deleted file mode 100644 index 63602eb780..0000000000 --- a/src/Microsoft.AspNetCore.Server.IISIntegration/IISAddressExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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.Hosting -{ - public static class IISAddressExtensions - { - // This is defined by IIS's AspNetCoreModule. - private static readonly string ServerPort = "ASPNETCORE_PORT"; - private static readonly string ServerPath = "ASPNETCORE_APPL_PATH"; - - /// - /// Configures the port and base path the server should listen on when running behind AspNetCoreModule. - /// - /// - /// - public static IWebHostBuilder UseIISUrl(this IWebHostBuilder app) - { - if (app == null) - { - throw new ArgumentNullException(nameof(app)); - } - - var port = Environment.GetEnvironmentVariable(ServerPort); - var path = Environment.GetEnvironmentVariable(ServerPath); - - if (!string.IsNullOrEmpty(port)) - { - var address = "http://localhost:" + port + path; - app.UseSetting(WebHostDefaults.ServerUrlsKey, address); - } - - return app; - } - } -} diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/IISMiddleware.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/IISMiddleware.cs index d8b9d20bdb..65564bc8ae 100644 --- a/src/Microsoft.AspNetCore.Server.IISIntegration/IISMiddleware.cs +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/IISMiddleware.cs @@ -21,15 +21,14 @@ namespace Microsoft.AspNetCore.Server.IISIntegration { private const string MSAspNetCoreWinAuthToken = "MS-ASPNETCORE-WINAUTHTOKEN"; private const string MSAspNetCoreClientCert = "MS-ASPNETCORE-CLIENTCERT"; - private const string AspNetCoreToken = "ASPNETCORE_TOKEN"; private const string MSAspNetCoreToken = "MS-ASPNETCORE-TOKEN"; private readonly RequestDelegate _next; private readonly IISOptions _options; private readonly ILogger _logger; - private readonly string _platformToken; + private readonly string _pairingToken; - public IISMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOptions options) + public IISMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOptions options, string pairingToken) { if (next == null) { @@ -43,24 +42,22 @@ namespace Microsoft.AspNetCore.Server.IISIntegration { throw new ArgumentNullException(nameof(options)); } + if (string.IsNullOrEmpty(pairingToken)) + { + throw new ArgumentException("Missing or empty pairing token."); + } _next = next; _options = options.Value; + _pairingToken = pairingToken; _logger = loggerFactory.CreateLogger(); - - _platformToken = Environment.GetEnvironmentVariable(AspNetCoreToken); - if (string.IsNullOrEmpty(_platformToken)) - { - _logger.LogInformation($"{AspNetCoreToken} not detected, {nameof(IISMiddleware)} will be skipped."); - } } public async Task Invoke(HttpContext httpContext) { - if (string.IsNullOrEmpty(_platformToken) - || !string.Equals(_platformToken, httpContext.Request.Headers[MSAspNetCoreToken], StringComparison.Ordinal)) + if (!string.Equals(_pairingToken, httpContext.Request.Headers[MSAspNetCoreToken], StringComparison.Ordinal)) { - _logger.LogTrace($"{MSAspNetCoreToken} not detected, skipping {nameof(IISMiddleware)}."); + _logger.LogTrace($"'{MSAspNetCoreToken}' does not match the expected pairing token '{_pairingToken}', skipping {nameof(IISMiddleware)}."); await _next(httpContext); return; } diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/IISMiddlewareExtensions.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/IISMiddlewareExtensions.cs deleted file mode 100644 index 0eb736cb2b..0000000000 --- a/src/Microsoft.AspNetCore.Server.IISIntegration/IISMiddlewareExtensions.cs +++ /dev/null @@ -1,49 +0,0 @@ -// 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.Server.IISIntegration; -using Microsoft.Extensions.Options; - -namespace Microsoft.AspNetCore.Builder -{ - public static class IISMiddlewareExtensions - { - /// - /// Adds middleware for interacting with the IIS AspNetCoreModule reverse proxy module. - /// This will handle forwarded Windows Authentication, client certificates, etc.. - /// - /// - /// - public static IApplicationBuilder UseIIS(this IApplicationBuilder app) - { - if (app == null) - { - throw new ArgumentNullException(nameof(app)); - } - - return app.UseMiddleware(); - } - - /// - /// Adds middleware for interacting with the IIS AspNetCoreModule reverse proxy module. - /// This will handle forwarded Windows Authentication, client certificates, etc.. - /// - /// - /// - /// - public static IApplicationBuilder UseIIS(this IApplicationBuilder app, IISOptions options) - { - if (app == null) - { - throw new ArgumentNullException(nameof(app)); - } - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - return app.UseMiddleware(Options.Create(options)); - } - } -} diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/IISSetupFilter.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/IISSetupFilter.cs new file mode 100644 index 0000000000..fdcc64b463 --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/IISSetupFilter.cs @@ -0,0 +1,28 @@ +// 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.Hosting; + +namespace Microsoft.AspNetCore.Server.IISIntegration +{ + internal class IISSetupFilter : IStartupFilter + { + private string _pairingToken; + + internal IISSetupFilter(string pairingToken) + { + _pairingToken = pairingToken; + } + + public Action Configure(Action next) + { + return app => + { + app.UseMiddleware(_pairingToken); + next(app); + }; + } + } +} diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/IISWebHostExtensions.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/IISWebHostExtensions.cs new file mode 100644 index 0000000000..2d4bbe7ed5 --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/IISWebHostExtensions.cs @@ -0,0 +1,47 @@ +// 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.Server.IISIntegration; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting +{ + public static class IISWebHostExtensions + { + // These are defined as ASPNETCORE_ environment variables by IIS's AspNetCoreModule. + private static readonly string ServerPort = "PORT"; + private static readonly string ServerPath = "APPL_PATH"; + private static readonly string PairingToken = "TOKEN"; + + /// + /// Configures the port and base path the server should listen on when running behind AspNetCoreModule. + /// + /// + /// + public static IWebHostBuilder UseIIS(this IWebHostBuilder app) + { + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + + var port = app.GetSetting(ServerPort); + var path = app.GetSetting(ServerPath); + var pairingToken = app.GetSetting(PairingToken); + + if (!string.IsNullOrEmpty(port) && !string.IsNullOrEmpty(path) && !string.IsNullOrEmpty(pairingToken)) + { + var address = "http://localhost:" + port + path; + app.UseSetting(WebHostDefaults.ServerUrlsKey, address); + + app.ConfigureServices(services => + { + services.AddSingleton(new IISSetupFilter(pairingToken)); + }); + } + + return app; + } + } +} diff --git a/test/Microsoft.AspNetCore.Server.IISIntegration.Tests/IISMiddlewareTests.cs b/test/Microsoft.AspNetCore.Server.IISIntegration.Tests/IISMiddlewareTests.cs index 218dae0cab..7e354abf41 100644 --- a/test/Microsoft.AspNetCore.Server.IISIntegration.Tests/IISMiddlewareTests.cs +++ b/test/Microsoft.AspNetCore.Server.IISIntegration.Tests/IISMiddlewareTests.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http.Features.Authentication; using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; using Xunit; namespace Microsoft.AspNetCore.Server.IISIntegration @@ -20,6 +21,37 @@ namespace Microsoft.AspNetCore.Server.IISIntegration var assertsExecuted = false; var builder = new WebHostBuilder() + .UseSetting("PORT", "12345") + .UseSetting("APPL_PATH", "/") + .UseIIS() + .Configure(app => + { + app.Run(context => + { + var auth = context.Features.Get(); + Assert.Null(auth); + assertsExecuted = true; + return Task.FromResult(0); + }); + }); + var server = new TestServer(builder); + + var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.TryAddWithoutValidation("MS-ASPNETCORE-TOKEN", "TestToken"); + await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); + } + + [Fact] + public async Task MiddlewareSkippedIfTokenHeaderIsMissing() + { + var assertsExecuted = false; + + var builder = new WebHostBuilder() + .UseSetting("TOKEN", "TestToken") + .UseSetting("PORT", "12345") + .UseSetting("APPL_PATH", "/") + .UseIIS() .Configure(app => { app.Run(context => @@ -43,15 +75,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration var assertsExecuted = false; var builder = new WebHostBuilder() + .UseSetting("TOKEN", "TestToken") + .UseSetting("PORT", "12345") + .UseSetting("APPL_PATH", "/") + .UseIIS() .Configure(app => { - Environment.SetEnvironmentVariable("ASPNETCORE_TOKEN", "TestToken"); - app.Use((context, next) => - { - context.Request.Headers["MS-ASPNETCORE-TOKEN"] = "TestToken"; - return next(); - }); - app.UseIIS(); app.Run(context => { var auth = context.Features.Get(); @@ -64,7 +93,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration var server = new TestServer(builder); var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.TryAddWithoutValidation("MS-ASPNETCORE-TOKEN", "TestToken"); await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); } @@ -75,18 +106,19 @@ namespace Microsoft.AspNetCore.Server.IISIntegration var assertsExecuted = false; var builder = new WebHostBuilder() + .UseSetting("TOKEN", "TestToken") + .UseSetting("PORT", "12345") + .UseSetting("APPL_PATH", "/") + .UseIIS() + .ConfigureServices(services => + { + services.Configure(options => + { + options.ForwardWindowsAuthentication = false; + }); + }) .Configure(app => { - Environment.SetEnvironmentVariable("ASPNETCORE_TOKEN", "TestToken"); - app.Use((context, next) => - { - context.Request.Headers["MS-ASPNETCORE-TOKEN"] = "TestToken"; - return next(); - }); - app.UseIIS(new IISOptions - { - ForwardWindowsAuthentication = false - }); app.Run(context => { var auth = context.Features.Get(); @@ -98,7 +130,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration var server = new TestServer(builder); var req = new HttpRequestMessage(HttpMethod.Get, ""); + req.Headers.TryAddWithoutValidation("MS-ASPNETCORE-TOKEN", "TestToken"); await server.CreateClient().SendAsync(req); + Assert.True(assertsExecuted); } } diff --git a/test/TestSites/Program.cs b/test/TestSites/Program.cs index 4ea84843c2..110f51ae6b 100644 --- a/test/TestSites/Program.cs +++ b/test/TestSites/Program.cs @@ -11,7 +11,7 @@ namespace TestSites { var host = new WebHostBuilder() .UseDefaultConfiguration(args) - .UseIISUrl() + .UseIIS() .UseStartup("TestSites") .UseServer("Microsoft.AspNetCore.Server.Kestrel") .Build(); diff --git a/test/TestSites/StartupHelloWorld.cs b/test/TestSites/StartupHelloWorld.cs index 34c22bf713..a67a66a1ce 100644 --- a/test/TestSites/StartupHelloWorld.cs +++ b/test/TestSites/StartupHelloWorld.cs @@ -12,7 +12,7 @@ namespace TestSites public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); - app.UseIIS(); + app.Run(ctx => { if (ctx.Request.Path.Value.StartsWith("/Path")) diff --git a/test/TestSites/StartupHttpsHelloWorld.cs b/test/TestSites/StartupHttpsHelloWorld.cs index 16e60f4682..5008b6e077 100644 --- a/test/TestSites/StartupHttpsHelloWorld.cs +++ b/test/TestSites/StartupHttpsHelloWorld.cs @@ -12,7 +12,7 @@ namespace TestSites public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); - app.UseIIS(); + app.Run(ctx => { if (ctx.Request.Path.Equals(new PathString("/checkclientcert"))) diff --git a/test/TestSites/StartupNtlmAuthentication.cs b/test/TestSites/StartupNtlmAuthentication.cs index 4c550ac4ea..59a1920005 100644 --- a/test/TestSites/StartupNtlmAuthentication.cs +++ b/test/TestSites/StartupNtlmAuthentication.cs @@ -37,8 +37,6 @@ namespace TestSites } }); - app.UseIIS(); - app.Use((context, next) => { if (context.Request.Path.Equals("/Anonymous"))