#32 - Port Cookies OnException notification from Katana.

This commit is contained in:
Chris Ross 2014-09-09 16:12:19 -07:00
parent 83bffe3542
commit b10cda80a7
4 changed files with 210 additions and 63 deletions

View File

@ -41,69 +41,84 @@ namespace Microsoft.AspNet.Security.Cookies
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{
string cookie = Options.CookieManager.GetRequestCookie(Context, Options.CookieName);
if (string.IsNullOrWhiteSpace(cookie))
AuthenticationTicket ticket = null;
try
{
return null;
}
AuthenticationTicket ticket = Options.TicketDataFormat.Unprotect(cookie);
if (ticket == null)
{
_logger.WriteWarning(@"Unprotect ticket failed");
return null;
}
if (Options.SessionStore != null)
{
Claim claim = ticket.Identity.Claims.FirstOrDefault(c => c.Type.Equals(SessionIdClaim));
if (claim == null)
string cookie = Options.CookieManager.GetRequestCookie(Context, Options.CookieName);
if (string.IsNullOrWhiteSpace(cookie))
{
_logger.WriteWarning(@"SessoinId missing");
return null;
}
_sessionKey = claim.Value;
ticket = await Options.SessionStore.RetrieveAsync(_sessionKey);
ticket = Options.TicketDataFormat.Unprotect(cookie);
if (ticket == null)
{
_logger.WriteWarning(@"Identity missing in session store");
_logger.WriteWarning(@"Unprotect ticket failed");
return null;
}
}
DateTimeOffset currentUtc = Options.SystemClock.UtcNow;
DateTimeOffset? issuedUtc = ticket.Properties.IssuedUtc;
DateTimeOffset? expiresUtc = ticket.Properties.ExpiresUtc;
if (expiresUtc != null && expiresUtc.Value < currentUtc)
{
if (Options.SessionStore != null)
{
await Options.SessionStore.RemoveAsync(_sessionKey);
Claim claim = ticket.Identity.Claims.FirstOrDefault(c => c.Type.Equals(SessionIdClaim));
if (claim == null)
{
_logger.WriteWarning(@"SessoinId missing");
return null;
}
_sessionKey = claim.Value;
ticket = await Options.SessionStore.RetrieveAsync(_sessionKey);
if (ticket == null)
{
_logger.WriteWarning(@"Identity missing in session store");
return null;
}
}
return null;
}
if (issuedUtc != null && expiresUtc != null && Options.SlidingExpiration)
{
TimeSpan timeElapsed = currentUtc.Subtract(issuedUtc.Value);
TimeSpan timeRemaining = expiresUtc.Value.Subtract(currentUtc);
DateTimeOffset currentUtc = Options.SystemClock.UtcNow;
DateTimeOffset? issuedUtc = ticket.Properties.IssuedUtc;
DateTimeOffset? expiresUtc = ticket.Properties.ExpiresUtc;
if (timeRemaining < timeElapsed)
if (expiresUtc != null && expiresUtc.Value < currentUtc)
{
_shouldRenew = true;
_renewIssuedUtc = currentUtc;
TimeSpan timeSpan = expiresUtc.Value.Subtract(issuedUtc.Value);
_renewExpiresUtc = currentUtc.Add(timeSpan);
if (Options.SessionStore != null)
{
await Options.SessionStore.RemoveAsync(_sessionKey);
}
return null;
}
if (issuedUtc != null && expiresUtc != null && Options.SlidingExpiration)
{
TimeSpan timeElapsed = currentUtc.Subtract(issuedUtc.Value);
TimeSpan timeRemaining = expiresUtc.Value.Subtract(currentUtc);
if (timeRemaining < timeElapsed)
{
_shouldRenew = true;
_renewIssuedUtc = currentUtc;
TimeSpan timeSpan = expiresUtc.Value.Subtract(issuedUtc.Value);
_renewExpiresUtc = currentUtc.Add(timeSpan);
}
}
var context = new CookieValidateIdentityContext(Context, ticket, Options);
await Options.Notifications.ValidateIdentity(context);
return new AuthenticationTicket(context.Identity, context.Properties);
}
catch (Exception exception)
{
CookieExceptionContext exceptionContext = new CookieExceptionContext(Context, Options,
CookieExceptionContext.ExceptionLocation.Authenticate, exception, ticket);
Options.Notifications.Exception(exceptionContext);
if (exceptionContext.Rethrow)
{
throw;
}
return exceptionContext.Ticket;
}
var context = new CookieValidateIdentityContext(Context, ticket, Options);
await Options.Notifications.ValidateIdentity(context);
return new AuthenticationTicket(context.Identity, context.Properties);
}
protected override void ApplyResponseGrant()
@ -118,9 +133,14 @@ namespace Microsoft.AspNet.Security.Cookies
var signout = SignOutContext;
bool shouldSignout = signout != null;
if (shouldSignin || shouldSignout || _shouldRenew)
if (!(shouldSignin || shouldSignout || _shouldRenew))
{
return;
}
AuthenticationTicket model = await AuthenticateAsync();
try
{
AuthenticationTicket model = await AuthenticateAsync();
var cookieOptions = new CookieOptions
{
Domain = Options.CookieDomain,
@ -274,6 +294,16 @@ namespace Microsoft.AspNet.Security.Cookies
}
}
}
catch (Exception exception)
{
CookieExceptionContext exceptionContext = new CookieExceptionContext(Context, Options,
CookieExceptionContext.ExceptionLocation.ApplyResponseGrant, exception, model);
Options.Notifications.Exception(exceptionContext);
if (exceptionContext.Rethrow)
{
throw;
}
}
}
private static bool IsHostRelative(string path)
@ -308,24 +338,37 @@ namespace Microsoft.AspNet.Security.Cookies
loginUri = new AuthenticationProperties(ChallengeContext.Properties).RedirectUri;
}
if (string.IsNullOrWhiteSpace(loginUri))
try
{
string currentUri =
Request.PathBase +
Request.Path +
Request.QueryString;
if (string.IsNullOrWhiteSpace(loginUri))
{
string currentUri =
Request.PathBase +
Request.Path +
Request.QueryString;
loginUri =
Request.Scheme +
"://" +
Request.Host +
Request.PathBase +
Options.LoginPath +
new QueryString(Options.ReturnUrlParameter, currentUri);
loginUri =
Request.Scheme +
"://" +
Request.Host +
Request.PathBase +
Options.LoginPath +
new QueryString(Options.ReturnUrlParameter, currentUri);
}
var redirectContext = new CookieApplyRedirectContext(Context, Options, loginUri);
Options.Notifications.ApplyRedirect(redirectContext);
}
catch (Exception exception)
{
CookieExceptionContext exceptionContext = new CookieExceptionContext(Context, Options,
CookieExceptionContext.ExceptionLocation.ApplyResponseChallenge, exception, ticket: null);
Options.Notifications.Exception(exceptionContext);
if (exceptionContext.Rethrow)
{
throw;
}
}
var redirectContext = new CookieApplyRedirectContext(Context, Options, loginUri);
Options.Notifications.ApplyRedirect(redirectContext);
}
}
}

View File

@ -24,6 +24,7 @@ namespace Microsoft.AspNet.Security.Cookies
OnResponseSignedIn = context => { };
OnResponseSignOut = context => { };
OnApplyRedirect = DefaultBehavior.ApplyRedirect;
OnException = context => { };
}
/// <summary>
@ -51,6 +52,11 @@ namespace Microsoft.AspNet.Security.Cookies
/// </summary>
public Action<CookieApplyRedirectContext> OnApplyRedirect { get; set; }
/// <summary>
/// A delegate assigned to this property will be invoked when the related method is called
/// </summary>
public Action<CookieExceptionContext> OnException { get; set; }
/// <summary>
/// Implements the interface method by invoking the related delegate method
/// </summary>
@ -96,5 +102,14 @@ namespace Microsoft.AspNet.Security.Cookies
{
OnApplyRedirect.Invoke(context);
}
/// <summary>
/// Implements the interface method by invoking the related delegate method
/// </summary>
/// <param name="context">Contains information about the event</param>
public virtual void Exception(CookieExceptionContext context)
{
OnException.Invoke(context);
}
}
}

View File

@ -0,0 +1,83 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Security.Notifications;
namespace Microsoft.AspNet.Security.Cookies
{
/// <summary>
/// Context object passed to the ICookieAuthenticationProvider method Exception.
/// </summary>
public class CookieExceptionContext : BaseContext<CookieAuthenticationOptions>
{
/// <summary>
/// Creates a new instance of the context object.
/// </summary>
/// <param name="context">The HTTP request context</param>
/// <param name="options">The middleware options</param>
/// <param name="location">The location of the exception</param>
/// <param name="exception">The exception thrown.</param>
/// <param name="ticket">The current ticket, if any.</param>
public CookieExceptionContext(
HttpContext context,
CookieAuthenticationOptions options,
ExceptionLocation location,
Exception exception,
AuthenticationTicket ticket)
: base(context, options)
{
Location = location;
Exception = exception;
Rethrow = true;
Ticket = ticket;
}
/// <summary>
/// The code paths where exceptions may be reported.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Scope = "type",
Target = "Microsoft.Owin.Security.Cookies.CookieExceptionContext+ExceptionLocation", Justification = "It is a directly related option.")]
public enum ExceptionLocation
{
/// <summary>
/// The exception was reported in the Authenticate code path.
/// </summary>
Authenticate,
/// <summary>
/// The exception was reported in the ApplyResponseGrant code path, during sign-in, sign-out, or refresh.
/// </summary>
ApplyResponseGrant,
/// <summary>
/// The exception was reported in the ApplyResponseChallenge code path, during redirect generation.
/// </summary>
ApplyResponseChallenge,
}
/// <summary>
/// The code path the exception occurred in.
/// </summary>
public ExceptionLocation Location { get; private set; }
/// <summary>
/// The exception thrown.
/// </summary>
public Exception Exception { get; private set; }
/// <summary>
/// True if the exception should be re-thrown (default), false if it should be suppressed.
/// </summary>
public bool Rethrow { get; set; }
/// <summary>
/// The current authentication ticket, if any.
/// In the AuthenticateAsync code path, if the given exception is not re-thrown then this ticket
/// will be returned to the application. The ticket may be replaced if needed.
/// </summary>
public AuthenticationTicket Ticket { get; set; }
}
}

View File

@ -43,5 +43,11 @@ namespace Microsoft.AspNet.Security.Cookies
/// </summary>
/// <param name="context">Contains information about the login session as well as information about the authentication cookie.</param>
void ResponseSignOut(CookieResponseSignOutContext context);
/// <summary>
/// Called when an exception occurs during request or response processing.
/// </summary>
/// <param name="context">Contains information about the exception that occurred</param>
void Exception(CookieExceptionContext context);
}
}