Skip to content

Commit 2fe2e0d

Browse files
committed
#1 Implement a full authentication handler.
1 parent 348ab7c commit 2fe2e0d

9 files changed

+347
-43
lines changed
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Linq;
6+
using System.Security.Claims;
7+
using System.Threading.Tasks;
8+
using Microsoft.AspNet.Http;
9+
using Microsoft.AspNet.Http.Features.Authentication;
10+
11+
namespace Microsoft.AspNet.IISPlatformHandler
12+
{
13+
internal class AuthenticationHandler : IAuthenticationHandler
14+
{
15+
internal AuthenticationHandler(HttpContext httpContext, IISPlatformHandlerOptions options, ClaimsPrincipal user)
16+
{
17+
HttpContext = httpContext;
18+
User = user;
19+
Options = options;
20+
}
21+
22+
internal HttpContext HttpContext { get; }
23+
24+
internal IISPlatformHandlerOptions Options { get; }
25+
26+
internal ClaimsPrincipal User { get; }
27+
28+
internal IAuthenticationHandler PriorHandler { get; set; }
29+
30+
public Task AuthenticateAsync(AuthenticateContext context)
31+
{
32+
if (ShouldHandleScheme(context.AuthenticationScheme))
33+
{
34+
if (User != null)
35+
{
36+
context.Authenticated(User, properties: null,
37+
description: Options.AuthenticationDescriptions.Where(descrip =>
38+
string.Equals(User.Identity.AuthenticationType, descrip.AuthenticationScheme, StringComparison.Ordinal)).FirstOrDefault()?.Items);
39+
}
40+
else
41+
{
42+
context.NotAuthenticated();
43+
}
44+
}
45+
46+
if (PriorHandler != null)
47+
{
48+
return PriorHandler.AuthenticateAsync(context);
49+
}
50+
return Task.FromResult(0);
51+
}
52+
53+
public Task ChallengeAsync(ChallengeContext context)
54+
{
55+
bool handled = false;
56+
if (ShouldHandleScheme(context.AuthenticationScheme))
57+
{
58+
switch (context.Behavior)
59+
{
60+
case ChallengeBehavior.Automatic:
61+
// If there is a principal already, invoke the forbidden code path
62+
if (User == null)
63+
{
64+
goto case ChallengeBehavior.Unauthorized;
65+
}
66+
else
67+
{
68+
goto case ChallengeBehavior.Forbidden;
69+
}
70+
case ChallengeBehavior.Unauthorized:
71+
HttpContext.Response.StatusCode = 401;
72+
// We would normally set the www-authenticate header here, but IIS does that for us.
73+
break;
74+
case ChallengeBehavior.Forbidden:
75+
HttpContext.Response.StatusCode = 403;
76+
handled = true; // No other handlers need to consider this challenge.
77+
break;
78+
}
79+
context.Accept();
80+
}
81+
82+
if (!handled && PriorHandler != null)
83+
{
84+
return PriorHandler.ChallengeAsync(context);
85+
}
86+
return Task.FromResult(0);
87+
}
88+
89+
public void GetDescriptions(DescribeSchemesContext context)
90+
{
91+
foreach (var description in Options.AuthenticationDescriptions)
92+
{
93+
context.Accept(description.Items);
94+
}
95+
96+
if (PriorHandler != null)
97+
{
98+
PriorHandler.GetDescriptions(context);
99+
}
100+
}
101+
102+
public Task SignInAsync(SignInContext context)
103+
{
104+
// Not supported, fall through
105+
if (PriorHandler != null)
106+
{
107+
return PriorHandler.SignInAsync(context);
108+
}
109+
return Task.FromResult(0);
110+
}
111+
112+
public Task SignOutAsync(SignOutContext context)
113+
{
114+
// Not supported, fall through
115+
if (PriorHandler != null)
116+
{
117+
return PriorHandler.SignOutAsync(context);
118+
}
119+
return Task.FromResult(0);
120+
}
121+
122+
private bool ShouldHandleScheme(string authenticationScheme)
123+
{
124+
if (Options.AutomaticAuthentication && string.IsNullOrEmpty(authenticationScheme))
125+
{
126+
return true;
127+
}
128+
foreach (var description in Options.AuthenticationDescriptions)
129+
{
130+
if (string.Equals(description.AuthenticationScheme, authenticationScheme, StringComparison.Ordinal))
131+
{
132+
return true;
133+
}
134+
}
135+
return false;
136+
}
137+
}
138+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.AspNet.IISPlatformHandler
5+
{
6+
public class IISPlatformHandlerDefaults
7+
{
8+
public const string Negotiate = "Negotiate";
9+
public const string Ntlm = "NTLM";
10+
}
11+
}

src/Microsoft.AspNet.IISPlatformHandler/IISPlatformHandlerMiddleware.cs

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@
33

44
using System;
55
using System.Globalization;
6+
using System.Linq;
67
using System.Net;
78
using System.Security.Principal;
89
using System.Threading.Tasks;
910
using Microsoft.AspNet.Builder;
1011
using Microsoft.AspNet.Http;
12+
using Microsoft.AspNet.Http.Features;
13+
using Microsoft.AspNet.Http.Features.Authentication;
14+
using Microsoft.AspNet.Http.Features.Authentication.Internal;
1115
using Microsoft.Extensions.Internal;
1216
using Microsoft.Extensions.Primitives;
17+
using Microsoft.Net.Http.Headers;
1318

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

2429
private readonly RequestDelegate _next;
30+
private readonly IISPlatformHandlerOptions _options;
2531

26-
public IISPlatformHandlerMiddleware(RequestDelegate next)
32+
public IISPlatformHandlerMiddleware(RequestDelegate next, IISPlatformHandlerOptions options)
2733
{
34+
if (next == null)
35+
{
36+
throw new ArgumentNullException(nameof(next));
37+
}
38+
if (options == null)
39+
{
40+
throw new ArgumentNullException(nameof(options));
41+
}
2842
_next = next;
43+
_options = options;
2944
}
3045

31-
public Task Invoke(HttpContext httpContext)
46+
public async Task Invoke(HttpContext httpContext)
47+
{
48+
UpdateScheme(httpContext);
49+
50+
UpdateRemoteIp(httpContext);
51+
52+
var winPrincipal = UpdateUser(httpContext);
53+
54+
var handler = new AuthenticationHandler(httpContext, _options, winPrincipal);
55+
AttachAuthenticationHandler(handler);
56+
57+
try
58+
{
59+
await _next(httpContext);
60+
}
61+
finally
62+
{
63+
DetachAuthenticationhandler(handler);
64+
}
65+
}
66+
67+
private static void UpdateScheme(HttpContext httpContext)
3268
{
3369
var xForwardProtoHeaderValue = httpContext.Request.Headers[XForwardedProtoHeaderName];
3470
if (!string.IsNullOrEmpty(xForwardProtoHeaderValue))
@@ -39,7 +75,10 @@ public Task Invoke(HttpContext httpContext)
3975
}
4076
httpContext.Request.Scheme = xForwardProtoHeaderValue;
4177
}
42-
78+
}
79+
80+
private static void UpdateRemoteIp(HttpContext httpContext)
81+
{
4382
var xForwardedForHeaderValue = httpContext.Request.Headers.GetCommaSeparatedValues(XForwardedForHeaderName);
4483
if (xForwardedForHeaderValue != null && xForwardedForHeaderValue.Length > 0)
4584
{
@@ -54,32 +93,62 @@ public Task Invoke(HttpContext httpContext)
5493
httpContext.Connection.RemoteIpAddress = ipFromHeader;
5594
}
5695
}
96+
}
5797

98+
private WindowsPrincipal UpdateUser(HttpContext httpContext)
99+
{
58100
var xIISWindowsAuthToken = httpContext.Request.Headers[XIISWindowsAuthToken];
59101
int hexHandle;
102+
WindowsPrincipal winPrincipal = null;
60103
if (!StringValues.IsNullOrEmpty(xIISWindowsAuthToken)
61104
&& int.TryParse(xIISWindowsAuthToken, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out hexHandle))
62105
{
106+
// Always create the identity if the handle exists, we need to dispose it so it does not leak.
63107
var handle = new IntPtr(hexHandle);
64108
var winIdentity = new WindowsIdentity(handle);
109+
65110
// WindowsIdentity just duplicated the handle so we need to close the original.
66111
NativeMethods.CloseHandle(handle);
67112

68113
httpContext.Response.RegisterForDispose(winIdentity);
69-
var winPrincipal = new WindowsPrincipal(winIdentity);
114+
winPrincipal = new WindowsPrincipal(winIdentity);
70115

71-
var existingPrincipal = httpContext.User;
72-
if (existingPrincipal != null)
116+
if (_options.AutomaticAuthentication)
73117
{
74-
httpContext.User = SecurityHelper.MergeUserPrincipal(existingPrincipal, winPrincipal);
75-
}
76-
else
77-
{
78-
httpContext.User = winPrincipal;
118+
var existingPrincipal = httpContext.User;
119+
if (existingPrincipal != null)
120+
{
121+
httpContext.User = SecurityHelper.MergeUserPrincipal(existingPrincipal, winPrincipal);
122+
}
123+
else
124+
{
125+
httpContext.User = winPrincipal;
126+
}
79127
}
80128
}
81129

82-
return _next(httpContext);
130+
return winPrincipal;
131+
}
132+
133+
private void AttachAuthenticationHandler(AuthenticationHandler handler)
134+
{
135+
var auth = handler.HttpContext.Features.Get<IHttpAuthenticationFeature>();
136+
if (auth == null)
137+
{
138+
auth = new HttpAuthenticationFeature();
139+
handler.HttpContext.Features.Set(auth);
140+
}
141+
handler.PriorHandler = auth.Handler;
142+
auth.Handler = handler;
143+
}
144+
145+
private void DetachAuthenticationhandler(AuthenticationHandler handler)
146+
{
147+
var auth = handler.HttpContext.Features.Get<IHttpAuthenticationFeature>();
148+
if (auth != null)
149+
{
150+
auth.Handler = handler.PriorHandler;
151+
}
83152
}
84153
}
85154
}

src/Microsoft.AspNet.IISPlatformHandler/IISPlatformHandlerMiddlewareExtensions.cs

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using Microsoft.AspNet.IISPlatformHandler;
56

67
namespace Microsoft.AspNet.Builder
@@ -13,9 +14,56 @@ public static class IISPlatformHandlerMiddlewareExtensions
1314
/// </summary>
1415
/// <param name="builder"></param>
1516
/// <returns></returns>
16-
public static IApplicationBuilder UseIISPlatformHandler(this IApplicationBuilder builder)
17+
public static IApplicationBuilder UseIISPlatformHandler(this IApplicationBuilder app)
1718
{
18-
return builder.UseMiddleware<IISPlatformHandlerMiddleware>();
19+
if (app == null)
20+
{
21+
throw new ArgumentNullException(nameof(app));
22+
}
23+
24+
return app.UseMiddleware<IISPlatformHandlerMiddleware>(new IISPlatformHandlerOptions());
25+
}
26+
27+
/// <summary>
28+
/// Adds middleware for interacting with the IIS HttpPlatformHandler reverse proxy module.
29+
/// This will handle forwarded Windows Authentication, request scheme, remote IPs, etc..
30+
/// </summary>
31+
/// <param name="builder"></param>
32+
/// <returns></returns>
33+
public static IApplicationBuilder UseIISPlatformHandler(this IApplicationBuilder app, IISPlatformHandlerOptions options)
34+
{
35+
if (app == null)
36+
{
37+
throw new ArgumentNullException(nameof(app));
38+
}
39+
if (options == null)
40+
{
41+
throw new ArgumentNullException(nameof(options));
42+
}
43+
44+
return app.UseMiddleware<IISPlatformHandlerMiddleware>(options);
45+
}
46+
47+
/// <summary>
48+
/// Adds middleware for interacting with the IIS HttpPlatformHandler reverse proxy module.
49+
/// This will handle forwarded Windows Authentication, request scheme, remote IPs, etc..
50+
/// </summary>
51+
/// <param name="builder"></param>
52+
/// <returns></returns>
53+
public static IApplicationBuilder UseIISPlatformHandler(this IApplicationBuilder app, Action<IISPlatformHandlerOptions> configureOptions)
54+
{
55+
if (app == null)
56+
{
57+
throw new ArgumentNullException(nameof(app));
58+
}
59+
60+
var options = new IISPlatformHandlerOptions();
61+
if (configureOptions != null)
62+
{
63+
configureOptions(options);
64+
}
65+
66+
return app.UseIISPlatformHandler(options);
1967
}
2068
}
2169
}

0 commit comments

Comments
 (0)