|
1 |
| -// Copyright (c) .NET Foundation. All rights reserved. |
| 1 | +// Copyright (c) .NET Foundation. All rights reserved. |
2 | 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
3 | 3 |
|
4 | 4 | using System;
|
5 |
| -using System.Linq; |
6 |
| -using System.Security.Claims; |
| 5 | +using System.Globalization; |
| 6 | +using System.Security.Principal; |
7 | 7 | using System.Threading.Tasks;
|
8 |
| -using Microsoft.AspNetCore.Builder; |
| 8 | +using Microsoft.AspNetCore.Authentication; |
9 | 9 | using Microsoft.AspNetCore.Http;
|
10 |
| -using Microsoft.AspNetCore.Http.Authentication; |
11 |
| -using Microsoft.AspNetCore.Http.Features.Authentication; |
12 | 10 | using Microsoft.Extensions.Internal;
|
| 11 | +using Microsoft.Extensions.Primitives; |
13 | 12 |
|
14 | 13 | namespace Microsoft.AspNetCore.Server.IISIntegration
|
15 | 14 | {
|
16 | 15 | internal class AuthenticationHandler : IAuthenticationHandler
|
17 | 16 | {
|
18 |
| - internal AuthenticationHandler(HttpContext httpContext, IISOptions options, ClaimsPrincipal user) |
19 |
| - { |
20 |
| - HttpContext = httpContext; |
21 |
| - User = user; |
22 |
| - Options = options; |
23 |
| - } |
24 |
| - |
25 |
| - internal HttpContext HttpContext { get; } |
| 17 | + private const string MSAspNetCoreWinAuthToken = "MS-ASPNETCORE-WINAUTHTOKEN"; |
| 18 | + private WindowsPrincipal _user; |
| 19 | + private HttpContext _context; |
26 | 20 |
|
27 |
| - internal IISOptions Options { get; } |
| 21 | + internal AuthenticationScheme Scheme { get; private set; } |
28 | 22 |
|
29 |
| - internal ClaimsPrincipal User { get; } |
30 |
| - |
31 |
| - internal IAuthenticationHandler PriorHandler { get; set; } |
32 |
| - |
33 |
| - public Task AuthenticateAsync(AuthenticateContext context) |
| 23 | + public Task<AuthenticateResult> AuthenticateAsync() |
34 | 24 | {
|
35 |
| - if (ShouldHandleScheme(context.AuthenticationScheme)) |
| 25 | + var user = GetUser(); |
| 26 | + if (user != null) |
36 | 27 | {
|
37 |
| - if (User != null) |
38 |
| - { |
39 |
| - context.Authenticated(User, properties: null, description: null); |
40 |
| - } |
41 |
| - else |
42 |
| - { |
43 |
| - context.NotAuthenticated(); |
44 |
| - } |
| 28 | + return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(user, Scheme.Name))); |
45 | 29 | }
|
46 |
| - |
47 |
| - if (PriorHandler != null) |
| 30 | + else |
48 | 31 | {
|
49 |
| - return PriorHandler.AuthenticateAsync(context); |
| 32 | + return Task.FromResult(AuthenticateResult.None()); |
50 | 33 | }
|
51 |
| - |
52 |
| - return TaskCache.CompletedTask; |
53 | 34 | }
|
54 | 35 |
|
55 |
| - public Task ChallengeAsync(ChallengeContext context) |
| 36 | + private WindowsPrincipal GetUser() |
56 | 37 | {
|
57 |
| - // Some other provider may have already accepted this challenge. Having multiple providers with |
58 |
| - // AutomaticChallenge = true is considered invalid, but changing the default would breaking |
59 |
| - // normal Windows auth users. |
60 |
| - if (!context.Accepted && ShouldHandleScheme(context.AuthenticationScheme)) |
| 38 | + if (_user == null) |
61 | 39 | {
|
62 |
| - switch (context.Behavior) |
63 |
| - { |
64 |
| - case ChallengeBehavior.Automatic: |
65 |
| - // If there is a principal already, invoke the forbidden code path |
66 |
| - if (User == null) |
67 |
| - { |
68 |
| - goto case ChallengeBehavior.Unauthorized; |
69 |
| - } |
70 |
| - else |
71 |
| - { |
72 |
| - goto case ChallengeBehavior.Forbidden; |
73 |
| - } |
74 |
| - case ChallengeBehavior.Unauthorized: |
75 |
| - HttpContext.Response.StatusCode = 401; |
76 |
| - // We would normally set the www-authenticate header here, but IIS does that for us. |
77 |
| - break; |
78 |
| - case ChallengeBehavior.Forbidden: |
79 |
| - HttpContext.Response.StatusCode = 403; |
80 |
| - break; |
81 |
| - } |
82 |
| - context.Accept(); |
83 |
| - } |
| 40 | + var tokenHeader = _context.Request.Headers[MSAspNetCoreWinAuthToken]; |
84 | 41 |
|
85 |
| - if (PriorHandler != null) |
86 |
| - { |
87 |
| - return PriorHandler.ChallengeAsync(context); |
88 |
| - } |
| 42 | + int hexHandle; |
| 43 | + if (!StringValues.IsNullOrEmpty(tokenHeader) |
| 44 | + && int.TryParse(tokenHeader, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out hexHandle)) |
| 45 | + { |
| 46 | + // Always create the identity if the handle exists, we need to dispose it so it does not leak. |
| 47 | + var handle = new IntPtr(hexHandle); |
| 48 | + var winIdentity = new WindowsIdentity(handle); |
89 | 49 |
|
90 |
| - return TaskCache.CompletedTask; |
91 |
| - } |
| 50 | + // WindowsIdentity just duplicated the handle so we need to close the original. |
| 51 | + NativeMethods.CloseHandle(handle); |
92 | 52 |
|
93 |
| - public void GetDescriptions(DescribeSchemesContext context) |
94 |
| - { |
95 |
| - foreach (var description in Options.AuthenticationDescriptions) |
96 |
| - { |
97 |
| - context.Accept(description.Items); |
| 53 | + _context.Response.RegisterForDispose(winIdentity); |
| 54 | + _user = new WindowsPrincipal(winIdentity); |
| 55 | + } |
98 | 56 | }
|
99 | 57 |
|
100 |
| - if (PriorHandler != null) |
101 |
| - { |
102 |
| - PriorHandler.GetDescriptions(context); |
103 |
| - } |
| 58 | + return _user; |
104 | 59 | }
|
105 | 60 |
|
106 |
| - public Task SignInAsync(SignInContext context) |
| 61 | + |
| 62 | + public Task ChallengeAsync(ChallengeContext context) |
107 | 63 | {
|
108 |
| - // Not supported, fall through |
109 |
| - if (PriorHandler != null) |
| 64 | + switch (context.Behavior) |
110 | 65 | {
|
111 |
| - return PriorHandler.SignInAsync(context); |
| 66 | + case ChallengeBehavior.Automatic: |
| 67 | + // If there is a principal already, invoke the forbidden code path |
| 68 | + if (GetUser() == null) |
| 69 | + { |
| 70 | + goto case ChallengeBehavior.Unauthorized; |
| 71 | + } |
| 72 | + else |
| 73 | + { |
| 74 | + goto case ChallengeBehavior.Forbidden; |
| 75 | + } |
| 76 | + case ChallengeBehavior.Unauthorized: |
| 77 | + context.HttpContext.Response.StatusCode = 401; |
| 78 | + // We would normally set the www-authenticate header here, but IIS does that for us. |
| 79 | + break; |
| 80 | + case ChallengeBehavior.Forbidden: |
| 81 | + context.HttpContext.Response.StatusCode = 403; |
| 82 | + break; |
112 | 83 | }
|
113 |
| - |
114 | 84 | return TaskCache.CompletedTask;
|
115 | 85 | }
|
116 | 86 |
|
117 |
| - public Task SignOutAsync(SignOutContext context) |
| 87 | + public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context) |
118 | 88 | {
|
119 |
| - // Not supported, fall through |
120 |
| - if (PriorHandler != null) |
121 |
| - { |
122 |
| - return PriorHandler.SignOutAsync(context); |
123 |
| - } |
124 |
| - |
| 89 | + Scheme = scheme; |
| 90 | + _context = context; |
125 | 91 | return TaskCache.CompletedTask;
|
126 | 92 | }
|
127 | 93 |
|
128 |
| - private bool ShouldHandleScheme(string authenticationScheme) |
| 94 | + public Task SignInAsync(SignInContext context) |
129 | 95 | {
|
130 |
| - if (Options.AutomaticAuthentication && string.Equals(AuthenticationManager.AutomaticScheme, authenticationScheme, StringComparison.Ordinal)) |
131 |
| - { |
132 |
| - return true; |
133 |
| - } |
| 96 | + throw new NotSupportedException(); |
| 97 | + } |
134 | 98 |
|
135 |
| - return Options.AuthenticationDescriptions.Any(description => string.Equals(description.AuthenticationScheme, authenticationScheme, StringComparison.Ordinal)); |
| 99 | + public Task SignOutAsync(SignOutContext context) |
| 100 | + { |
| 101 | + return TaskCache.CompletedTask; |
136 | 102 | }
|
137 | 103 | }
|
138 | 104 | }
|
0 commit comments