Fix cookie bugs, Authenticate => HandleAuthenticate
This commit is contained in:
parent
5065835a05
commit
ab4ba794e5
|
|
@ -27,75 +27,101 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
||||||
private DateTimeOffset? _renewIssuedUtc;
|
private DateTimeOffset? _renewIssuedUtc;
|
||||||
private DateTimeOffset? _renewExpiresUtc;
|
private DateTimeOffset? _renewExpiresUtc;
|
||||||
private string _sessionKey;
|
private string _sessionKey;
|
||||||
|
private Task<AuthenticationTicket> _cookieTicketTask;
|
||||||
|
|
||||||
protected override async Task<AuthenticationTicket> AuthenticateAsync()
|
private Task<AuthenticationTicket> EnsureCookieTicket()
|
||||||
|
{
|
||||||
|
// We only need to read the ticket once
|
||||||
|
if (_cookieTicketTask == null)
|
||||||
|
{
|
||||||
|
_cookieTicketTask = ReadCookieTicket();
|
||||||
|
}
|
||||||
|
return _cookieTicketTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<AuthenticationTicket> ReadCookieTicket()
|
||||||
|
{
|
||||||
|
var cookie = Options.CookieManager.GetRequestCookie(Context, Options.CookieName);
|
||||||
|
if (string.IsNullOrEmpty(cookie))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ticket = Options.TicketDataFormat.Unprotect(cookie);
|
||||||
|
if (ticket == null)
|
||||||
|
{
|
||||||
|
Logger.LogWarning(@"Unprotect ticket failed");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Options.SessionStore != null)
|
||||||
|
{
|
||||||
|
var claim = ticket.Principal.Claims.FirstOrDefault(c => c.Type.Equals(SessionIdClaim));
|
||||||
|
if (claim == null)
|
||||||
|
{
|
||||||
|
Logger.LogWarning(@"SessionId missing");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
_sessionKey = claim.Value;
|
||||||
|
ticket = await Options.SessionStore.RetrieveAsync(_sessionKey);
|
||||||
|
if (ticket == null)
|
||||||
|
{
|
||||||
|
Logger.LogWarning(@"Identity missing in session store");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentUtc = Options.SystemClock.UtcNow;
|
||||||
|
var issuedUtc = ticket.Properties.IssuedUtc;
|
||||||
|
var expiresUtc = ticket.Properties.ExpiresUtc;
|
||||||
|
|
||||||
|
if (expiresUtc != null && expiresUtc.Value < currentUtc)
|
||||||
|
{
|
||||||
|
if (Options.SessionStore != null)
|
||||||
|
{
|
||||||
|
await Options.SessionStore.RemoveAsync(_sessionKey);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var allowRefresh = ticket.Properties.AllowRefresh ?? true;
|
||||||
|
if (issuedUtc != null && expiresUtc != null && Options.SlidingExpiration && allowRefresh)
|
||||||
|
{
|
||||||
|
var timeElapsed = currentUtc.Subtract(issuedUtc.Value);
|
||||||
|
var timeRemaining = expiresUtc.Value.Subtract(currentUtc);
|
||||||
|
|
||||||
|
if (timeRemaining < timeElapsed)
|
||||||
|
{
|
||||||
|
_shouldRenew = true;
|
||||||
|
_renewIssuedUtc = currentUtc;
|
||||||
|
var timeSpan = expiresUtc.Value.Subtract(issuedUtc.Value);
|
||||||
|
_renewExpiresUtc = currentUtc.Add(timeSpan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally we have a valid ticket
|
||||||
|
return ticket;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<AuthenticationTicket> HandleAuthenticateAsync()
|
||||||
{
|
{
|
||||||
AuthenticationTicket ticket = null;
|
AuthenticationTicket ticket = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var cookie = Options.CookieManager.GetRequestCookie(Context, Options.CookieName);
|
ticket = await EnsureCookieTicket();
|
||||||
if (string.IsNullOrEmpty(cookie))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ticket = Options.TicketDataFormat.Unprotect(cookie);
|
|
||||||
|
|
||||||
if (ticket == null)
|
if (ticket == null)
|
||||||
{
|
{
|
||||||
Logger.LogWarning(@"Unprotect ticket failed");
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Options.SessionStore != null)
|
|
||||||
{
|
|
||||||
var claim = ticket.Principal.Claims.FirstOrDefault(c => c.Type.Equals(SessionIdClaim));
|
|
||||||
if (claim == null)
|
|
||||||
{
|
|
||||||
Logger.LogWarning(@"SessionId missing");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
_sessionKey = claim.Value;
|
|
||||||
ticket = await Options.SessionStore.RetrieveAsync(_sessionKey);
|
|
||||||
if (ticket == null)
|
|
||||||
{
|
|
||||||
Logger.LogWarning(@"Identity missing in session store");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentUtc = Options.SystemClock.UtcNow;
|
|
||||||
var issuedUtc = ticket.Properties.IssuedUtc;
|
|
||||||
var expiresUtc = ticket.Properties.ExpiresUtc;
|
|
||||||
|
|
||||||
if (expiresUtc != null && expiresUtc.Value < currentUtc)
|
|
||||||
{
|
|
||||||
if (Options.SessionStore != null)
|
|
||||||
{
|
|
||||||
await Options.SessionStore.RemoveAsync(_sessionKey);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var allowRefresh = ticket.Properties.AllowRefresh ?? true;
|
|
||||||
if (issuedUtc != null && expiresUtc != null && Options.SlidingExpiration && allowRefresh)
|
|
||||||
{
|
|
||||||
var timeElapsed = currentUtc.Subtract(issuedUtc.Value);
|
|
||||||
var timeRemaining = expiresUtc.Value.Subtract(currentUtc);
|
|
||||||
|
|
||||||
if (timeRemaining < timeElapsed)
|
|
||||||
{
|
|
||||||
_shouldRenew = true;
|
|
||||||
_renewIssuedUtc = currentUtc;
|
|
||||||
var timeSpan = expiresUtc.Value.Subtract(issuedUtc.Value);
|
|
||||||
_renewExpiresUtc = currentUtc.Add(timeSpan);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var context = new CookieValidatePrincipalContext(Context, ticket, Options);
|
var context = new CookieValidatePrincipalContext(Context, ticket, Options);
|
||||||
|
|
||||||
await Options.Notifications.ValidatePrincipal(context);
|
await Options.Notifications.ValidatePrincipal(context);
|
||||||
|
|
||||||
|
if (context.Principal == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (context.ShouldRenew)
|
if (context.ShouldRenew)
|
||||||
{
|
{
|
||||||
_shouldRenew = true;
|
_shouldRenew = true;
|
||||||
|
|
@ -135,46 +161,6 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
||||||
return cookieOptions;
|
return cookieOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ApplyCookie(AuthenticationTicket ticket)
|
|
||||||
{
|
|
||||||
if (_renewIssuedUtc.HasValue)
|
|
||||||
{
|
|
||||||
ticket.Properties.IssuedUtc = _renewIssuedUtc;
|
|
||||||
}
|
|
||||||
if (_renewExpiresUtc.HasValue)
|
|
||||||
{
|
|
||||||
ticket.Properties.ExpiresUtc = _renewExpiresUtc;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Options.SessionStore != null && _sessionKey != null)
|
|
||||||
{
|
|
||||||
await Options.SessionStore.RenewAsync(_sessionKey, ticket);
|
|
||||||
var principal = new ClaimsPrincipal(
|
|
||||||
new ClaimsIdentity(
|
|
||||||
new[] { new Claim(SessionIdClaim, _sessionKey, ClaimValueTypes.String, Options.ClaimsIssuer) },
|
|
||||||
Options.AuthenticationScheme));
|
|
||||||
ticket = new AuthenticationTicket(principal, null, Options.AuthenticationScheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
var cookieValue = Options.TicketDataFormat.Protect(ticket);
|
|
||||||
|
|
||||||
var cookieOptions = BuildCookieOptions();
|
|
||||||
if (ticket.Properties.IsPersistent && _renewExpiresUtc.HasValue)
|
|
||||||
{
|
|
||||||
cookieOptions.Expires = _renewExpiresUtc.Value.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()
|
protected override async Task FinishResponseAsync()
|
||||||
{
|
{
|
||||||
// Only renew if requested, and neither sign in or sign out was called
|
// Only renew if requested, and neither sign in or sign out was called
|
||||||
|
|
@ -183,15 +169,48 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ticket = await AuthenticateOnceAsync();
|
var ticket = await HandleAuthenticateOnceAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ApplyCookie(ticket);
|
if (_renewIssuedUtc.HasValue)
|
||||||
|
{
|
||||||
|
ticket.Properties.IssuedUtc = _renewIssuedUtc;
|
||||||
|
}
|
||||||
|
if (_renewExpiresUtc.HasValue)
|
||||||
|
{
|
||||||
|
ticket.Properties.ExpiresUtc = _renewExpiresUtc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Options.SessionStore != null && _sessionKey != null)
|
||||||
|
{
|
||||||
|
await Options.SessionStore.RenewAsync(_sessionKey, ticket);
|
||||||
|
var principal = new ClaimsPrincipal(
|
||||||
|
new ClaimsIdentity(
|
||||||
|
new[] { new Claim(SessionIdClaim, _sessionKey, ClaimValueTypes.String, Options.ClaimsIssuer) },
|
||||||
|
Options.AuthenticationScheme));
|
||||||
|
ticket = new AuthenticationTicket(principal, null, Options.AuthenticationScheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
var cookieValue = Options.TicketDataFormat.Protect(ticket);
|
||||||
|
|
||||||
|
var cookieOptions = BuildCookieOptions();
|
||||||
|
if (ticket.Properties.IsPersistent && _renewExpiresUtc.HasValue)
|
||||||
|
{
|
||||||
|
cookieOptions.Expires = _renewExpiresUtc.Value.ToUniversalTime().DateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
Options.CookieManager.AppendResponseCookie(
|
||||||
|
Context,
|
||||||
|
Options.CookieName,
|
||||||
|
cookieValue,
|
||||||
|
cookieOptions);
|
||||||
|
|
||||||
|
ApplyHeaders();
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
var exceptionContext = new CookieExceptionContext(Context, Options,
|
var exceptionContext = new CookieExceptionContext(Context, Options,
|
||||||
CookieExceptionContext.ExceptionLocation.ApplyResponseGrant, exception, ticket);
|
CookieExceptionContext.ExceptionLocation.FinishResponse, exception, ticket);
|
||||||
Options.Notifications.Exception(exceptionContext);
|
Options.Notifications.Exception(exceptionContext);
|
||||||
if (exceptionContext.Rethrow)
|
if (exceptionContext.Rethrow)
|
||||||
{
|
{
|
||||||
|
|
@ -202,7 +221,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
||||||
|
|
||||||
protected override async Task HandleSignInAsync(SignInContext signin)
|
protected override async Task HandleSignInAsync(SignInContext signin)
|
||||||
{
|
{
|
||||||
var model = await AuthenticateAsync();
|
var ticket = await EnsureCookieTicket();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var cookieOptions = BuildCookieOptions();
|
var cookieOptions = BuildCookieOptions();
|
||||||
|
|
@ -239,21 +258,21 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
||||||
signInContext.CookieOptions.Expires = expiresUtc.ToUniversalTime().DateTime;
|
signInContext.CookieOptions.Expires = expiresUtc.ToUniversalTime().DateTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
model = new AuthenticationTicket(signInContext.Principal, signInContext.Properties, signInContext.AuthenticationScheme);
|
ticket = new AuthenticationTicket(signInContext.Principal, signInContext.Properties, signInContext.AuthenticationScheme);
|
||||||
if (Options.SessionStore != null)
|
if (Options.SessionStore != null)
|
||||||
{
|
{
|
||||||
if (_sessionKey != null)
|
if (_sessionKey != null)
|
||||||
{
|
{
|
||||||
await Options.SessionStore.RemoveAsync(_sessionKey);
|
await Options.SessionStore.RemoveAsync(_sessionKey);
|
||||||
}
|
}
|
||||||
_sessionKey = await Options.SessionStore.StoreAsync(model);
|
_sessionKey = await Options.SessionStore.StoreAsync(ticket);
|
||||||
var principal = new ClaimsPrincipal(
|
var principal = new ClaimsPrincipal(
|
||||||
new ClaimsIdentity(
|
new ClaimsIdentity(
|
||||||
new[] { new Claim(SessionIdClaim, _sessionKey, ClaimValueTypes.String, Options.ClaimsIssuer) },
|
new[] { new Claim(SessionIdClaim, _sessionKey, ClaimValueTypes.String, Options.ClaimsIssuer) },
|
||||||
Options.ClaimsIssuer));
|
Options.ClaimsIssuer));
|
||||||
model = new AuthenticationTicket(principal, null, Options.AuthenticationScheme);
|
ticket = new AuthenticationTicket(principal, null, Options.AuthenticationScheme);
|
||||||
}
|
}
|
||||||
var cookieValue = Options.TicketDataFormat.Protect(model);
|
var cookieValue = Options.TicketDataFormat.Protect(ticket);
|
||||||
|
|
||||||
Options.CookieManager.AppendResponseCookie(
|
Options.CookieManager.AppendResponseCookie(
|
||||||
Context,
|
Context,
|
||||||
|
|
@ -270,36 +289,13 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
||||||
|
|
||||||
Options.Notifications.ResponseSignedIn(signedInContext);
|
Options.Notifications.ResponseSignedIn(signedInContext);
|
||||||
|
|
||||||
Response.Headers.Set(
|
var shouldLoginRedirect = Options.LoginPath.HasValue && OriginalPath == Options.LoginPath;
|
||||||
HeaderNameCacheControl,
|
ApplyHeaders(shouldLoginRedirect);
|
||||||
HeaderValueNoCache);
|
|
||||||
|
|
||||||
Response.Headers.Set(
|
|
||||||
HeaderNamePragma,
|
|
||||||
HeaderValueNoCache);
|
|
||||||
|
|
||||||
Response.Headers.Set(
|
|
||||||
HeaderNameExpires,
|
|
||||||
HeaderValueMinusOne);
|
|
||||||
|
|
||||||
var shouldLoginRedirect = Options.LoginPath.HasValue && Request.Path == Options.LoginPath;
|
|
||||||
|
|
||||||
if ((shouldLoginRedirect) && Response.StatusCode == 200)
|
|
||||||
{
|
|
||||||
var query = Request.Query;
|
|
||||||
var redirectUri = query.Get(Options.ReturnUrlParameter);
|
|
||||||
if (!string.IsNullOrEmpty(redirectUri)
|
|
||||||
&& IsHostRelative(redirectUri))
|
|
||||||
{
|
|
||||||
var redirectContext = new CookieApplyRedirectContext(Context, Options, redirectUri);
|
|
||||||
Options.Notifications.ApplyRedirect(redirectContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
var exceptionContext = new CookieExceptionContext(Context, Options,
|
var exceptionContext = new CookieExceptionContext(Context, Options,
|
||||||
CookieExceptionContext.ExceptionLocation.ApplyResponseGrant, exception, model);
|
CookieExceptionContext.ExceptionLocation.SignIn, exception, ticket);
|
||||||
Options.Notifications.Exception(exceptionContext);
|
Options.Notifications.Exception(exceptionContext);
|
||||||
if (exceptionContext.Rethrow)
|
if (exceptionContext.Rethrow)
|
||||||
{
|
{
|
||||||
|
|
@ -310,11 +306,10 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
||||||
|
|
||||||
protected override async Task HandleSignOutAsync(SignOutContext signOutContext)
|
protected override async Task HandleSignOutAsync(SignOutContext signOutContext)
|
||||||
{
|
{
|
||||||
var model = await AuthenticateAsync();
|
var ticket = await EnsureCookieTicket();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var cookieOptions = BuildCookieOptions();
|
var cookieOptions = BuildCookieOptions();
|
||||||
|
|
||||||
if (Options.SessionStore != null && _sessionKey != null)
|
if (Options.SessionStore != null && _sessionKey != null)
|
||||||
{
|
{
|
||||||
await Options.SessionStore.RemoveAsync(_sessionKey);
|
await Options.SessionStore.RemoveAsync(_sessionKey);
|
||||||
|
|
@ -332,43 +327,38 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
||||||
Options.CookieName,
|
Options.CookieName,
|
||||||
context.CookieOptions);
|
context.CookieOptions);
|
||||||
|
|
||||||
Response.Headers.Set(
|
var shouldLogoutRedirect = Options.LogoutPath.HasValue && OriginalPath == Options.LogoutPath;
|
||||||
HeaderNameCacheControl,
|
ApplyHeaders(shouldLogoutRedirect);
|
||||||
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.IsNullOrEmpty(redirectUri)
|
|
||||||
&& IsHostRelative(redirectUri))
|
|
||||||
{
|
|
||||||
var redirectContext = new CookieApplyRedirectContext(Context, Options, redirectUri);
|
|
||||||
Options.Notifications.ApplyRedirect(redirectContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
var exceptionContext = new CookieExceptionContext(Context, Options,
|
var exceptionContext = new CookieExceptionContext(Context, Options,
|
||||||
CookieExceptionContext.ExceptionLocation.ApplyResponseGrant, exception, model);
|
CookieExceptionContext.ExceptionLocation.SignOut, exception, ticket);
|
||||||
Options.Notifications.Exception(exceptionContext);
|
Options.Notifications.Exception(exceptionContext);
|
||||||
if (exceptionContext.Rethrow)
|
if (exceptionContext.Rethrow)
|
||||||
{
|
{
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyHeaders(bool shouldRedirectToReturnUrl = false)
|
||||||
|
{
|
||||||
|
Response.Headers.Set(HeaderNameCacheControl, HeaderValueNoCache);
|
||||||
|
Response.Headers.Set(HeaderNamePragma, HeaderValueNoCache);
|
||||||
|
Response.Headers.Set(HeaderNameExpires, HeaderValueMinusOne);
|
||||||
|
|
||||||
|
if (shouldRedirectToReturnUrl && Response.StatusCode == 200)
|
||||||
|
{
|
||||||
|
var query = Request.Query;
|
||||||
|
var redirectUri = query.Get(Options.ReturnUrlParameter);
|
||||||
|
if (!string.IsNullOrEmpty(redirectUri)
|
||||||
|
&& IsHostRelative(redirectUri))
|
||||||
|
{
|
||||||
|
var redirectContext = new CookieApplyRedirectContext(Context, Options, redirectUri);
|
||||||
|
Options.Notifications.ApplyRedirect(redirectContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsHostRelative(string path)
|
private static bool IsHostRelative(string path)
|
||||||
|
|
@ -387,36 +377,34 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
||||||
protected override Task<bool> HandleForbiddenAsync(ChallengeContext context)
|
protected override Task<bool> HandleForbiddenAsync(ChallengeContext context)
|
||||||
{
|
{
|
||||||
// HandleForbidden by redirecting to AccessDeniedPath if set
|
// HandleForbidden by redirecting to AccessDeniedPath if set
|
||||||
if (Options.AccessDeniedPath.HasValue)
|
if (!Options.AccessDeniedPath.HasValue)
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var accessDeniedUri =
|
|
||||||
Request.Scheme +
|
|
||||||
"://" +
|
|
||||||
Request.Host +
|
|
||||||
Request.PathBase +
|
|
||||||
Options.AccessDeniedPath;
|
|
||||||
|
|
||||||
var redirectContext = new CookieApplyRedirectContext(Context, Options, accessDeniedUri);
|
|
||||||
Options.Notifications.ApplyRedirect(redirectContext);
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
var exceptionContext = new CookieExceptionContext(Context, Options,
|
|
||||||
CookieExceptionContext.ExceptionLocation.ApplyResponseChallenge, exception, ticket: null);
|
|
||||||
Options.Notifications.Exception(exceptionContext);
|
|
||||||
if (exceptionContext.Rethrow)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
return base.HandleForbiddenAsync(context);
|
return base.HandleForbiddenAsync(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var accessDeniedUri =
|
||||||
|
Request.Scheme +
|
||||||
|
"://" +
|
||||||
|
Request.Host +
|
||||||
|
OriginalPathBase +
|
||||||
|
Options.AccessDeniedPath;
|
||||||
|
|
||||||
|
var redirectContext = new CookieApplyRedirectContext(Context, Options, accessDeniedUri);
|
||||||
|
Options.Notifications.ApplyRedirect(redirectContext);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
var exceptionContext = new CookieExceptionContext(Context, Options,
|
||||||
|
CookieExceptionContext.ExceptionLocation.Forbidden, exception, ticket: null);
|
||||||
|
Options.Notifications.Exception(exceptionContext);
|
||||||
|
if (exceptionContext.Rethrow)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Task<bool> HandleUnauthorizedAsync([NotNull] ChallengeContext context)
|
protected override Task<bool> HandleUnauthorizedAsync([NotNull] ChallengeContext context)
|
||||||
|
|
@ -431,27 +419,17 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(redirectUri))
|
if (string.IsNullOrEmpty(redirectUri))
|
||||||
{
|
{
|
||||||
redirectUri =
|
redirectUri = OriginalPathBase + Request.Path + Request.QueryString;
|
||||||
Request.PathBase +
|
|
||||||
Request.Path +
|
|
||||||
Request.QueryString;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var loginUri =
|
var loginUri = Options.LoginPath + QueryString.Create(Options.ReturnUrlParameter, redirectUri);
|
||||||
Request.Scheme +
|
var redirectContext = new CookieApplyRedirectContext(Context, Options, BuildRedirectUri(loginUri));
|
||||||
"://" +
|
|
||||||
Request.Host +
|
|
||||||
Request.PathBase +
|
|
||||||
Options.LoginPath +
|
|
||||||
QueryString.Create(Options.ReturnUrlParameter, redirectUri);
|
|
||||||
|
|
||||||
var redirectContext = new CookieApplyRedirectContext(Context, Options, loginUri);
|
|
||||||
Options.Notifications.ApplyRedirect(redirectContext);
|
Options.Notifications.ApplyRedirect(redirectContext);
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
var exceptionContext = new CookieExceptionContext(Context, Options,
|
var exceptionContext = new CookieExceptionContext(Context, Options,
|
||||||
CookieExceptionContext.ExceptionLocation.ApplyResponseChallenge, exception, ticket: null);
|
CookieExceptionContext.ExceptionLocation.Unauthorized, exception, ticket: null);
|
||||||
Options.Notifications.Exception(exceptionContext);
|
Options.Notifications.Exception(exceptionContext);
|
||||||
if (exceptionContext.Rethrow)
|
if (exceptionContext.Rethrow)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -48,14 +48,30 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
||||||
Authenticate,
|
Authenticate,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The exception was reported in the ApplyResponseGrant code path, during sign-in, sign-out, or refresh.
|
/// The exception was reported in the FinishResponse code path, during sign-in, sign-out, or refresh.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ApplyResponseGrant,
|
FinishResponse,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The exception was reported in the ApplyResponseChallenge code path, during redirect generation.
|
/// The exception was reported in the Unauthorized code path, during redirect generation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ApplyResponseChallenge,
|
Unauthorized,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The exception was reported in the Forbidden code path, during redirect generation.
|
||||||
|
/// </summary>
|
||||||
|
Forbidden,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The exception was reported in the SignIn code path
|
||||||
|
/// </summary>
|
||||||
|
SignIn,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The exception was reported in the SignOut code path
|
||||||
|
/// </summary>
|
||||||
|
SignOut,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
||||||
|
|
||||||
public async Task<bool> InvokeReturnPathAsync()
|
public async Task<bool> InvokeReturnPathAsync()
|
||||||
{
|
{
|
||||||
var ticket = await AuthenticateAsync();
|
var ticket = await HandleAuthenticateOnceAsync();
|
||||||
if (ticket == null)
|
if (ticket == null)
|
||||||
{
|
{
|
||||||
Logger.LogWarning("Invalid return state, unable to redirect.");
|
Logger.LogWarning("Invalid return state, unable to redirect.");
|
||||||
|
|
@ -78,7 +78,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
||||||
return context.IsRequestCompleted;
|
return context.IsRequestCompleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task<AuthenticationTicket> AuthenticateAsync()
|
protected override async Task<AuthenticationTicket> HandleAuthenticateAsync()
|
||||||
{
|
{
|
||||||
AuthenticationProperties properties = null;
|
AuthenticationProperties properties = null;
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
|
||||||
/// 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> AuthenticateAsync()
|
protected override async Task<AuthenticationTicket> HandleAuthenticateAsync()
|
||||||
{
|
{
|
||||||
string token = null;
|
string token = null;
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,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> AuthenticateAsync()
|
protected override async Task<AuthenticationTicket> HandleAuthenticateAsync()
|
||||||
{
|
{
|
||||||
Logger.LogDebug(Resources.OIDCH_0000_AuthenticateCoreAsync, this.GetType());
|
Logger.LogDebug(Resources.OIDCH_0000_AuthenticateCoreAsync, this.GetType());
|
||||||
|
|
||||||
|
|
@ -903,7 +903,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
|
||||||
|
|
||||||
private async Task<bool> InvokeReplyPathAsync()
|
private async Task<bool> InvokeReplyPathAsync()
|
||||||
{
|
{
|
||||||
var ticket = await AuthenticateAsync();
|
var ticket = await HandleAuthenticateOnceAsync();
|
||||||
if (ticket != null)
|
if (ticket != null)
|
||||||
{
|
{
|
||||||
if (ticket.Principal != null)
|
if (ticket.Principal != null)
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task<AuthenticationTicket> AuthenticateAsync()
|
protected override async Task<AuthenticationTicket> HandleAuthenticateAsync()
|
||||||
{
|
{
|
||||||
AuthenticationProperties properties = null;
|
AuthenticationProperties properties = null;
|
||||||
try
|
try
|
||||||
|
|
@ -169,7 +169,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
||||||
|
|
||||||
public async Task<bool> InvokeReturnPathAsync()
|
public async Task<bool> InvokeReturnPathAsync()
|
||||||
{
|
{
|
||||||
var model = await AuthenticateAsync();
|
var model = await HandleAuthenticateOnceAsync();
|
||||||
if (model == null)
|
if (model == null)
|
||||||
{
|
{
|
||||||
Logger.LogWarning("Invalid return state, unable to redirect.");
|
Logger.LogWarning("Invalid return state, unable to redirect.");
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ namespace Microsoft.AspNet.Authentication
|
||||||
|
|
||||||
protected PathString OriginalPathBase { get; private set; }
|
protected PathString OriginalPathBase { get; private set; }
|
||||||
|
|
||||||
|
protected PathString OriginalPath { get; private set; }
|
||||||
|
|
||||||
protected ILogger Logger { get; private set; }
|
protected ILogger Logger { get; private set; }
|
||||||
|
|
||||||
protected IUrlEncoder UrlEncoder { get; private set; }
|
protected IUrlEncoder UrlEncoder { get; private set; }
|
||||||
|
|
@ -62,6 +64,7 @@ namespace Microsoft.AspNet.Authentication
|
||||||
_baseOptions = options;
|
_baseOptions = options;
|
||||||
Context = context;
|
Context = context;
|
||||||
OriginalPathBase = Request.PathBase;
|
OriginalPathBase = Request.PathBase;
|
||||||
|
OriginalPath = Request.Path;
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
UrlEncoder = encoder;
|
UrlEncoder = encoder;
|
||||||
|
|
||||||
|
|
@ -72,7 +75,7 @@ namespace Microsoft.AspNet.Authentication
|
||||||
// Automatic authentication is the empty scheme
|
// Automatic authentication is the empty scheme
|
||||||
if (ShouldHandleScheme(string.Empty))
|
if (ShouldHandleScheme(string.Empty))
|
||||||
{
|
{
|
||||||
var ticket = await AuthenticateOnceAsync();
|
var ticket = await HandleAuthenticateOnceAsync();
|
||||||
if (ticket?.Principal != null)
|
if (ticket?.Principal != null)
|
||||||
{
|
{
|
||||||
Context.User = SecurityHelper.MergeUserPrincipal(Context.User, ticket.Principal);
|
Context.User = SecurityHelper.MergeUserPrincipal(Context.User, ticket.Principal);
|
||||||
|
|
@ -157,13 +160,10 @@ namespace Microsoft.AspNet.Authentication
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Task<AuthenticationTicket> AuthenticateOnceAsync()
|
public bool ShouldHandleScheme(string authenticationScheme)
|
||||||
{
|
{
|
||||||
if (_authenticateTask == null)
|
return string.Equals(BaseOptions.AuthenticationScheme, authenticationScheme, StringComparison.Ordinal) ||
|
||||||
{
|
(BaseOptions.AutomaticAuthentication && string.IsNullOrEmpty(authenticationScheme));
|
||||||
_authenticateTask = AuthenticateAsync();
|
|
||||||
}
|
|
||||||
return _authenticateTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AuthenticateAsync(AuthenticateContext context)
|
public async Task AuthenticateAsync(AuthenticateContext context)
|
||||||
|
|
@ -171,11 +171,10 @@ namespace Microsoft.AspNet.Authentication
|
||||||
if (ShouldHandleScheme(context.AuthenticationScheme))
|
if (ShouldHandleScheme(context.AuthenticationScheme))
|
||||||
{
|
{
|
||||||
// Calling Authenticate more than once should always return the original value.
|
// Calling Authenticate more than once should always return the original value.
|
||||||
var ticket = await AuthenticateOnceAsync();
|
var ticket = await HandleAuthenticateOnceAsync();
|
||||||
if (ticket?.Principal != null)
|
if (ticket?.Principal != null)
|
||||||
{
|
{
|
||||||
context.Authenticated(ticket.Principal, ticket.Properties.Items, BaseOptions.Description.Items);
|
context.Authenticated(ticket.Principal, ticket.Properties.Items, BaseOptions.Description.Items);
|
||||||
_authenticateTask = Task.FromResult(ticket);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -189,14 +188,17 @@ namespace Microsoft.AspNet.Authentication
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Task<AuthenticationTicket> AuthenticateAsync();
|
protected Task<AuthenticationTicket> HandleAuthenticateOnceAsync()
|
||||||
|
|
||||||
public bool ShouldHandleScheme(string authenticationScheme)
|
|
||||||
{
|
{
|
||||||
return string.Equals(BaseOptions.AuthenticationScheme, authenticationScheme, StringComparison.Ordinal) ||
|
if (_authenticateTask == null)
|
||||||
(BaseOptions.AutomaticAuthentication && string.IsNullOrWhiteSpace(authenticationScheme));
|
{
|
||||||
|
_authenticateTask = HandleAuthenticateAsync();
|
||||||
|
}
|
||||||
|
return _authenticateTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract Task<AuthenticationTicket> HandleAuthenticateAsync();
|
||||||
|
|
||||||
public async Task SignInAsync(SignInContext context)
|
public async Task SignInAsync(SignInContext context)
|
||||||
{
|
{
|
||||||
if (ShouldHandleScheme(context.AuthenticationScheme))
|
if (ShouldHandleScheme(context.AuthenticationScheme))
|
||||||
|
|
@ -270,7 +272,7 @@ namespace Microsoft.AspNet.Authentication
|
||||||
{
|
{
|
||||||
case ChallengeBehavior.Automatic:
|
case ChallengeBehavior.Automatic:
|
||||||
// If there is a principal already, invoke the forbidden code path
|
// If there is a principal already, invoke the forbidden code path
|
||||||
var ticket = await AuthenticateAsync();
|
var ticket = await HandleAuthenticateOnceAsync();
|
||||||
if (ticket?.Principal != null)
|
if (ticket?.Principal != null)
|
||||||
{
|
{
|
||||||
handled = await HandleForbiddenAsync(context);
|
handled = await HandleForbiddenAsync(context);
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,7 @@ namespace Microsoft.AspNet.Authentication.DataHandler.Serializer
|
||||||
var principal = model.Principal;
|
var principal = model.Principal;
|
||||||
if (principal == null)
|
if (principal == null)
|
||||||
{
|
{
|
||||||
// Use -1 to signal null
|
throw new ArgumentNullException("model.Principal");
|
||||||
writer.Write(-1);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -75,37 +74,36 @@ namespace Microsoft.AspNet.Authentication.DataHandler.Serializer
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
string authenticationScheme = reader.ReadString();
|
var authenticationScheme = reader.ReadString();
|
||||||
int identityCount = reader.ReadInt32();
|
var identityCount = reader.ReadInt32();
|
||||||
ClaimsPrincipal principal = null;
|
|
||||||
|
|
||||||
// Negative values are used to signify null
|
if (identityCount < 0)
|
||||||
if (identityCount >= 0)
|
|
||||||
{
|
{
|
||||||
var identities = new ClaimsIdentity[identityCount];
|
return null;
|
||||||
for (int i = 0; i != identityCount; ++i)
|
}
|
||||||
|
|
||||||
|
var identities = new ClaimsIdentity[identityCount];
|
||||||
|
for (var i = 0; i != identityCount; ++i)
|
||||||
|
{
|
||||||
|
var authenticationType = reader.ReadString();
|
||||||
|
var nameClaimType = ReadWithDefault(reader, DefaultValues.NameClaimType);
|
||||||
|
var roleClaimType = ReadWithDefault(reader, DefaultValues.RoleClaimType);
|
||||||
|
var count = reader.ReadInt32();
|
||||||
|
var claims = new Claim[count];
|
||||||
|
for (int index = 0; index != count; ++index)
|
||||||
{
|
{
|
||||||
string authenticationType = reader.ReadString();
|
var type = ReadWithDefault(reader, nameClaimType);
|
||||||
string nameClaimType = ReadWithDefault(reader, DefaultValues.NameClaimType);
|
var value = reader.ReadString();
|
||||||
string roleClaimType = ReadWithDefault(reader, DefaultValues.RoleClaimType);
|
var valueType = ReadWithDefault(reader, DefaultValues.StringValueType);
|
||||||
int count = reader.ReadInt32();
|
var issuer = ReadWithDefault(reader, DefaultValues.LocalAuthority);
|
||||||
var claims = new Claim[count];
|
var originalIssuer = ReadWithDefault(reader, issuer);
|
||||||
for (int index = 0; index != count; ++index)
|
claims[index] = new Claim(type, value, valueType, issuer, originalIssuer);
|
||||||
{
|
|
||||||
string type = ReadWithDefault(reader, nameClaimType);
|
|
||||||
string value = reader.ReadString();
|
|
||||||
string valueType = ReadWithDefault(reader, DefaultValues.StringValueType);
|
|
||||||
string issuer = ReadWithDefault(reader, DefaultValues.LocalAuthority);
|
|
||||||
string originalIssuer = ReadWithDefault(reader, issuer);
|
|
||||||
claims[index] = new Claim(type, value, valueType, issuer, originalIssuer);
|
|
||||||
}
|
|
||||||
identities[i] = new ClaimsIdentity(claims, authenticationType, nameClaimType, roleClaimType);
|
|
||||||
}
|
}
|
||||||
principal = new ClaimsPrincipal(identities);
|
identities[i] = new ClaimsIdentity(claims, authenticationType, nameClaimType, roleClaimType);
|
||||||
}
|
}
|
||||||
|
|
||||||
var properties = PropertiesSerializer.Read(reader);
|
var properties = PropertiesSerializer.Read(reader);
|
||||||
return new AuthenticationTicket(principal, properties, authenticationScheme);
|
return new AuthenticationTicket(new ClaimsPrincipal(identities), properties, authenticationScheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void WriteWithDefault(BinaryWriter writer, string value, string defaultValue)
|
private static void WriteWithDefault(BinaryWriter writer, string value, string defaultValue)
|
||||||
|
|
@ -122,7 +120,7 @@ namespace Microsoft.AspNet.Authentication.DataHandler.Serializer
|
||||||
|
|
||||||
private static string ReadWithDefault(BinaryReader reader, string defaultValue)
|
private static string ReadWithDefault(BinaryReader reader, string defaultValue)
|
||||||
{
|
{
|
||||||
string value = reader.ReadString();
|
var value = reader.ReadString();
|
||||||
if (string.Equals(value, DefaultValues.DefaultStringPlaceholder, StringComparison.Ordinal))
|
if (string.Equals(value, DefaultValues.DefaultStringPlaceholder, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,20 @@ namespace Microsoft.AspNet.Authentication
|
||||||
Assert.True(handler.ShouldHandleScheme(""));
|
Assert.True(handler.ShouldHandleScheme(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AutomaticHandlerHandlesNullScheme()
|
||||||
|
{
|
||||||
|
var handler = new TestAutoHandler("ignored", true);
|
||||||
|
Assert.True(handler.ShouldHandleScheme(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AutomaticHandlerIgnoresWhitespaceScheme()
|
||||||
|
{
|
||||||
|
var handler = new TestAutoHandler("ignored", true);
|
||||||
|
Assert.False(handler.ShouldHandleScheme(" "));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AutomaticHandlerShouldHandleSchemeWhenSchemeMatches()
|
public void AutomaticHandlerShouldHandleSchemeWhenSchemeMatches()
|
||||||
{
|
{
|
||||||
|
|
@ -53,6 +67,37 @@ namespace Microsoft.AspNet.Authentication
|
||||||
Assert.False(handler.ShouldHandleScheme("Alpha"));
|
Assert.False(handler.ShouldHandleScheme("Alpha"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Alpha")]
|
||||||
|
[InlineData("")]
|
||||||
|
public async Task AuthHandlerAuthenticateCachesTicket(string scheme)
|
||||||
|
{
|
||||||
|
var handler = new CountHandler(scheme);
|
||||||
|
var context = new AuthenticateContext(scheme);
|
||||||
|
await handler.AuthenticateAsync(context);
|
||||||
|
await handler.AuthenticateAsync(context);
|
||||||
|
Assert.Equal(1, handler.AuthCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CountHandler : AuthenticationHandler<AuthenticationOptions>
|
||||||
|
{
|
||||||
|
public int AuthCount = 0;
|
||||||
|
|
||||||
|
public CountHandler(string scheme)
|
||||||
|
{
|
||||||
|
Initialize(new TestOptions(), new DefaultHttpContext(), new LoggerFactory().CreateLogger("TestHandler"), Framework.WebEncoders.UrlEncoder.Default);
|
||||||
|
Options.AuthenticationScheme = scheme;
|
||||||
|
Options.AutomaticAuthentication = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task<AuthenticationTicket> HandleAuthenticateAsync()
|
||||||
|
{
|
||||||
|
AuthCount++;
|
||||||
|
return Task.FromResult(new AuthenticationTicket(null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private class TestHandler : AuthenticationHandler<AuthenticationOptions>
|
private class TestHandler : AuthenticationHandler<AuthenticationOptions>
|
||||||
{
|
{
|
||||||
public TestHandler(string scheme)
|
public TestHandler(string scheme)
|
||||||
|
|
@ -61,7 +106,7 @@ namespace Microsoft.AspNet.Authentication
|
||||||
Options.AuthenticationScheme = scheme;
|
Options.AuthenticationScheme = scheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Task<AuthenticationTicket> AuthenticateAsync()
|
protected override Task<AuthenticationTicket> HandleAuthenticateAsync()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +131,7 @@ namespace Microsoft.AspNet.Authentication
|
||||||
Options.AutomaticAuthentication = auto;
|
Options.AutomaticAuthentication = auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Task<AuthenticationTicket> AuthenticateAsync()
|
protected override Task<AuthenticationTicket> HandleAuthenticateAsync()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -371,6 +371,36 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
||||||
FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe(null);
|
FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CookieCanBeRejectedAndSignedOutByValidator()
|
||||||
|
{
|
||||||
|
var clock = new TestClock();
|
||||||
|
var server = CreateServer(options =>
|
||||||
|
{
|
||||||
|
options.SystemClock = clock;
|
||||||
|
options.ExpireTimeSpan = TimeSpan.FromMinutes(10);
|
||||||
|
options.SlidingExpiration = false;
|
||||||
|
options.Notifications = new CookieAuthenticationNotifications
|
||||||
|
{
|
||||||
|
OnValidatePrincipal = ctx =>
|
||||||
|
{
|
||||||
|
ctx.RejectPrincipal();
|
||||||
|
ctx.HttpContext.Authentication.SignOutAsync("Cookies");
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
context =>
|
||||||
|
context.Authentication.SignInAsync("Cookies",
|
||||||
|
new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies")))));
|
||||||
|
|
||||||
|
var transaction1 = await SendAsync(server, "http://example.com/testpath");
|
||||||
|
|
||||||
|
var transaction2 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
|
||||||
|
transaction2.SetCookie.ShouldContain(".AspNet.Cookies=; expires=");
|
||||||
|
FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe(null);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CookieCanBeRenewedByValidator()
|
public async Task CookieCanBeRenewedByValidator()
|
||||||
{
|
{
|
||||||
|
|
@ -671,6 +701,149 @@ namespace Microsoft.AspNet.Authentication.Cookies
|
||||||
transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized);
|
transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MapWillNotAffectChallenge()
|
||||||
|
{
|
||||||
|
var server = TestServer.Create(app =>
|
||||||
|
{
|
||||||
|
app.UseCookieAuthentication(options => options.LoginPath = new PathString("/page"));
|
||||||
|
app.Map("/login", signoutApp => signoutApp.Run(context => context.Authentication.ChallengeAsync("Cookies", new AuthenticationProperties() { RedirectUri = "/" })));
|
||||||
|
},
|
||||||
|
services => services.AddAuthentication());
|
||||||
|
|
||||||
|
var transaction = await server.SendAsync("http://example.com/login");
|
||||||
|
|
||||||
|
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||||
|
|
||||||
|
var location = transaction.Response.Headers.Location;
|
||||||
|
location.LocalPath.ShouldBe("/page");
|
||||||
|
location.Query.ShouldBe("?ReturnUrl=%2F");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MapWithSignInOnlyRedirectToReturnUrlOnLoginPath()
|
||||||
|
{
|
||||||
|
var server = TestServer.Create(app =>
|
||||||
|
{
|
||||||
|
app.UseCookieAuthentication(options => options.LoginPath = new PathString("/login"));
|
||||||
|
app.Map("/notlogin", signoutApp => signoutApp.Run(context => context.Authentication.SignInAsync("Cookies",
|
||||||
|
new ClaimsPrincipal())));
|
||||||
|
},
|
||||||
|
services => services.AddAuthentication());
|
||||||
|
|
||||||
|
var transaction = await server.SendAsync("http://example.com/notlogin?ReturnUrl=%2Fpage");
|
||||||
|
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||||
|
transaction.SetCookie.ShouldNotBe(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MapWillNotAffectSignInRedirectToReturnUrl()
|
||||||
|
{
|
||||||
|
var server = TestServer.Create(app =>
|
||||||
|
{
|
||||||
|
app.UseCookieAuthentication(options => options.LoginPath = new PathString("/login"));
|
||||||
|
app.Map("/login", signoutApp => signoutApp.Run(context => context.Authentication.SignInAsync("Cookies",
|
||||||
|
new ClaimsPrincipal())));
|
||||||
|
},
|
||||||
|
services => services.AddAuthentication());
|
||||||
|
|
||||||
|
var transaction = await server.SendAsync("http://example.com/login?ReturnUrl=%2Fpage");
|
||||||
|
|
||||||
|
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||||
|
transaction.SetCookie.ShouldNotBe(null);
|
||||||
|
|
||||||
|
var location = transaction.Response.Headers.Location;
|
||||||
|
location.OriginalString.ShouldBe("/page");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MapWithSignOutOnlyRedirectToReturnUrlOnLogoutPath()
|
||||||
|
{
|
||||||
|
var server = TestServer.Create(app =>
|
||||||
|
{
|
||||||
|
app.UseCookieAuthentication(options => options.LogoutPath = new PathString("/logout"));
|
||||||
|
app.Map("/notlogout", signoutApp => signoutApp.Run(context => context.Authentication.SignOutAsync("Cookies")));
|
||||||
|
},
|
||||||
|
services => services.AddAuthentication());
|
||||||
|
|
||||||
|
var transaction = await server.SendAsync("http://example.com/notlogout?ReturnUrl=%2Fpage");
|
||||||
|
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||||
|
transaction.SetCookie[0].ShouldContain(".AspNet.Cookies=; expires=");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MapWillNotAffectSignOutRedirectToReturnUrl()
|
||||||
|
{
|
||||||
|
var server = TestServer.Create(app =>
|
||||||
|
{
|
||||||
|
app.UseCookieAuthentication(options => options.LogoutPath = new PathString("/logout"));
|
||||||
|
app.Map("/logout", signoutApp => signoutApp.Run(context => context.Authentication.SignOutAsync("Cookies")));
|
||||||
|
},
|
||||||
|
services => services.AddAuthentication());
|
||||||
|
|
||||||
|
var transaction = await server.SendAsync("http://example.com/logout?ReturnUrl=%2Fpage");
|
||||||
|
|
||||||
|
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||||
|
transaction.SetCookie[0].ShouldContain(".AspNet.Cookies=; expires=");
|
||||||
|
|
||||||
|
var location = transaction.Response.Headers.Location;
|
||||||
|
location.OriginalString.ShouldBe("/page");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MapWillNotAffectAccessDenied()
|
||||||
|
{
|
||||||
|
var server = TestServer.Create(app =>
|
||||||
|
{
|
||||||
|
app.UseCookieAuthentication(options => options.AccessDeniedPath = new PathString("/denied"));
|
||||||
|
app.Map("/forbid", signoutApp => signoutApp.Run(context => context.Authentication.ForbidAsync("Cookies")));
|
||||||
|
},
|
||||||
|
services => services.AddAuthentication());
|
||||||
|
var transaction = await server.SendAsync("http://example.com/forbid");
|
||||||
|
|
||||||
|
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||||
|
|
||||||
|
var location = transaction.Response.Headers.Location;
|
||||||
|
location.LocalPath.ShouldBe("/denied");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task NestedMapWillNotAffectLogin()
|
||||||
|
{
|
||||||
|
var server = TestServer.Create(app =>
|
||||||
|
app.Map("/base", map =>
|
||||||
|
{
|
||||||
|
map.UseCookieAuthentication(options => options.LoginPath = new PathString("/page"));
|
||||||
|
map.Map("/login", signoutApp => signoutApp.Run(context => context.Authentication.ChallengeAsync("Cookies", new AuthenticationProperties() { RedirectUri = "/" })));
|
||||||
|
}),
|
||||||
|
services => services.AddAuthentication());
|
||||||
|
var transaction = await server.SendAsync("http://example.com/base/login");
|
||||||
|
|
||||||
|
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||||
|
|
||||||
|
var location = transaction.Response.Headers.Location;
|
||||||
|
location.LocalPath.ShouldBe("/base/page");
|
||||||
|
location.Query.ShouldBe("?ReturnUrl=%2F");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task NestedMapWillNotAffectAccessDenied()
|
||||||
|
{
|
||||||
|
var server = TestServer.Create(app =>
|
||||||
|
app.Map("/base", map =>
|
||||||
|
{
|
||||||
|
map.UseCookieAuthentication(options => options.AccessDeniedPath = new PathString("/denied"));
|
||||||
|
map.Map("/forbid", signoutApp => signoutApp.Run(context => context.Authentication.ForbidAsync("Cookies")));
|
||||||
|
}),
|
||||||
|
services => services.AddAuthentication());
|
||||||
|
var transaction = await server.SendAsync("http://example.com/base/forbid");
|
||||||
|
|
||||||
|
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||||
|
|
||||||
|
var location = transaction.Response.Headers.Location;
|
||||||
|
location.LocalPath.ShouldBe("/base/denied");
|
||||||
|
}
|
||||||
|
|
||||||
private static string FindClaimValue(Transaction transaction, string claimType)
|
private static string FindClaimValue(Transaction transaction, string claimType)
|
||||||
{
|
{
|
||||||
var claim = transaction.ResponseElement.Elements("claim").SingleOrDefault(elt => elt.Attribute("type").Value == claimType);
|
var claim = transaction.ResponseElement.Elements("claim").SingleOrDefault(elt => elt.Attribute("type").Value == claimType);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// 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.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
|
@ -14,7 +15,7 @@ namespace Microsoft.AspNet.Authentication.DataHandler.Encoder
|
||||||
public class TicketSerializerTests
|
public class TicketSerializerTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanRoundTripNullPrincipal()
|
public void NullPrincipalThrows()
|
||||||
{
|
{
|
||||||
var properties = new AuthenticationProperties();
|
var properties = new AuthenticationProperties();
|
||||||
properties.RedirectUri = "bye";
|
properties.RedirectUri = "bye";
|
||||||
|
|
@ -24,12 +25,7 @@ namespace Microsoft.AspNet.Authentication.DataHandler.Encoder
|
||||||
using (var writer = new BinaryWriter(stream))
|
using (var writer = new BinaryWriter(stream))
|
||||||
using (var reader = new BinaryReader(stream))
|
using (var reader = new BinaryReader(stream))
|
||||||
{
|
{
|
||||||
TicketSerializer.Write(writer, ticket);
|
Assert.Throws<ArgumentNullException>(() => TicketSerializer.Write(writer, ticket));
|
||||||
stream.Position = 0;
|
|
||||||
var readTicket = TicketSerializer.Read(reader);
|
|
||||||
readTicket.Principal.ShouldBe(null);
|
|
||||||
readTicket.Properties.RedirectUri.ShouldBe("bye");
|
|
||||||
readTicket.AuthenticationScheme.ShouldBe("Hello");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue