AuthN and AuthZ API changes (Async, Challenge)
This commit is contained in:
parent
43b428941b
commit
3a8ea672ea
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,6 @@ namespace Microsoft.AspNet.Authentication
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
handler.Faulted = true;
|
|
||||||
await handler.TeardownAsync();
|
await handler.TeardownAsync();
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
|
|
|
||||||
|
|
@ -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 });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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; } }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
|
|
||||||
|
|
@ -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() { }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 =
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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" });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue