BasicMiddleware 257 B

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. commit 77c9bc38f914b78f53fd5828c68c4ceca44bd841
  2. Author: Nate Barbettini <[email protected]>
  3. Date: Mon May 7 08:18:04 2018 -0700
  4. Add logging to HstsMiddleware (#327)
  5. diff --git a/src/Microsoft.AspNetCore.HttpsPolicy/HstsMiddleware.cs b/src/Microsoft.AspNetCore.HttpsPolicy/HstsMiddleware.cs
  6. index 252ae44c1e8..da5aa3af4b6 100644
  7. --- a/src/Microsoft.AspNetCore.HttpsPolicy/HstsMiddleware.cs
  8. +++ b/src/Microsoft.AspNetCore.HttpsPolicy/HstsMiddleware.cs
  9. @@ -6,6 +6,8 @@ using System.Collections.Generic;
  10. using System.Globalization;
  11. using System.Threading.Tasks;
  12. using Microsoft.AspNetCore.Http;
  13. +using Microsoft.AspNetCore.HttpsPolicy.Internal;
  14. +using Microsoft.Extensions.Logging;
  15. using Microsoft.Extensions.Options;
  16. using Microsoft.Extensions.Primitives;
  17. using Microsoft.Net.Http.Headers;
  18. @@ -24,13 +26,15 @@ namespace Microsoft.AspNetCore.HttpsPolicy
  19. private readonly RequestDelegate _next;
  20. private readonly StringValues _strictTransportSecurityValue;
  21. private readonly IList<string> _excludedHosts;
  22. + private readonly ILogger _logger;
  23. /// <summary>
  24. /// Initialize the HSTS middleware.
  25. /// </summary>
  26. /// <param name="next"></param>
  27. /// <param name="options"></param>
  28. - public HstsMiddleware(RequestDelegate next, IOptions<HstsOptions> options)
  29. + /// <param name="loggerFactory"></param>
  30. + public HstsMiddleware(RequestDelegate next, IOptions<HstsOptions> options, ILoggerFactory loggerFactory)
  31. {
  32. if (options == null)
  33. {
  34. @@ -46,6 +50,7 @@ namespace Microsoft.AspNetCore.HttpsPolicy
  35. var preload = hstsOptions.Preload ? Preload : StringSegment.Empty;
  36. _strictTransportSecurityValue = new StringValues($"max-age={maxAge}{includeSubdomains}{preload}");
  37. _excludedHosts = hstsOptions.ExcludedHosts;
  38. + _logger = loggerFactory.CreateLogger<HstsMiddleware>();
  39. }
  40. /// <summary>
  41. @@ -55,11 +60,21 @@ namespace Microsoft.AspNetCore.HttpsPolicy
  42. /// <returns></returns>
  43. public Task Invoke(HttpContext context)
  44. {
  45. - if (context.Request.IsHttps && !IsHostExcluded(context.Request.Host.Host))
  46. + if (!context.Request.IsHttps)
  47. {
  48. - context.Response.Headers[HeaderNames.StrictTransportSecurity] = _strictTransportSecurityValue;
  49. + _logger.SkippingInsecure();
  50. + return _next(context);
  51. }
  52. + if (IsHostExcluded(context.Request.Host.Host))
  53. + {
  54. + _logger.SkippingExcludedHost(context.Request.Host.Host);
  55. + return _next(context);
  56. + }
  57. +
  58. + context.Response.Headers[HeaderNames.StrictTransportSecurity] = _strictTransportSecurityValue;
  59. + _logger.AddingHstsHeader();
  60. +
  61. return _next(context);
  62. }
  63. diff --git a/src/Microsoft.AspNetCore.HttpsPolicy/internal/HstsLoggingExtensions.cs b/src/Microsoft.AspNetCore.HttpsPolicy/internal/HstsLoggingExtensions.cs
  64. new file mode 100644
  65. index 00000000000..5162ccb9f5e
  66. --- /dev/null
  67. +++ b/src/Microsoft.AspNetCore.HttpsPolicy/internal/HstsLoggingExtensions.cs
  68. @@ -0,0 +1,37 @@
  69. +// Copyright (c) .NET Foundation. All rights reserved.
  70. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  71. +
  72. +using System;
  73. +using Microsoft.Extensions.Logging;
  74. +
  75. +namespace Microsoft.AspNetCore.HttpsPolicy.Internal
  76. +{
  77. + internal static class HstsLoggingExtensions
  78. + {
  79. + private static readonly Action<ILogger, Exception> _notSecure;
  80. + private static readonly Action<ILogger, string, Exception> _excludedHost;
  81. + private static readonly Action<ILogger, Exception> _addingHstsHeader;
  82. +
  83. + static HstsLoggingExtensions()
  84. + {
  85. + _notSecure = LoggerMessage.Define(LogLevel.Debug, 1, "The request is insecure. Skipping HSTS header.");
  86. + _excludedHost = LoggerMessage.Define<string>(LogLevel.Debug, 2, "The host '{host}' is excluded. Skipping HSTS header.");
  87. + _addingHstsHeader = LoggerMessage.Define(LogLevel.Trace, 3, "Adding HSTS header to response.");
  88. + }
  89. +
  90. + public static void SkippingInsecure(this ILogger logger)
  91. + {
  92. + _notSecure(logger, null);
  93. + }
  94. +
  95. + public static void SkippingExcludedHost(this ILogger logger, string host)
  96. + {
  97. + _excludedHost(logger, host, null);
  98. + }
  99. +
  100. + public static void AddingHstsHeader(this ILogger logger)
  101. + {
  102. + _addingHstsHeader(logger, null);
  103. + }
  104. + }
  105. +}
  106. diff --git a/test/Microsoft.AspNetCore.HttpsPolicy.Tests/HstsMiddlewareTests.cs b/test/Microsoft.AspNetCore.HttpsPolicy.Tests/HstsMiddlewareTests.cs
  107. index 08df78f7c2c..0cb5f5755c9 100644
  108. --- a/test/Microsoft.AspNetCore.HttpsPolicy.Tests/HstsMiddlewareTests.cs
  109. +++ b/test/Microsoft.AspNetCore.HttpsPolicy.Tests/HstsMiddlewareTests.cs
  110. @@ -12,6 +12,8 @@ using Microsoft.AspNetCore.Hosting;
  111. using Microsoft.AspNetCore.Http;
  112. using Microsoft.AspNetCore.TestHost;
  113. using Microsoft.Extensions.DependencyInjection;
  114. +using Microsoft.Extensions.Logging;
  115. +using Microsoft.Extensions.Logging.Testing;
  116. using Microsoft.Net.Http.Headers;
  117. using Xunit;
  118. @@ -131,7 +133,16 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
  119. [InlineData("[::1]")]
  120. public async Task DefaultExcludesCommonLocalhostDomains_DoesNotSetHstsHeader(string host)
  121. {
  122. + var sink = new TestSink(
  123. + TestSink.EnableWithTypeName<HstsMiddleware>,
  124. + TestSink.EnableWithTypeName<HstsMiddleware>);
  125. + var loggerFactory = new TestLoggerFactory(sink, enabled: true);
  126. +
  127. var builder = new WebHostBuilder()
  128. + .ConfigureServices(services =>
  129. + {
  130. + services.AddSingleton<ILoggerFactory>(loggerFactory);
  131. + })
  132. .Configure(app =>
  133. {
  134. app.UseHsts();
  135. @@ -149,6 +160,13 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
  136. Assert.Equal(HttpStatusCode.OK, response.StatusCode);
  137. Assert.Empty(response.Headers);
  138. +
  139. + var logMessages = sink.Writes.ToList();
  140. +
  141. + Assert.Single(logMessages);
  142. + var message = logMessages.Single();
  143. + Assert.Equal(LogLevel.Debug, message.LogLevel);
  144. + Assert.Equal($"The host '{host}' is excluded. Skipping HSTS header.", message.State.ToString(), ignoreCase: true);
  145. }
  146. [Theory]
  147. @@ -157,9 +175,16 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
  148. [InlineData("[::1]")]
  149. public async Task AllowLocalhostDomainsIfListIsReset_SetHstsHeader(string host)
  150. {
  151. + var sink = new TestSink(
  152. + TestSink.EnableWithTypeName<HstsMiddleware>,
  153. + TestSink.EnableWithTypeName<HstsMiddleware>);
  154. + var loggerFactory = new TestLoggerFactory(sink, enabled: true);
  155. +
  156. var builder = new WebHostBuilder()
  157. .ConfigureServices(services =>
  158. {
  159. + services.AddSingleton<ILoggerFactory>(loggerFactory);
  160. +
  161. services.AddHsts(options =>
  162. {
  163. options.ExcludedHosts.Clear();
  164. @@ -182,6 +207,13 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
  165. Assert.Equal(HttpStatusCode.OK, response.StatusCode);
  166. Assert.Single(response.Headers);
  167. +
  168. + var logMessages = sink.Writes.ToList();
  169. +
  170. + Assert.Single(logMessages);
  171. + var message = logMessages.Single();
  172. + Assert.Equal(LogLevel.Trace, message.LogLevel);
  173. + Assert.Equal("Adding HSTS header to response.", message.State.ToString());
  174. }
  175. [Theory]
  176. @@ -190,9 +222,16 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
  177. [InlineData("EXAMPLE.COM")]
  178. public async Task AddExcludedDomains_DoesNotAddHstsHeader(string host)
  179. {
  180. + var sink = new TestSink(
  181. + TestSink.EnableWithTypeName<HstsMiddleware>,
  182. + TestSink.EnableWithTypeName<HstsMiddleware>);
  183. + var loggerFactory = new TestLoggerFactory(sink, enabled: true);
  184. +
  185. var builder = new WebHostBuilder()
  186. .ConfigureServices(services =>
  187. {
  188. + services.AddSingleton<ILoggerFactory>(loggerFactory);
  189. +
  190. services.AddHsts(options => {
  191. options.ExcludedHosts.Add(host);
  192. });
  193. @@ -214,6 +253,91 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
  194. Assert.Equal(HttpStatusCode.OK, response.StatusCode);
  195. Assert.Empty(response.Headers);
  196. +
  197. + var logMessages = sink.Writes.ToList();
  198. +
  199. + Assert.Single(logMessages);
  200. + var message = logMessages.Single();
  201. + Assert.Equal(LogLevel.Debug, message.LogLevel);
  202. + Assert.Equal($"The host '{host}' is excluded. Skipping HSTS header.", message.State.ToString(), ignoreCase: true);
  203. + }
  204. +
  205. + [Fact]
  206. + public async Task WhenRequestIsInsecure_DoesNotAddHstsHeader()
  207. + {
  208. + var sink = new TestSink(
  209. + TestSink.EnableWithTypeName<HstsMiddleware>,
  210. + TestSink.EnableWithTypeName<HstsMiddleware>);
  211. + var loggerFactory = new TestLoggerFactory(sink, enabled: true);
  212. +
  213. + var builder = new WebHostBuilder()
  214. + .ConfigureServices(services =>
  215. + {
  216. + services.AddSingleton<ILoggerFactory>(loggerFactory);
  217. + })
  218. + .Configure(app =>
  219. + {
  220. + app.UseHsts();
  221. + app.Run(context =>
  222. + {
  223. + return context.Response.WriteAsync("Hello world");
  224. + });
  225. + });
  226. + var server = new TestServer(builder);
  227. + var client = server.CreateClient();
  228. + client.BaseAddress = new Uri("http://example.com:5050");
  229. + var request = new HttpRequestMessage(HttpMethod.Get, "");
  230. +
  231. + var response = await client.SendAsync(request);
  232. +
  233. + Assert.Equal(HttpStatusCode.OK, response.StatusCode);
  234. + Assert.Empty(response.Headers);
  235. +
  236. + var logMessages = sink.Writes.ToList();
  237. +
  238. + Assert.Single(logMessages);
  239. + var message = logMessages.Single();
  240. + Assert.Equal(LogLevel.Debug, message.LogLevel);
  241. + Assert.Equal("The request is insecure. Skipping HSTS header.", message.State.ToString());
  242. + }
  243. +
  244. + [Fact]
  245. + public async Task WhenRequestIsSecure_AddsHstsHeader()
  246. + {
  247. + var sink = new TestSink(
  248. + TestSink.EnableWithTypeName<HstsMiddleware>,
  249. + TestSink.EnableWithTypeName<HstsMiddleware>);
  250. + var loggerFactory = new TestLoggerFactory(sink, enabled: true);
  251. +
  252. + var builder = new WebHostBuilder()
  253. + .ConfigureServices(services =>
  254. + {
  255. + services.AddSingleton<ILoggerFactory>(loggerFactory);
  256. + })
  257. + .Configure(app =>
  258. + {
  259. + app.UseHsts();
  260. + app.Run(context =>
  261. + {
  262. + return context.Response.WriteAsync("Hello world");
  263. + });
  264. + });
  265. + var server = new TestServer(builder);
  266. + var client = server.CreateClient();
  267. + client.BaseAddress = new Uri("https://example.com:5050");
  268. + var request = new HttpRequestMessage(HttpMethod.Get, "");
  269. +
  270. + var response = await client.SendAsync(request);
  271. +
  272. + Assert.Equal(HttpStatusCode.OK, response.StatusCode);
  273. + Assert.Contains(response.Headers, x => x.Key == HeaderNames.StrictTransportSecurity);
  274. +
  275. + var logMessages = sink.Writes.ToList();
  276. +
  277. + Assert.Single(logMessages);
  278. + var message = logMessages.Single();
  279. + Assert.Equal(LogLevel.Trace, message.LogLevel);
  280. + Assert.Equal("Adding HSTS header to response.", message.State.ToString());
  281. }
  282. }
  283. }