AuthN and AuthZ API changes (Async, Challenge)

This commit is contained in:
Hao Kung 2015-06-25 17:19:27 -07:00
parent 43b428941b
commit 3a8ea672ea
38 changed files with 933 additions and 843 deletions

View File

@ -28,7 +28,7 @@ namespace CookieSample
if (string.IsNullOrEmpty(context.User.Identity.Name)) if (string.IsNullOrEmpty(context.User.Identity.Name))
{ {
var user = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "bob") })); var user = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "bob") }));
context.Authentication.SignIn(CookieAuthenticationDefaults.AuthenticationScheme, user); await context.Authentication.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, user);
context.Response.ContentType = "text/plain"; context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Hello First timer"); await context.Response.WriteAsync("Hello First timer");
return; return;

View File

@ -36,7 +36,7 @@ namespace CookieSessionSample
{ {
claims.Add(new Claim(ClaimTypes.Role, "SomeRandomGroup" + i, ClaimValueTypes.String, "IssuedByBob", "OriginalIssuerJoe")); claims.Add(new Claim(ClaimTypes.Role, "SomeRandomGroup" + i, ClaimValueTypes.String, "IssuedByBob", "OriginalIssuerJoe"));
} }
context.Authentication.SignIn(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(new ClaimsIdentity(claims))); await context.Authentication.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(new ClaimsIdentity(claims)));
context.Response.ContentType = "text/plain"; context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Hello First timer"); await context.Response.WriteAsync("Hello First timer");
return; return;

View File

@ -40,7 +40,7 @@ namespace OpenIdConnectSample
{ {
if (string.IsNullOrEmpty(context.User.Identity.Name)) if (string.IsNullOrEmpty(context.User.Identity.Name))
{ {
context.Authentication.Challenge(OpenIdConnectAuthenticationDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/" }); await context.Authentication.ChallengeAsync(OpenIdConnectAuthenticationDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/" });
context.Response.ContentType = "text/plain"; context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Hello First timer"); await context.Response.WriteAsync("Hello First timer");

View File

@ -187,7 +187,7 @@ namespace CookieSample
{ {
// By default the client will be redirect back to the URL that issued the challenge (/login?authtype=foo), // By default the client will be redirect back to the URL that issued the challenge (/login?authtype=foo),
// send them to the home page instead (/). // send them to the home page instead (/).
context.Authentication.Challenge(authType, new AuthenticationProperties() { RedirectUri = "/" }); await context.Authentication.ChallengeAsync(authType, new AuthenticationProperties() { RedirectUri = "/" });
return; return;
} }
@ -207,7 +207,7 @@ namespace CookieSample
{ {
signoutApp.Run(async context => signoutApp.Run(async context =>
{ {
context.Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationScheme); await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
context.Response.ContentType = "text/html"; context.Response.ContentType = "text/html";
await context.Response.WriteAsync("<html><body>"); await context.Response.WriteAsync("<html><body>");
await context.Response.WriteAsync("You have been logged out. Goodbye " + context.User.Identity.Name + "<br>"); await context.Response.WriteAsync("You have been logged out. Goodbye " + context.User.Identity.Name + "<br>");
@ -222,7 +222,7 @@ namespace CookieSample
if (string.IsNullOrEmpty(context.User.Identity.Name)) if (string.IsNullOrEmpty(context.User.Identity.Name))
{ {
// The cookie middleware will intercept this 401 and redirect to /login // The cookie middleware will intercept this 401 and redirect to /login
context.Authentication.Challenge(); await context.Authentication.ChallengeAsync();
return; return;
} }
await next(); await next();

View File

@ -8,6 +8,8 @@ using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication; using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging; using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Authentication.Cookies namespace Microsoft.AspNet.Authentication.Cookies
@ -26,12 +28,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
private DateTimeOffset _renewExpiresUtc; private DateTimeOffset _renewExpiresUtc;
private string _sessionKey; private string _sessionKey;
protected override AuthenticationTicket AuthenticateCore() public override async Task<AuthenticationTicket> AuthenticateAsync()
{
return AuthenticateCoreAsync().GetAwaiter().GetResult();
}
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{ {
AuthenticationTicket ticket = null; AuthenticationTicket ticket = null;
try try
@ -99,7 +96,6 @@ namespace Microsoft.AspNet.Authentication.Cookies
await Options.Notifications.ValidatePrincipal(context); await Options.Notifications.ValidatePrincipal(context);
AuthenticateCalled = true;
return new AuthenticationTicket(context.Principal, context.Properties, Options.AuthenticationScheme); return new AuthenticationTicket(context.Principal, context.Properties, Options.AuthenticationScheme);
} }
catch (Exception exception) catch (Exception exception)
@ -115,19 +111,72 @@ namespace Microsoft.AspNet.Authentication.Cookies
} }
} }
protected override void ApplyResponseGrant() private CookieOptions BuildCookieOptions()
{ {
ApplyResponseGrantAsync().GetAwaiter().GetResult(); var cookieOptions = new CookieOptions
{
Domain = Options.CookieDomain,
HttpOnly = Options.CookieHttpOnly,
Path = Options.CookiePath ?? (RequestPathBase.HasValue ? RequestPathBase.ToString() : "/"),
};
if (Options.CookieSecure == CookieSecureOption.SameAsRequest)
{
cookieOptions.Secure = Request.IsHttps;
}
else
{
cookieOptions.Secure = Options.CookieSecure == CookieSecureOption.Always;
}
return cookieOptions;
} }
protected override async Task ApplyResponseGrantAsync() private async Task ApplyCookie(AuthenticationTicket model)
{ {
var signin = SignInContext; var cookieOptions = BuildCookieOptions();
var shouldSignin = signin != null;
var signout = SignOutContext;
var shouldSignout = signout != null;
if (!(shouldSignin || shouldSignout || _shouldRenew)) model.Properties.IssuedUtc = _renewIssuedUtc;
model.Properties.ExpiresUtc = _renewExpiresUtc;
if (Options.SessionStore != null && _sessionKey != null)
{
await Options.SessionStore.RenewAsync(_sessionKey, model);
var principal = new ClaimsPrincipal(
new ClaimsIdentity(
new[] { new Claim(SessionIdClaim, _sessionKey, ClaimValueTypes.String, Options.ClaimsIssuer) },
Options.AuthenticationScheme));
model = new AuthenticationTicket(principal, null, Options.AuthenticationScheme);
}
var cookieValue = Options.TicketDataFormat.Protect(model);
if (model.Properties.IsPersistent)
{
cookieOptions.Expires = _renewExpiresUtc.ToUniversalTime().DateTime;
}
Options.CookieManager.AppendResponseCookie(
Context,
Options.CookieName,
cookieValue,
cookieOptions);
Response.Headers.Set(
HeaderNameCacheControl,
HeaderValueNoCache);
Response.Headers.Set(
HeaderNamePragma,
HeaderValueNoCache);
Response.Headers.Set(
HeaderNameExpires,
HeaderValueMinusOne);
}
protected override async Task FinishResponseAsync()
{
// Only renew if requested, and neither sign in or sign out was called
if (!_shouldRenew || SignInAccepted || SignOutAccepted)
{ {
return; return;
} }
@ -135,133 +184,89 @@ namespace Microsoft.AspNet.Authentication.Cookies
var model = await AuthenticateAsync(); var model = await AuthenticateAsync();
try try
{ {
var cookieOptions = new CookieOptions await ApplyCookie(model);
}
catch (Exception exception)
{
var exceptionContext = new CookieExceptionContext(Context, Options,
CookieExceptionContext.ExceptionLocation.ApplyResponseGrant, exception, model);
Options.Notifications.Exception(exceptionContext);
if (exceptionContext.Rethrow)
{ {
Domain = Options.CookieDomain, throw;
HttpOnly = Options.CookieHttpOnly, }
Path = Options.CookiePath ?? (RequestPathBase.HasValue ? RequestPathBase.ToString() : "/"), }
}; }
if (Options.CookieSecure == CookieSecureOption.SameAsRequest)
protected override async Task HandleSignInAsync(SignInContext signin)
{
var model = await AuthenticateAsync();
try
{
var cookieOptions = BuildCookieOptions();
var signInContext = new CookieResponseSignInContext(
Context,
Options,
Options.AuthenticationScheme,
signin.Principal,
new AuthenticationProperties(signin.Properties),
cookieOptions);
DateTimeOffset issuedUtc;
if (signInContext.Properties.IssuedUtc.HasValue)
{ {
cookieOptions.Secure = Request.IsHttps; issuedUtc = signInContext.Properties.IssuedUtc.Value;
} }
else else
{ {
cookieOptions.Secure = Options.CookieSecure == CookieSecureOption.Always; issuedUtc = Options.SystemClock.UtcNow;
signInContext.Properties.IssuedUtc = issuedUtc;
} }
if (shouldSignin) if (!signInContext.Properties.ExpiresUtc.HasValue)
{ {
var signInContext = new CookieResponseSignInContext( signInContext.Properties.ExpiresUtc = issuedUtc.Add(Options.ExpireTimeSpan);
Context,
Options,
Options.AuthenticationScheme,
signin.Principal,
new AuthenticationProperties(signin.Properties),
cookieOptions);
DateTimeOffset issuedUtc;
if (signInContext.Properties.IssuedUtc.HasValue)
{
issuedUtc = signInContext.Properties.IssuedUtc.Value;
}
else
{
issuedUtc = Options.SystemClock.UtcNow;
signInContext.Properties.IssuedUtc = issuedUtc;
}
if (!signInContext.Properties.ExpiresUtc.HasValue)
{
signInContext.Properties.ExpiresUtc = issuedUtc.Add(Options.ExpireTimeSpan);
}
Options.Notifications.ResponseSignIn(signInContext);
if (signInContext.Properties.IsPersistent)
{
var expiresUtc = signInContext.Properties.ExpiresUtc ?? issuedUtc.Add(Options.ExpireTimeSpan);
signInContext.CookieOptions.Expires = expiresUtc.ToUniversalTime().DateTime;
}
model = new AuthenticationTicket(signInContext.Principal, signInContext.Properties, signInContext.AuthenticationScheme);
if (Options.SessionStore != null)
{
if (_sessionKey != null)
{
await Options.SessionStore.RemoveAsync(_sessionKey);
}
_sessionKey = await Options.SessionStore.StoreAsync(model);
var principal = new ClaimsPrincipal(
new ClaimsIdentity(
new[] { new Claim(SessionIdClaim, _sessionKey, ClaimValueTypes.String, Options.ClaimsIssuer) },
Options.ClaimsIssuer));
model = new AuthenticationTicket(principal, null, Options.AuthenticationScheme);
}
var cookieValue = Options.TicketDataFormat.Protect(model);
Options.CookieManager.AppendResponseCookie(
Context,
Options.CookieName,
cookieValue,
signInContext.CookieOptions);
var signedInContext = new CookieResponseSignedInContext(
Context,
Options,
Options.AuthenticationScheme,
signInContext.Principal,
signInContext.Properties);
Options.Notifications.ResponseSignedIn(signedInContext);
} }
else if (shouldSignout)
Options.Notifications.ResponseSignIn(signInContext);
if (signInContext.Properties.IsPersistent)
{ {
if (Options.SessionStore != null && _sessionKey != null) var expiresUtc = signInContext.Properties.ExpiresUtc ?? issuedUtc.Add(Options.ExpireTimeSpan);
signInContext.CookieOptions.Expires = expiresUtc.ToUniversalTime().DateTime;
}
model = new AuthenticationTicket(signInContext.Principal, signInContext.Properties, signInContext.AuthenticationScheme);
if (Options.SessionStore != null)
{
if (_sessionKey != null)
{ {
await Options.SessionStore.RemoveAsync(_sessionKey); await Options.SessionStore.RemoveAsync(_sessionKey);
} }
_sessionKey = await Options.SessionStore.StoreAsync(model);
var context = new CookieResponseSignOutContext( var principal = new ClaimsPrincipal(
Context, new ClaimsIdentity(
Options, new[] { new Claim(SessionIdClaim, _sessionKey, ClaimValueTypes.String, Options.ClaimsIssuer) },
cookieOptions); Options.ClaimsIssuer));
model = new AuthenticationTicket(principal, null, Options.AuthenticationScheme);
Options.Notifications.ResponseSignOut(context);
Options.CookieManager.DeleteCookie(
Context,
Options.CookieName,
context.CookieOptions);
} }
else if (_shouldRenew) var cookieValue = Options.TicketDataFormat.Protect(model);
{
model.Properties.IssuedUtc = _renewIssuedUtc;
model.Properties.ExpiresUtc = _renewExpiresUtc;
if (Options.SessionStore != null && _sessionKey != null) Options.CookieManager.AppendResponseCookie(
{ Context,
await Options.SessionStore.RenewAsync(_sessionKey, model); Options.CookieName,
var principal = new ClaimsPrincipal( cookieValue,
new ClaimsIdentity( signInContext.CookieOptions);
new[] { new Claim(SessionIdClaim, _sessionKey, ClaimValueTypes.String, Options.ClaimsIssuer) },
Options.AuthenticationScheme));
model = new AuthenticationTicket(principal, null, Options.AuthenticationScheme);
}
var cookieValue = Options.TicketDataFormat.Protect(model); var signedInContext = new CookieResponseSignedInContext(
Context,
Options,
Options.AuthenticationScheme,
signInContext.Principal,
signInContext.Properties);
if (model.Properties.IsPersistent) Options.Notifications.ResponseSignedIn(signedInContext);
{
cookieOptions.Expires = _renewExpiresUtc.ToUniversalTime().DateTime;
}
Options.CookieManager.AppendResponseCookie(
Context,
Options.CookieName,
cookieValue,
cookieOptions);
}
Response.Headers.Set( Response.Headers.Set(
HeaderNameCacheControl, HeaderNameCacheControl,
@ -275,10 +280,9 @@ namespace Microsoft.AspNet.Authentication.Cookies
HeaderNameExpires, HeaderNameExpires,
HeaderValueMinusOne); HeaderValueMinusOne);
var shouldLoginRedirect = shouldSignin && Options.LoginPath.HasValue && Request.Path == Options.LoginPath; var shouldLoginRedirect = Options.LoginPath.HasValue && Request.Path == Options.LoginPath;
var shouldLogoutRedirect = shouldSignout && Options.LogoutPath.HasValue && Request.Path == Options.LogoutPath;
if ((shouldLoginRedirect || shouldLogoutRedirect) && Response.StatusCode == 200) if ((shouldLoginRedirect) && Response.StatusCode == 200)
{ {
var query = Request.Query; var query = Request.Query;
var redirectUri = query.Get(Options.ReturnUrlParameter); var redirectUri = query.Get(Options.ReturnUrlParameter);
@ -302,6 +306,69 @@ namespace Microsoft.AspNet.Authentication.Cookies
} }
} }
protected override async Task HandleSignOutAsync(SignOutContext signOutContext)
{
var model = await AuthenticateAsync();
try
{
var cookieOptions = BuildCookieOptions();
if (Options.SessionStore != null && _sessionKey != null)
{
await Options.SessionStore.RemoveAsync(_sessionKey);
}
var context = new CookieResponseSignOutContext(
Context,
Options,
cookieOptions);
Options.Notifications.ResponseSignOut(context);
Options.CookieManager.DeleteCookie(
Context,
Options.CookieName,
context.CookieOptions);
Response.Headers.Set(
HeaderNameCacheControl,
HeaderValueNoCache);
Response.Headers.Set(
HeaderNamePragma,
HeaderValueNoCache);
Response.Headers.Set(
HeaderNameExpires,
HeaderValueMinusOne);
var shouldLogoutRedirect = Options.LogoutPath.HasValue && Request.Path == Options.LogoutPath;
if (shouldLogoutRedirect && Response.StatusCode == 200)
{
var query = Request.Query;
var redirectUri = query.Get(Options.ReturnUrlParameter);
if (!string.IsNullOrWhiteSpace(redirectUri)
&& IsHostRelative(redirectUri))
{
var redirectContext = new CookieApplyRedirectContext(Context, Options, redirectUri);
Options.Notifications.ApplyRedirect(redirectContext);
}
}
}
catch (Exception exception)
{
var exceptionContext = new CookieExceptionContext(Context, Options,
CookieExceptionContext.ExceptionLocation.ApplyResponseGrant, exception, model);
Options.Notifications.Exception(exceptionContext);
if (exceptionContext.Rethrow)
{
throw;
}
}
}
private static bool IsHostRelative(string path) private static bool IsHostRelative(string path)
{ {
if (string.IsNullOrEmpty(path)) if (string.IsNullOrEmpty(path))
@ -315,60 +382,49 @@ namespace Microsoft.AspNet.Authentication.Cookies
return path[0] == '/' && path[1] != '/' && path[1] != '\\'; return path[0] == '/' && path[1] != '/' && path[1] != '\\';
} }
protected override void ApplyResponseChallenge() protected override Task<bool> HandleForbiddenAsync(ChallengeContext context)
{ {
if (ShouldConvertChallengeToForbidden()) // HandleForbidden by redirecting to AccessDeniedPath if set
if (Options.AccessDeniedPath.HasValue)
{ {
// Handle 403 by redirecting to AccessDeniedPath if set try
if (Options.AccessDeniedPath.HasValue)
{ {
try var accessDeniedUri =
{ Request.Scheme +
var accessDeniedUri = "://" +
Request.Scheme + Request.Host +
"://" + Request.PathBase +
Request.Host + Options.AccessDeniedPath;
Request.PathBase +
Options.AccessDeniedPath;
var redirectContext = new CookieApplyRedirectContext(Context, Options, accessDeniedUri); var redirectContext = new CookieApplyRedirectContext(Context, Options, accessDeniedUri);
Options.Notifications.ApplyRedirect(redirectContext); Options.Notifications.ApplyRedirect(redirectContext);
} }
catch (Exception exception) catch (Exception exception)
{
var exceptionContext = new CookieExceptionContext(Context, Options,
CookieExceptionContext.ExceptionLocation.ApplyResponseChallenge, exception, ticket: null);
Options.Notifications.Exception(exceptionContext);
if (exceptionContext.Rethrow)
{ {
var exceptionContext = new CookieExceptionContext(Context, Options, throw;
CookieExceptionContext.ExceptionLocation.ApplyResponseChallenge, exception, ticket: null);
Options.Notifications.Exception(exceptionContext);
if (exceptionContext.Rethrow)
{
throw;
}
} }
} }
else return Task.FromResult(true);
{
Response.StatusCode = 403;
}
return;
} }
else
if (Response.StatusCode != 401 || !Options.LoginPath.HasValue )
{ {
return; return base.HandleForbiddenAsync(context);
} }
}
// Automatic middleware should redirect on 401 even if there wasn't an explicit challenge. protected override Task<bool> HandleUnauthorizedAsync([NotNull] ChallengeContext context)
if (ChallengeContext == null && !Options.AutomaticAuthentication) {
if (!Options.LoginPath.HasValue)
{ {
return; return base.HandleUnauthorizedAsync(context);
}
var redirectUri = string.Empty;
if (ChallengeContext != null)
{
redirectUri = new AuthenticationProperties(ChallengeContext.Properties).RedirectUri;
} }
var redirectUri = new AuthenticationProperties(context.Properties).RedirectUri;
try try
{ {
if (string.IsNullOrWhiteSpace(redirectUri)) if (string.IsNullOrWhiteSpace(redirectUri))
@ -400,6 +456,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
throw; throw;
} }
} }
return Task.FromResult(true);
} }
} }
} }

View File

@ -6,10 +6,11 @@ using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication; using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.Http.Extensions; using Microsoft.AspNet.Http.Extensions;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.AspNet.WebUtilities; using Microsoft.AspNet.WebUtilities;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging; using Microsoft.Framework.Logging;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -37,7 +38,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
public async Task<bool> InvokeReturnPathAsync() public async Task<bool> InvokeReturnPathAsync()
{ {
AuthenticationTicket ticket = await AuthenticateAsync(); var ticket = await AuthenticateAsync();
if (ticket == null) if (ticket == null)
{ {
Logger.LogWarning("Invalid return state, unable to redirect."); Logger.LogWarning("Invalid return state, unable to redirect.");
@ -56,7 +57,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
if (context.SignInScheme != null && context.Principal != null) if (context.SignInScheme != null && context.Principal != null)
{ {
Context.Authentication.SignIn(context.SignInScheme, context.Principal, context.Properties); await Context.Authentication.SignInAsync(context.SignInScheme, context.Principal, context.Properties);
} }
if (!context.IsRequestCompleted && context.RedirectUri != null) if (!context.IsRequestCompleted && context.RedirectUri != null)
@ -73,17 +74,12 @@ namespace Microsoft.AspNet.Authentication.OAuth
return context.IsRequestCompleted; return context.IsRequestCompleted;
} }
protected override AuthenticationTicket AuthenticateCore() public override async Task<AuthenticationTicket> AuthenticateAsync()
{
return AuthenticateCoreAsync().GetAwaiter().GetResult();
}
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{ {
AuthenticationProperties properties = null; AuthenticationProperties properties = null;
try try
{ {
IReadableStringCollection query = Request.Query; var query = Request.Query;
// TODO: Is this a standard error returned by servers? // TODO: Is this a standard error returned by servers?
var value = query.Get("error"); var value = query.Get("error");
@ -94,8 +90,8 @@ namespace Microsoft.AspNet.Authentication.OAuth
return null; return null;
} }
string code = query.Get("code"); var code = query.Get("code");
string state = query.Get("state"); var state = query.Get("state");
properties = Options.StateDataFormat.Unprotect(state); properties = Options.StateDataFormat.Unprotect(state);
if (properties == null) if (properties == null)
@ -115,8 +111,8 @@ namespace Microsoft.AspNet.Authentication.OAuth
return new AuthenticationTicket(properties, Options.AuthenticationScheme); return new AuthenticationTicket(properties, Options.AuthenticationScheme);
} }
string requestPrefix = Request.Scheme + "://" + Request.Host; var requestPrefix = Request.Scheme + "://" + Request.Host;
string redirectUri = requestPrefix + RequestPathBase + Options.CallbackPath; var redirectUri = requestPrefix + RequestPathBase + Options.CallbackPath;
var tokens = await ExchangeCodeAsync(code, redirectUri); var tokens = await ExchangeCodeAsync(code, redirectUri);
@ -151,11 +147,11 @@ namespace Microsoft.AspNet.Authentication.OAuth
var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint); var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint);
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
requestMessage.Content = requestContent; requestMessage.Content = requestContent;
HttpResponseMessage response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted); var response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
string oauthTokenResponse = await response.Content.ReadAsStringAsync(); var oauthTokenResponse = await response.Content.ReadAsStringAsync();
JObject oauth2Token = JObject.Parse(oauthTokenResponse); var oauth2Token = JObject.Parse(oauthTokenResponse);
return new TokenResponse(oauth2Token); return new TokenResponse(oauth2Token);
} }
@ -169,40 +165,13 @@ namespace Microsoft.AspNet.Authentication.OAuth
return new AuthenticationTicket(context.Principal, context.Properties, Options.AuthenticationScheme); return new AuthenticationTicket(context.Principal, context.Properties, Options.AuthenticationScheme);
} }
protected override void ApplyResponseChallenge() protected override Task<bool> HandleUnauthorizedAsync([NotNull] ChallengeContext context)
{ {
if (ShouldConvertChallengeToForbidden()) var baseUri = Request.Scheme + "://" + Request.Host + Request.PathBase;
{ var currentUri = baseUri + Request.Path + Request.QueryString;
Response.StatusCode = 403; var redirectUri = baseUri + Options.CallbackPath;
return;
}
if (Response.StatusCode != 401) var properties = new AuthenticationProperties(context.Properties);
{
return;
}
// When Automatic should redirect on 401 even if there wasn't an explicit challenge.
if (ChallengeContext == null && !Options.AutomaticAuthentication)
{
return;
}
string baseUri = Request.Scheme + "://" + Request.Host + Request.PathBase;
string currentUri = baseUri + Request.Path + Request.QueryString;
string redirectUri = baseUri + Options.CallbackPath;
AuthenticationProperties properties;
if (ChallengeContext == null)
{
properties = new AuthenticationProperties();
}
else
{
properties = new AuthenticationProperties(ChallengeContext.Properties);
}
if (string.IsNullOrEmpty(properties.RedirectUri)) if (string.IsNullOrEmpty(properties.RedirectUri))
{ {
properties.RedirectUri = currentUri; properties.RedirectUri = currentUri;
@ -211,19 +180,35 @@ namespace Microsoft.AspNet.Authentication.OAuth
// OAuth2 10.12 CSRF // OAuth2 10.12 CSRF
GenerateCorrelationId(properties); GenerateCorrelationId(properties);
string authorizationEndpoint = BuildChallengeUrl(properties, redirectUri); var authorizationEndpoint = BuildChallengeUrl(properties, redirectUri);
var redirectContext = new OAuthApplyRedirectContext( var redirectContext = new OAuthApplyRedirectContext(
Context, Options, Context, Options,
properties, authorizationEndpoint); properties, authorizationEndpoint);
Options.Notifications.ApplyRedirect(redirectContext); Options.Notifications.ApplyRedirect(redirectContext);
return Task.FromResult(true);
}
protected override Task HandleSignOutAsync(SignOutContext context)
{
throw new NotSupportedException();
}
protected override Task HandleSignInAsync(SignInContext context)
{
throw new NotSupportedException();
}
protected override Task<bool> HandleForbiddenAsync(ChallengeContext context)
{
throw new NotSupportedException();
} }
protected virtual string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri) protected virtual string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri)
{ {
string scope = FormatScope(); var scope = FormatScope();
string state = Options.StateDataFormat.Protect(properties); var state = Options.StateDataFormat.Protect(properties);
var queryBuilder = new QueryBuilder() var queryBuilder = new QueryBuilder()
{ {
@ -241,10 +226,5 @@ namespace Microsoft.AspNet.Authentication.OAuth
// OAuth2 3.3 space separated // OAuth2 3.3 space separated
return string.Join(" ", Options.Scope); return string.Join(" ", Options.Scope);
} }
protected override void ApplyResponseGrant()
{
// N/A - No SignIn or SignOut support.
}
} }
} }

View File

@ -2,14 +2,13 @@
// 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.
using System; using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens; using System.IdentityModel.Tokens;
using System.Linq; using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Authentication.Notifications; using Microsoft.AspNet.Authentication.Notifications;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication; using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.Framework.Logging; using Microsoft.Framework.Logging;
using Microsoft.IdentityModel.Protocols; using Microsoft.IdentityModel.Protocols;
@ -19,16 +18,11 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
{ {
private OpenIdConnectConfiguration _configuration; private OpenIdConnectConfiguration _configuration;
protected override AuthenticationTicket AuthenticateCore()
{
return AuthenticateCoreAsync().GetAwaiter().GetResult();
}
/// <summary> /// <summary>
/// Searches the 'Authorization' header for a 'Bearer' token. If the 'Bearer' token is found, it is validated using <see cref="TokenValidationParameters"/> set in the options. /// Searches the 'Authorization' header for a 'Bearer' token. If the 'Bearer' token is found, it is validated using <see cref="TokenValidationParameters"/> set in the options.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync() public override async Task<AuthenticationTicket> AuthenticateAsync()
{ {
string token = null; string token = null;
try try
@ -179,30 +173,21 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
} }
} }
protected override void ApplyResponseChallenge() protected override async Task<bool> HandleUnauthorizedAsync(ChallengeContext context)
{ {
ApplyResponseChallengeAsync().GetAwaiter().GetResult(); Response.StatusCode = 401;
}
protected override async Task ApplyResponseChallengeAsync()
{
if (ShouldConvertChallengeToForbidden())
{
Response.StatusCode = 403;
return;
}
if ((Response.StatusCode != 401) || (ChallengeContext == null && !Options.AutomaticAuthentication))
{
return;
}
await Options.Notifications.ApplyChallenge(new AuthenticationChallengeNotification<OAuthBearerAuthenticationOptions>(Context, Options)); await Options.Notifications.ApplyChallenge(new AuthenticationChallengeNotification<OAuthBearerAuthenticationOptions>(Context, Options));
return false;
} }
protected override void ApplyResponseGrant() protected override Task HandleSignOutAsync(SignOutContext context)
{ {
// N/A throw new NotSupportedException();
}
protected override Task HandleSignInAsync(SignInContext context)
{
throw new NotSupportedException();
} }
} }
} }

View File

@ -11,6 +11,8 @@ using System.Threading.Tasks;
using Microsoft.AspNet.Authentication.Notifications; using Microsoft.AspNet.Authentication.Notifications;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication; using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging; using Microsoft.Framework.Logging;
using Microsoft.IdentityModel.Protocols; using Microsoft.IdentityModel.Protocols;
@ -38,18 +40,12 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
} }
} }
protected override void ApplyResponseGrant()
{
ApplyResponseGrantAsync().GetAwaiter().GetResult();
}
/// <summary> /// <summary>
/// Handles Signout /// Handles Signout
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
protected override async Task ApplyResponseGrantAsync() protected override async Task HandleSignOutAsync(SignOutContext signout)
{ {
var signout = SignOutContext;
if (signout != null) if (signout != null)
{ {
if (_configuration == null && Options.ConfigurationManager != null) if (_configuration == null && Options.ConfigurationManager != null)
@ -96,52 +92,19 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
} }
} }
protected override void ApplyResponseChallenge()
{
ApplyResponseChallengeAsync().GetAwaiter().GetResult();
}
/// <summary> /// <summary>
/// Responds to a 401 Challenge. Sends an OpenIdConnect message to the 'identity authority' to obtain an identity. /// Responds to a 401 Challenge. Sends an OpenIdConnect message to the 'identity authority' to obtain an identity.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
/// <remarks>Uses log id's OIDCH-0026 - OIDCH-0050, next num: 37</remarks> /// <remarks>Uses log id's OIDCH-0026 - OIDCH-0050, next num: 37</remarks>
protected override async Task ApplyResponseChallengeAsync() protected override async Task<bool> HandleUnauthorizedAsync([NotNull] ChallengeContext context)
{ {
Logger.LogDebug(Resources.OIDCH_0026_ApplyResponseChallengeAsync, this.GetType()); Logger.LogDebug(Resources.OIDCH_0026_ApplyResponseChallengeAsync, this.GetType());
if (ShouldConvertChallengeToForbidden())
{
Logger.LogDebug(Resources.OIDCH_0027_401_ConvertedTo_403);
Response.StatusCode = 403;
return;
}
if (Response.StatusCode != 401)
{
Logger.LogDebug(Resources.OIDCH_0028_StatusCodeNot401, Response.StatusCode);
return;
}
// When Automatic should redirect on 401 even if there wasn't an explicit challenge.
if (ChallengeContext == null && !Options.AutomaticAuthentication)
{
Logger.LogDebug(Resources.OIDCH_0029_ChallengeContextEqualsNull);
return;
}
// order for local RedirectUri // order for local RedirectUri
// 1. challenge.Properties.RedirectUri // 1. challenge.Properties.RedirectUri
// 2. CurrentUri if Options.DefaultToCurrentUriOnRedirect is true) // 2. CurrentUri if Options.DefaultToCurrentUriOnRedirect is true)
AuthenticationProperties properties; AuthenticationProperties properties = new AuthenticationProperties(context.Properties);
if (ChallengeContext == null)
{
properties = new AuthenticationProperties();
}
else
{
properties = new AuthenticationProperties(ChallengeContext.Properties);
}
if (!string.IsNullOrWhiteSpace(properties.RedirectUri)) if (!string.IsNullOrWhiteSpace(properties.RedirectUri))
{ {
@ -209,12 +172,12 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
if (redirectToIdentityProviderNotification.HandledResponse) if (redirectToIdentityProviderNotification.HandledResponse)
{ {
Logger.LogInformation(Resources.OIDCH_0034_RedirectToIdentityProviderNotificationHandledResponse); Logger.LogInformation(Resources.OIDCH_0034_RedirectToIdentityProviderNotificationHandledResponse);
return; return true; // REVIEW: Make sure this should stop all other handlers
} }
else if (redirectToIdentityProviderNotification.Skipped) else if (redirectToIdentityProviderNotification.Skipped)
{ {
Logger.LogInformation(Resources.OIDCH_0035_RedirectToIdentityProviderNotificationSkipped); Logger.LogInformation(Resources.OIDCH_0035_RedirectToIdentityProviderNotificationSkipped);
return; return false; // REVIEW: Make sure this should not stop all other handlers
} }
var redirectUri = redirectToIdentityProviderNotification.ProtocolMessage.CreateAuthenticationRequestUrl(); var redirectUri = redirectToIdentityProviderNotification.ProtocolMessage.CreateAuthenticationRequestUrl();
@ -224,11 +187,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
} }
Response.Redirect(redirectUri); Response.Redirect(redirectUri);
} return true;
protected override AuthenticationTicket AuthenticateCore()
{
return AuthenticateCoreAsync().GetAwaiter().GetResult();
} }
/// <summary> /// <summary>
@ -236,7 +195,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// </summary> /// </summary>
/// <returns>An <see cref="AuthenticationTicket"/> if successful.</returns> /// <returns>An <see cref="AuthenticationTicket"/> if successful.</returns>
/// <remarks>Uses log id's OIDCH-0000 - OIDCH-0025</remarks> /// <remarks>Uses log id's OIDCH-0000 - OIDCH-0025</remarks>
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync() public override async Task<AuthenticationTicket> AuthenticateAsync()
{ {
Logger.LogDebug(Resources.OIDCH_0000_AuthenticateCoreAsync, this.GetType()); Logger.LogDebug(Resources.OIDCH_0000_AuthenticateCoreAsync, this.GetType());
@ -632,7 +591,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
{ {
if (ticket.Principal != null) if (ticket.Principal != null)
{ {
Request.HttpContext.Authentication.SignIn(Options.SignInScheme, ticket.Principal, ticket.Properties); await Request.HttpContext.Authentication.SignInAsync(Options.SignInScheme, ticket.Principal, ticket.Properties);
} }
// Redirect back to the original secured resource, if any. // Redirect back to the original secured resource, if any.

View File

@ -12,8 +12,10 @@ using System.Threading.Tasks;
using Microsoft.AspNet.Authentication.Twitter.Messages; using Microsoft.AspNet.Authentication.Twitter.Messages;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication; using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.WebUtilities; using Microsoft.AspNet.WebUtilities;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging; using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Authentication.Twitter namespace Microsoft.AspNet.Authentication.Twitter
@ -42,12 +44,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
return false; return false;
} }
protected override AuthenticationTicket AuthenticateCore() public override async Task<AuthenticationTicket> AuthenticateAsync()
{
return AuthenticateCoreAsync().GetAwaiter().GetResult();
}
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{ {
AuthenticationProperties properties = null; AuthenticationProperties properties = null;
try try
@ -121,49 +118,18 @@ namespace Microsoft.AspNet.Authentication.Twitter
return new AuthenticationTicket(properties, Options.AuthenticationScheme); return new AuthenticationTicket(properties, Options.AuthenticationScheme);
} }
} }
protected override void ApplyResponseChallenge() protected override async Task<bool> HandleUnauthorizedAsync([NotNull] ChallengeContext context)
{ {
ApplyResponseChallengeAsync().GetAwaiter().GetResult();
}
protected override async Task ApplyResponseChallengeAsync()
{
if (ShouldConvertChallengeToForbidden())
{
Response.StatusCode = 403;
return;
}
if (Response.StatusCode != 401)
{
return;
}
// When Automatic should redirect on 401 even if there wasn't an explicit challenge.
if (ChallengeContext == null && !Options.AutomaticAuthentication)
{
return;
}
var requestPrefix = Request.Scheme + "://" + Request.Host; var requestPrefix = Request.Scheme + "://" + Request.Host;
var callBackUrl = requestPrefix + RequestPathBase + Options.CallbackPath; var callBackUrl = requestPrefix + RequestPathBase + Options.CallbackPath;
AuthenticationProperties properties; var properties = new AuthenticationProperties(context.Properties);
if (ChallengeContext == null)
{
properties = new AuthenticationProperties();
}
else
{
properties = new AuthenticationProperties(ChallengeContext.Properties);
}
if (string.IsNullOrEmpty(properties.RedirectUri)) if (string.IsNullOrEmpty(properties.RedirectUri))
{ {
properties.RedirectUri = requestPrefix + Request.PathBase + Request.Path + Request.QueryString; properties.RedirectUri = requestPrefix + Request.PathBase + Request.Path + Request.QueryString;
} }
var requestToken = await ObtainRequestTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, callBackUrl, properties); var requestToken = await ObtainRequestTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, callBackUrl, properties);
if (requestToken.CallbackConfirmed) if (requestToken.CallbackConfirmed)
{ {
var twitterAuthenticationEndpoint = AuthenticationEndpoint + requestToken.Token; var twitterAuthenticationEndpoint = AuthenticationEndpoint + requestToken.Token;
@ -180,11 +146,13 @@ namespace Microsoft.AspNet.Authentication.Twitter
Context, Options, Context, Options,
properties, twitterAuthenticationEndpoint); properties, twitterAuthenticationEndpoint);
Options.Notifications.ApplyRedirect(redirectContext); Options.Notifications.ApplyRedirect(redirectContext);
return true;
} }
else else
{ {
Logger.LogError("requestToken CallbackConfirmed!=true"); Logger.LogError("requestToken CallbackConfirmed!=true");
} }
return false; // REVIEW: Make sure this should not stop other handlers
} }
public async Task<bool> InvokeReturnPathAsync() public async Task<bool> InvokeReturnPathAsync()
@ -208,7 +176,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
if (context.SignInScheme != null && context.Principal != null) if (context.SignInScheme != null && context.Principal != null)
{ {
Context.Authentication.SignIn(context.SignInScheme, context.Principal, context.Properties); await Context.Authentication.SignInAsync(context.SignInScheme, context.Principal, context.Properties);
} }
if (!context.IsRequestCompleted && context.RedirectUri != null) if (!context.IsRequestCompleted && context.RedirectUri != null)
@ -225,6 +193,21 @@ namespace Microsoft.AspNet.Authentication.Twitter
return context.IsRequestCompleted; return context.IsRequestCompleted;
} }
protected override Task HandleSignOutAsync(SignOutContext context)
{
throw new NotSupportedException();
}
protected override Task HandleSignInAsync(SignInContext context)
{
throw new NotSupportedException();
}
protected override Task<bool> HandleForbiddenAsync(ChallengeContext context)
{
throw new NotSupportedException();
}
private async Task<RequestToken> ObtainRequestTokenAsync(string consumerKey, string consumerSecret, string callBackUri, AuthenticationProperties properties) private async Task<RequestToken> ObtainRequestTokenAsync(string consumerKey, string consumerSecret, string callBackUri, AuthenticationProperties properties)
{ {
Logger.LogVerbose("ObtainRequestToken"); Logger.LogVerbose("ObtainRequestToken");
@ -380,10 +363,5 @@ namespace Microsoft.AspNet.Authentication.Twitter
return Convert.ToBase64String(hash); return Convert.ToBase64String(hash);
} }
} }
protected override void ApplyResponseGrant()
{
// N/A - No SignIn or SignOut support.
}
} }
} }

View File

@ -3,7 +3,6 @@
using System; using System;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Authentication.DataHandler.Encoder; using Microsoft.AspNet.Authentication.DataHandler.Encoder;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
@ -22,19 +21,12 @@ namespace Microsoft.AspNet.Authentication
{ {
private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create(); private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create();
private Task<AuthenticationTicket> _authenticate; private bool _finishCalled;
private bool _authenticateInitialized;
private object _authenticateSyncLock;
private Task _applyResponse;
private bool _applyResponseInitialized;
private object _applyResponseSyncLock;
private AuthenticationOptions _baseOptions; private AuthenticationOptions _baseOptions;
protected ChallengeContext ChallengeContext { get; set; } protected bool SignInAccepted { get; set; }
protected SignInContext SignInContext { get; set; } protected bool SignOutAccepted { get; set; }
protected SignOutContext SignOutContext { get; set; } protected bool ChallengeCalled { get; set; }
protected HttpContext Context { get; private set; } protected HttpContext Context { get; private set; }
@ -59,13 +51,8 @@ namespace Microsoft.AspNet.Authentication
get { return _baseOptions; } get { return _baseOptions; }
} }
// REVIEW: Overriding Authenticate and not calling base requires manually calling this for 401-403 to work
protected bool AuthenticateCalled { get; set; }
public IAuthenticationHandler PriorHandler { get; set; } public IAuthenticationHandler PriorHandler { get; set; }
public bool Faulted { get; set; }
protected async Task BaseInitializeAsync([NotNull] AuthenticationOptions options, [NotNull] HttpContext context, [NotNull] ILogger logger, [NotNull] IUrlEncoder encoder) protected async Task BaseInitializeAsync([NotNull] AuthenticationOptions options, [NotNull] HttpContext context, [NotNull] ILogger logger, [NotNull] IUrlEncoder encoder)
{ {
_baseOptions = options; _baseOptions = options;
@ -76,9 +63,7 @@ namespace Microsoft.AspNet.Authentication
RegisterAuthenticationHandler(); RegisterAuthenticationHandler();
Response.OnResponseStarting(OnSendingHeaderCallback, this); Response.OnStarting(OnStartingCallback, this);
await InitializeCoreAsync();
if (BaseOptions.AutomaticAuthentication) if (BaseOptions.AutomaticAuthentication)
{ {
@ -90,50 +75,49 @@ namespace Microsoft.AspNet.Authentication
} }
} }
private static void OnSendingHeaderCallback(object state) private static async Task OnStartingCallback(object state)
{ {
AuthenticationHandler handler = (AuthenticationHandler)state; var handler = (AuthenticationHandler)state;
handler.ApplyResponse(); await handler.FinishResponseOnce();
} }
protected virtual Task InitializeCoreAsync() private async Task FinishResponseOnce()
{
if (!_finishCalled)
{
_finishCalled = true;
await FinishResponseAsync();
await HandleAutomaticChallengeIfNeeded();
}
}
/// <summary>
/// Hook that is called when the response about to be sent
/// </summary>
/// <returns></returns>
protected virtual Task FinishResponseAsync()
{ {
return Task.FromResult(0); return Task.FromResult(0);
} }
private async Task HandleAutomaticChallengeIfNeeded()
{
if (!ChallengeCalled && BaseOptions.AutomaticAuthentication && Response.StatusCode == 401)
{
await HandleUnauthorizedAsync(new ChallengeContext(BaseOptions.AuthenticationScheme));
}
}
/// <summary> /// <summary>
/// Called once per request after Initialize and Invoke. /// Called once after Invoke by AuthenticationMiddleware.
/// </summary> /// </summary>
/// <returns>async completion</returns> /// <returns>async completion</returns>
internal async Task TeardownAsync() internal async Task TeardownAsync()
{ {
try await FinishResponseOnce();
{
await ApplyResponseAsync();
}
catch (Exception)
{
try
{
await TeardownCoreAsync();
}
catch (Exception)
{
// Don't mask the original exception
}
UnregisterAuthenticationHandler();
throw;
}
await TeardownCoreAsync();
UnregisterAuthenticationHandler(); UnregisterAuthenticationHandler();
} }
protected virtual Task TeardownCoreAsync()
{
return Task.FromResult(0);
}
/// <summary> /// <summary>
/// Called once by common code after initialization. If an authentication middleware responds directly to /// Called once by common code after initialization. If an authentication middleware responds directly to
/// specifically known paths it must override this virtual, compare the request path to it's known paths, /// specifically known paths it must override this virtual, compare the request path to it's known paths,
@ -147,7 +131,7 @@ namespace Microsoft.AspNet.Authentication
return Task.FromResult(false); return Task.FromResult(false);
} }
public virtual void GetDescriptions(DescribeSchemesContext describeContext) public void GetDescriptions(DescribeSchemesContext describeContext)
{ {
describeContext.Accept(BaseOptions.Description.Items); describeContext.Accept(BaseOptions.Description.Items);
@ -157,36 +141,13 @@ namespace Microsoft.AspNet.Authentication
} }
} }
public virtual void Authenticate(AuthenticateContext context) public async Task AuthenticateAsync(AuthenticateContext context)
{
if (ShouldHandleScheme(context.AuthenticationScheme))
{
var ticket = Authenticate();
if (ticket?.Principal != null)
{
AuthenticateCalled = true;
context.Authenticated(ticket.Principal, ticket.Properties.Items, BaseOptions.Description.Items);
}
else
{
context.NotAuthenticated();
}
}
if (PriorHandler != null)
{
PriorHandler.Authenticate(context);
}
}
public virtual async Task AuthenticateAsync(AuthenticateContext context)
{ {
if (ShouldHandleScheme(context.AuthenticationScheme)) if (ShouldHandleScheme(context.AuthenticationScheme))
{ {
var ticket = await AuthenticateAsync(); var ticket = await AuthenticateAsync();
if (ticket?.Principal != null) if (ticket?.Principal != null)
{ {
AuthenticateCalled = true;
context.Authenticated(ticket.Principal, ticket.Properties.Items, BaseOptions.Description.Items); context.Authenticated(ticket.Principal, ticket.Properties.Items, BaseOptions.Description.Items);
} }
else else
@ -201,193 +162,66 @@ namespace Microsoft.AspNet.Authentication
} }
} }
public AuthenticationTicket Authenticate()
{
return LazyInitializer.EnsureInitialized(
ref _authenticate,
ref _authenticateInitialized,
ref _authenticateSyncLock,
() =>
{
return Task.FromResult(AuthenticateCore());
}).GetAwaiter().GetResult();
}
protected abstract AuthenticationTicket AuthenticateCore();
/// <summary> /// <summary>
/// Causes the authentication logic in AuthenticateCore to be performed for the current request /// Calling Authenticate more than once should always return the original value.
/// at most once and returns the results. Calling Authenticate more than once will always return
/// the original value.
///
/// This method should always be called instead of calling AuthenticateCore directly.
/// </summary> /// </summary>
/// <returns>The ticket data provided by the authentication logic</returns> /// <returns>The ticket data provided by the authentication logic</returns>
public Task<AuthenticationTicket> AuthenticateAsync() public abstract Task<AuthenticationTicket> AuthenticateAsync();
{
return LazyInitializer.EnsureInitialized(
ref _authenticate,
ref _authenticateInitialized,
ref _authenticateSyncLock,
AuthenticateCoreAsync);
}
/// <summary> public bool ShouldHandleScheme(string authenticationScheme)
/// The core authentication logic which must be provided by the handler. Will be invoked at most
/// once per request. Do not call directly, call the wrapping Authenticate method instead.
/// </summary>
/// <returns>The ticket data provided by the authentication logic</returns>
protected virtual Task<AuthenticationTicket> AuthenticateCoreAsync()
{
return Task.FromResult(AuthenticateCore());
}
private void ApplyResponse()
{
// If ApplyResponse already failed in the OnSendingHeaderCallback or TeardownAsync code path then a
// failed task is cached. If called again the same error will be re-thrown. This breaks error handling
// scenarios like the ability to display the error page or re-execute the request.
try
{
if (!Faulted)
{
LazyInitializer.EnsureInitialized(
ref _applyResponse,
ref _applyResponseInitialized,
ref _applyResponseSyncLock,
() =>
{
ApplyResponseCore();
return Task.FromResult(0);
}).GetAwaiter().GetResult(); // Block if the async version is in progress.
}
}
catch (Exception)
{
Faulted = true;
throw;
}
}
protected virtual void ApplyResponseCore()
{
ApplyResponseGrant();
ApplyResponseChallenge();
}
/// <summary>
/// Causes the ApplyResponseCore to be invoked at most once per request. This method will be
/// invoked either earlier, when the response headers are sent as a result of a response write or flush,
/// or later, as the last step when the original async call to the middleware is returning.
/// </summary>
/// <returns></returns>
private async Task ApplyResponseAsync()
{
// If ApplyResponse already failed in the OnSendingHeaderCallback or TeardownAsync code path then a
// failed task is cached. If called again the same error will be re-thrown. This breaks error handling
// scenarios like the ability to display the error page or re-execute the request.
try
{
if (!Faulted)
{
await LazyInitializer.EnsureInitialized(
ref _applyResponse,
ref _applyResponseInitialized,
ref _applyResponseSyncLock,
ApplyResponseCoreAsync);
}
}
catch (Exception)
{
Faulted = true;
throw;
}
}
/// <summary>
/// Core method that may be overridden by handler. The default behavior is to call two common response
/// activities, one that deals with sign-in/sign-out concerns, and a second to deal with 401 challenges.
/// </summary>
/// <returns></returns>
protected virtual async Task ApplyResponseCoreAsync()
{
await ApplyResponseGrantAsync();
await ApplyResponseChallengeAsync();
}
protected abstract void ApplyResponseGrant();
/// <summary>
/// Override this method to dela with sign-in/sign-out concerns, if an authentication scheme in question
/// deals with grant/revoke as part of it's request flow. (like setting/deleting cookies)
/// </summary>
/// <returns></returns>
protected virtual Task ApplyResponseGrantAsync()
{
ApplyResponseGrant();
return Task.FromResult(0);
}
public virtual void SignIn(SignInContext context)
{
if (ShouldHandleScheme(context.AuthenticationScheme))
{
SignInContext = context;
SignOutContext = null;
context.Accept();
}
if (PriorHandler != null)
{
PriorHandler.SignIn(context);
}
}
public virtual void SignOut(SignOutContext context)
{
if (ShouldHandleScheme(context.AuthenticationScheme))
{
SignInContext = null;
SignOutContext = context;
context.Accept();
}
if (PriorHandler != null)
{
PriorHandler.SignOut(context);
}
}
public virtual void Challenge(ChallengeContext context)
{
if (ShouldHandleScheme(context.AuthenticationScheme))
{
ChallengeContext = context;
context.Accept();
}
if (PriorHandler != null)
{
PriorHandler.Challenge(context);
}
}
protected abstract void ApplyResponseChallenge();
public virtual bool ShouldHandleScheme(string authenticationScheme)
{ {
return string.Equals(BaseOptions.AuthenticationScheme, authenticationScheme, StringComparison.Ordinal) || return string.Equals(BaseOptions.AuthenticationScheme, authenticationScheme, StringComparison.Ordinal) ||
(BaseOptions.AutomaticAuthentication && string.IsNullOrWhiteSpace(authenticationScheme)); (BaseOptions.AutomaticAuthentication && string.IsNullOrWhiteSpace(authenticationScheme));
} }
public virtual bool ShouldConvertChallengeToForbidden() public async Task SignInAsync(SignInContext context)
{ {
// Return 403 iff 401 and this handler's authenticate was called if (ShouldHandleScheme(context.AuthenticationScheme))
// and the challenge is for the authentication type {
return Response.StatusCode == 401 && SignInAccepted = true;
AuthenticateCalled && await HandleSignInAsync(context);
ChallengeContext != null && context.Accept();
ShouldHandleScheme(ChallengeContext.AuthenticationScheme); }
if (PriorHandler != null)
{
await PriorHandler.SignInAsync(context);
}
}
protected virtual Task HandleSignInAsync(SignInContext context)
{
return Task.FromResult(0);
}
public async Task SignOutAsync(SignOutContext context)
{
if (ShouldHandleScheme(context.AuthenticationScheme))
{
SignOutAccepted = true;
await HandleSignOutAsync(context);
context.Accept();
}
if (PriorHandler != null)
{
await PriorHandler.SignOutAsync(context);
}
}
protected virtual Task HandleSignOutAsync(SignOutContext context)
{
return Task.FromResult(0);
}
/// <summary>
/// </summary>
/// <param name="context"></param>
/// <returns>True if no other handlers should be called</returns>
protected virtual Task<bool> HandleForbiddenAsync(ChallengeContext context)
{
Response.StatusCode = 403;
return Task.FromResult(true);
} }
/// <summary> /// <summary>
@ -395,11 +229,48 @@ namespace Microsoft.AspNet.Authentication
/// deals an authentication interaction as part of it's request flow. (like adding a response header, or /// deals an authentication interaction as part of it's request flow. (like adding a response header, or
/// changing the 401 result to 302 of a login page or external sign-in location.) /// changing the 401 result to 302 of a login page or external sign-in location.)
/// </summary> /// </summary>
/// <returns></returns> /// <param name="context"></param>
protected virtual Task ApplyResponseChallengeAsync() /// <returns>True if no other handlers should be called</returns>
protected virtual Task<bool> HandleUnauthorizedAsync(ChallengeContext context)
{ {
ApplyResponseChallenge(); Response.StatusCode = 401;
return Task.FromResult(0); return Task.FromResult(false);
}
public async Task ChallengeAsync(ChallengeContext context)
{
bool handled = false;
ChallengeCalled = true;
if (ShouldHandleScheme(context.AuthenticationScheme))
{
switch (context.Behavior)
{
case ChallengeBehavior.Automatic:
// If there is a principal already, invoke the forbidden code path
var ticket = await AuthenticateAsync();
if (ticket?.Principal != null)
{
handled = await HandleForbiddenAsync(context);
}
else
{
handled = await HandleUnauthorizedAsync(context);
}
break;
case ChallengeBehavior.Unauthorized:
handled = await HandleUnauthorizedAsync(context);
break;
case ChallengeBehavior.Forbidden:
handled = await HandleForbiddenAsync(context);
break;
}
context.Accept();
}
if (!handled && PriorHandler != null)
{
await PriorHandler.ChallengeAsync(context);
}
} }
protected void GenerateCorrelationId([NotNull] AuthenticationProperties properties) protected void GenerateCorrelationId([NotNull] AuthenticationProperties properties)

View File

@ -67,7 +67,6 @@ namespace Microsoft.AspNet.Authentication
{ {
try try
{ {
handler.Faulted = true;
await handler.TeardownAsync(); await handler.TeardownAsync();
} }
catch (Exception) catch (Exception)

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Authentication; using Microsoft.AspNet.Authentication;
using Microsoft.Framework.Internal; using Microsoft.Framework.Internal;
@ -24,7 +25,12 @@ namespace Microsoft.Framework.DependencyInjection
public static IServiceCollection ConfigureClaimsTransformation([NotNull] this IServiceCollection services, [NotNull] Func<ClaimsPrincipal, ClaimsPrincipal> transform) public static IServiceCollection ConfigureClaimsTransformation([NotNull] this IServiceCollection services, [NotNull] Func<ClaimsPrincipal, ClaimsPrincipal> transform)
{ {
return services.Configure<ClaimsTransformationOptions>(o => o.Transformation = transform); return services.Configure<ClaimsTransformationOptions>(o => o.Transformer = new ClaimsTransformer { TransformSyncDelegate = transform });
}
public static IServiceCollection ConfigureClaimsTransformation([NotNull] this IServiceCollection services, [NotNull] Func<ClaimsPrincipal, Task<ClaimsPrincipal>> asyncTransform)
{
return services.Configure<ClaimsTransformationOptions>(o => o.Transformer = new ClaimsTransformer { TransformAsyncDelegate = asyncTransform });
} }
} }

View File

@ -1,8 +1,6 @@
// 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.
using System;
using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http.Features.Authentication; using Microsoft.AspNet.Http.Features.Authentication;
@ -13,56 +11,37 @@ namespace Microsoft.AspNet.Authentication
/// </summary> /// </summary>
public class ClaimsTransformationAuthenticationHandler : IAuthenticationHandler public class ClaimsTransformationAuthenticationHandler : IAuthenticationHandler
{ {
private readonly Func<ClaimsPrincipal, ClaimsPrincipal> _transform; private readonly IClaimsTransformer _transform;
public ClaimsTransformationAuthenticationHandler(Func<ClaimsPrincipal, ClaimsPrincipal> transform) public ClaimsTransformationAuthenticationHandler(IClaimsTransformer transform)
{ {
_transform = transform; _transform = transform;
} }
public IAuthenticationHandler PriorHandler { get; set; } public IAuthenticationHandler PriorHandler { get; set; }
private void ApplyTransform(AuthenticateContext context)
{
if (_transform != null)
{
// REVIEW: this cast seems really bad (missing interface way to get the result back out?)
var authContext = context as AuthenticateContext;
if (authContext?.Principal != null)
{
context.Authenticated(
_transform.Invoke(authContext.Principal),
authContext.Properties,
authContext.Description);
}
}
}
public void Authenticate(AuthenticateContext context)
{
if (PriorHandler != null)
{
PriorHandler.Authenticate(context);
ApplyTransform(context);
}
}
public async Task AuthenticateAsync(AuthenticateContext context) public async Task AuthenticateAsync(AuthenticateContext context)
{ {
if (PriorHandler != null) if (PriorHandler != null)
{ {
await PriorHandler.AuthenticateAsync(context); await PriorHandler.AuthenticateAsync(context);
ApplyTransform(context); if (_transform != null && context?.Principal != null)
{
context.Authenticated(
await _transform.TransformAsync(context.Principal),
context.Properties,
context.Description);
}
} }
} }
public void Challenge(ChallengeContext context) public Task ChallengeAsync(ChallengeContext context)
{ {
if (PriorHandler != null) if (PriorHandler != null)
{ {
PriorHandler.Challenge(context); return PriorHandler.ChallengeAsync(context);
} }
return Task.FromResult(0);
} }
public void GetDescriptions(DescribeSchemesContext context) public void GetDescriptions(DescribeSchemesContext context)
@ -73,20 +52,22 @@ namespace Microsoft.AspNet.Authentication
} }
} }
public void SignIn(SignInContext context) public Task SignInAsync(SignInContext context)
{ {
if (PriorHandler != null) if (PriorHandler != null)
{ {
PriorHandler.SignIn(context); return PriorHandler.SignInAsync(context);
} }
return Task.FromResult(0);
} }
public void SignOut(SignOutContext context) public Task SignOutAsync(SignOutContext context)
{ {
if (PriorHandler != null) if (PriorHandler != null)
{ {
PriorHandler.SignOut(context); return PriorHandler.SignOutAsync(context);
} }
return Task.FromResult(0);
} }
public void RegisterAuthenticationHandler(IHttpAuthenticationFeature auth) public void RegisterAuthenticationHandler(IHttpAuthenticationFeature auth)

View File

@ -34,12 +34,12 @@ namespace Microsoft.AspNet.Authentication
public async Task Invoke(HttpContext context) public async Task Invoke(HttpContext context)
{ {
var handler = new ClaimsTransformationAuthenticationHandler(Options.Transformation); var handler = new ClaimsTransformationAuthenticationHandler(Options.Transformer);
handler.RegisterAuthenticationHandler(context.GetAuthentication()); handler.RegisterAuthenticationHandler(context.GetAuthentication());
try { try {
if (Options.Transformation != null) if (Options.Transformer != null)
{ {
context.User = Options.Transformation.Invoke(context.User); context.User = await Options.Transformer.TransformAsync(context.User);
} }
await _next(context); await _next(context);
} }

View File

@ -3,11 +3,42 @@
using System; using System;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Authentication namespace Microsoft.AspNet.Authentication
{ {
public class ClaimsTransformationOptions public class ClaimsTransformationOptions
{ {
public Func<ClaimsPrincipal, ClaimsPrincipal> Transformation { get; set; } public IClaimsTransformer Transformer { get; set; }
}
public interface IClaimsTransformer
{
Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal);
ClaimsPrincipal Transform(ClaimsPrincipal principal);
}
public class ClaimsTransformer : IClaimsTransformer
{
public Func<ClaimsPrincipal, Task<ClaimsPrincipal>> TransformAsyncDelegate { get; set; }
public Func<ClaimsPrincipal, ClaimsPrincipal> TransformSyncDelegate { get; set; }
public virtual ClaimsPrincipal Transform(ClaimsPrincipal principal)
{
if (TransformSyncDelegate != null)
{
return TransformSyncDelegate(principal);
}
return principal;
}
public virtual Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
if (TransformAsyncDelegate != null)
{
return TransformAsyncDelegate(principal);
}
return Task.FromResult(Transform(principal));
}
} }
} }

View File

@ -9,7 +9,7 @@ using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Authorization namespace Microsoft.AspNet.Authorization
{ {
/// <summary> /// <summary>
/// Contains authorization information used by <see cref="IAuthorizationPolicyHandler"/>. /// Contains authorization information used by <see cref="IAuthorizationHandler"/>.
/// </summary> /// </summary>
public class AuthorizationContext public class AuthorizationContext
{ {
@ -28,9 +28,9 @@ namespace Microsoft.AspNet.Authorization
_pendingRequirements = new HashSet<IAuthorizationRequirement>(requirements); _pendingRequirements = new HashSet<IAuthorizationRequirement>(requirements);
} }
public IEnumerable<IAuthorizationRequirement> Requirements { get; private set; } public IEnumerable<IAuthorizationRequirement> Requirements { get; }
public ClaimsPrincipal User { get; private set; } public ClaimsPrincipal User { get; }
public object Resource { get; private set; } public object Resource { get; }
public IEnumerable<IAuthorizationRequirement> PendingRequirements { get { return _pendingRequirements; } } public IEnumerable<IAuthorizationRequirement> PendingRequirements { get { return _pendingRequirements; } }

View File

@ -17,14 +17,21 @@ namespace Microsoft.AspNet.Authorization
} }
} }
public virtual Task HandleAsync(AuthorizationContext context) public virtual async Task HandleAsync(AuthorizationContext context)
{ {
Handle(context); foreach (var req in context.Requirements.OfType<TRequirement>())
return Task.FromResult(0); {
await HandleAsync(context, req);
}
} }
// REVIEW: do we need an async hook too? protected abstract void Handle(AuthorizationContext context, TRequirement requirement);
public abstract void Handle(AuthorizationContext context, TRequirement requirement);
protected virtual Task HandleAsync(AuthorizationContext context, TRequirement requirement)
{
Handle(context, requirement);
return Task.FromResult(0);
}
} }
public abstract class AuthorizationHandler<TRequirement, TResource> : IAuthorizationHandler public abstract class AuthorizationHandler<TRequirement, TResource> : IAuthorizationHandler
@ -34,17 +41,13 @@ namespace Microsoft.AspNet.Authorization
public virtual async Task HandleAsync(AuthorizationContext context) public virtual async Task HandleAsync(AuthorizationContext context)
{ {
var resource = context.Resource as TResource; var resource = context.Resource as TResource;
// REVIEW: should we allow null resources? foreach (var req in context.Requirements.OfType<TRequirement>())
if (resource != null)
{ {
foreach (var req in context.Requirements.OfType<TRequirement>()) await HandleAsync(context, req, resource);
{
await HandleAsync(context, req, resource);
}
} }
} }
public virtual Task HandleAsync(AuthorizationContext context, TRequirement requirement, TResource resource) protected virtual Task HandleAsync(AuthorizationContext context, TRequirement requirement, TResource resource)
{ {
Handle(context, requirement, resource); Handle(context, requirement, resource);
return Task.FromResult(0); return Task.FromResult(0);
@ -63,6 +66,6 @@ namespace Microsoft.AspNet.Authorization
} }
} }
public abstract void Handle(AuthorizationContext context, TRequirement requirement, TResource resource); protected abstract void Handle(AuthorizationContext context, TRequirement requirement, TResource resource);
} }
} }

View File

@ -9,8 +9,7 @@ namespace Microsoft.AspNet.Authorization
{ {
public class AuthorizationOptions public class AuthorizationOptions
{ {
// TODO: make this case insensitive private IDictionary<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>(StringComparer.OrdinalIgnoreCase);
private IDictionary<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>();
public void AddPolicy([NotNull] string name, [NotNull] AuthorizationPolicy policy) public void AddPolicy([NotNull] string name, [NotNull] AuthorizationPolicy policy)
{ {

View File

@ -20,15 +20,14 @@ namespace Microsoft.AspNet.Authorization
ActiveAuthenticationSchemes = new List<string>(activeAuthenticationSchemes).AsReadOnly(); ActiveAuthenticationSchemes = new List<string>(activeAuthenticationSchemes).AsReadOnly();
} }
public IReadOnlyList<IAuthorizationRequirement> Requirements { get; private set; } public IReadOnlyList<IAuthorizationRequirement> Requirements { get; }
public IReadOnlyList<string> ActiveAuthenticationSchemes { get; private set; } public IReadOnlyList<string> ActiveAuthenticationSchemes { get; }
public static AuthorizationPolicy Combine([NotNull] params AuthorizationPolicy[] policies) public static AuthorizationPolicy Combine([NotNull] params AuthorizationPolicy[] policies)
{ {
return Combine((IEnumerable<AuthorizationPolicy>)policies); return Combine((IEnumerable<AuthorizationPolicy>)policies);
} }
// TODO: Add unit tests
public static AuthorizationPolicy Combine([NotNull] IEnumerable<AuthorizationPolicy> policies) public static AuthorizationPolicy Combine([NotNull] IEnumerable<AuthorizationPolicy> policies)
{ {
var builder = new AuthorizationPolicyBuilder(); var builder = new AuthorizationPolicyBuilder();

View File

@ -1,9 +1,9 @@
// 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.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security.Claims;
using Microsoft.Framework.Internal; using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Authorization namespace Microsoft.AspNet.Authorization
@ -88,6 +88,12 @@ namespace Microsoft.AspNet.Authorization
return this; return this;
} }
public AuthorizationPolicyBuilder RequireDelegate([NotNull] Action<AuthorizationContext, DelegateRequirement> handler)
{
Requirements.Add(new DelegateRequirement(handler));
return this;
}
public AuthorizationPolicy Build() public AuthorizationPolicy Build()
{ {
return new AuthorizationPolicy(Requirements, ActiveAuthenticationSchemes.Distinct()); return new AuthorizationPolicy(Requirements, ActiveAuthenticationSchemes.Distinct());

View File

@ -6,7 +6,7 @@ using System;
namespace Microsoft.AspNet.Authorization namespace Microsoft.AspNet.Authorization
{ {
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class AuthorizeAttribute : Attribute public class AuthorizeAttribute : Attribute, IAuthorizeData
{ {
public AuthorizeAttribute() { } public AuthorizeAttribute() { }

View File

@ -21,7 +21,7 @@ namespace Microsoft.AspNet.Authorization
public string ClaimType { get; } public string ClaimType { get; }
public IEnumerable<string> AllowedValues { get; } public IEnumerable<string> AllowedValues { get; }
public override void Handle(AuthorizationContext context, ClaimsAuthorizationRequirement requirement) protected override void Handle(AuthorizationContext context, ClaimsAuthorizationRequirement requirement)
{ {
if (context.User != null) if (context.User != null)
{ {

View File

@ -35,6 +35,7 @@ namespace Microsoft.AspNet.Authorization
foreach (var handler in _handlers) foreach (var handler in _handlers)
{ {
handler.Handle(authContext); handler.Handle(authContext);
//REVIEW: Do we want to consider short circuiting on failure
} }
return authContext.HasSucceeded; return authContext.HasSucceeded;
} }

View File

@ -0,0 +1,22 @@
// 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;
namespace Microsoft.AspNet.Authorization
{
public class DelegateRequirement : AuthorizationHandler<DelegateRequirement>, IAuthorizationRequirement
{
public Action<AuthorizationContext, DelegateRequirement> Handler { get; }
public DelegateRequirement(Action<AuthorizationContext, DelegateRequirement> handleMe)
{
Handler = handleMe;
}
protected override void Handle(AuthorizationContext context, DelegateRequirement requirement)
{
Handler.Invoke(context, requirement);
}
}
}

View File

@ -7,7 +7,7 @@ namespace Microsoft.AspNet.Authorization
{ {
public class DenyAnonymousAuthorizationRequirement : AuthorizationHandler<DenyAnonymousAuthorizationRequirement>, IAuthorizationRequirement public class DenyAnonymousAuthorizationRequirement : AuthorizationHandler<DenyAnonymousAuthorizationRequirement>, IAuthorizationRequirement
{ {
public override void Handle(AuthorizationContext context, DenyAnonymousAuthorizationRequirement requirement) protected override void Handle(AuthorizationContext context, DenyAnonymousAuthorizationRequirement requirement)
{ {
var user = context.User; var user = context.User;
var userIsAnonymous = var userIsAnonymous =

View File

@ -0,0 +1,14 @@
// 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.Authorization
{
public interface IAuthorizeData
{
string Policy { get; set; }
string Roles { get; set; }
string ActiveAuthenticationSchemes { get; set; }
}
}

View File

@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Authorization
public string RequiredName { get; } public string RequiredName { get; }
public override void Handle(AuthorizationContext context, NameAuthorizationRequirement requirement) protected override void Handle(AuthorizationContext context, NameAuthorizationRequirement requirement)
{ {
if (context.User != null) if (context.User != null)
{ {

View File

@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Authorization
public IEnumerable<string> AllowedRoles { get; } public IEnumerable<string> AllowedRoles { get; }
public override void Handle(AuthorizationContext context, RolesAuthorizationRequirement requirement) protected override void Handle(AuthorizationContext context, RolesAuthorizationRequirement requirement)
{ {
if (context.User != null) if (context.User != null)
{ {

View File

@ -2,6 +2,8 @@
// 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.
using System; using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
using Microsoft.Framework.Logging; using Microsoft.Framework.Logging;
using Xunit; using Xunit;
@ -59,17 +61,7 @@ namespace Microsoft.AspNet.Authentication
Options.AuthenticationScheme = scheme; Options.AuthenticationScheme = scheme;
} }
protected override void ApplyResponseChallenge() public override Task<AuthenticationTicket> AuthenticateAsync()
{
throw new NotImplementedException();
}
protected override void ApplyResponseGrant()
{
throw new NotImplementedException();
}
protected override AuthenticationTicket AuthenticateCore()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@ -94,17 +86,7 @@ namespace Microsoft.AspNet.Authentication
Options.AutomaticAuthentication = auto; Options.AutomaticAuthentication = auto;
} }
protected override void ApplyResponseChallenge() public override Task<AuthenticationTicket> AuthenticateAsync()
{
throw new NotImplementedException();
}
protected override void ApplyResponseGrant()
{
throw new NotImplementedException();
}
protected override AuthenticationTicket AuthenticateCore()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

View File

@ -15,6 +15,7 @@ using System.Xml.Linq;
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication; using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.AspNet.TestHost; using Microsoft.AspNet.TestHost;
using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection;
using Shouldly; using Shouldly;
@ -50,7 +51,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
transaction.Response.StatusCode.ShouldBe(auto ? HttpStatusCode.Redirect : HttpStatusCode.Unauthorized); transaction.Response.StatusCode.ShouldBe(auto ? HttpStatusCode.Redirect : HttpStatusCode.Unauthorized);
if (auto) if (auto)
{ {
Uri location = transaction.Response.Headers.Location; var location = transaction.Response.Headers.Location;
location.LocalPath.ShouldBe("/login"); location.LocalPath.ShouldBe("/login");
location.Query.ShouldBe("?ReturnUrl=%2Fprotected"); location.Query.ShouldBe("?ReturnUrl=%2Fprotected");
} }
@ -69,34 +70,32 @@ namespace Microsoft.AspNet.Authentication.Cookies
var transaction = await SendAsync(server, "http://example.com/protected/CustomRedirect"); var transaction = await SendAsync(server, "http://example.com/protected/CustomRedirect");
transaction.Response.StatusCode.ShouldBe(auto ? HttpStatusCode.Redirect : HttpStatusCode.Unauthorized); // REVIEW: Now when Cookies are not in auto, noone handles the challenge so the Status stays OK, is that reasonable??
transaction.Response.StatusCode.ShouldBe(auto ? HttpStatusCode.Redirect : HttpStatusCode.OK);
if (auto) if (auto)
{ {
Uri location = transaction.Response.Headers.Location; var location = transaction.Response.Headers.Location;
location.ToString().ShouldBe("http://example.com/login?ReturnUrl=%2FCustomRedirect"); location.ToString().ShouldBe("http://example.com/login?ReturnUrl=%2FCustomRedirect");
} }
} }
private Task SignInAsAlice(HttpContext context) private Task SignInAsAlice(HttpContext context)
{ {
context.Authentication.SignIn("Cookies", return context.Authentication.SignInAsync("Cookies",
new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies"))), new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies"))),
new AuthenticationProperties()); new AuthenticationProperties());
return Task.FromResult<object>(null);
} }
private Task SignInAsWrong(HttpContext context) private Task SignInAsWrong(HttpContext context)
{ {
context.Authentication.SignIn("Oops", return context.Authentication.SignInAsync("Oops",
new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies"))), new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies"))),
new AuthenticationProperties()); new AuthenticationProperties());
return Task.FromResult<object>(null);
} }
private Task SignOutAsWrong(HttpContext context) private Task SignOutAsWrong(HttpContext context)
{ {
context.Authentication.SignOut("Oops"); return context.Authentication.SignOutAsync("Oops");
return Task.FromResult<object>(null);
} }
[Fact] [Fact]
@ -241,17 +240,30 @@ namespace Microsoft.AspNet.Authentication.Cookies
}, },
SignInAsAlice, SignInAsAlice,
baseAddress: null, baseAddress: null,
claimsTransform: o => o.Transformation = (p => claimsTransform: o => o.Transformer = new ClaimsTransformer
{ {
if (!p.Identities.Any(i => i.AuthenticationType == "xform")) TransformSyncDelegate = p =>
{ {
// REVIEW: Xform runs twice, once on Authenticate, and then once from the middleware if (!p.Identities.Any(i => i.AuthenticationType == "xform"))
var id = new ClaimsIdentity("xform"); {
id.AddClaim(new Claim("xform", "yup")); var id = new ClaimsIdentity("xform");
p.AddIdentity(id); id.AddClaim(new Claim("sync", "no"));
p.AddIdentity(id);
}
return p;
},
TransformAsyncDelegate = p =>
{
if (!p.Identities.Any(i => i.AuthenticationType == "xform"))
{
// REVIEW: Xform runs twice, once on Authenticate, and then once from the middleware
var id = new ClaimsIdentity("xform");
id.AddClaim(new Claim("xform", "yup"));
p.AddIdentity(id);
}
return Task.FromResult(p);
} }
return p; });
}));
var transaction1 = await SendAsync(server, "http://example.com/testpath"); var transaction1 = await SendAsync(server, "http://example.com/testpath");
@ -259,6 +271,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe("Alice"); FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe("Alice");
FindClaimValue(transaction2, "xform").ShouldBe("yup"); FindClaimValue(transaction2, "xform").ShouldBe("yup");
FindClaimValue(transaction2, "sync").ShouldBe(null);
} }
@ -304,12 +317,9 @@ namespace Microsoft.AspNet.Authentication.Cookies
options.SlidingExpiration = false; options.SlidingExpiration = false;
}, },
context => context =>
{ context.Authentication.SignInAsync("Cookies",
context.Authentication.SignIn("Cookies",
new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies"))), new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies"))),
new AuthenticationProperties() { ExpiresUtc = clock.UtcNow.Add(TimeSpan.FromMinutes(5)) }); new AuthenticationProperties() { ExpiresUtc = clock.UtcNow.Add(TimeSpan.FromMinutes(5)) }));
return Task.FromResult<object>(null);
});
var transaction1 = await SendAsync(server, "http://example.com/testpath"); var transaction1 = await SendAsync(server, "http://example.com/testpath");
@ -433,9 +443,8 @@ namespace Microsoft.AspNet.Authentication.Cookies
context => context =>
{ {
Assert.Equal(new PathString("/base"), context.Request.PathBase); Assert.Equal(new PathString("/base"), context.Request.PathBase);
context.Authentication.SignIn("Cookies", return context.Authentication.SignInAsync("Cookies",
new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies")))); new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies"))));
return Task.FromResult<object>(null);
}, },
new Uri("http://example.com/base")); new Uri("http://example.com/base"));
@ -446,7 +455,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
[Theory] [Theory]
[InlineData(true)] [InlineData(true)]
[InlineData(false)] [InlineData(false)]
public async Task CookieTurns401To403IfAuthenticated(bool automatic) public async Task CookieTurns401To403WithCookie(bool automatic)
{ {
var clock = new TestClock(); var clock = new TestClock();
var server = CreateServer(options => var server = CreateServer(options =>
@ -458,18 +467,56 @@ namespace Microsoft.AspNet.Authentication.Cookies
var transaction1 = await SendAsync(server, "http://example.com/testpath"); var transaction1 = await SendAsync(server, "http://example.com/testpath");
var url = "http://example.com/unauthorized"; var url = "http://example.com/challenge";
if (automatic)
{
url += "auto";
}
var transaction2 = await SendAsync(server, url, transaction1.CookieNameValue); var transaction2 = await SendAsync(server, url, transaction1.CookieNameValue);
transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Forbidden); transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Forbidden);
} }
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task CookieChallengeRedirectsToLoginWithoutCookie(bool automatic)
{
var clock = new TestClock();
var server = CreateServer(options =>
{
options.LoginPath = new PathString("/login");
options.AutomaticAuthentication = automatic;
options.AccessDeniedPath = new PathString("/accessdenied");
options.SystemClock = clock;
},
SignInAsAlice);
var url = "http://example.com/challenge";
var transaction = await SendAsync(server, url);
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
var location = transaction.Response.Headers.Location;
location.LocalPath.ShouldBe("/login");
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task CookieForbidTurns401To403WithoutCookie(bool automatic)
{
var clock = new TestClock();
var server = CreateServer(options =>
{
options.AutomaticAuthentication = automatic;
options.SystemClock = clock;
},
SignInAsAlice);
var url = "http://example.com/forbid";
var transaction = await SendAsync(server, url);
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Forbidden);
}
[Fact] [Fact]
public async Task CookieTurns401ToAccessDeniedWhenSetAndIfAuthenticated() public async Task CookieTurns401ToAccessDeniedWhenSetWithCookie()
{ {
var clock = new TestClock(); var clock = new TestClock();
var server = CreateServer(options => var server = CreateServer(options =>
@ -481,7 +528,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
var transaction1 = await SendAsync(server, "http://example.com/testpath"); var transaction1 = await SendAsync(server, "http://example.com/testpath");
var transaction2 = await SendAsync(server, "http://example.com/unauthorized", transaction1.CookieNameValue); var transaction2 = await SendAsync(server, "http://example.com/challenge", transaction1.CookieNameValue);
transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect); transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
@ -490,7 +537,23 @@ namespace Microsoft.AspNet.Authentication.Cookies
} }
[Fact] [Fact]
public async Task CookieDoesNothingTo401IfNotAuthenticated() public async Task CookieChallengeDoesNothingIfNotAuthenticated()
{
var clock = new TestClock();
var server = CreateServer(options =>
{
options.SystemClock = clock;
});
var transaction1 = await SendAsync(server, "http://example.com/testpath");
var transaction2 = await SendAsync(server, "http://example.com/challenge", transaction1.CookieNameValue);
transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized);
}
[Fact]
public async Task CookieChallengeWithUnauthorizedRedirectsToLoginIfNotAuthenticated()
{ {
var clock = new TestClock(); var clock = new TestClock();
var server = CreateServer(options => var server = CreateServer(options =>
@ -549,29 +612,33 @@ namespace Microsoft.AspNet.Authentication.Cookies
{ {
res.StatusCode = 401; res.StatusCode = 401;
} }
else if (req.Path == new PathString("/forbid")) // Simulate forbidden
{
await context.Authentication.ForbidAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
else if (req.Path == new PathString("/challenge"))
{
await context.Authentication.ChallengeAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
else if (req.Path == new PathString("/unauthorized")) else if (req.Path == new PathString("/unauthorized"))
{ {
// Simulate Authorization failure await context.Authentication.ChallengeAsync(CookieAuthenticationDefaults.AuthenticationScheme, new AuthenticationProperties(), ChallengeBehavior.Unauthorized);
var result = await context.Authentication.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
context.Authentication.Challenge(CookieAuthenticationDefaults.AuthenticationScheme);
}
else if (req.Path == new PathString("/unauthorizedauto"))
{
// Simulate Authorization failure
context.Authentication.Challenge(CookieAuthenticationDefaults.AuthenticationScheme);
} }
else if (req.Path == new PathString("/protected/CustomRedirect")) else if (req.Path == new PathString("/protected/CustomRedirect"))
{ {
context.Authentication.Challenge(new AuthenticationProperties() { RedirectUri = "/CustomRedirect" }); await context.Authentication.ChallengeAsync(new AuthenticationProperties() { RedirectUri = "/CustomRedirect" });
} }
else if (req.Path == new PathString("/me")) else if (req.Path == new PathString("/me"))
{ {
Describe(res, new AuthenticationResult(context.User, new AuthenticationProperties(), new AuthenticationDescription())); var authContext = new AuthenticateContext(CookieAuthenticationDefaults.AuthenticationScheme);
authContext.Authenticated(context.User, properties: null, description: null);
Describe(res, authContext);
} }
else if (req.Path.StartsWithSegments(new PathString("/me"), out remainder)) else if (req.Path.StartsWithSegments(new PathString("/me"), out remainder))
{ {
var result = await context.Authentication.AuthenticateAsync(remainder.Value.Substring(1)); var authContext = new AuthenticateContext(remainder.Value.Substring(1));
Describe(res, result); await context.Authentication.AuthenticateAsync(authContext);
Describe(res, authContext);
} }
else if (req.Path == new PathString("/testpath") && testpath != null) else if (req.Path == new PathString("/testpath") && testpath != null)
{ {
@ -595,7 +662,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
return server; return server;
} }
private static void Describe(HttpResponse res, AuthenticationResult result) private static void Describe(HttpResponse res, AuthenticateContext result)
{ {
res.StatusCode = 200; res.StatusCode = 200;
res.ContentType = "text/xml"; res.ContentType = "text/xml";
@ -606,7 +673,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
} }
if (result != null && result.Properties != null) if (result != null && result.Properties != null)
{ {
xml.Add(result.Properties.Items.Select(extra => new XElement("extra", new XAttribute("type", extra.Key), new XAttribute("value", extra.Value)))); xml.Add(result.Properties.Select(extra => new XElement("extra", new XAttribute("type", extra.Key), new XAttribute("value", extra.Value))));
} }
using (var memory = new MemoryStream()) using (var memory = new MemoryStream())
{ {

View File

@ -51,7 +51,8 @@ namespace Microsoft.AspNet.Authentication.Facebook
}, },
context => context =>
{ {
context.Authentication.Challenge("Facebook"); // REVIEW: Gross.
context.Authentication.ChallengeAsync("Facebook").GetAwaiter().GetResult();
return true; return true;
}); });
var transaction = await server.SendAsync("http://example.com/challenge"); var transaction = await server.SendAsync("http://example.com/challenge");
@ -88,7 +89,8 @@ namespace Microsoft.AspNet.Authentication.Facebook
}, },
context => context =>
{ {
context.Authentication.Challenge("Facebook"); // REVIEW: gross
context.Authentication.ChallengeAsync("Facebook").GetAwaiter().GetResult();
return true; return true;
}); });
var transaction = await server.SendAsync("http://example.com/challenge"); var transaction = await server.SendAsync("http://example.com/challenge");

View File

@ -46,6 +46,42 @@ namespace Microsoft.AspNet.Authentication.Google
location.ShouldNotContain("login_hint="); location.ShouldNotContain("login_hint=");
} }
[Fact]
public async Task SignInThrows()
{
var server = CreateServer(options =>
{
options.ClientId = "Test Id";
options.ClientSecret = "Test Secret";
});
var transaction = await server.SendAsync("https://example.com/signIn");
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
[Fact]
public async Task SignOutThrows()
{
var server = CreateServer(options =>
{
options.ClientId = "Test Id";
options.ClientSecret = "Test Secret";
});
var transaction = await server.SendAsync("https://example.com/signOut");
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
[Fact]
public async Task ForbidThrows()
{
var server = CreateServer(options =>
{
options.ClientId = "Test Id";
options.ClientSecret = "Test Secret";
});
var transaction = await server.SendAsync("https://example.com/signOut");
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
[Fact] [Fact]
public async Task Challenge401WillTriggerRedirection() public async Task Challenge401WillTriggerRedirection()
{ {
@ -134,7 +170,7 @@ namespace Microsoft.AspNet.Authentication.Google
var res = context.Response; var res = context.Response;
if (req.Path == new PathString("/challenge2")) if (req.Path == new PathString("/challenge2"))
{ {
context.Authentication.Challenge("Google", new AuthenticationProperties( return context.Authentication.ChallengeAsync("Google", new AuthenticationProperties(
new Dictionary<string, string>() new Dictionary<string, string>()
{ {
{ "scope", "https://www.googleapis.com/auth/plus.login" }, { "scope", "https://www.googleapis.com/auth/plus.login" },
@ -142,7 +178,6 @@ namespace Microsoft.AspNet.Authentication.Google
{ "approval_prompt", "force" }, { "approval_prompt", "force" },
{ "login_hint", "test@example.com" } { "login_hint", "test@example.com" }
})); }));
res.StatusCode = 401;
} }
return Task.FromResult<object>(null); return Task.FromResult<object>(null);
@ -177,35 +212,6 @@ namespace Microsoft.AspNet.Authentication.Google
query.ShouldContain("custom=test"); query.ShouldContain("custom=test");
} }
// TODO: Fix these tests to path (Need some test logic for Authenticate("Google") to return a ticket still
//[Fact]
//public async Task GoogleTurns401To403WhenAuthenticated()
//{
// TestServer server = CreateServer(options =>
// {
// options.ClientId = "Test Id";
// options.ClientSecret = "Test Secret";
// });
// Transaction transaction1 = await SendAsync(server, "http://example.com/unauthorized");
// transaction1.Response.StatusCode.ShouldBe(HttpStatusCode.Forbidden);
//}
//[Fact]
//public async Task GoogleTurns401To403WhenAutomatic()
//{
// TestServer server = CreateServer(options =>
// {
// options.ClientId = "Test Id";
// options.ClientSecret = "Test Secret";
// options.AutomaticAuthentication = true;
// });
// Debugger.Launch();
// Transaction transaction1 = await SendAsync(server, "http://example.com/unauthorizedAuto");
// transaction1.Response.StatusCode.ShouldBe(HttpStatusCode.Forbidden);
//}
[Fact] [Fact]
public async Task ReplyPathWithoutStateQueryStringWillBeRejected() public async Task ReplyPathWithoutStateQueryStringWillBeRejected()
{ {
@ -218,8 +224,6 @@ namespace Microsoft.AspNet.Authentication.Google
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.InternalServerError); transaction.Response.StatusCode.ShouldBe(HttpStatusCode.InternalServerError);
} }
[Theory] [Theory]
[InlineData(null)] [InlineData(null)]
[InlineData("CustomIssuer")] [InlineData("CustomIssuer")]
@ -459,24 +463,14 @@ namespace Microsoft.AspNet.Authentication.Google
options.AutomaticAuthentication = true; options.AutomaticAuthentication = true;
}); });
app.UseGoogleAuthentication(configureOptions); app.UseGoogleAuthentication(configureOptions);
app.UseClaimsTransformation(o => app.UseClaimsTransformation();
{
o.Transformation = p =>
{
var id = new ClaimsIdentity("xform");
id.AddClaim(new Claim("xform", "yup"));
p.AddIdentity(id);
return p;
};
});
app.Use(async (context, next) => app.Use(async (context, next) =>
{ {
var req = context.Request; var req = context.Request;
var res = context.Response; var res = context.Response;
if (req.Path == new PathString("/challenge")) if (req.Path == new PathString("/challenge"))
{ {
context.Authentication.Challenge("Google"); await context.Authentication.ChallengeAsync("Google");
res.StatusCode = 401;
} }
else if (req.Path == new PathString("/me")) else if (req.Path == new PathString("/me"))
{ {
@ -486,18 +480,29 @@ namespace Microsoft.AspNet.Authentication.Google
{ {
// Simulate Authorization failure // Simulate Authorization failure
var result = await context.Authentication.AuthenticateAsync("Google"); var result = await context.Authentication.AuthenticateAsync("Google");
context.Authentication.Challenge("Google"); await context.Authentication.ChallengeAsync("Google");
} }
else if (req.Path == new PathString("/unauthorizedAuto")) else if (req.Path == new PathString("/unauthorizedAuto"))
{ {
var result = await context.Authentication.AuthenticateAsync("Google"); var result = await context.Authentication.AuthenticateAsync("Google");
res.StatusCode = 401; await context.Authentication.ChallengeAsync();
context.Authentication.Challenge();
} }
else if (req.Path == new PathString("/401")) else if (req.Path == new PathString("/401"))
{ {
res.StatusCode = 401; res.StatusCode = 401;
} }
else if (req.Path == new PathString("/signIn"))
{
await Assert.ThrowsAsync<NotSupportedException>(() => context.Authentication.SignInAsync("Google", new ClaimsPrincipal()));
}
else if (req.Path == new PathString("/signOut"))
{
await Assert.ThrowsAsync<NotSupportedException>(() => context.Authentication.SignOutAsync("Google"));
}
else if (req.Path == new PathString("/forbid"))
{
await Assert.ThrowsAsync<NotSupportedException>(() => context.Authentication.ForbidAsync("Google"));
}
else if (testpath != null) else if (testpath != null)
{ {
await testpath(context); await testpath(context);
@ -515,8 +520,14 @@ namespace Microsoft.AspNet.Authentication.Google
{ {
options.SignInScheme = TestExtensions.CookieAuthenticationScheme; options.SignInScheme = TestExtensions.CookieAuthenticationScheme;
}); });
services.ConfigureClaimsTransformation(p =>
{
var id = new ClaimsIdentity("xform");
id.AddClaim(new Claim("xform", "yup"));
p.AddIdentity(id);
return p;
});
}); });
} }
} }
} }

View File

@ -45,6 +45,42 @@ namespace Microsoft.AspNet.Authentication.Tests.MicrosoftAccount
query.ShouldContain("custom=test"); query.ShouldContain("custom=test");
} }
[Fact]
public async Task SignInThrows()
{
var server = CreateServer(options =>
{
options.ClientId = "Test Id";
options.ClientSecret = "Test Secret";
});
var transaction = await server.SendAsync("https://example.com/signIn");
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
[Fact]
public async Task SignOutThrows()
{
var server = CreateServer(options =>
{
options.ClientId = "Test Id";
options.ClientSecret = "Test Secret";
});
var transaction = await server.SendAsync("https://example.com/signOut");
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
[Fact]
public async Task ForbidThrows()
{
var server = CreateServer(options =>
{
options.ClientId = "Test Id";
options.ClientSecret = "Test Secret";
});
var transaction = await server.SendAsync("https://example.com/signOut");
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
[Fact] [Fact]
public async Task ChallengeWillTriggerRedirection() public async Task ChallengeWillTriggerRedirection()
{ {
@ -155,13 +191,24 @@ namespace Microsoft.AspNet.Authentication.Tests.MicrosoftAccount
var res = context.Response; var res = context.Response;
if (req.Path == new PathString("/challenge")) if (req.Path == new PathString("/challenge"))
{ {
context.Authentication.Challenge("Microsoft"); await context.Authentication.ChallengeAsync("Microsoft");
res.StatusCode = 401;
} }
else if (req.Path == new PathString("/me")) else if (req.Path == new PathString("/me"))
{ {
res.Describe(context.User); res.Describe(context.User);
} }
else if (req.Path == new PathString("/signIn"))
{
await Assert.ThrowsAsync<NotSupportedException>(() => context.Authentication.SignInAsync("Microsoft", new ClaimsPrincipal()));
}
else if (req.Path == new PathString("/signOut"))
{
await Assert.ThrowsAsync<NotSupportedException>(() => context.Authentication.SignOutAsync("Microsoft"));
}
else if (req.Path == new PathString("/forbid"))
{
await Assert.ThrowsAsync<NotSupportedException>(() => context.Authentication.ForbidAsync("Microsoft"));
}
else else
{ {
await next(); await next();

View File

@ -40,6 +40,29 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
response.Response.StatusCode.ShouldBe(HttpStatusCode.OK); response.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
} }
[Fact]
public async Task SignInThrows()
{
var server = CreateServer(options =>
{
options.AutomaticAuthentication = true;
});
var transaction = await server.SendAsync("https://example.com/signIn");
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
[Fact]
public async Task SignOutThrows()
{
var server = CreateServer(options =>
{
options.AutomaticAuthentication = true;
});
var transaction = await server.SendAsync("https://example.com/signOut");
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
[Fact] [Fact]
public async Task CustomHeaderReceived() public async Task CustomHeaderReceived()
{ {
@ -326,9 +349,17 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
{ {
// Simulate Authorization failure // Simulate Authorization failure
var result = await context.Authentication.AuthenticateAsync(OAuthBearerAuthenticationDefaults.AuthenticationScheme); var result = await context.Authentication.AuthenticateAsync(OAuthBearerAuthenticationDefaults.AuthenticationScheme);
context.Authentication.Challenge(OAuthBearerAuthenticationDefaults.AuthenticationScheme); await context.Authentication.ChallengeAsync(OAuthBearerAuthenticationDefaults.AuthenticationScheme);
} }
else if (context.Request.Path == new PathString("/signIn"))
{
await Assert.ThrowsAsync<NotSupportedException>(() => context.Authentication.SignInAsync(OAuthBearerAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal()));
}
else if (context.Request.Path == new PathString("/signOut"))
{
await Assert.ThrowsAsync<NotSupportedException>(() => context.Authentication.SignOutAsync(OAuthBearerAuthenticationDefaults.AuthenticationScheme));
}
else else
{ {
await next(); await next();

View File

@ -549,26 +549,14 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
await base.BaseInitializeAsync(options, context, logger, encoder); await base.BaseInitializeAsync(options, context, logger, encoder);
} }
public override bool ShouldHandleScheme(string authenticationScheme) protected override async Task<bool> HandleUnauthorizedAsync(ChallengeContext context)
{
return true;
}
public override void Challenge(ChallengeContext context)
{
}
protected override void ApplyResponseChallenge()
{
}
protected override async Task ApplyResponseChallengeAsync()
{ {
var redirectToIdentityProviderNotification = new RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options) var redirectToIdentityProviderNotification = new RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
{ {
}; };
await Options.Notifications.RedirectToIdentityProvider(redirectToIdentityProviderNotification); await Options.Notifications.RedirectToIdentityProvider(redirectToIdentityProviderNotification);
return true;
} }
} }

View File

@ -196,7 +196,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
{ {
app.UseCookieAuthentication(options => app.UseCookieAuthentication(options =>
{ {
options.AuthenticationScheme = "OpenIdConnect"; options.AuthenticationScheme = OpenIdConnectAuthenticationDefaults.AuthenticationScheme;
}); });
app.UseOpenIdConnectAuthentication(configureOptions); app.UseOpenIdConnectAuthentication(configureOptions);
app.Use(async (context, next) => app.Use(async (context, next) =>
@ -205,21 +205,19 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
var res = context.Response; var res = context.Response;
if (req.Path == new PathString("/challenge")) if (req.Path == new PathString("/challenge"))
{ {
context.Authentication.Challenge("OpenIdConnect"); await context.Authentication.ChallengeAsync(OpenIdConnectAuthenticationDefaults.AuthenticationScheme);
res.StatusCode = 401;
} }
else if (req.Path == new PathString("/signin")) else if (req.Path == new PathString("/signin"))
{ {
// REVIEW: this used to just be res.SignIn() await context.Authentication.SignInAsync(OpenIdConnectAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal());
context.Authentication.SignIn("OpenIdConnect", new ClaimsPrincipal());
} }
else if (req.Path == new PathString("/signout")) else if (req.Path == new PathString("/signout"))
{ {
context.Authentication.SignOut(OpenIdConnectAuthenticationDefaults.AuthenticationScheme); await context.Authentication.SignOutAsync(OpenIdConnectAuthenticationDefaults.AuthenticationScheme);
} }
else if (req.Path == new PathString("/signout_with_specific_redirect_uri")) else if (req.Path == new PathString("/signout_with_specific_redirect_uri"))
{ {
context.Authentication.SignOut( await context.Authentication.SignOutAsync(
OpenIdConnectAuthenticationDefaults.AuthenticationScheme, OpenIdConnectAuthenticationDefaults.AuthenticationScheme,
new AuthenticationProperties() { RedirectUri = "http://www.example.com/specific_redirect_uri" }); new AuthenticationProperties() { RedirectUri = "http://www.example.com/specific_redirect_uri" });
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Security.Claims;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Builder;
@ -19,8 +20,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
[Fact] [Fact]
public async Task ChallengeWillTriggerApplyRedirectEvent() public async Task ChallengeWillTriggerApplyRedirectEvent()
{ {
var server = CreateServer( var server = CreateServer(options =>
app => app.UseTwitterAuthentication(options =>
{ {
options.ConsumerKey = "Test Consumer Key"; options.ConsumerKey = "Test Consumer Key";
options.ConsumerSecret = "Test Consumer Secret"; options.ConsumerSecret = "Test Consumer Secret";
@ -49,10 +49,11 @@ namespace Microsoft.AspNet.Authentication.Twitter
} }
}; };
options.BackchannelCertificateValidator = null; options.BackchannelCertificateValidator = null;
}), },
context => context =>
{ {
context.Authentication.Challenge("Twitter"); // REVIEW: Gross
context.Authentication.ChallengeAsync("Twitter").GetAwaiter().GetResult();
return true; return true;
}); });
var transaction = await server.SendAsync("http://example.com/challenge"); var transaction = await server.SendAsync("http://example.com/challenge");
@ -61,11 +62,47 @@ namespace Microsoft.AspNet.Authentication.Twitter
query.ShouldContain("custom=test"); query.ShouldContain("custom=test");
} }
[Fact]
public async Task SignInThrows()
{
var server = CreateServer(options =>
{
options.ConsumerKey = "Test Consumer Key";
options.ConsumerSecret = "Test Consumer Secret";
});
var transaction = await server.SendAsync("https://example.com/signIn");
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
[Fact]
public async Task SignOutThrows()
{
var server = CreateServer(options =>
{
options.ConsumerKey = "Test Consumer Key";
options.ConsumerSecret = "Test Consumer Secret";
});
var transaction = await server.SendAsync("https://example.com/signOut");
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
[Fact]
public async Task ForbidThrows()
{
var server = CreateServer(options =>
{
options.ConsumerKey = "Test Consumer Key";
options.ConsumerSecret = "Test Consumer Secret";
});
var transaction = await server.SendAsync("https://example.com/signOut");
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
[Fact] [Fact]
public async Task ChallengeWillTriggerRedirection() public async Task ChallengeWillTriggerRedirection()
{ {
var server = CreateServer( var server = CreateServer(options =>
app => app.UseTwitterAuthentication(options =>
{ {
options.ConsumerKey = "Test Consumer Key"; options.ConsumerKey = "Test Consumer Key";
options.ConsumerSecret = "Test Consumer Secret"; options.ConsumerSecret = "Test Consumer Secret";
@ -87,10 +124,11 @@ namespace Microsoft.AspNet.Authentication.Twitter
} }
}; };
options.BackchannelCertificateValidator = null; options.BackchannelCertificateValidator = null;
}), },
context => context =>
{ {
context.Authentication.Challenge("Twitter"); // REVIEW: gross
context.Authentication.ChallengeAsync("Twitter").GetAwaiter().GetResult();
return true; return true;
}); });
var transaction = await server.SendAsync("http://example.com/challenge"); var transaction = await server.SendAsync("http://example.com/challenge");
@ -99,7 +137,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
location.ShouldContain("https://twitter.com/oauth/authenticate?oauth_token="); location.ShouldContain("https://twitter.com/oauth/authenticate?oauth_token=");
} }
private static TestServer CreateServer(Action<IApplicationBuilder> configure, Func<HttpContext, bool> handler) private static TestServer CreateServer(Action<TwitterAuthenticationOptions> configure, Func<HttpContext, bool> handler = null)
{ {
return TestServer.Create(app => return TestServer.Create(app =>
{ {
@ -107,13 +145,24 @@ namespace Microsoft.AspNet.Authentication.Twitter
{ {
options.AuthenticationScheme = "External"; options.AuthenticationScheme = "External";
}); });
if (configure != null) app.UseTwitterAuthentication(configure);
{
configure(app);
}
app.Use(async (context, next) => app.Use(async (context, next) =>
{ {
if (handler == null || !handler(context)) var req = context.Request;
var res = context.Response;
if (req.Path == new PathString("/signIn"))
{
await Assert.ThrowsAsync<NotSupportedException>(() => context.Authentication.SignInAsync("Twitter", new ClaimsPrincipal()));
}
else if (req.Path == new PathString("/signOut"))
{
await Assert.ThrowsAsync<NotSupportedException>(() => context.Authentication.SignOutAsync("Twitter"));
}
else if (req.Path == new PathString("/forbid"))
{
await Assert.ThrowsAsync<NotSupportedException>(() => context.Authentication.ForbidAsync("Twitter"));
}
else if (handler == null || !handler(context))
{ {
await next(); await next();
} }

View File

@ -582,7 +582,7 @@ namespace Microsoft.AspNet.Authorization.Test
public class CustomRequirement : IAuthorizationRequirement { } public class CustomRequirement : IAuthorizationRequirement { }
public class CustomHandler : AuthorizationHandler<CustomRequirement> public class CustomHandler : AuthorizationHandler<CustomRequirement>
{ {
public override void Handle(AuthorizationContext context, CustomRequirement requirement) protected override void Handle(AuthorizationContext context, CustomRequirement requirement)
{ {
context.Succeed(requirement); context.Succeed(requirement);
} }
@ -638,7 +638,7 @@ namespace Microsoft.AspNet.Authorization.Test
public bool Succeed { get; set; } public bool Succeed { get; set; }
public override void Handle(AuthorizationContext context, PassThroughRequirement requirement) protected override void Handle(AuthorizationContext context, PassThroughRequirement requirement)
{ {
if (Succeed) { if (Succeed) {
context.Succeed(requirement); context.Succeed(requirement);
@ -668,6 +668,7 @@ namespace Microsoft.AspNet.Authorization.Test
Assert.Equal(shouldSucceed, allowed); Assert.Equal(shouldSucceed, allowed);
} }
[Fact]
public async Task CanCombinePolicies() public async Task CanCombinePolicies()
{ {
// Arrange // Arrange
@ -695,6 +696,7 @@ namespace Microsoft.AspNet.Authorization.Test
Assert.True(allowed); Assert.True(allowed);
} }
[Fact]
public async Task CombinePoliciesWillFailIfBasePolicyFails() public async Task CombinePoliciesWillFailIfBasePolicyFails()
{ {
// Arrange // Arrange
@ -721,6 +723,7 @@ namespace Microsoft.AspNet.Authorization.Test
Assert.False(allowed); Assert.False(allowed);
} }
[Fact]
public async Task CombinedPoliciesWillFailIfExtraRequirementFails() public async Task CombinedPoliciesWillFailIfExtraRequirementFails()
{ {
// Arrange // Arrange
@ -765,7 +768,7 @@ namespace Microsoft.AspNet.Authorization.Test
private IEnumerable<OperationAuthorizationRequirement> _allowed; private IEnumerable<OperationAuthorizationRequirement> _allowed;
public override void Handle(AuthorizationContext context, OperationAuthorizationRequirement requirement, ExpenseReport resource) protected override void Handle(AuthorizationContext context, OperationAuthorizationRequirement requirement, ExpenseReport resource)
{ {
if (_allowed.Contains(requirement)) if (_allowed.Contains(requirement))
{ {
@ -776,7 +779,7 @@ namespace Microsoft.AspNet.Authorization.Test
public class SuperUserHandler : AuthorizationHandler<OperationAuthorizationRequirement> public class SuperUserHandler : AuthorizationHandler<OperationAuthorizationRequirement>
{ {
public override void Handle(AuthorizationContext context, OperationAuthorizationRequirement requirement) protected override void Handle(AuthorizationContext context, OperationAuthorizationRequirement requirement)
{ {
if (context.User.HasClaim("SuperUser", "yes")) if (context.User.HasClaim("SuperUser", "yes"))
{ {
@ -785,6 +788,7 @@ namespace Microsoft.AspNet.Authorization.Test
} }
} }
[Fact]
public async Task CanAuthorizeAllSuperuserOperations() public async Task CanAuthorizeAllSuperuserOperations()
{ {
// Arrange // Arrange
@ -808,6 +812,7 @@ namespace Microsoft.AspNet.Authorization.Test
Assert.True(await authorizationService.AuthorizeAsync(user, null, Operations.Create)); Assert.True(await authorizationService.AuthorizeAsync(user, null, Operations.Create));
} }
[Fact]
public async Task CanAuthorizeOnlyAllowedOperations() public async Task CanAuthorizeOnlyAllowedOperations()
{ {
// Arrange // Arrange
@ -824,5 +829,24 @@ namespace Microsoft.AspNet.Authorization.Test
Assert.False(await authorizationService.AuthorizeAsync(user, null, Operations.Delete)); Assert.False(await authorizationService.AuthorizeAsync(user, null, Operations.Delete));
Assert.False(await authorizationService.AuthorizeAsync(user, null, Operations.Create)); Assert.False(await authorizationService.AuthorizeAsync(user, null, Operations.Create));
} }
[Fact]
public void CanAuthorizeWithDelegateRequirement()
{
var authorizationService = BuildAuthorizationService(services =>
{
services.ConfigureAuthorization(options =>
{
options.AddPolicy("Basic", policy => policy.RequireDelegate((context, req) => context.Succeed(req)));
});
});
var user = new ClaimsPrincipal();
// Act
var allowed = authorizationService.Authorize(user, "Basic");
// Assert
Assert.True(allowed);
}
} }
} }