IISIntegration 721 B

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758
  1. commit 552163ab77c269bc1f6c22bbf2b7560e0677c421
  2. Author: Justin Kotalik <[email protected]>
  3. Date: Fri Nov 10 17:02:31 2017 -0800
  4. Adds windows Auth support (#471)
  5. diff --git a/samples/NativeIISSample/Properties/launchSettings.json b/samples/NativeIISSample/Properties/launchSettings.json
  6. index 03665b8024c..ca62d6c6486 100644
  7. --- a/samples/NativeIISSample/Properties/launchSettings.json
  8. +++ b/samples/NativeIISSample/Properties/launchSettings.json
  9. @@ -1,6 +1,6 @@
  10. {
  11. "iisSettings": {
  12. - "windowsAuthentication": false,
  13. + "windowsAuthentication": true,
  14. "anonymousAuthentication": true,
  15. "iisExpress": {
  16. "applicationUrl": "http://localhost:5762/",
  17. diff --git a/samples/NativeIISSample/Startup.cs b/samples/NativeIISSample/Startup.cs
  18. index 85f6a5dd512..ef7bbad8ce6 100644
  19. --- a/samples/NativeIISSample/Startup.cs
  20. +++ b/samples/NativeIISSample/Startup.cs
  21. @@ -3,9 +3,11 @@
  22. using System;
  23. using System.Linq;
  24. +using Microsoft.AspNetCore.Authentication;
  25. using Microsoft.AspNetCore.Builder;
  26. using Microsoft.AspNetCore.Hosting;
  27. using Microsoft.AspNetCore.Http;
  28. +using Microsoft.AspNetCore.Server.IISIntegration;
  29. namespace NativeIISSample
  30. {
  31. @@ -13,7 +15,7 @@ namespace NativeIISSample
  32. {
  33. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
  34. - public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  35. + public void Configure(IApplicationBuilder app, IHostingEnvironment env, IAuthenticationSchemeProvider authSchemeProvider)
  36. {
  37. app.Run(async (context) =>
  38. {
  39. @@ -38,8 +40,8 @@ namespace NativeIISSample
  40. await context.Response.WriteAsync(Environment.NewLine);
  41. await context.Response.WriteAsync("User: " + context.User.Identity.Name + Environment.NewLine);
  42. - //var scheme = await authSchemeProvider.GetSchemeAsync(IISDefaults.AuthenticationScheme);
  43. - //await context.Response.WriteAsync("DisplayName: " + scheme?.DisplayName + Environment.NewLine);
  44. + var scheme = await authSchemeProvider.GetSchemeAsync(IISDefaults.AuthenticationScheme);
  45. + await context.Response.WriteAsync("DisplayName: " + scheme?.DisplayName + Environment.NewLine);
  46. await context.Response.WriteAsync(Environment.NewLine);
  47. diff --git a/samples/NativeIISSample/applicationhost.config b/samples/NativeIISSample/applicationhost.config
  48. index b11f212ef2a..dd5daa04545 100644
  49. --- a/samples/NativeIISSample/applicationhost.config
  50. +++ b/samples/NativeIISSample/applicationhost.config
  51. @@ -299,12 +299,12 @@
  52. <application name="Active Server Pages" groupId="ASP" />
  53. </applicationDependencies>
  54. <authentication>
  55. - <anonymousAuthentication enabled="true" userName="" />
  56. + <anonymousAuthentication enabled="false" userName="" />
  57. <basicAuthentication enabled="false" />
  58. <clientCertificateMappingAuthentication enabled="false" />
  59. <digestAuthentication enabled="false" />
  60. <iisClientCertificateMappingAuthentication enabled="false"></iisClientCertificateMappingAuthentication>
  61. - <windowsAuthentication enabled="false">
  62. + <windowsAuthentication enabled="true">
  63. <providers>
  64. <add value="Negotiate" />
  65. <add value="NTLM" />
  66. diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/NativeMethods.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/NativeMethods.cs
  67. index 8015f9c0044..4d718793d67 100644
  68. --- a/src/Microsoft.AspNetCore.Server.IISIntegration/NativeMethods.cs
  69. +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/NativeMethods.cs
  70. @@ -75,7 +75,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
  71. public unsafe static extern bool http_set_managed_context(IntPtr pHttpContext, IntPtr pvManagedContext);
  72. [DllImport(AspNetCoreModuleDll)]
  73. - public unsafe static extern int http_get_application_paths([MarshalAs(UnmanagedType.BStr)] out string fullPath, [MarshalAs(UnmanagedType.BStr)] out string virtualPath);
  74. + public unsafe static extern int http_get_application_properties(ref IISConfigurationData iiConfigData);
  75. [DllImport(AspNetCoreModuleDll)]
  76. public unsafe static extern bool http_shutdown();
  77. @@ -98,6 +98,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
  78. [DllImport(AspNetCoreModuleDll)]
  79. internal unsafe static extern int http_response_set_known_header(IntPtr pHttpContext, int headerId, byte* pHeaderValue, ushort length, bool fReplace);
  80. + [DllImport(AspNetCoreModuleDll)]
  81. + public unsafe static extern int http_get_authentication_information(IntPtr pHttpContext, [MarshalAs(UnmanagedType.BStr)] out string authType, out IntPtr token);
  82. +
  83. [DllImport("kernel32.dll")]
  84. public static extern IntPtr GetModuleHandle(string lpModuleName);
  85. @@ -105,6 +108,5 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
  86. {
  87. return GetModuleHandle(AspNetCoreModuleDll) != IntPtr.Zero;
  88. }
  89. -
  90. }
  91. }
  92. diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISConfigurationData.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISConfigurationData.cs
  93. new file mode 100644
  94. index 00000000000..76910ca9d39
  95. --- /dev/null
  96. +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISConfigurationData.cs
  97. @@ -0,0 +1,19 @@
  98. +// Copyright (c) .NET Foundation. All rights reserved.
  99. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  100. +
  101. +using System.Runtime.InteropServices;
  102. +
  103. +namespace Microsoft.AspNetCore.Server.IISIntegration
  104. +{
  105. + [StructLayout(LayoutKind.Sequential)]
  106. + internal unsafe struct IISConfigurationData
  107. + {
  108. + [MarshalAs(UnmanagedType.BStr)]
  109. + public string pwzFullApplicationPath;
  110. + [MarshalAs(UnmanagedType.BStr)]
  111. + public string pwzVirtualApplicationPath;
  112. + public bool fWindowsAuthEnabled;
  113. + public bool fBasicAuthEnabled;
  114. + public bool fAnonymousAuthEnable;
  115. + }
  116. +}
  117. diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.FeatureCollection.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.FeatureCollection.cs
  118. index 27962f0c08f..2c2c9ef5fe0 100644
  119. --- a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.FeatureCollection.cs
  120. +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.FeatureCollection.cs
  121. @@ -7,10 +7,12 @@ using System.Collections.Generic;
  122. using System.IO;
  123. using System.Linq;
  124. using System.Net;
  125. +using System.Security.Claims;
  126. using System.Threading;
  127. using System.Threading.Tasks;
  128. using Microsoft.AspNetCore.Http;
  129. using Microsoft.AspNetCore.Http.Features;
  130. +using Microsoft.AspNetCore.Http.Features.Authentication;
  131. using Microsoft.AspNetCore.WebUtilities;
  132. using Microsoft.Extensions.Primitives;
  133. @@ -22,7 +24,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
  134. IHttpUpgradeFeature,
  135. IHttpConnectionFeature,
  136. IHttpRequestLifetimeFeature,
  137. - IHttpRequestIdentifierFeature
  138. + IHttpRequestIdentifierFeature,
  139. + IHttpAuthenticationFeature
  140. {
  141. // NOTE: When feature interfaces are added to or removed from this HttpProtocol implementation,
  142. // then the list of `implementedFeatures` in the generated code project MUST also be updated.
  143. @@ -223,6 +226,14 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
  144. set => TraceIdentifier = value;
  145. }
  146. + ClaimsPrincipal IHttpAuthenticationFeature.User
  147. + {
  148. + get => User;
  149. + set => User = value;
  150. + }
  151. +
  152. + public IAuthenticationHandler Handler { get; set; }
  153. +
  154. object IFeatureCollection.this[Type key]
  155. {
  156. get => FastFeatureGet(key);
  157. diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.Features.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.Features.cs
  158. index 6b534405f5c..5ad7590adec 100644
  159. --- a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.Features.cs
  160. +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.Features.cs
  161. @@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
  162. private static readonly Type ISessionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ISessionFeature);
  163. private static readonly Type IHttpBodyControlFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature);
  164. private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
  165. + private static readonly Type IISHttpContextType = typeof(IISHttpContext);
  166. private object _currentIHttpRequestFeature;
  167. private object _currentIHttpResponseFeature;
  168. @@ -61,6 +62,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
  169. _currentIHttpMinRequestBodyDataRateFeature = this;
  170. _currentIHttpMinResponseDataRateFeature = this;
  171. _currentIHttpBodyControlFeature = this;
  172. + _currentIHttpAuthenticationFeature = this;
  173. }
  174. internal object FastFeatureGet(Type key)
  175. @@ -133,6 +135,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
  176. {
  177. return _currentIHttpSendFileFeature;
  178. }
  179. + if (key == IISHttpContextType)
  180. + {
  181. + return this;
  182. + }
  183. +
  184. return ExtraFeatureGet(key);
  185. }
  186. @@ -224,6 +231,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
  187. {
  188. _currentIHttpSendFileFeature = feature;
  189. return;
  190. + }
  191. + if (key == IISHttpContextType)
  192. + {
  193. + throw new InvalidOperationException("Cannot set IISHttpContext in feature collection");
  194. };
  195. ExtraFeatureSet(key, feature);
  196. }
  197. diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.cs
  198. index a6520425e28..db83adc7cb2 100644
  199. --- a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.cs
  200. +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContext.cs
  201. @@ -10,9 +10,12 @@ using System.IO;
  202. using System.IO.Pipelines;
  203. using System.Net;
  204. using System.Runtime.InteropServices;
  205. +using System.Security.Claims;
  206. +using System.Security.Principal;
  207. using System.Text;
  208. using System.Threading;
  209. using System.Threading.Tasks;
  210. +using Microsoft.AspNetCore.Builder;
  211. using Microsoft.AspNetCore.Http;
  212. using Microsoft.AspNetCore.HttpSys.Internal;
  213. using Microsoft.AspNetCore.WebUtilities;
  214. @@ -57,7 +60,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
  215. private CurrentOperationType _currentOperationType;
  216. private Task _currentOperation = Task.CompletedTask;
  217. - internal unsafe IISHttpContext(PipeFactory pipeFactory, IntPtr pHttpContext)
  218. + private const string NtlmString = "NTLM";
  219. + private const string NegotiateString = "Negotiate";
  220. + private const string BasicString = "Basic";
  221. +
  222. + internal unsafe IISHttpContext(PipeFactory pipeFactory, IntPtr pHttpContext, IISOptions options)
  223. : base((HttpApiTypes.HTTP_REQUEST*)NativeMethods.http_get_raw_request(pHttpContext))
  224. {
  225. _thisHandle = GCHandle.Alloc(this);
  226. @@ -120,6 +127,15 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
  227. HttpResponseHeaders = new HeaderCollection(); // TODO Optimize for known headers
  228. ResponseHeaders = HttpResponseHeaders;
  229. + if (options.ForwardWindowsAuthentication)
  230. + {
  231. + WindowsUser = GetWindowsPrincipal();
  232. + if (options.AutomaticAuthentication)
  233. + {
  234. + User = WindowsUser;
  235. + }
  236. + }
  237. +
  238. ResetFeatureCollection();
  239. }
  240. @@ -146,7 +162,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
  241. public int LocalPort { get; set; }
  242. public string RequestConnectionId { get; set; }
  243. public string TraceIdentifier { get; set; }
  244. -
  245. + public ClaimsPrincipal User { get; set; }
  246. + internal WindowsPrincipal WindowsUser { get; set; }
  247. public Stream RequestBody { get; set; }
  248. public Stream ResponseBody { get; set; }
  249. public IPipe Input { get; set; }
  250. @@ -827,7 +844,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
  251. // TODO: dispose managed state (managed objects).
  252. _thisHandle.Free();
  253. }
  254. -
  255. + if (WindowsUser?.Identity is WindowsIdentity wi)
  256. + {
  257. + wi.Dispose();
  258. + }
  259. disposedValue = true;
  260. }
  261. }
  262. @@ -851,5 +871,21 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
  263. Write,
  264. Flush
  265. }
  266. +
  267. + private WindowsPrincipal GetWindowsPrincipal()
  268. + {
  269. + var hr = NativeMethods.http_get_authentication_information(_pHttpContext, out var authenticationType, out var token);
  270. +
  271. + if (hr == 0 && token != IntPtr.Zero && authenticationType != null)
  272. + {
  273. + if ((authenticationType.Equals(NtlmString, StringComparison.OrdinalIgnoreCase)
  274. + || authenticationType.Equals(NegotiateString, StringComparison.OrdinalIgnoreCase)
  275. + || authenticationType.Equals(BasicString, StringComparison.OrdinalIgnoreCase)))
  276. + {
  277. + return new WindowsPrincipal(new WindowsIdentity(token, authenticationType));
  278. + }
  279. + }
  280. + return null;
  281. + }
  282. }
  283. }
  284. diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContextOfT.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContextOfT.cs
  285. index 4fbd3b59563..1701122ec68 100644
  286. --- a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContextOfT.cs
  287. +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpContextOfT.cs
  288. @@ -3,9 +3,10 @@
  289. using System;
  290. using System.Threading.Tasks;
  291. -using Microsoft.AspNetCore.Hosting.Server;
  292. using System.Threading;
  293. using System.IO.Pipelines;
  294. +using Microsoft.AspNetCore.Builder;
  295. +using Microsoft.AspNetCore.Hosting.Server;
  296. namespace Microsoft.AspNetCore.Server.IISIntegration
  297. {
  298. @@ -13,8 +14,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
  299. {
  300. private readonly IHttpApplication<TContext> _application;
  301. - public IISHttpContextOfT(PipeFactory pipeFactory, IHttpApplication<TContext> application, IntPtr pHttpContext)
  302. - : base(pipeFactory, pHttpContext)
  303. + public IISHttpContextOfT(PipeFactory pipeFactory, IHttpApplication<TContext> application, IntPtr pHttpContext, IISOptions options)
  304. + : base(pipeFactory, pHttpContext, options)
  305. {
  306. _application = application;
  307. }
  308. diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpServer.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpServer.cs
  309. index 92602936136..4417e54c5b0 100644
  310. --- a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpServer.cs
  311. +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISHttpServer.cs
  312. @@ -6,9 +6,12 @@ using System.IO.Pipelines;
  313. using System.Runtime.InteropServices;
  314. using System.Threading;
  315. using System.Threading.Tasks;
  316. +using Microsoft.AspNetCore.Authentication;
  317. +using Microsoft.AspNetCore.Builder;
  318. using Microsoft.AspNetCore.Hosting;
  319. using Microsoft.AspNetCore.Hosting.Server;
  320. using Microsoft.AspNetCore.Http.Features;
  321. +using Microsoft.Extensions.Options;
  322. namespace Microsoft.AspNetCore.Server.IISIntegration
  323. {
  324. @@ -19,22 +22,30 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
  325. private static NativeMethods.PFN_ASYNC_COMPLETION _onAsyncCompletion = OnAsyncCompletion;
  326. private IISContextFactory _iisContextFactory;
  327. -
  328. private PipeFactory _pipeFactory = new PipeFactory();
  329. private GCHandle _httpServerHandle;
  330. - private IApplicationLifetime _applicationLifetime;
  331. + private readonly IApplicationLifetime _applicationLifetime;
  332. + private readonly IAuthenticationSchemeProvider _authentication;
  333. + private readonly IISOptions _options;
  334. public IFeatureCollection Features { get; } = new FeatureCollection();
  335. - public IISHttpServer(IApplicationLifetime applicationLifetime)
  336. + public IISHttpServer(IApplicationLifetime applicationLifetime, IAuthenticationSchemeProvider authentication, IOptions<IISOptions> options)
  337. {
  338. _applicationLifetime = applicationLifetime;
  339. + _authentication = authentication;
  340. + _options = options.Value;
  341. +
  342. + if (_options.ForwardWindowsAuthentication)
  343. + {
  344. + authentication.AddScheme(new AuthenticationScheme(IISDefaults.AuthenticationScheme, _options.AuthenticationDisplayName, typeof(IISServerAuthenticationHandler)));
  345. + }
  346. }
  347. public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
  348. {
  349. _httpServerHandle = GCHandle.Alloc(this);
  350. - _iisContextFactory = new IISContextFactory<TContext>(_pipeFactory, application);
  351. + _iisContextFactory = new IISContextFactory<TContext>(_pipeFactory, application, _options);
  352. // Start the server by registering the callback
  353. NativeMethods.register_callbacks(_requestHandler, _shutdownHandler, _onAsyncCompletion, (IntPtr)_httpServerHandle, (IntPtr)_httpServerHandle);
  354. @@ -115,16 +126,18 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
  355. {
  356. private readonly IHttpApplication<T> _application;
  357. private readonly PipeFactory _pipeFactory;
  358. + private readonly IISOptions _options;
  359. - public IISContextFactory(PipeFactory pipeFactory, IHttpApplication<T> application)
  360. + public IISContextFactory(PipeFactory pipeFactory, IHttpApplication<T> application, IISOptions options)
  361. {
  362. _application = application;
  363. _pipeFactory = pipeFactory;
  364. + _options = options;
  365. }
  366. public IISHttpContext CreateHttpContext(IntPtr pHttpContext)
  367. {
  368. - return new IISHttpContextOfT<T>(_pipeFactory, _application, pHttpContext);
  369. + return new IISHttpContextOfT<T>(_pipeFactory, _application, pHttpContext, _options);
  370. }
  371. }
  372. }
  373. diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISServerAuthenticationHandler.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISServerAuthenticationHandler.cs
  374. new file mode 100644
  375. index 00000000000..eba243f9f3d
  376. --- /dev/null
  377. +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/Server/IISServerAuthenticationHandler.cs
  378. @@ -0,0 +1,59 @@
  379. +// Copyright (c) .NET Foundation. All rights reserved.
  380. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  381. +
  382. +using System;
  383. +using System.Security.Principal;
  384. +using System.Threading.Tasks;
  385. +using Microsoft.AspNetCore.Authentication;
  386. +using Microsoft.AspNetCore.Http;
  387. +
  388. +namespace Microsoft.AspNetCore.Server.IISIntegration
  389. +{
  390. + public class IISServerAuthenticationHandler : IAuthenticationHandler
  391. + {
  392. + private HttpContext _context;
  393. + private IISHttpContext _iisHttpContext;
  394. +
  395. + internal AuthenticationScheme Scheme { get; private set; }
  396. +
  397. + public Task<AuthenticateResult> AuthenticateAsync()
  398. + {
  399. + var user = _iisHttpContext.WindowsUser;
  400. + if (user != null && user.Identity != null && user.Identity.IsAuthenticated)
  401. + {
  402. + return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(user, Scheme.Name)));
  403. + }
  404. + else
  405. + {
  406. + return Task.FromResult(AuthenticateResult.NoResult());
  407. + }
  408. + }
  409. +
  410. + public Task ChallengeAsync(AuthenticationProperties properties)
  411. + {
  412. + // We would normally set the www-authenticate header here, but IIS does that for us.
  413. + _context.Response.StatusCode = 401;
  414. + return Task.CompletedTask;
  415. + }
  416. +
  417. + public Task ForbidAsync(AuthenticationProperties properties)
  418. + {
  419. + _context.Response.StatusCode = 403;
  420. + return Task.CompletedTask;
  421. + }
  422. +
  423. + public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
  424. + {
  425. + _iisHttpContext = context.Features.Get<IISHttpContext>();
  426. + if (_iisHttpContext == null)
  427. + {
  428. + throw new InvalidOperationException("No IISHttpContext found.");
  429. + }
  430. +
  431. + Scheme = scheme;
  432. + _context = context;
  433. +
  434. + return Task.CompletedTask;
  435. + }
  436. + }
  437. +}
  438. diff --git a/src/Microsoft.AspNetCore.Server.IISIntegration/WebHostBuilderIISExtensions.cs b/src/Microsoft.AspNetCore.Server.IISIntegration/WebHostBuilderIISExtensions.cs
  439. index 8a92946ae14..89ecd567397 100644
  440. --- a/src/Microsoft.AspNetCore.Server.IISIntegration/WebHostBuilderIISExtensions.cs
  441. +++ b/src/Microsoft.AspNetCore.Server.IISIntegration/WebHostBuilderIISExtensions.cs
  442. @@ -46,19 +46,27 @@ namespace Microsoft.AspNetCore.Hosting
  443. hostBuilder.CaptureStartupErrors(true);
  444. // TODO consider adding a configuration load where all variables needed are loaded from ANCM in one call.
  445. - var hResult = NativeMethods.http_get_application_paths(out var fullPath, out var virtualPath);
  446. + var iisConfigData = new IISConfigurationData();
  447. + var hResult = NativeMethods.http_get_application_properties(ref iisConfigData);
  448. +
  449. var exception = Marshal.GetExceptionForHR(hResult);
  450. if (exception != null)
  451. {
  452. throw exception;
  453. }
  454. - hostBuilder.UseContentRoot(fullPath);
  455. + hostBuilder.UseContentRoot(iisConfigData.pwzFullApplicationPath);
  456. return hostBuilder.ConfigureServices(services =>
  457. {
  458. services.AddSingleton<IServer, IISHttpServer>();
  459. - services.AddSingleton<IStartupFilter>(new IISServerSetupFilter(virtualPath));
  460. + services.AddSingleton<IStartupFilter>(new IISServerSetupFilter(iisConfigData.pwzVirtualApplicationPath));
  461. services.AddAuthenticationCore();
  462. + services.Configure<IISOptions>(
  463. + options =>
  464. + {
  465. + options.ForwardWindowsAuthentication = iisConfigData.fWindowsAuthEnabled || iisConfigData.fBasicAuthEnabled;
  466. + }
  467. + );
  468. });
  469. }
  470. diff --git a/test/IISIntegration.FunctionalTests/NtlmAuthentationTest.cs b/test/IISIntegration.FunctionalTests/NtlmAuthentationTest.cs
  471. index a61cd3f4474..20f2c65c9c4 100644
  472. --- a/test/IISIntegration.FunctionalTests/NtlmAuthentationTest.cs
  473. +++ b/test/IISIntegration.FunctionalTests/NtlmAuthentationTest.cs
  474. @@ -1,4 +1,4 @@
  475. -// Copyright (c) .NET Foundation. All rights reserved.
  476. +// Copyright (c) .NET Foundation. All rights reserved.
  477. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  478. #if NET461
  479. diff --git a/test/IISIntegration.IISServerFunctionalTests/AuthenticationTests.cs b/test/IISIntegration.IISServerFunctionalTests/AuthenticationTests.cs
  480. new file mode 100644
  481. index 00000000000..d069b5211e9
  482. --- /dev/null
  483. +++ b/test/IISIntegration.IISServerFunctionalTests/AuthenticationTests.cs
  484. @@ -0,0 +1,121 @@
  485. +// Copyright (c) .NET Foundation. All rights reserved.
  486. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  487. +
  488. +#if NET461
  489. +
  490. +using System;
  491. +using System.Collections.Generic;
  492. +using System.IO;
  493. +using System.Net;
  494. +using System.Net.Http;
  495. +using System.Text;
  496. +using System.Threading.Tasks;
  497. +using Microsoft.AspNetCore.Server.IIS.FunctionalTests;
  498. +using Microsoft.AspNetCore.Server.IntegrationTesting;
  499. +using Microsoft.Extensions.Logging;
  500. +using Microsoft.Extensions.Logging.Testing;
  501. +using Xunit;
  502. +using Xunit.Abstractions;
  503. +using Xunit.Sdk;
  504. +
  505. +namespace IISIntegration.IISServerFunctionalTests
  506. +{
  507. + public class AuthenticationTests : LoggedTest
  508. + {
  509. + public AuthenticationTests(ITestOutputHelper output) : base(output)
  510. + {
  511. + }
  512. +
  513. + [Fact(Skip = "See https://github.com/aspnet/IISIntegration/issues/424")]
  514. + public Task Authentication_InProcess_IISExpress()
  515. + {
  516. + return Authentication();
  517. + }
  518. +
  519. + private async Task Authentication()
  520. + {
  521. + var serverType = ServerType.IISExpress;
  522. + var architecture = RuntimeArchitecture.x64;
  523. + var testName = $"Authentication_{RuntimeFlavor.CoreClr}";
  524. + using (StartLog(out var loggerFactory, testName))
  525. + {
  526. + var logger = loggerFactory.CreateLogger("AuthenticationTest");
  527. +
  528. + var deploymentParameters = new DeploymentParameters(Helpers.GetTestSitesPath(), serverType, RuntimeFlavor.CoreClr, architecture)
  529. + {
  530. + ApplicationBaseUriHint = $"http://localhost:5051",
  531. + EnvironmentName = "Authentication", // Will pick the Start class named 'StartupAuthentication',
  532. + ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText("Http.config") : null,
  533. + SiteName = "HttpTestSite", // This is configured in the Http.config
  534. + TargetFramework = "netcoreapp2.0",
  535. + ApplicationType = ApplicationType.Portable
  536. + };
  537. +
  538. + using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory))
  539. + {
  540. + var deploymentResult = await deployer.DeployAsync();
  541. + var httpClient = deploymentResult.HttpClient;
  542. + deploymentResult.HttpClient.Timeout = TimeSpan.FromSeconds(5);
  543. +
  544. + // Request to base address and check if various parts of the body are rendered & measure the cold startup time.
  545. + var response = await RetryHelper.RetryRequest(() =>
  546. + {
  547. + return deploymentResult.HttpClient.GetAsync(string.Empty);
  548. + }, logger, deploymentResult.HostShutdownToken, retryCount: 30);
  549. +
  550. + var responseText = await response.Content.ReadAsStringAsync();
  551. + try
  552. + {
  553. + Assert.Equal(HttpStatusCode.OK, response.StatusCode);
  554. + Assert.Equal("Hello World", responseText);
  555. +
  556. + response = await httpClient.GetAsync("/Anonymous");
  557. + responseText = await response.Content.ReadAsStringAsync();
  558. + Assert.Equal(HttpStatusCode.OK, response.StatusCode);
  559. + Assert.Equal("Anonymous?True", responseText);
  560. +
  561. + response = await httpClient.GetAsync("/Restricted");
  562. + responseText = await response.Content.ReadAsStringAsync();
  563. + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
  564. + Assert.Contains("NTLM", response.Headers.WwwAuthenticate.ToString());
  565. + Assert.Contains("Negotiate", response.Headers.WwwAuthenticate.ToString());
  566. +
  567. + response = await httpClient.GetAsync("/RestrictedNTLM");
  568. + responseText = await response.Content.ReadAsStringAsync();
  569. + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
  570. + Assert.Contains("NTLM", response.Headers.WwwAuthenticate.ToString());
  571. + // Note we can't restrict a challenge to a specific auth type, the native auth modules always add themselves.
  572. + Assert.Contains("Negotiate", response.Headers.WwwAuthenticate.ToString());
  573. +
  574. + response = await httpClient.GetAsync("/Forbidden");
  575. + responseText = await response.Content.ReadAsStringAsync();
  576. + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
  577. +
  578. + var httpClientHandler = new HttpClientHandler() { UseDefaultCredentials = true };
  579. + httpClient = deploymentResult.CreateHttpClient(httpClientHandler);
  580. +
  581. + response = await httpClient.GetAsync("/Anonymous");
  582. + responseText = await response.Content.ReadAsStringAsync();
  583. + Assert.Equal(HttpStatusCode.OK, response.StatusCode);
  584. + Assert.Equal("Anonymous?True", responseText);
  585. +
  586. + response = await httpClient.GetAsync("/Restricted");
  587. + responseText = await response.Content.ReadAsStringAsync();
  588. + Assert.Equal(HttpStatusCode.OK, response.StatusCode);
  589. + Assert.NotEmpty(responseText);
  590. + }
  591. + catch (XunitException)
  592. + {
  593. + logger.LogWarning(response.ToString());
  594. + logger.LogWarning(responseText);
  595. + throw;
  596. + }
  597. + }
  598. + }
  599. + }
  600. + }
  601. +}
  602. +#elif NETCOREAPP2_0
  603. +#else
  604. +#error Target frameworks need to be updated
  605. +#endif
  606. diff --git a/test/IISIntegration.IISServerFunctionalTests/HelloWorldTests.cs b/test/IISIntegration.IISServerFunctionalTests/HelloWorldTests.cs
  607. index 1411934bf9e..610ac8c977d 100644
  608. --- a/test/IISIntegration.IISServerFunctionalTests/HelloWorldTests.cs
  609. +++ b/test/IISIntegration.IISServerFunctionalTests/HelloWorldTests.cs
  610. @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
  611. {
  612. }
  613. - [Fact( Skip ="See https://github.com/aspnet/IISIntegration/issues/424")]
  614. + [Fact(Skip = "See https://github.com/aspnet/IISIntegration/issues/424")]
  615. public Task HelloWorld_InProcess_IISExpress_CoreClr_X64_Portable()
  616. {
  617. return HelloWorld(RuntimeFlavor.CoreClr, ApplicationType.Portable);
  618. diff --git a/test/IISIntegration.IISServerFunctionalTests/Http.config b/test/IISIntegration.IISServerFunctionalTests/Http.config
  619. index a92444f4538..c044b3294c8 100644
  620. --- a/test/IISIntegration.IISServerFunctionalTests/Http.config
  621. +++ b/test/IISIntegration.IISServerFunctionalTests/Http.config
  622. @@ -1026,4 +1026,14 @@
  623. </handlers>
  624. </system.webServer>
  625. </location>
  626. + <location path="HttpTestSite">
  627. + <system.webServer>
  628. + <security>
  629. + <authentication>
  630. + <anonymousAuthentication enabled="true" />
  631. + <windowsAuthentication enabled="true" />
  632. + </authentication>
  633. + </security>
  634. + </system.webServer>
  635. + </location>
  636. </configuration>
  637. diff --git a/test/IISTestSite/StartupAuthentication.cs b/test/IISTestSite/StartupAuthentication.cs
  638. new file mode 100644
  639. index 00000000000..1306332d1fb
  640. --- /dev/null
  641. +++ b/test/IISTestSite/StartupAuthentication.cs
  642. @@ -0,0 +1,80 @@
  643. +// Copyright (c) .NET Foundation. All rights reserved.
  644. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  645. +
  646. +using System;
  647. +using System.Security.Principal;
  648. +using Microsoft.AspNetCore.Authentication;
  649. +using Microsoft.AspNetCore.Builder;
  650. +using Microsoft.AspNetCore.Http;
  651. +using Microsoft.AspNetCore.Server.IISIntegration;
  652. +using Microsoft.Extensions.Logging;
  653. +using Xunit;
  654. +
  655. +namespace IISTestSite
  656. +{
  657. + public class StartupAuthentication
  658. + {
  659. + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
  660. + {
  661. + // Simple error page without depending on Diagnostics.
  662. + app.Use(async (context, next) =>
  663. + {
  664. + try
  665. + {
  666. + await next();
  667. + }
  668. + catch (Exception ex)
  669. + {
  670. + if (context.Response.HasStarted)
  671. + {
  672. + throw;
  673. + }
  674. +
  675. + context.Response.Clear();
  676. + context.Response.StatusCode = 500;
  677. + await context.Response.WriteAsync(ex.ToString());
  678. + }
  679. + });
  680. +
  681. + app.Use((context, next) =>
  682. + {
  683. + if (context.Request.Path.Equals("/Anonymous"))
  684. + {
  685. + return context.Response.WriteAsync("Anonymous?" + !context.User.Identity.IsAuthenticated);
  686. + }
  687. +
  688. + if (context.Request.Path.Equals("/Restricted"))
  689. + {
  690. + if (context.User.Identity.IsAuthenticated)
  691. + {
  692. + Assert.IsType<WindowsPrincipal>(context.User);
  693. + return context.Response.WriteAsync(context.User.Identity.AuthenticationType);
  694. + }
  695. + else
  696. + {
  697. + return context.ChallengeAsync(IISDefaults.AuthenticationScheme);
  698. + }
  699. + }
  700. +
  701. + if (context.Request.Path.Equals("/Forbidden"))
  702. + {
  703. + return context.ForbidAsync(IISDefaults.AuthenticationScheme);
  704. + }
  705. +
  706. + if (context.Request.Path.Equals("/RestrictedNTLM"))
  707. + {
  708. + if (string.Equals("NTLM", context.User.Identity.AuthenticationType, StringComparison.Ordinal))
  709. + {
  710. + return context.Response.WriteAsync("NTLM");
  711. + }
  712. + else
  713. + {
  714. + return context.ChallengeAsync(IISDefaults.AuthenticationScheme);
  715. + }
  716. + }
  717. +
  718. + return context.Response.WriteAsync("Hello World");
  719. + });
  720. + }
  721. + }
  722. +}