Adds windows Auth support (#471)
This commit is contained in:
parent
9c065bf107
commit
552163ab77
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"windowsAuthentication": true,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:5762/",
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@
|
|||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.IISIntegration;
|
||||
|
||||
namespace NativeIISSample
|
||||
{
|
||||
|
|
@ -13,7 +15,7 @@ namespace NativeIISSample
|
|||
{
|
||||
|
||||
// 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) =>
|
||||
{
|
||||
|
|
@ -38,8 +40,8 @@ namespace NativeIISSample
|
|||
await context.Response.WriteAsync(Environment.NewLine);
|
||||
|
||||
await context.Response.WriteAsync("User: " + context.User.Identity.Name + Environment.NewLine);
|
||||
//var scheme = await authSchemeProvider.GetSchemeAsync(IISDefaults.AuthenticationScheme);
|
||||
//await context.Response.WriteAsync("DisplayName: " + scheme?.DisplayName + Environment.NewLine);
|
||||
var scheme = await authSchemeProvider.GetSchemeAsync(IISDefaults.AuthenticationScheme);
|
||||
await context.Response.WriteAsync("DisplayName: " + scheme?.DisplayName + Environment.NewLine);
|
||||
|
||||
await context.Response.WriteAsync(Environment.NewLine);
|
||||
|
||||
|
|
|
|||
|
|
@ -299,12 +299,12 @@
|
|||
<application name="Active Server Pages" groupId="ASP" />
|
||||
</applicationDependencies>
|
||||
<authentication>
|
||||
<anonymousAuthentication enabled="true" userName="" />
|
||||
<anonymousAuthentication enabled="false" userName="" />
|
||||
<basicAuthentication enabled="false" />
|
||||
<clientCertificateMappingAuthentication enabled="false" />
|
||||
<digestAuthentication enabled="false" />
|
||||
<iisClientCertificateMappingAuthentication enabled="false"></iisClientCertificateMappingAuthentication>
|
||||
<windowsAuthentication enabled="false">
|
||||
<windowsAuthentication enabled="true">
|
||||
<providers>
|
||||
<add value="Negotiate" />
|
||||
<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);
|
||||
|
||||
[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)]
|
||||
public unsafe static extern bool http_shutdown();
|
||||
|
|
@ -98,6 +98,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
[DllImport(AspNetCoreModuleDll)]
|
||||
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")]
|
||||
public static extern IntPtr GetModuleHandle(string lpModuleName);
|
||||
|
||||
|
|
@ -105,6 +108,5 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
{
|
||||
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.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Claims;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Http.Features.Authentication;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
|
|
@ -22,7 +24,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
IHttpUpgradeFeature,
|
||||
IHttpConnectionFeature,
|
||||
IHttpRequestLifetimeFeature,
|
||||
IHttpRequestIdentifierFeature
|
||||
IHttpRequestIdentifierFeature,
|
||||
IHttpAuthenticationFeature
|
||||
{
|
||||
// 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.
|
||||
|
|
@ -223,6 +226,14 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
set => TraceIdentifier = value;
|
||||
}
|
||||
|
||||
ClaimsPrincipal IHttpAuthenticationFeature.User
|
||||
{
|
||||
get => User;
|
||||
set => User = value;
|
||||
}
|
||||
|
||||
public IAuthenticationHandler Handler { get; set; }
|
||||
|
||||
object IFeatureCollection.this[Type 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 IHttpBodyControlFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature);
|
||||
private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
|
||||
private static readonly Type IISHttpContextType = typeof(IISHttpContext);
|
||||
|
||||
private object _currentIHttpRequestFeature;
|
||||
private object _currentIHttpResponseFeature;
|
||||
|
|
@ -61,6 +62,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
_currentIHttpMinRequestBodyDataRateFeature = this;
|
||||
_currentIHttpMinResponseDataRateFeature = this;
|
||||
_currentIHttpBodyControlFeature = this;
|
||||
_currentIHttpAuthenticationFeature = this;
|
||||
}
|
||||
|
||||
internal object FastFeatureGet(Type key)
|
||||
|
|
@ -133,6 +135,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
{
|
||||
return _currentIHttpSendFileFeature;
|
||||
}
|
||||
if (key == IISHttpContextType)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
return ExtraFeatureGet(key);
|
||||
}
|
||||
|
||||
|
|
@ -224,6 +231,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
{
|
||||
_currentIHttpSendFileFeature = feature;
|
||||
return;
|
||||
}
|
||||
if (key == IISHttpContextType)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot set IISHttpContext in feature collection");
|
||||
};
|
||||
ExtraFeatureSet(key, feature);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,12 @@ using System.IO;
|
|||
using System.IO.Pipelines;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.HttpSys.Internal;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
|
|
@ -57,7 +60,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
private CurrentOperationType _currentOperationType;
|
||||
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))
|
||||
{
|
||||
_thisHandle = GCHandle.Alloc(this);
|
||||
|
|
@ -120,6 +127,15 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
HttpResponseHeaders = new HeaderCollection(); // TODO Optimize for known headers
|
||||
ResponseHeaders = HttpResponseHeaders;
|
||||
|
||||
if (options.ForwardWindowsAuthentication)
|
||||
{
|
||||
WindowsUser = GetWindowsPrincipal();
|
||||
if (options.AutomaticAuthentication)
|
||||
{
|
||||
User = WindowsUser;
|
||||
}
|
||||
}
|
||||
|
||||
ResetFeatureCollection();
|
||||
}
|
||||
|
||||
|
|
@ -146,7 +162,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
public int LocalPort { get; set; }
|
||||
public string RequestConnectionId { get; set; }
|
||||
public string TraceIdentifier { get; set; }
|
||||
|
||||
public ClaimsPrincipal User { get; set; }
|
||||
internal WindowsPrincipal WindowsUser { get; set; }
|
||||
public Stream RequestBody { get; set; }
|
||||
public Stream ResponseBody { get; set; }
|
||||
public IPipe Input { get; set; }
|
||||
|
|
@ -827,7 +844,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
// TODO: dispose managed state (managed objects).
|
||||
_thisHandle.Free();
|
||||
}
|
||||
|
||||
if (WindowsUser?.Identity is WindowsIdentity wi)
|
||||
{
|
||||
wi.Dispose();
|
||||
}
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -851,5 +871,21 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
Write,
|
||||
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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using System.Threading;
|
||||
using System.IO.Pipelines;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||
{
|
||||
|
|
@ -13,8 +14,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
{
|
||||
private readonly IHttpApplication<TContext> _application;
|
||||
|
||||
public IISHttpContextOfT(PipeFactory pipeFactory, IHttpApplication<TContext> application, IntPtr pHttpContext)
|
||||
: base(pipeFactory, pHttpContext)
|
||||
public IISHttpContextOfT(PipeFactory pipeFactory, IHttpApplication<TContext> application, IntPtr pHttpContext, IISOptions options)
|
||||
: base(pipeFactory, pHttpContext, options)
|
||||
{
|
||||
_application = application;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,12 @@ using System.IO.Pipelines;
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||
{
|
||||
|
|
@ -19,22 +22,30 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
private static NativeMethods.PFN_ASYNC_COMPLETION _onAsyncCompletion = OnAsyncCompletion;
|
||||
|
||||
private IISContextFactory _iisContextFactory;
|
||||
|
||||
private PipeFactory _pipeFactory = new PipeFactory();
|
||||
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 IISHttpServer(IApplicationLifetime applicationLifetime)
|
||||
public IISHttpServer(IApplicationLifetime applicationLifetime, IAuthenticationSchemeProvider authentication, IOptions<IISOptions> options)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
_httpServerHandle = GCHandle.Alloc(this);
|
||||
|
||||
_iisContextFactory = new IISContextFactory<TContext>(_pipeFactory, application);
|
||||
_iisContextFactory = new IISContextFactory<TContext>(_pipeFactory, application, _options);
|
||||
|
||||
// Start the server by registering the callback
|
||||
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 PipeFactory _pipeFactory;
|
||||
private readonly IISOptions _options;
|
||||
|
||||
public IISContextFactory(PipeFactory pipeFactory, IHttpApplication<T> application)
|
||||
public IISContextFactory(PipeFactory pipeFactory, IHttpApplication<T> application, IISOptions options)
|
||||
{
|
||||
_application = application;
|
||||
_pipeFactory = pipeFactory;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 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);
|
||||
if (exception != null)
|
||||
{
|
||||
throw exception;
|
||||
}
|
||||
|
||||
hostBuilder.UseContentRoot(fullPath);
|
||||
hostBuilder.UseContentRoot(iisConfigData.pwzFullApplicationPath);
|
||||
return hostBuilder.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton<IServer, IISHttpServer>();
|
||||
services.AddSingleton<IStartupFilter>(new IISServerSetupFilter(virtualPath));
|
||||
services.AddSingleton<IStartupFilter>(new IISServerSetupFilter(iisConfigData.pwzVirtualApplicationPath));
|
||||
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.
|
||||
|
||||
#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()
|
||||
{
|
||||
return HelloWorld(RuntimeFlavor.CoreClr, ApplicationType.Portable);
|
||||
|
|
|
|||
|
|
@ -1026,4 +1026,14 @@
|
|||
</handlers>
|
||||
</system.webServer>
|
||||
</location>
|
||||
<location path="HttpTestSite">
|
||||
<system.webServer>
|
||||
<security>
|
||||
<authentication>
|
||||
<anonymousAuthentication enabled="true" />
|
||||
<windowsAuthentication enabled="true" />
|
||||
</authentication>
|
||||
</security>
|
||||
</system.webServer>
|
||||
</location>
|
||||
</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