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))
{
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";
await context.Response.WriteAsync("Hello First timer");
return;

View File

@ -36,7 +36,7 @@ namespace CookieSessionSample
{
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";
await context.Response.WriteAsync("Hello First timer");
return;

View File

@ -40,7 +40,7 @@ namespace OpenIdConnectSample
{
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";
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),
// send them to the home page instead (/).
context.Authentication.Challenge(authType, new AuthenticationProperties() { RedirectUri = "/" });
await context.Authentication.ChallengeAsync(authType, new AuthenticationProperties() { RedirectUri = "/" });
return;
}
@ -207,7 +207,7 @@ namespace CookieSample
{
signoutApp.Run(async context =>
{
context.Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationScheme);
await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
context.Response.ContentType = "text/html";
await context.Response.WriteAsync("<html><body>");
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))
{
// The cookie middleware will intercept this 401 and redirect to /login
context.Authentication.Challenge();
await context.Authentication.ChallengeAsync();
return;
}
await next();

View File

@ -8,6 +8,8 @@ using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Authentication.Cookies
@ -26,12 +28,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
private DateTimeOffset _renewExpiresUtc;
private string _sessionKey;
protected override AuthenticationTicket AuthenticateCore()
{
return AuthenticateCoreAsync().GetAwaiter().GetResult();
}
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
public override async Task<AuthenticationTicket> AuthenticateAsync()
{
AuthenticationTicket ticket = null;
try
@ -99,7 +96,6 @@ namespace Microsoft.AspNet.Authentication.Cookies
await Options.Notifications.ValidatePrincipal(context);
AuthenticateCalled = true;
return new AuthenticationTicket(context.Principal, context.Properties, Options.AuthenticationScheme);
}
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 shouldSignin = signin != null;
var signout = SignOutContext;
var shouldSignout = signout != null;
var cookieOptions = BuildCookieOptions();
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;
}
@ -135,133 +184,89 @@ namespace Microsoft.AspNet.Authentication.Cookies
var model = await AuthenticateAsync();
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,
HttpOnly = Options.CookieHttpOnly,
Path = Options.CookiePath ?? (RequestPathBase.HasValue ? RequestPathBase.ToString() : "/"),
};
if (Options.CookieSecure == CookieSecureOption.SameAsRequest)
throw;
}
}
}
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
{
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(
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);
signInContext.Properties.ExpiresUtc = issuedUtc.Add(Options.ExpireTimeSpan);
}
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);
}
var context = new CookieResponseSignOutContext(
Context,
Options,
cookieOptions);
Options.Notifications.ResponseSignOut(context);
Options.CookieManager.DeleteCookie(
Context,
Options.CookieName,
context.CookieOptions);
_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);
}
else if (_shouldRenew)
{
model.Properties.IssuedUtc = _renewIssuedUtc;
model.Properties.ExpiresUtc = _renewExpiresUtc;
var cookieValue = Options.TicketDataFormat.Protect(model);
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);
}
Options.CookieManager.AppendResponseCookie(
Context,
Options.CookieName,
cookieValue,
signInContext.CookieOptions);
var cookieValue = Options.TicketDataFormat.Protect(model);
var signedInContext = new CookieResponseSignedInContext(
Context,
Options,
Options.AuthenticationScheme,
signInContext.Principal,
signInContext.Properties);
if (model.Properties.IsPersistent)
{
cookieOptions.Expires = _renewExpiresUtc.ToUniversalTime().DateTime;
}
Options.CookieManager.AppendResponseCookie(
Context,
Options.CookieName,
cookieValue,
cookieOptions);
}
Options.Notifications.ResponseSignedIn(signedInContext);
Response.Headers.Set(
HeaderNameCacheControl,
@ -275,10 +280,9 @@ namespace Microsoft.AspNet.Authentication.Cookies
HeaderNameExpires,
HeaderValueMinusOne);
var shouldLoginRedirect = shouldSignin && Options.LoginPath.HasValue && Request.Path == Options.LoginPath;
var shouldLogoutRedirect = shouldSignout && Options.LogoutPath.HasValue && Request.Path == Options.LogoutPath;
var shouldLoginRedirect = Options.LoginPath.HasValue && Request.Path == Options.LoginPath;
if ((shouldLoginRedirect || shouldLogoutRedirect) && Response.StatusCode == 200)
if ((shouldLoginRedirect) && Response.StatusCode == 200)
{
var query = Request.Query;
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)
{
if (string.IsNullOrEmpty(path))
@ -315,60 +382,49 @@ namespace Microsoft.AspNet.Authentication.Cookies
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
if (Options.AccessDeniedPath.HasValue)
try
{
try
{
var accessDeniedUri =
Request.Scheme +
"://" +
Request.Host +
Request.PathBase +
Options.AccessDeniedPath;
var accessDeniedUri =
Request.Scheme +
"://" +
Request.Host +
Request.PathBase +
Options.AccessDeniedPath;
var redirectContext = new CookieApplyRedirectContext(Context, Options, accessDeniedUri);
Options.Notifications.ApplyRedirect(redirectContext);
}
catch (Exception exception)
var redirectContext = new CookieApplyRedirectContext(Context, Options, accessDeniedUri);
Options.Notifications.ApplyRedirect(redirectContext);
}
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,
CookieExceptionContext.ExceptionLocation.ApplyResponseChallenge, exception, ticket: null);
Options.Notifications.Exception(exceptionContext);
if (exceptionContext.Rethrow)
{
throw;
}
throw;
}
}
else
{
Response.StatusCode = 403;
}
return;
return Task.FromResult(true);
}
if (Response.StatusCode != 401 || !Options.LoginPath.HasValue )
else
{
return;
return base.HandleForbiddenAsync(context);
}
}
// Automatic middleware should redirect on 401 even if there wasn't an explicit challenge.
if (ChallengeContext == null && !Options.AutomaticAuthentication)
protected override Task<bool> HandleUnauthorizedAsync([NotNull] ChallengeContext context)
{
if (!Options.LoginPath.HasValue)
{
return;
}
var redirectUri = string.Empty;
if (ChallengeContext != null)
{
redirectUri = new AuthenticationProperties(ChallengeContext.Properties).RedirectUri;
return base.HandleUnauthorizedAsync(context);
}
var redirectUri = new AuthenticationProperties(context.Properties).RedirectUri;
try
{
if (string.IsNullOrWhiteSpace(redirectUri))
@ -400,6 +456,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
throw;
}
}
return Task.FromResult(true);
}
}
}

View File

@ -6,10 +6,11 @@ using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.Http.Extensions;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.AspNet.WebUtilities;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging;
using Newtonsoft.Json.Linq;
@ -37,7 +38,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
public async Task<bool> InvokeReturnPathAsync()
{
AuthenticationTicket ticket = await AuthenticateAsync();
var ticket = await AuthenticateAsync();
if (ticket == null)
{
Logger.LogWarning("Invalid return state, unable to redirect.");
@ -56,7 +57,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
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)
@ -73,17 +74,12 @@ namespace Microsoft.AspNet.Authentication.OAuth
return context.IsRequestCompleted;
}
protected override AuthenticationTicket AuthenticateCore()
{
return AuthenticateCoreAsync().GetAwaiter().GetResult();
}
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
public override async Task<AuthenticationTicket> AuthenticateAsync()
{
AuthenticationProperties properties = null;
try
{
IReadableStringCollection query = Request.Query;
var query = Request.Query;
// TODO: Is this a standard error returned by servers?
var value = query.Get("error");
@ -94,8 +90,8 @@ namespace Microsoft.AspNet.Authentication.OAuth
return null;
}
string code = query.Get("code");
string state = query.Get("state");
var code = query.Get("code");
var state = query.Get("state");
properties = Options.StateDataFormat.Unprotect(state);
if (properties == null)
@ -115,8 +111,8 @@ namespace Microsoft.AspNet.Authentication.OAuth
return new AuthenticationTicket(properties, Options.AuthenticationScheme);
}
string requestPrefix = Request.Scheme + "://" + Request.Host;
string redirectUri = requestPrefix + RequestPathBase + Options.CallbackPath;
var requestPrefix = Request.Scheme + "://" + Request.Host;
var redirectUri = requestPrefix + RequestPathBase + Options.CallbackPath;
var tokens = await ExchangeCodeAsync(code, redirectUri);
@ -151,11 +147,11 @@ namespace Microsoft.AspNet.Authentication.OAuth
var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint);
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
requestMessage.Content = requestContent;
HttpResponseMessage response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted);
var response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted);
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);
}
@ -169,40 +165,13 @@ namespace Microsoft.AspNet.Authentication.OAuth
return new AuthenticationTicket(context.Principal, context.Properties, Options.AuthenticationScheme);
}
protected override void ApplyResponseChallenge()
protected override Task<bool> HandleUnauthorizedAsync([NotNull] ChallengeContext context)
{
if (ShouldConvertChallengeToForbidden())
{
Response.StatusCode = 403;
return;
}
var baseUri = Request.Scheme + "://" + Request.Host + Request.PathBase;
var currentUri = baseUri + Request.Path + Request.QueryString;
var redirectUri = baseUri + Options.CallbackPath;
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;
}
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);
}
var properties = new AuthenticationProperties(context.Properties);
if (string.IsNullOrEmpty(properties.RedirectUri))
{
properties.RedirectUri = currentUri;
@ -211,19 +180,35 @@ namespace Microsoft.AspNet.Authentication.OAuth
// OAuth2 10.12 CSRF
GenerateCorrelationId(properties);
string authorizationEndpoint = BuildChallengeUrl(properties, redirectUri);
var authorizationEndpoint = BuildChallengeUrl(properties, redirectUri);
var redirectContext = new OAuthApplyRedirectContext(
Context, Options,
properties, authorizationEndpoint);
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)
{
string scope = FormatScope();
var scope = FormatScope();
string state = Options.StateDataFormat.Protect(properties);
var state = Options.StateDataFormat.Protect(properties);
var queryBuilder = new QueryBuilder()
{
@ -241,10 +226,5 @@ namespace Microsoft.AspNet.Authentication.OAuth
// OAuth2 3.3 space separated
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.
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Authentication.Notifications;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.Framework.Logging;
using Microsoft.IdentityModel.Protocols;
@ -19,16 +18,11 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
{
private OpenIdConnectConfiguration _configuration;
protected override AuthenticationTicket AuthenticateCore()
{
return AuthenticateCoreAsync().GetAwaiter().GetResult();
}
/// <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.
/// </summary>
/// <returns></returns>
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
public override async Task<AuthenticationTicket> AuthenticateAsync()
{
string token = null;
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();
}
protected override async Task ApplyResponseChallengeAsync()
{
if (ShouldConvertChallengeToForbidden())
{
Response.StatusCode = 403;
return;
}
if ((Response.StatusCode != 401) || (ChallengeContext == null && !Options.AutomaticAuthentication))
{
return;
}
Response.StatusCode = 401;
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.Http;
using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging;
using Microsoft.IdentityModel.Protocols;
@ -38,18 +40,12 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
}
protected override void ApplyResponseGrant()
{
ApplyResponseGrantAsync().GetAwaiter().GetResult();
}
/// <summary>
/// Handles Signout
/// </summary>
/// <returns></returns>
protected override async Task ApplyResponseGrantAsync()
protected override async Task HandleSignOutAsync(SignOutContext signout)
{
var signout = SignOutContext;
if (signout != null)
{
if (_configuration == null && Options.ConfigurationManager != null)
@ -96,52 +92,19 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
}
protected override void ApplyResponseChallenge()
{
ApplyResponseChallengeAsync().GetAwaiter().GetResult();
}
/// <summary>
/// Responds to a 401 Challenge. Sends an OpenIdConnect message to the 'identity authority' to obtain an identity.
/// </summary>
/// <returns></returns>
/// <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());
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
// 1. challenge.Properties.RedirectUri
// 2. CurrentUri if Options.DefaultToCurrentUriOnRedirect is true)
AuthenticationProperties properties;
if (ChallengeContext == null)
{
properties = new AuthenticationProperties();
}
else
{
properties = new AuthenticationProperties(ChallengeContext.Properties);
}
AuthenticationProperties properties = new AuthenticationProperties(context.Properties);
if (!string.IsNullOrWhiteSpace(properties.RedirectUri))
{
@ -209,12 +172,12 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
if (redirectToIdentityProviderNotification.HandledResponse)
{
Logger.LogInformation(Resources.OIDCH_0034_RedirectToIdentityProviderNotificationHandledResponse);
return;
return true; // REVIEW: Make sure this should stop all other handlers
}
else if (redirectToIdentityProviderNotification.Skipped)
{
Logger.LogInformation(Resources.OIDCH_0035_RedirectToIdentityProviderNotificationSkipped);
return;
return false; // REVIEW: Make sure this should not stop all other handlers
}
var redirectUri = redirectToIdentityProviderNotification.ProtocolMessage.CreateAuthenticationRequestUrl();
@ -224,11 +187,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
Response.Redirect(redirectUri);
}
protected override AuthenticationTicket AuthenticateCore()
{
return AuthenticateCoreAsync().GetAwaiter().GetResult();
return true;
}
/// <summary>
@ -236,7 +195,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// </summary>
/// <returns>An <see cref="AuthenticationTicket"/> if successful.</returns>
/// <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());
@ -632,7 +591,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
{
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.

View File

@ -12,8 +12,10 @@ using System.Threading.Tasks;
using Microsoft.AspNet.Authentication.Twitter.Messages;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.WebUtilities;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Authentication.Twitter
@ -42,12 +44,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
return false;
}
protected override AuthenticationTicket AuthenticateCore()
{
return AuthenticateCoreAsync().GetAwaiter().GetResult();
}
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
public override async Task<AuthenticationTicket> AuthenticateAsync()
{
AuthenticationProperties properties = null;
try
@ -121,49 +118,18 @@ namespace Microsoft.AspNet.Authentication.Twitter
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 callBackUrl = requestPrefix + RequestPathBase + Options.CallbackPath;
AuthenticationProperties properties;
if (ChallengeContext == null)
{
properties = new AuthenticationProperties();
}
else
{
properties = new AuthenticationProperties(ChallengeContext.Properties);
}
var properties = new AuthenticationProperties(context.Properties);
if (string.IsNullOrEmpty(properties.RedirectUri))
{
properties.RedirectUri = requestPrefix + Request.PathBase + Request.Path + Request.QueryString;
}
var requestToken = await ObtainRequestTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, callBackUrl, properties);
if (requestToken.CallbackConfirmed)
{
var twitterAuthenticationEndpoint = AuthenticationEndpoint + requestToken.Token;
@ -180,11 +146,13 @@ namespace Microsoft.AspNet.Authentication.Twitter
Context, Options,
properties, twitterAuthenticationEndpoint);
Options.Notifications.ApplyRedirect(redirectContext);
return true;
}
else
{
Logger.LogError("requestToken CallbackConfirmed!=true");
}
return false; // REVIEW: Make sure this should not stop other handlers
}
public async Task<bool> InvokeReturnPathAsync()
@ -208,7 +176,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
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)
@ -225,6 +193,21 @@ namespace Microsoft.AspNet.Authentication.Twitter
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)
{
Logger.LogVerbose("ObtainRequestToken");
@ -380,10 +363,5 @@ namespace Microsoft.AspNet.Authentication.Twitter
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.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Authentication.DataHandler.Encoder;
using Microsoft.AspNet.Http;
@ -22,19 +21,12 @@ namespace Microsoft.AspNet.Authentication
{
private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create();
private Task<AuthenticationTicket> _authenticate;
private bool _authenticateInitialized;
private object _authenticateSyncLock;
private Task _applyResponse;
private bool _applyResponseInitialized;
private object _applyResponseSyncLock;
private bool _finishCalled;
private AuthenticationOptions _baseOptions;
protected ChallengeContext ChallengeContext { get; set; }
protected SignInContext SignInContext { get; set; }
protected SignOutContext SignOutContext { get; set; }
protected bool SignInAccepted { get; set; }
protected bool SignOutAccepted { get; set; }
protected bool ChallengeCalled { get; set; }
protected HttpContext Context { get; private set; }
@ -59,13 +51,8 @@ namespace Microsoft.AspNet.Authentication
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 bool Faulted { get; set; }
protected async Task BaseInitializeAsync([NotNull] AuthenticationOptions options, [NotNull] HttpContext context, [NotNull] ILogger logger, [NotNull] IUrlEncoder encoder)
{
_baseOptions = options;
@ -76,9 +63,7 @@ namespace Microsoft.AspNet.Authentication
RegisterAuthenticationHandler();
Response.OnResponseStarting(OnSendingHeaderCallback, this);
await InitializeCoreAsync();
Response.OnStarting(OnStartingCallback, this);
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;
handler.ApplyResponse();
var handler = (AuthenticationHandler)state;
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);
}
private async Task HandleAutomaticChallengeIfNeeded()
{
if (!ChallengeCalled && BaseOptions.AutomaticAuthentication && Response.StatusCode == 401)
{
await HandleUnauthorizedAsync(new ChallengeContext(BaseOptions.AuthenticationScheme));
}
}
/// <summary>
/// Called once per request after Initialize and Invoke.
/// Called once after Invoke by AuthenticationMiddleware.
/// </summary>
/// <returns>async completion</returns>
internal async Task TeardownAsync()
{
try
{
await ApplyResponseAsync();
}
catch (Exception)
{
try
{
await TeardownCoreAsync();
}
catch (Exception)
{
// Don't mask the original exception
}
UnregisterAuthenticationHandler();
throw;
}
await TeardownCoreAsync();
await FinishResponseOnce();
UnregisterAuthenticationHandler();
}
protected virtual Task TeardownCoreAsync()
{
return Task.FromResult(0);
}
/// <summary>
/// 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,
@ -147,7 +131,7 @@ namespace Microsoft.AspNet.Authentication
return Task.FromResult(false);
}
public virtual void GetDescriptions(DescribeSchemesContext describeContext)
public void GetDescriptions(DescribeSchemesContext describeContext)
{
describeContext.Accept(BaseOptions.Description.Items);
@ -157,36 +141,13 @@ namespace Microsoft.AspNet.Authentication
}
}
public virtual void Authenticate(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)
public async Task AuthenticateAsync(AuthenticateContext context)
{
if (ShouldHandleScheme(context.AuthenticationScheme))
{
var ticket = await AuthenticateAsync();
if (ticket?.Principal != null)
{
AuthenticateCalled = true;
context.Authenticated(ticket.Principal, ticket.Properties.Items, BaseOptions.Description.Items);
}
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>
/// Causes the authentication logic in AuthenticateCore to be performed for the current request
/// 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.
/// Calling Authenticate more than once should always return the original value.
/// </summary>
/// <returns>The ticket data provided by the authentication logic</returns>
public Task<AuthenticationTicket> AuthenticateAsync()
{
return LazyInitializer.EnsureInitialized(
ref _authenticate,
ref _authenticateInitialized,
ref _authenticateSyncLock,
AuthenticateCoreAsync);
}
public abstract Task<AuthenticationTicket> AuthenticateAsync();
/// <summary>
/// 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)
public bool ShouldHandleScheme(string authenticationScheme)
{
return string.Equals(BaseOptions.AuthenticationScheme, authenticationScheme, StringComparison.Ordinal) ||
(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
// and the challenge is for the authentication type
return Response.StatusCode == 401 &&
AuthenticateCalled &&
ChallengeContext != null &&
ShouldHandleScheme(ChallengeContext.AuthenticationScheme);
if (ShouldHandleScheme(context.AuthenticationScheme))
{
SignInAccepted = true;
await HandleSignInAsync(context);
context.Accept();
}
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>
@ -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
/// changing the 401 result to 302 of a login page or external sign-in location.)
/// </summary>
/// <returns></returns>
protected virtual Task ApplyResponseChallengeAsync()
/// <param name="context"></param>
/// <returns>True if no other handlers should be called</returns>
protected virtual Task<bool> HandleUnauthorizedAsync(ChallengeContext context)
{
ApplyResponseChallenge();
return Task.FromResult(0);
Response.StatusCode = 401;
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)

View File

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

View File

@ -3,6 +3,7 @@
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Authentication;
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)
{
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.
// 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 Microsoft.AspNet.Http.Features.Authentication;
@ -13,56 +11,37 @@ namespace Microsoft.AspNet.Authentication
/// </summary>
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;
}
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)
{
if (PriorHandler != null)
{
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)
{
PriorHandler.Challenge(context);
return PriorHandler.ChallengeAsync(context);
}
return Task.FromResult(0);
}
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)
{
PriorHandler.SignIn(context);
return PriorHandler.SignInAsync(context);
}
return Task.FromResult(0);
}
public void SignOut(SignOutContext context)
public Task SignOutAsync(SignOutContext context)
{
if (PriorHandler != null)
{
PriorHandler.SignOut(context);
return PriorHandler.SignOutAsync(context);
}
return Task.FromResult(0);
}
public void RegisterAuthenticationHandler(IHttpAuthenticationFeature auth)

View File

@ -34,12 +34,12 @@ namespace Microsoft.AspNet.Authentication
public async Task Invoke(HttpContext context)
{
var handler = new ClaimsTransformationAuthenticationHandler(Options.Transformation);
var handler = new ClaimsTransformationAuthenticationHandler(Options.Transformer);
handler.RegisterAuthenticationHandler(context.GetAuthentication());
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);
}

View File

@ -3,11 +3,42 @@
using System;
using System.Security.Claims;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Authentication
{
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
{
/// <summary>
/// Contains authorization information used by <see cref="IAuthorizationPolicyHandler"/>.
/// Contains authorization information used by <see cref="IAuthorizationHandler"/>.
/// </summary>
public class AuthorizationContext
{
@ -28,9 +28,9 @@ namespace Microsoft.AspNet.Authorization
_pendingRequirements = new HashSet<IAuthorizationRequirement>(requirements);
}
public IEnumerable<IAuthorizationRequirement> Requirements { get; private set; }
public ClaimsPrincipal User { get; private set; }
public object Resource { get; private set; }
public IEnumerable<IAuthorizationRequirement> Requirements { get; }
public ClaimsPrincipal User { get; }
public object Resource { get; }
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);
return Task.FromResult(0);
foreach (var req in context.Requirements.OfType<TRequirement>())
{
await HandleAsync(context, req);
}
}
// REVIEW: do we need an async hook too?
public abstract void Handle(AuthorizationContext context, TRequirement requirement);
protected 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
@ -34,17 +41,13 @@ namespace Microsoft.AspNet.Authorization
public virtual async Task HandleAsync(AuthorizationContext context)
{
var resource = context.Resource as TResource;
// REVIEW: should we allow null resources?
if (resource != null)
foreach (var req in context.Requirements.OfType<TRequirement>())
{
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);
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
{
// TODO: make this case insensitive
private IDictionary<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>();
private IDictionary<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>(StringComparer.OrdinalIgnoreCase);
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();
}
public IReadOnlyList<IAuthorizationRequirement> Requirements { get; private set; }
public IReadOnlyList<string> ActiveAuthenticationSchemes { get; private set; }
public IReadOnlyList<IAuthorizationRequirement> Requirements { get; }
public IReadOnlyList<string> ActiveAuthenticationSchemes { get; }
public static AuthorizationPolicy Combine([NotNull] params AuthorizationPolicy[] policies)
{
return Combine((IEnumerable<AuthorizationPolicy>)policies);
}
// TODO: Add unit tests
public static AuthorizationPolicy Combine([NotNull] IEnumerable<AuthorizationPolicy> policies)
{
var builder = new AuthorizationPolicyBuilder();

View File

@ -1,9 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Authorization
@ -88,6 +88,12 @@ namespace Microsoft.AspNet.Authorization
return this;
}
public AuthorizationPolicyBuilder RequireDelegate([NotNull] Action<AuthorizationContext, DelegateRequirement> handler)
{
Requirements.Add(new DelegateRequirement(handler));
return this;
}
public AuthorizationPolicy Build()
{
return new AuthorizationPolicy(Requirements, ActiveAuthenticationSchemes.Distinct());

View File

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

View File

@ -21,7 +21,7 @@ namespace Microsoft.AspNet.Authorization
public string ClaimType { 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)
{

View File

@ -35,6 +35,7 @@ namespace Microsoft.AspNet.Authorization
foreach (var handler in _handlers)
{
handler.Handle(authContext);
//REVIEW: Do we want to consider short circuiting on failure
}
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 override void Handle(AuthorizationContext context, DenyAnonymousAuthorizationRequirement requirement)
protected override void Handle(AuthorizationContext context, DenyAnonymousAuthorizationRequirement requirement)
{
var user = context.User;
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 override void Handle(AuthorizationContext context, NameAuthorizationRequirement requirement)
protected override void Handle(AuthorizationContext context, NameAuthorizationRequirement requirement)
{
if (context.User != null)
{

View File

@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Authorization
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)
{

View File

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.AspNet.Http.Internal;
using Microsoft.Framework.Logging;
using Xunit;
@ -59,17 +61,7 @@ namespace Microsoft.AspNet.Authentication
Options.AuthenticationScheme = scheme;
}
protected override void ApplyResponseChallenge()
{
throw new NotImplementedException();
}
protected override void ApplyResponseGrant()
{
throw new NotImplementedException();
}
protected override AuthenticationTicket AuthenticateCore()
public override Task<AuthenticationTicket> AuthenticateAsync()
{
throw new NotImplementedException();
}
@ -94,17 +86,7 @@ namespace Microsoft.AspNet.Authentication
Options.AutomaticAuthentication = auto;
}
protected override void ApplyResponseChallenge()
{
throw new NotImplementedException();
}
protected override void ApplyResponseGrant()
{
throw new NotImplementedException();
}
protected override AuthenticationTicket AuthenticateCore()
public override Task<AuthenticationTicket> AuthenticateAsync()
{
throw new NotImplementedException();
}

View File

@ -15,6 +15,7 @@ using System.Xml.Linq;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.AspNet.TestHost;
using Microsoft.Framework.DependencyInjection;
using Shouldly;
@ -50,7 +51,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
transaction.Response.StatusCode.ShouldBe(auto ? HttpStatusCode.Redirect : HttpStatusCode.Unauthorized);
if (auto)
{
Uri location = transaction.Response.Headers.Location;
var location = transaction.Response.Headers.Location;
location.LocalPath.ShouldBe("/login");
location.Query.ShouldBe("?ReturnUrl=%2Fprotected");
}
@ -69,34 +70,32 @@ namespace Microsoft.AspNet.Authentication.Cookies
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)
{
Uri location = transaction.Response.Headers.Location;
var location = transaction.Response.Headers.Location;
location.ToString().ShouldBe("http://example.com/login?ReturnUrl=%2FCustomRedirect");
}
}
private Task SignInAsAlice(HttpContext context)
{
context.Authentication.SignIn("Cookies",
return context.Authentication.SignInAsync("Cookies",
new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies"))),
new AuthenticationProperties());
return Task.FromResult<object>(null);
}
private Task SignInAsWrong(HttpContext context)
{
context.Authentication.SignIn("Oops",
return context.Authentication.SignInAsync("Oops",
new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies"))),
new AuthenticationProperties());
return Task.FromResult<object>(null);
}
private Task SignOutAsWrong(HttpContext context)
{
context.Authentication.SignOut("Oops");
return Task.FromResult<object>(null);
return context.Authentication.SignOutAsync("Oops");
}
[Fact]
@ -241,17 +240,30 @@ namespace Microsoft.AspNet.Authentication.Cookies
},
SignInAsAlice,
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
var id = new ClaimsIdentity("xform");
id.AddClaim(new Claim("xform", "yup"));
p.AddIdentity(id);
if (!p.Identities.Any(i => i.AuthenticationType == "xform"))
{
var id = new ClaimsIdentity("xform");
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");
@ -259,6 +271,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe("Alice");
FindClaimValue(transaction2, "xform").ShouldBe("yup");
FindClaimValue(transaction2, "sync").ShouldBe(null);
}
@ -304,12 +317,9 @@ namespace Microsoft.AspNet.Authentication.Cookies
options.SlidingExpiration = false;
},
context =>
{
context.Authentication.SignIn("Cookies",
context.Authentication.SignInAsync("Cookies",
new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies"))),
new AuthenticationProperties() { ExpiresUtc = clock.UtcNow.Add(TimeSpan.FromMinutes(5)) });
return Task.FromResult<object>(null);
});
new AuthenticationProperties() { ExpiresUtc = clock.UtcNow.Add(TimeSpan.FromMinutes(5)) }));
var transaction1 = await SendAsync(server, "http://example.com/testpath");
@ -433,9 +443,8 @@ namespace Microsoft.AspNet.Authentication.Cookies
context =>
{
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"))));
return Task.FromResult<object>(null);
},
new Uri("http://example.com/base"));
@ -446,7 +455,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task CookieTurns401To403IfAuthenticated(bool automatic)
public async Task CookieTurns401To403WithCookie(bool automatic)
{
var clock = new TestClock();
var server = CreateServer(options =>
@ -458,18 +467,56 @@ namespace Microsoft.AspNet.Authentication.Cookies
var transaction1 = await SendAsync(server, "http://example.com/testpath");
var url = "http://example.com/unauthorized";
if (automatic)
{
url += "auto";
}
var url = "http://example.com/challenge";
var transaction2 = await SendAsync(server, url, transaction1.CookieNameValue);
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]
public async Task CookieTurns401ToAccessDeniedWhenSetAndIfAuthenticated()
public async Task CookieTurns401ToAccessDeniedWhenSetWithCookie()
{
var clock = new TestClock();
var server = CreateServer(options =>
@ -481,7 +528,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
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);
@ -490,7 +537,23 @@ namespace Microsoft.AspNet.Authentication.Cookies
}
[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 server = CreateServer(options =>
@ -549,29 +612,33 @@ namespace Microsoft.AspNet.Authentication.Cookies
{
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"))
{
// Simulate Authorization failure
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);
await context.Authentication.ChallengeAsync(CookieAuthenticationDefaults.AuthenticationScheme, new AuthenticationProperties(), ChallengeBehavior.Unauthorized);
}
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"))
{
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))
{
var result = await context.Authentication.AuthenticateAsync(remainder.Value.Substring(1));
Describe(res, result);
var authContext = new AuthenticateContext(remainder.Value.Substring(1));
await context.Authentication.AuthenticateAsync(authContext);
Describe(res, authContext);
}
else if (req.Path == new PathString("/testpath") && testpath != null)
{
@ -595,7 +662,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
return server;
}
private static void Describe(HttpResponse res, AuthenticationResult result)
private static void Describe(HttpResponse res, AuthenticateContext result)
{
res.StatusCode = 200;
res.ContentType = "text/xml";
@ -606,7 +673,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
}
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())
{

View File

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

View File

@ -46,6 +46,42 @@ namespace Microsoft.AspNet.Authentication.Google
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]
public async Task Challenge401WillTriggerRedirection()
{
@ -134,7 +170,7 @@ namespace Microsoft.AspNet.Authentication.Google
var res = context.Response;
if (req.Path == new PathString("/challenge2"))
{
context.Authentication.Challenge("Google", new AuthenticationProperties(
return context.Authentication.ChallengeAsync("Google", new AuthenticationProperties(
new Dictionary<string, string>()
{
{ "scope", "https://www.googleapis.com/auth/plus.login" },
@ -142,7 +178,6 @@ namespace Microsoft.AspNet.Authentication.Google
{ "approval_prompt", "force" },
{ "login_hint", "test@example.com" }
}));
res.StatusCode = 401;
}
return Task.FromResult<object>(null);
@ -177,35 +212,6 @@ namespace Microsoft.AspNet.Authentication.Google
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]
public async Task ReplyPathWithoutStateQueryStringWillBeRejected()
{
@ -218,8 +224,6 @@ namespace Microsoft.AspNet.Authentication.Google
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.InternalServerError);
}
[Theory]
[InlineData(null)]
[InlineData("CustomIssuer")]
@ -459,24 +463,14 @@ namespace Microsoft.AspNet.Authentication.Google
options.AutomaticAuthentication = true;
});
app.UseGoogleAuthentication(configureOptions);
app.UseClaimsTransformation(o =>
{
o.Transformation = p =>
{
var id = new ClaimsIdentity("xform");
id.AddClaim(new Claim("xform", "yup"));
p.AddIdentity(id);
return p;
};
});
app.UseClaimsTransformation();
app.Use(async (context, next) =>
{
var req = context.Request;
var res = context.Response;
if (req.Path == new PathString("/challenge"))
{
context.Authentication.Challenge("Google");
res.StatusCode = 401;
await context.Authentication.ChallengeAsync("Google");
}
else if (req.Path == new PathString("/me"))
{
@ -486,18 +480,29 @@ namespace Microsoft.AspNet.Authentication.Google
{
// Simulate Authorization failure
var result = await context.Authentication.AuthenticateAsync("Google");
context.Authentication.Challenge("Google");
await context.Authentication.ChallengeAsync("Google");
}
else if (req.Path == new PathString("/unauthorizedAuto"))
{
var result = await context.Authentication.AuthenticateAsync("Google");
res.StatusCode = 401;
context.Authentication.Challenge();
await context.Authentication.ChallengeAsync();
}
else if (req.Path == new PathString("/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)
{
await testpath(context);
@ -515,8 +520,14 @@ namespace Microsoft.AspNet.Authentication.Google
{
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");
}
[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]
public async Task ChallengeWillTriggerRedirection()
{
@ -155,13 +191,24 @@ namespace Microsoft.AspNet.Authentication.Tests.MicrosoftAccount
var res = context.Response;
if (req.Path == new PathString("/challenge"))
{
context.Authentication.Challenge("Microsoft");
res.StatusCode = 401;
await context.Authentication.ChallengeAsync("Microsoft");
}
else if (req.Path == new PathString("/me"))
{
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
{
await next();

View File

@ -40,6 +40,29 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
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]
public async Task CustomHeaderReceived()
{
@ -326,9 +349,17 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
{
// Simulate Authorization failure
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
{
await next();

View File

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

View File

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

View File

@ -3,6 +3,7 @@
using System;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
@ -19,8 +20,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
[Fact]
public async Task ChallengeWillTriggerApplyRedirectEvent()
{
var server = CreateServer(
app => app.UseTwitterAuthentication(options =>
var server = CreateServer(options =>
{
options.ConsumerKey = "Test Consumer Key";
options.ConsumerSecret = "Test Consumer Secret";
@ -49,10 +49,11 @@ namespace Microsoft.AspNet.Authentication.Twitter
}
};
options.BackchannelCertificateValidator = null;
}),
context =>
},
context =>
{
context.Authentication.Challenge("Twitter");
// REVIEW: Gross
context.Authentication.ChallengeAsync("Twitter").GetAwaiter().GetResult();
return true;
});
var transaction = await server.SendAsync("http://example.com/challenge");
@ -61,11 +62,47 @@ namespace Microsoft.AspNet.Authentication.Twitter
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]
public async Task ChallengeWillTriggerRedirection()
{
var server = CreateServer(
app => app.UseTwitterAuthentication(options =>
var server = CreateServer(options =>
{
options.ConsumerKey = "Test Consumer Key";
options.ConsumerSecret = "Test Consumer Secret";
@ -87,10 +124,11 @@ namespace Microsoft.AspNet.Authentication.Twitter
}
};
options.BackchannelCertificateValidator = null;
}),
},
context =>
{
context.Authentication.Challenge("Twitter");
// REVIEW: gross
context.Authentication.ChallengeAsync("Twitter").GetAwaiter().GetResult();
return true;
});
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=");
}
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 =>
{
@ -107,13 +145,24 @@ namespace Microsoft.AspNet.Authentication.Twitter
{
options.AuthenticationScheme = "External";
});
if (configure != null)
{
configure(app);
}
app.UseTwitterAuthentication(configure);
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();
}

View File

@ -582,7 +582,7 @@ namespace Microsoft.AspNet.Authorization.Test
public class CustomRequirement : IAuthorizationRequirement { }
public class CustomHandler : AuthorizationHandler<CustomRequirement>
{
public override void Handle(AuthorizationContext context, CustomRequirement requirement)
protected override void Handle(AuthorizationContext context, CustomRequirement requirement)
{
context.Succeed(requirement);
}
@ -638,7 +638,7 @@ namespace Microsoft.AspNet.Authorization.Test
public bool Succeed { get; set; }
public override void Handle(AuthorizationContext context, PassThroughRequirement requirement)
protected override void Handle(AuthorizationContext context, PassThroughRequirement requirement)
{
if (Succeed) {
context.Succeed(requirement);
@ -668,6 +668,7 @@ namespace Microsoft.AspNet.Authorization.Test
Assert.Equal(shouldSucceed, allowed);
}
[Fact]
public async Task CanCombinePolicies()
{
// Arrange
@ -695,6 +696,7 @@ namespace Microsoft.AspNet.Authorization.Test
Assert.True(allowed);
}
[Fact]
public async Task CombinePoliciesWillFailIfBasePolicyFails()
{
// Arrange
@ -721,6 +723,7 @@ namespace Microsoft.AspNet.Authorization.Test
Assert.False(allowed);
}
[Fact]
public async Task CombinedPoliciesWillFailIfExtraRequirementFails()
{
// Arrange
@ -765,7 +768,7 @@ namespace Microsoft.AspNet.Authorization.Test
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))
{
@ -776,7 +779,7 @@ namespace Microsoft.AspNet.Authorization.Test
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"))
{
@ -785,6 +788,7 @@ namespace Microsoft.AspNet.Authorization.Test
}
}
[Fact]
public async Task CanAuthorizeAllSuperuserOperations()
{
// Arrange
@ -808,6 +812,7 @@ namespace Microsoft.AspNet.Authorization.Test
Assert.True(await authorizationService.AuthorizeAsync(user, null, Operations.Create));
}
[Fact]
public async Task CanAuthorizeOnlyAllowedOperations()
{
// 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.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);
}
}
}