aspnetcore/src/Microsoft.AspNet.Security.C.../CookieAuthenticationHandler.cs

250 lines
8.9 KiB
C#

// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Security.Infrastructure;
using Microsoft.AspNet.Logging;
using Microsoft.AspNet.PipelineCore.Collections;
using Microsoft.AspNet.Abstractions;
using Microsoft.AspNet.HttpFeature.Security;
namespace Microsoft.AspNet.Security.Cookies
{
internal class CookieAuthenticationHandler : AuthenticationHandler<CookieAuthenticationOptions>
{
private const string HeaderNameCacheControl = "Cache-Control";
private const string HeaderNamePragma = "Pragma";
private const string HeaderNameExpires = "Expires";
private const string HeaderValueNoCache = "no-cache";
private const string HeaderValueMinusOne = "-1";
private readonly ILogger _logger;
private bool _shouldRenew;
private DateTimeOffset _renewIssuedUtc;
private DateTimeOffset _renewExpiresUtc;
public CookieAuthenticationHandler(ILogger logger)
{
/*
if (logger == null)
{
throw new ArgumentNullException("logger");
}*/
_logger = logger;
}
protected override AuthenticationTicket AuthenticateCore()
{
return AuthenticateCoreAsync().Result;
}
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{
IReadableStringCollection cookies = Request.Cookies;
string cookie = cookies[Options.CookieName];
if (string.IsNullOrWhiteSpace(cookie))
{
return null;
}
AuthenticationTicket ticket = Options.TicketDataFormat.Unprotect(cookie);
if (ticket == null)
{
// TODO: _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)
{
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.Provider.ValidateIdentity(context);
return new AuthenticationTicket(context.Identity, context.Properties);
}
protected override void ApplyResponseGrant()
{
ApplyResponseGrantAsync().Wait();
}
protected override async Task ApplyResponseGrantAsync()
{
var signin = SignInIdentityContext;
bool shouldSignin = signin != null;
var signout = SignOutContext;
bool shouldSignout = signout != null;
if (shouldSignin || shouldSignout || _shouldRenew)
{
var cookieOptions = new CookieOptions
{
Domain = Options.CookieDomain,
HttpOnly = Options.CookieHttpOnly,
Path = Options.CookiePath ?? "/",
};
if (Options.CookieSecure == CookieSecureOption.SameAsRequest)
{
cookieOptions.Secure = Request.IsSecure;
}
else
{
cookieOptions.Secure = Options.CookieSecure == CookieSecureOption.Always;
}
if (shouldSignin)
{
var context = new CookieResponseSignInContext(
Context,
Options,
Options.AuthenticationType,
signin.Identity,
signin.Properties,
cookieOptions);
DateTimeOffset issuedUtc = Options.SystemClock.UtcNow;
DateTimeOffset expiresUtc = issuedUtc.Add(Options.ExpireTimeSpan);
context.Properties.IssuedUtc = issuedUtc;
context.Properties.ExpiresUtc = expiresUtc;
Options.Provider.ResponseSignIn(context);
if (context.Properties.IsPersistent)
{
cookieOptions.Expires = expiresUtc.ToUniversalTime().DateTime;
}
var model = new AuthenticationTicket(context.Identity, context.Properties);
string cookieValue = Options.TicketDataFormat.Protect(model);
Response.Cookies.Append(
Options.CookieName,
cookieValue,
cookieOptions);
}
else if (shouldSignout)
{
var context = new CookieResponseSignOutContext(
Context,
Options,
cookieOptions);
Options.Provider.ResponseSignOut(context);
Response.Cookies.Delete(
Options.CookieName,
cookieOptions);
}
else if (_shouldRenew)
{
AuthenticationTicket model = await AuthenticateAsync();
model.Properties.IssuedUtc = _renewIssuedUtc;
model.Properties.ExpiresUtc = _renewExpiresUtc;
string cookieValue = Options.TicketDataFormat.Protect(model);
if (model.Properties.IsPersistent)
{
cookieOptions.Expires = _renewExpiresUtc.ToUniversalTime().DateTime;
}
Response.Cookies.Append(
Options.CookieName,
cookieValue,
cookieOptions);
}
Response.Headers.Set(
HeaderNameCacheControl,
HeaderValueNoCache);
Response.Headers.Set(
HeaderNamePragma,
HeaderValueNoCache);
Response.Headers.Set(
HeaderNameExpires,
HeaderValueMinusOne);
bool shouldLoginRedirect = shouldSignin && Options.LoginPath.HasValue && Request.Path == Options.LoginPath;
bool shouldLogoutRedirect = shouldSignout && Options.LogoutPath.HasValue && Request.Path == Options.LogoutPath;
if ((shouldLoginRedirect || shouldLogoutRedirect) && Response.StatusCode == 200)
{
IReadableStringCollection query = Request.Query;
string redirectUri = query.Get(Options.ReturnUrlParameter);
if (!string.IsNullOrWhiteSpace(redirectUri)
&& IsHostRelative(redirectUri))
{
var redirectContext = new CookieApplyRedirectContext(Context, Options, redirectUri);
Options.Provider.ApplyRedirect(redirectContext);
}
}
}
}
private static bool IsHostRelative(string path)
{
if (string.IsNullOrEmpty(path))
{
return false;
}
if (path.Length == 1)
{
return path[0] == '/';
}
return path[0] == '/' && path[1] != '/' && path[1] != '\\';
}
protected override void ApplyResponseChallenge()
{
if (Response.StatusCode != 401 || !Options.LoginPath.HasValue || ChallengeContext == null)
{
return;
}
string currentUri =
Request.PathBase +
Request.Path +
Request.QueryString;
string loginUri =
Request.Scheme +
"://" +
Request.Host +
Request.PathBase +
Options.LoginPath +
new QueryString(Options.ReturnUrlParameter, currentUri);
var redirectContext = new CookieApplyRedirectContext(Context, Options, loginUri);
Options.Provider.ApplyRedirect(redirectContext);
}
}
}