Skip to content

Commit

Permalink
#1 Implement a full authentication handler.
Browse files Browse the repository at this point in the history
  • Loading branch information
Tratcher committed Oct 13, 2015
1 parent 348ab7c commit 2fe2e0d
Show file tree
Hide file tree
Showing 9 changed files with 347 additions and 43 deletions.
138 changes: 138 additions & 0 deletions src/Microsoft.AspNet.IISPlatformHandler/AuthenticationHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// 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.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features.Authentication;

namespace Microsoft.AspNet.IISPlatformHandler
{
internal class AuthenticationHandler : IAuthenticationHandler
{
internal AuthenticationHandler(HttpContext httpContext, IISPlatformHandlerOptions options, ClaimsPrincipal user)
{
HttpContext = httpContext;
User = user;
Options = options;
}

internal HttpContext HttpContext { get; }

internal IISPlatformHandlerOptions Options { get; }

internal ClaimsPrincipal User { get; }

internal IAuthenticationHandler PriorHandler { get; set; }

public Task AuthenticateAsync(AuthenticateContext context)
{
if (ShouldHandleScheme(context.AuthenticationScheme))
{
if (User != null)
{
context.Authenticated(User, properties: null,
description: Options.AuthenticationDescriptions.Where(descrip =>
string.Equals(User.Identity.AuthenticationType, descrip.AuthenticationScheme, StringComparison.Ordinal)).FirstOrDefault()?.Items);
}
else
{
context.NotAuthenticated();
}
}

if (PriorHandler != null)
{
return PriorHandler.AuthenticateAsync(context);
}
return Task.FromResult(0);
}

public Task ChallengeAsync(ChallengeContext context)
{
bool handled = false;
if (ShouldHandleScheme(context.AuthenticationScheme))
{
switch (context.Behavior)
{
case ChallengeBehavior.Automatic:
// If there is a principal already, invoke the forbidden code path
if (User == null)
{
goto case ChallengeBehavior.Unauthorized;
}
else
{
goto case ChallengeBehavior.Forbidden;
}
case ChallengeBehavior.Unauthorized:
HttpContext.Response.StatusCode = 401;
// We would normally set the www-authenticate header here, but IIS does that for us.
break;
case ChallengeBehavior.Forbidden:
HttpContext.Response.StatusCode = 403;
handled = true; // No other handlers need to consider this challenge.
break;
}
context.Accept();
}

if (!handled && PriorHandler != null)
{
return PriorHandler.ChallengeAsync(context);
}
return Task.FromResult(0);
}

public void GetDescriptions(DescribeSchemesContext context)
{
foreach (var description in Options.AuthenticationDescriptions)
{
context.Accept(description.Items);
}

if (PriorHandler != null)
{
PriorHandler.GetDescriptions(context);
}
}

public Task SignInAsync(SignInContext context)
{
// Not supported, fall through
if (PriorHandler != null)
{
return PriorHandler.SignInAsync(context);
}
return Task.FromResult(0);
}

public Task SignOutAsync(SignOutContext context)
{
// Not supported, fall through
if (PriorHandler != null)
{
return PriorHandler.SignOutAsync(context);
}
return Task.FromResult(0);
}

private bool ShouldHandleScheme(string authenticationScheme)
{
if (Options.AutomaticAuthentication && string.IsNullOrEmpty(authenticationScheme))
{
return true;
}
foreach (var description in Options.AuthenticationDescriptions)
{
if (string.Equals(description.AuthenticationScheme, authenticationScheme, StringComparison.Ordinal))
{
return true;
}
}
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// 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.

namespace Microsoft.AspNet.IISPlatformHandler
{
public class IISPlatformHandlerDefaults
{
public const string Negotiate = "Negotiate";
public const string Ntlm = "NTLM";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@

using System;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.AspNet.Http.Features.Authentication.Internal;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;

namespace Microsoft.AspNet.IISPlatformHandler
{
Expand All @@ -22,13 +27,44 @@ public class IISPlatformHandlerMiddleware
private const string XOriginalIPName = "X-Original-IP";

private readonly RequestDelegate _next;
private readonly IISPlatformHandlerOptions _options;

public IISPlatformHandlerMiddleware(RequestDelegate next)
public IISPlatformHandlerMiddleware(RequestDelegate next, IISPlatformHandlerOptions options)
{
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
_next = next;
_options = options;
}

public Task Invoke(HttpContext httpContext)
public async Task Invoke(HttpContext httpContext)
{
UpdateScheme(httpContext);

UpdateRemoteIp(httpContext);

var winPrincipal = UpdateUser(httpContext);

var handler = new AuthenticationHandler(httpContext, _options, winPrincipal);
AttachAuthenticationHandler(handler);

try
{
await _next(httpContext);
}
finally
{
DetachAuthenticationhandler(handler);
}
}

private static void UpdateScheme(HttpContext httpContext)
{
var xForwardProtoHeaderValue = httpContext.Request.Headers[XForwardedProtoHeaderName];
if (!string.IsNullOrEmpty(xForwardProtoHeaderValue))
Expand All @@ -39,7 +75,10 @@ public Task Invoke(HttpContext httpContext)
}
httpContext.Request.Scheme = xForwardProtoHeaderValue;
}

}

private static void UpdateRemoteIp(HttpContext httpContext)
{
var xForwardedForHeaderValue = httpContext.Request.Headers.GetCommaSeparatedValues(XForwardedForHeaderName);
if (xForwardedForHeaderValue != null && xForwardedForHeaderValue.Length > 0)
{
Expand All @@ -54,32 +93,62 @@ public Task Invoke(HttpContext httpContext)
httpContext.Connection.RemoteIpAddress = ipFromHeader;
}
}
}

private WindowsPrincipal UpdateUser(HttpContext httpContext)
{
var xIISWindowsAuthToken = httpContext.Request.Headers[XIISWindowsAuthToken];
int hexHandle;
WindowsPrincipal winPrincipal = null;
if (!StringValues.IsNullOrEmpty(xIISWindowsAuthToken)
&& int.TryParse(xIISWindowsAuthToken, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out hexHandle))
{
// Always create the identity if the handle exists, we need to dispose it so it does not leak.
var handle = new IntPtr(hexHandle);
var winIdentity = new WindowsIdentity(handle);

// WindowsIdentity just duplicated the handle so we need to close the original.
NativeMethods.CloseHandle(handle);

httpContext.Response.RegisterForDispose(winIdentity);
var winPrincipal = new WindowsPrincipal(winIdentity);
winPrincipal = new WindowsPrincipal(winIdentity);

var existingPrincipal = httpContext.User;
if (existingPrincipal != null)
if (_options.AutomaticAuthentication)
{
httpContext.User = SecurityHelper.MergeUserPrincipal(existingPrincipal, winPrincipal);
}
else
{
httpContext.User = winPrincipal;
var existingPrincipal = httpContext.User;
if (existingPrincipal != null)
{
httpContext.User = SecurityHelper.MergeUserPrincipal(existingPrincipal, winPrincipal);
}
else
{
httpContext.User = winPrincipal;
}
}
}

return _next(httpContext);
return winPrincipal;
}

private void AttachAuthenticationHandler(AuthenticationHandler handler)
{
var auth = handler.HttpContext.Features.Get<IHttpAuthenticationFeature>();
if (auth == null)
{
auth = new HttpAuthenticationFeature();
handler.HttpContext.Features.Set(auth);
}
handler.PriorHandler = auth.Handler;
auth.Handler = handler;
}

private void DetachAuthenticationhandler(AuthenticationHandler handler)
{
var auth = handler.HttpContext.Features.Get<IHttpAuthenticationFeature>();
if (auth != null)
{
auth.Handler = handler.PriorHandler;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Microsoft.AspNet.IISPlatformHandler;

namespace Microsoft.AspNet.Builder
Expand All @@ -13,9 +14,56 @@ public static class IISPlatformHandlerMiddlewareExtensions
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IApplicationBuilder UseIISPlatformHandler(this IApplicationBuilder builder)
public static IApplicationBuilder UseIISPlatformHandler(this IApplicationBuilder app)
{
return builder.UseMiddleware<IISPlatformHandlerMiddleware>();
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}

return app.UseMiddleware<IISPlatformHandlerMiddleware>(new IISPlatformHandlerOptions());
}

/// <summary>
/// Adds middleware for interacting with the IIS HttpPlatformHandler reverse proxy module.
/// This will handle forwarded Windows Authentication, request scheme, remote IPs, etc..
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IApplicationBuilder UseIISPlatformHandler(this IApplicationBuilder app, IISPlatformHandlerOptions options)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}

return app.UseMiddleware<IISPlatformHandlerMiddleware>(options);
}

/// <summary>
/// Adds middleware for interacting with the IIS HttpPlatformHandler reverse proxy module.
/// This will handle forwarded Windows Authentication, request scheme, remote IPs, etc..
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IApplicationBuilder UseIISPlatformHandler(this IApplicationBuilder app, Action<IISPlatformHandlerOptions> configureOptions)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}

var options = new IISPlatformHandlerOptions();
if (configureOptions != null)
{
configureOptions(options);
}

return app.UseIISPlatformHandler(options);
}
}
}
Loading

0 comments on commit 2fe2e0d

Please sign in to comment.