Adds windows Auth support (#471)
This commit is contained in:
parent
9c065bf107
commit
552163ab77
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"iisSettings": {
|
"iisSettings": {
|
||||||
"windowsAuthentication": false,
|
"windowsAuthentication": true,
|
||||||
"anonymousAuthentication": true,
|
"anonymousAuthentication": true,
|
||||||
"iisExpress": {
|
"iisExpress": {
|
||||||
"applicationUrl": "http://localhost:5762/",
|
"applicationUrl": "http://localhost:5762/",
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Server.IISIntegration;
|
||||||
|
|
||||||
namespace NativeIISSample
|
namespace NativeIISSample
|
||||||
{
|
{
|
||||||
|
|
@ -13,7 +15,7 @@ namespace NativeIISSample
|
||||||
{
|
{
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IAuthenticationSchemeProvider authSchemeProvider)
|
||||||
{
|
{
|
||||||
app.Run(async (context) =>
|
app.Run(async (context) =>
|
||||||
{
|
{
|
||||||
|
|
@ -38,8 +40,8 @@ namespace NativeIISSample
|
||||||
await context.Response.WriteAsync(Environment.NewLine);
|
await context.Response.WriteAsync(Environment.NewLine);
|
||||||
|
|
||||||
await context.Response.WriteAsync("User: " + context.User.Identity.Name + Environment.NewLine);
|
await context.Response.WriteAsync("User: " + context.User.Identity.Name + Environment.NewLine);
|
||||||
//var scheme = await authSchemeProvider.GetSchemeAsync(IISDefaults.AuthenticationScheme);
|
var scheme = await authSchemeProvider.GetSchemeAsync(IISDefaults.AuthenticationScheme);
|
||||||
//await context.Response.WriteAsync("DisplayName: " + scheme?.DisplayName + Environment.NewLine);
|
await context.Response.WriteAsync("DisplayName: " + scheme?.DisplayName + Environment.NewLine);
|
||||||
|
|
||||||
await context.Response.WriteAsync(Environment.NewLine);
|
await context.Response.WriteAsync(Environment.NewLine);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -299,12 +299,12 @@
|
||||||
<application name="Active Server Pages" groupId="ASP" />
|
<application name="Active Server Pages" groupId="ASP" />
|
||||||
</applicationDependencies>
|
</applicationDependencies>
|
||||||
<authentication>
|
<authentication>
|
||||||
<anonymousAuthentication enabled="true" userName="" />
|
<anonymousAuthentication enabled="false" userName="" />
|
||||||
<basicAuthentication enabled="false" />
|
<basicAuthentication enabled="false" />
|
||||||
<clientCertificateMappingAuthentication enabled="false" />
|
<clientCertificateMappingAuthentication enabled="false" />
|
||||||
<digestAuthentication enabled="false" />
|
<digestAuthentication enabled="false" />
|
||||||
<iisClientCertificateMappingAuthentication enabled="false"></iisClientCertificateMappingAuthentication>
|
<iisClientCertificateMappingAuthentication enabled="false"></iisClientCertificateMappingAuthentication>
|
||||||
<windowsAuthentication enabled="false">
|
<windowsAuthentication enabled="true">
|
||||||
<providers>
|
<providers>
|
||||||
<add value="Negotiate" />
|
<add value="Negotiate" />
|
||||||
<add value="NTLM" />
|
<add value="NTLM" />
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
public unsafe static extern bool http_set_managed_context(IntPtr pHttpContext, IntPtr pvManagedContext);
|
public unsafe static extern bool http_set_managed_context(IntPtr pHttpContext, IntPtr pvManagedContext);
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
[DllImport(AspNetCoreModuleDll)]
|
||||||
public unsafe static extern int http_get_application_paths([MarshalAs(UnmanagedType.BStr)] out string fullPath, [MarshalAs(UnmanagedType.BStr)] out string virtualPath);
|
public unsafe static extern int http_get_application_properties(ref IISConfigurationData iiConfigData);
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
[DllImport(AspNetCoreModuleDll)]
|
||||||
public unsafe static extern bool http_shutdown();
|
public unsafe static extern bool http_shutdown();
|
||||||
|
|
@ -98,6 +98,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
[DllImport(AspNetCoreModuleDll)]
|
||||||
internal unsafe static extern int http_response_set_known_header(IntPtr pHttpContext, int headerId, byte* pHeaderValue, ushort length, bool fReplace);
|
internal unsafe static extern int http_response_set_known_header(IntPtr pHttpContext, int headerId, byte* pHeaderValue, ushort length, bool fReplace);
|
||||||
|
|
||||||
|
[DllImport(AspNetCoreModuleDll)]
|
||||||
|
public unsafe static extern int http_get_authentication_information(IntPtr pHttpContext, [MarshalAs(UnmanagedType.BStr)] out string authType, out IntPtr token);
|
||||||
|
|
||||||
[DllImport("kernel32.dll")]
|
[DllImport("kernel32.dll")]
|
||||||
public static extern IntPtr GetModuleHandle(string lpModuleName);
|
public static extern IntPtr GetModuleHandle(string lpModuleName);
|
||||||
|
|
||||||
|
|
@ -105,6 +108,5 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
{
|
{
|
||||||
return GetModuleHandle(AspNetCoreModuleDll) != IntPtr.Zero;
|
return GetModuleHandle(AspNetCoreModuleDll) != IntPtr.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// 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.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
internal unsafe struct IISConfigurationData
|
||||||
|
{
|
||||||
|
[MarshalAs(UnmanagedType.BStr)]
|
||||||
|
public string pwzFullApplicationPath;
|
||||||
|
[MarshalAs(UnmanagedType.BStr)]
|
||||||
|
public string pwzVirtualApplicationPath;
|
||||||
|
public bool fWindowsAuthEnabled;
|
||||||
|
public bool fBasicAuthEnabled;
|
||||||
|
public bool fAnonymousAuthEnable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,10 +7,12 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Security.Claims;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.AspNetCore.Http.Features.Authentication;
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
|
|
@ -22,7 +24,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
IHttpUpgradeFeature,
|
IHttpUpgradeFeature,
|
||||||
IHttpConnectionFeature,
|
IHttpConnectionFeature,
|
||||||
IHttpRequestLifetimeFeature,
|
IHttpRequestLifetimeFeature,
|
||||||
IHttpRequestIdentifierFeature
|
IHttpRequestIdentifierFeature,
|
||||||
|
IHttpAuthenticationFeature
|
||||||
{
|
{
|
||||||
// NOTE: When feature interfaces are added to or removed from this HttpProtocol implementation,
|
// NOTE: When feature interfaces are added to or removed from this HttpProtocol implementation,
|
||||||
// then the list of `implementedFeatures` in the generated code project MUST also be updated.
|
// then the list of `implementedFeatures` in the generated code project MUST also be updated.
|
||||||
|
|
@ -223,6 +226,14 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
set => TraceIdentifier = value;
|
set => TraceIdentifier = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClaimsPrincipal IHttpAuthenticationFeature.User
|
||||||
|
{
|
||||||
|
get => User;
|
||||||
|
set => User = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IAuthenticationHandler Handler { get; set; }
|
||||||
|
|
||||||
object IFeatureCollection.this[Type key]
|
object IFeatureCollection.this[Type key]
|
||||||
{
|
{
|
||||||
get => FastFeatureGet(key);
|
get => FastFeatureGet(key);
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
private static readonly Type ISessionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ISessionFeature);
|
private static readonly Type ISessionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ISessionFeature);
|
||||||
private static readonly Type IHttpBodyControlFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature);
|
private static readonly Type IHttpBodyControlFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature);
|
||||||
private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
|
private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
|
||||||
|
private static readonly Type IISHttpContextType = typeof(IISHttpContext);
|
||||||
|
|
||||||
private object _currentIHttpRequestFeature;
|
private object _currentIHttpRequestFeature;
|
||||||
private object _currentIHttpResponseFeature;
|
private object _currentIHttpResponseFeature;
|
||||||
|
|
@ -61,6 +62,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
_currentIHttpMinRequestBodyDataRateFeature = this;
|
_currentIHttpMinRequestBodyDataRateFeature = this;
|
||||||
_currentIHttpMinResponseDataRateFeature = this;
|
_currentIHttpMinResponseDataRateFeature = this;
|
||||||
_currentIHttpBodyControlFeature = this;
|
_currentIHttpBodyControlFeature = this;
|
||||||
|
_currentIHttpAuthenticationFeature = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal object FastFeatureGet(Type key)
|
internal object FastFeatureGet(Type key)
|
||||||
|
|
@ -133,6 +135,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
{
|
{
|
||||||
return _currentIHttpSendFileFeature;
|
return _currentIHttpSendFileFeature;
|
||||||
}
|
}
|
||||||
|
if (key == IISHttpContextType)
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
return ExtraFeatureGet(key);
|
return ExtraFeatureGet(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,6 +231,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
{
|
{
|
||||||
_currentIHttpSendFileFeature = feature;
|
_currentIHttpSendFileFeature = feature;
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
if (key == IISHttpContextType)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Cannot set IISHttpContext in feature collection");
|
||||||
};
|
};
|
||||||
ExtraFeatureSet(key, feature);
|
ExtraFeatureSet(key, feature);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,12 @@ using System.IO;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Security.Principal;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.HttpSys.Internal;
|
using Microsoft.AspNetCore.HttpSys.Internal;
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
|
|
@ -57,7 +60,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
private CurrentOperationType _currentOperationType;
|
private CurrentOperationType _currentOperationType;
|
||||||
private Task _currentOperation = Task.CompletedTask;
|
private Task _currentOperation = Task.CompletedTask;
|
||||||
|
|
||||||
internal unsafe IISHttpContext(PipeFactory pipeFactory, IntPtr pHttpContext)
|
private const string NtlmString = "NTLM";
|
||||||
|
private const string NegotiateString = "Negotiate";
|
||||||
|
private const string BasicString = "Basic";
|
||||||
|
|
||||||
|
internal unsafe IISHttpContext(PipeFactory pipeFactory, IntPtr pHttpContext, IISOptions options)
|
||||||
: base((HttpApiTypes.HTTP_REQUEST*)NativeMethods.http_get_raw_request(pHttpContext))
|
: base((HttpApiTypes.HTTP_REQUEST*)NativeMethods.http_get_raw_request(pHttpContext))
|
||||||
{
|
{
|
||||||
_thisHandle = GCHandle.Alloc(this);
|
_thisHandle = GCHandle.Alloc(this);
|
||||||
|
|
@ -120,6 +127,15 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
HttpResponseHeaders = new HeaderCollection(); // TODO Optimize for known headers
|
HttpResponseHeaders = new HeaderCollection(); // TODO Optimize for known headers
|
||||||
ResponseHeaders = HttpResponseHeaders;
|
ResponseHeaders = HttpResponseHeaders;
|
||||||
|
|
||||||
|
if (options.ForwardWindowsAuthentication)
|
||||||
|
{
|
||||||
|
WindowsUser = GetWindowsPrincipal();
|
||||||
|
if (options.AutomaticAuthentication)
|
||||||
|
{
|
||||||
|
User = WindowsUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ResetFeatureCollection();
|
ResetFeatureCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,7 +162,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
public int LocalPort { get; set; }
|
public int LocalPort { get; set; }
|
||||||
public string RequestConnectionId { get; set; }
|
public string RequestConnectionId { get; set; }
|
||||||
public string TraceIdentifier { get; set; }
|
public string TraceIdentifier { get; set; }
|
||||||
|
public ClaimsPrincipal User { get; set; }
|
||||||
|
internal WindowsPrincipal WindowsUser { get; set; }
|
||||||
public Stream RequestBody { get; set; }
|
public Stream RequestBody { get; set; }
|
||||||
public Stream ResponseBody { get; set; }
|
public Stream ResponseBody { get; set; }
|
||||||
public IPipe Input { get; set; }
|
public IPipe Input { get; set; }
|
||||||
|
|
@ -827,7 +844,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
// TODO: dispose managed state (managed objects).
|
// TODO: dispose managed state (managed objects).
|
||||||
_thisHandle.Free();
|
_thisHandle.Free();
|
||||||
}
|
}
|
||||||
|
if (WindowsUser?.Identity is WindowsIdentity wi)
|
||||||
|
{
|
||||||
|
wi.Dispose();
|
||||||
|
}
|
||||||
disposedValue = true;
|
disposedValue = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -851,5 +871,21 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
Write,
|
Write,
|
||||||
Flush
|
Flush
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private WindowsPrincipal GetWindowsPrincipal()
|
||||||
|
{
|
||||||
|
var hr = NativeMethods.http_get_authentication_information(_pHttpContext, out var authenticationType, out var token);
|
||||||
|
|
||||||
|
if (hr == 0 && token != IntPtr.Zero && authenticationType != null)
|
||||||
|
{
|
||||||
|
if ((authenticationType.Equals(NtlmString, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| authenticationType.Equals(NegotiateString, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| authenticationType.Equals(BasicString, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
return new WindowsPrincipal(new WindowsIdentity(token, authenticationType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
{
|
{
|
||||||
|
|
@ -13,8 +14,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
{
|
{
|
||||||
private readonly IHttpApplication<TContext> _application;
|
private readonly IHttpApplication<TContext> _application;
|
||||||
|
|
||||||
public IISHttpContextOfT(PipeFactory pipeFactory, IHttpApplication<TContext> application, IntPtr pHttpContext)
|
public IISHttpContextOfT(PipeFactory pipeFactory, IHttpApplication<TContext> application, IntPtr pHttpContext, IISOptions options)
|
||||||
: base(pipeFactory, pHttpContext)
|
: base(pipeFactory, pHttpContext, options)
|
||||||
{
|
{
|
||||||
_application = application;
|
_application = application;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,12 @@ using System.IO.Pipelines;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
{
|
{
|
||||||
|
|
@ -19,22 +22,30 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
private static NativeMethods.PFN_ASYNC_COMPLETION _onAsyncCompletion = OnAsyncCompletion;
|
private static NativeMethods.PFN_ASYNC_COMPLETION _onAsyncCompletion = OnAsyncCompletion;
|
||||||
|
|
||||||
private IISContextFactory _iisContextFactory;
|
private IISContextFactory _iisContextFactory;
|
||||||
|
|
||||||
private PipeFactory _pipeFactory = new PipeFactory();
|
private PipeFactory _pipeFactory = new PipeFactory();
|
||||||
private GCHandle _httpServerHandle;
|
private GCHandle _httpServerHandle;
|
||||||
private IApplicationLifetime _applicationLifetime;
|
private readonly IApplicationLifetime _applicationLifetime;
|
||||||
|
private readonly IAuthenticationSchemeProvider _authentication;
|
||||||
|
private readonly IISOptions _options;
|
||||||
|
|
||||||
public IFeatureCollection Features { get; } = new FeatureCollection();
|
public IFeatureCollection Features { get; } = new FeatureCollection();
|
||||||
public IISHttpServer(IApplicationLifetime applicationLifetime)
|
public IISHttpServer(IApplicationLifetime applicationLifetime, IAuthenticationSchemeProvider authentication, IOptions<IISOptions> options)
|
||||||
{
|
{
|
||||||
_applicationLifetime = applicationLifetime;
|
_applicationLifetime = applicationLifetime;
|
||||||
|
_authentication = authentication;
|
||||||
|
_options = options.Value;
|
||||||
|
|
||||||
|
if (_options.ForwardWindowsAuthentication)
|
||||||
|
{
|
||||||
|
authentication.AddScheme(new AuthenticationScheme(IISDefaults.AuthenticationScheme, _options.AuthenticationDisplayName, typeof(IISServerAuthenticationHandler)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
|
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_httpServerHandle = GCHandle.Alloc(this);
|
_httpServerHandle = GCHandle.Alloc(this);
|
||||||
|
|
||||||
_iisContextFactory = new IISContextFactory<TContext>(_pipeFactory, application);
|
_iisContextFactory = new IISContextFactory<TContext>(_pipeFactory, application, _options);
|
||||||
|
|
||||||
// Start the server by registering the callback
|
// Start the server by registering the callback
|
||||||
NativeMethods.register_callbacks(_requestHandler, _shutdownHandler, _onAsyncCompletion, (IntPtr)_httpServerHandle, (IntPtr)_httpServerHandle);
|
NativeMethods.register_callbacks(_requestHandler, _shutdownHandler, _onAsyncCompletion, (IntPtr)_httpServerHandle, (IntPtr)_httpServerHandle);
|
||||||
|
|
@ -115,16 +126,18 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
{
|
{
|
||||||
private readonly IHttpApplication<T> _application;
|
private readonly IHttpApplication<T> _application;
|
||||||
private readonly PipeFactory _pipeFactory;
|
private readonly PipeFactory _pipeFactory;
|
||||||
|
private readonly IISOptions _options;
|
||||||
|
|
||||||
public IISContextFactory(PipeFactory pipeFactory, IHttpApplication<T> application)
|
public IISContextFactory(PipeFactory pipeFactory, IHttpApplication<T> application, IISOptions options)
|
||||||
{
|
{
|
||||||
_application = application;
|
_application = application;
|
||||||
_pipeFactory = pipeFactory;
|
_pipeFactory = pipeFactory;
|
||||||
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IISHttpContext CreateHttpContext(IntPtr pHttpContext)
|
public IISHttpContext CreateHttpContext(IntPtr pHttpContext)
|
||||||
{
|
{
|
||||||
return new IISHttpContextOfT<T>(_pipeFactory, _application, pHttpContext);
|
return new IISHttpContextOfT<T>(_pipeFactory, _application, pHttpContext, _options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
// 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.Security.Principal;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
|
{
|
||||||
|
public class IISServerAuthenticationHandler : IAuthenticationHandler
|
||||||
|
{
|
||||||
|
private HttpContext _context;
|
||||||
|
private IISHttpContext _iisHttpContext;
|
||||||
|
|
||||||
|
internal AuthenticationScheme Scheme { get; private set; }
|
||||||
|
|
||||||
|
public Task<AuthenticateResult> AuthenticateAsync()
|
||||||
|
{
|
||||||
|
var user = _iisHttpContext.WindowsUser;
|
||||||
|
if (user != null && user.Identity != null && user.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(user, Scheme.Name)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Task.FromResult(AuthenticateResult.NoResult());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ChallengeAsync(AuthenticationProperties properties)
|
||||||
|
{
|
||||||
|
// We would normally set the www-authenticate header here, but IIS does that for us.
|
||||||
|
_context.Response.StatusCode = 401;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ForbidAsync(AuthenticationProperties properties)
|
||||||
|
{
|
||||||
|
_context.Response.StatusCode = 403;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
|
||||||
|
{
|
||||||
|
_iisHttpContext = context.Features.Get<IISHttpContext>();
|
||||||
|
if (_iisHttpContext == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("No IISHttpContext found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Scheme = scheme;
|
||||||
|
_context = context;
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -46,19 +46,27 @@ namespace Microsoft.AspNetCore.Hosting
|
||||||
hostBuilder.CaptureStartupErrors(true);
|
hostBuilder.CaptureStartupErrors(true);
|
||||||
|
|
||||||
// TODO consider adding a configuration load where all variables needed are loaded from ANCM in one call.
|
// TODO consider adding a configuration load where all variables needed are loaded from ANCM in one call.
|
||||||
var hResult = NativeMethods.http_get_application_paths(out var fullPath, out var virtualPath);
|
var iisConfigData = new IISConfigurationData();
|
||||||
|
var hResult = NativeMethods.http_get_application_properties(ref iisConfigData);
|
||||||
|
|
||||||
var exception = Marshal.GetExceptionForHR(hResult);
|
var exception = Marshal.GetExceptionForHR(hResult);
|
||||||
if (exception != null)
|
if (exception != null)
|
||||||
{
|
{
|
||||||
throw exception;
|
throw exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
hostBuilder.UseContentRoot(fullPath);
|
hostBuilder.UseContentRoot(iisConfigData.pwzFullApplicationPath);
|
||||||
return hostBuilder.ConfigureServices(services =>
|
return hostBuilder.ConfigureServices(services =>
|
||||||
{
|
{
|
||||||
services.AddSingleton<IServer, IISHttpServer>();
|
services.AddSingleton<IServer, IISHttpServer>();
|
||||||
services.AddSingleton<IStartupFilter>(new IISServerSetupFilter(virtualPath));
|
services.AddSingleton<IStartupFilter>(new IISServerSetupFilter(iisConfigData.pwzVirtualApplicationPath));
|
||||||
services.AddAuthenticationCore();
|
services.AddAuthenticationCore();
|
||||||
|
services.Configure<IISOptions>(
|
||||||
|
options =>
|
||||||
|
{
|
||||||
|
options.ForwardWindowsAuthentication = iisConfigData.fWindowsAuthEnabled || iisConfigData.fBasicAuthEnabled;
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
#if NET461
|
#if NET461
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#if NET461
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Server.IIS.FunctionalTests;
|
||||||
|
using Microsoft.AspNetCore.Server.IntegrationTesting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Testing;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
|
namespace IISIntegration.IISServerFunctionalTests
|
||||||
|
{
|
||||||
|
public class AuthenticationTests : LoggedTest
|
||||||
|
{
|
||||||
|
public AuthenticationTests(ITestOutputHelper output) : base(output)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact(Skip = "See https://github.com/aspnet/IISIntegration/issues/424")]
|
||||||
|
public Task Authentication_InProcess_IISExpress()
|
||||||
|
{
|
||||||
|
return Authentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Authentication()
|
||||||
|
{
|
||||||
|
var serverType = ServerType.IISExpress;
|
||||||
|
var architecture = RuntimeArchitecture.x64;
|
||||||
|
var testName = $"Authentication_{RuntimeFlavor.CoreClr}";
|
||||||
|
using (StartLog(out var loggerFactory, testName))
|
||||||
|
{
|
||||||
|
var logger = loggerFactory.CreateLogger("AuthenticationTest");
|
||||||
|
|
||||||
|
var deploymentParameters = new DeploymentParameters(Helpers.GetTestSitesPath(), serverType, RuntimeFlavor.CoreClr, architecture)
|
||||||
|
{
|
||||||
|
ApplicationBaseUriHint = $"http://localhost:5051",
|
||||||
|
EnvironmentName = "Authentication", // Will pick the Start class named 'StartupAuthentication',
|
||||||
|
ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText("Http.config") : null,
|
||||||
|
SiteName = "HttpTestSite", // This is configured in the Http.config
|
||||||
|
TargetFramework = "netcoreapp2.0",
|
||||||
|
ApplicationType = ApplicationType.Portable
|
||||||
|
};
|
||||||
|
|
||||||
|
using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory))
|
||||||
|
{
|
||||||
|
var deploymentResult = await deployer.DeployAsync();
|
||||||
|
var httpClient = deploymentResult.HttpClient;
|
||||||
|
deploymentResult.HttpClient.Timeout = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
|
// Request to base address and check if various parts of the body are rendered & measure the cold startup time.
|
||||||
|
var response = await RetryHelper.RetryRequest(() =>
|
||||||
|
{
|
||||||
|
return deploymentResult.HttpClient.GetAsync(string.Empty);
|
||||||
|
}, logger, deploymentResult.HostShutdownToken, retryCount: 30);
|
||||||
|
|
||||||
|
var responseText = await response.Content.ReadAsStringAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Equal("Hello World", responseText);
|
||||||
|
|
||||||
|
response = await httpClient.GetAsync("/Anonymous");
|
||||||
|
responseText = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Equal("Anonymous?True", responseText);
|
||||||
|
|
||||||
|
response = await httpClient.GetAsync("/Restricted");
|
||||||
|
responseText = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||||
|
Assert.Contains("NTLM", response.Headers.WwwAuthenticate.ToString());
|
||||||
|
Assert.Contains("Negotiate", response.Headers.WwwAuthenticate.ToString());
|
||||||
|
|
||||||
|
response = await httpClient.GetAsync("/RestrictedNTLM");
|
||||||
|
responseText = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||||
|
Assert.Contains("NTLM", response.Headers.WwwAuthenticate.ToString());
|
||||||
|
// Note we can't restrict a challenge to a specific auth type, the native auth modules always add themselves.
|
||||||
|
Assert.Contains("Negotiate", response.Headers.WwwAuthenticate.ToString());
|
||||||
|
|
||||||
|
response = await httpClient.GetAsync("/Forbidden");
|
||||||
|
responseText = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
|
||||||
|
|
||||||
|
var httpClientHandler = new HttpClientHandler() { UseDefaultCredentials = true };
|
||||||
|
httpClient = deploymentResult.CreateHttpClient(httpClientHandler);
|
||||||
|
|
||||||
|
response = await httpClient.GetAsync("/Anonymous");
|
||||||
|
responseText = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Equal("Anonymous?True", responseText);
|
||||||
|
|
||||||
|
response = await httpClient.GetAsync("/Restricted");
|
||||||
|
responseText = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.NotEmpty(responseText);
|
||||||
|
}
|
||||||
|
catch (XunitException)
|
||||||
|
{
|
||||||
|
logger.LogWarning(response.ToString());
|
||||||
|
logger.LogWarning(responseText);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#elif NETCOREAPP2_0
|
||||||
|
#else
|
||||||
|
#error Target frameworks need to be updated
|
||||||
|
#endif
|
||||||
|
|
@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact( Skip ="See https://github.com/aspnet/IISIntegration/issues/424")]
|
[Fact(Skip = "See https://github.com/aspnet/IISIntegration/issues/424")]
|
||||||
public Task HelloWorld_InProcess_IISExpress_CoreClr_X64_Portable()
|
public Task HelloWorld_InProcess_IISExpress_CoreClr_X64_Portable()
|
||||||
{
|
{
|
||||||
return HelloWorld(RuntimeFlavor.CoreClr, ApplicationType.Portable);
|
return HelloWorld(RuntimeFlavor.CoreClr, ApplicationType.Portable);
|
||||||
|
|
|
||||||
|
|
@ -1026,4 +1026,14 @@
|
||||||
</handlers>
|
</handlers>
|
||||||
</system.webServer>
|
</system.webServer>
|
||||||
</location>
|
</location>
|
||||||
|
<location path="HttpTestSite">
|
||||||
|
<system.webServer>
|
||||||
|
<security>
|
||||||
|
<authentication>
|
||||||
|
<anonymousAuthentication enabled="true" />
|
||||||
|
<windowsAuthentication enabled="true" />
|
||||||
|
</authentication>
|
||||||
|
</security>
|
||||||
|
</system.webServer>
|
||||||
|
</location>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
// 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.Security.Principal;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Server.IISIntegration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace IISTestSite
|
||||||
|
{
|
||||||
|
public class StartupAuthentication
|
||||||
|
{
|
||||||
|
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
// Simple error page without depending on Diagnostics.
|
||||||
|
app.Use(async (context, next) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (context.Response.HasStarted)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.Clear();
|
||||||
|
context.Response.StatusCode = 500;
|
||||||
|
await context.Response.WriteAsync(ex.ToString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.Use((context, next) =>
|
||||||
|
{
|
||||||
|
if (context.Request.Path.Equals("/Anonymous"))
|
||||||
|
{
|
||||||
|
return context.Response.WriteAsync("Anonymous?" + !context.User.Identity.IsAuthenticated);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.Request.Path.Equals("/Restricted"))
|
||||||
|
{
|
||||||
|
if (context.User.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
Assert.IsType<WindowsPrincipal>(context.User);
|
||||||
|
return context.Response.WriteAsync(context.User.Identity.AuthenticationType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return context.ChallengeAsync(IISDefaults.AuthenticationScheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.Request.Path.Equals("/Forbidden"))
|
||||||
|
{
|
||||||
|
return context.ForbidAsync(IISDefaults.AuthenticationScheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.Request.Path.Equals("/RestrictedNTLM"))
|
||||||
|
{
|
||||||
|
if (string.Equals("NTLM", context.User.Identity.AuthenticationType, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return context.Response.WriteAsync("NTLM");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return context.ChallengeAsync(IISDefaults.AuthenticationScheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.Response.WriteAsync("Hello World");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue