120 lines
4.8 KiB
C#
120 lines
4.8 KiB
C#
using System;
|
|
using System.Security.Claims;
|
|
using System.Threading.Tasks;
|
|
using System.Web;
|
|
using Microsoft.AspNet.Identity;
|
|
using Microsoft.AspNet.Identity.Owin;
|
|
using Microsoft.Owin;
|
|
using Microsoft.Owin.Infrastructure;
|
|
using Microsoft.Owin.Security.Cookies;
|
|
using Umbraco.Core;
|
|
using Umbraco.Core.Configuration;
|
|
using Umbraco.Core.Security;
|
|
using Constants = Umbraco.Core.Constants;
|
|
|
|
namespace Umbraco.Web.Security
|
|
{
|
|
/// <summary>
|
|
/// Static helper class used to configure a CookieAuthenticationProvider to validate a cookie against a user's session id
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This uses another cookie to track the last checked time which is done for a few reasons:
|
|
/// * We can't use the user's auth ticket to do this because we'd be re-issuing the auth ticket all of the time and it would never expire
|
|
/// plus the auth ticket size is much larger than this small value
|
|
/// * This will execute quite often (every minute per user) and in some cases there might be several requests that end up re-issuing the cookie so the cookie value should be small
|
|
/// * We want to avoid the user lookup if it's not required so that will only happen when the time diff is great enough in the cookie
|
|
/// </remarks>
|
|
internal static class SessionIdValidator
|
|
{
|
|
public const string CookieName = "UMB_UCONTEXT_C";
|
|
|
|
public static async Task ValidateSessionAsync(TimeSpan validateInterval, CookieValidateIdentityContext context, IGlobalSettings globalSettings)
|
|
{
|
|
if (context.Request.Uri.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath, globalSettings) == false)
|
|
return;
|
|
|
|
var valid = await ValidateSessionAsync(validateInterval, context.OwinContext, context.Options.CookieManager, context.Options.SystemClock, context.Properties.IssuedUtc, context.Identity, globalSettings);
|
|
|
|
if (valid == false)
|
|
{
|
|
context.RejectIdentity();
|
|
context.OwinContext.Authentication.SignOut(context.Options.AuthenticationType);
|
|
}
|
|
}
|
|
|
|
public static async Task<bool> ValidateSessionAsync(
|
|
TimeSpan validateInterval,
|
|
IOwinContext owinCtx,
|
|
ICookieManager cookieManager,
|
|
ISystemClock systemClock,
|
|
DateTimeOffset? authTicketIssueDate,
|
|
ClaimsIdentity currentIdentity,
|
|
IGlobalSettings globalSettings)
|
|
{
|
|
if (owinCtx == null) throw new ArgumentNullException("owinCtx");
|
|
if (cookieManager == null) throw new ArgumentNullException("cookieManager");
|
|
if (systemClock == null) throw new ArgumentNullException("systemClock");
|
|
|
|
DateTimeOffset? issuedUtc = null;
|
|
var currentUtc = systemClock.UtcNow;
|
|
|
|
//read the last checked time from a custom cookie
|
|
var lastCheckedCookie = cookieManager.GetRequestCookie(owinCtx, CookieName);
|
|
|
|
if (lastCheckedCookie.IsNullOrWhiteSpace() == false)
|
|
{
|
|
DateTimeOffset parsed;
|
|
if (DateTimeOffset.TryParse(lastCheckedCookie, out parsed))
|
|
{
|
|
issuedUtc = parsed;
|
|
}
|
|
}
|
|
|
|
//no cookie, use the issue time of the auth ticket
|
|
if (issuedUtc.HasValue == false)
|
|
{
|
|
issuedUtc = authTicketIssueDate;
|
|
}
|
|
|
|
// Only validate if enough time has elapsed
|
|
var validate = issuedUtc.HasValue == false;
|
|
if (issuedUtc.HasValue)
|
|
{
|
|
var timeElapsed = currentUtc.Subtract(issuedUtc.Value);
|
|
validate = timeElapsed > validateInterval;
|
|
}
|
|
|
|
if (validate == false)
|
|
return true;
|
|
|
|
var manager = owinCtx.GetUserManager<BackOfficeUserManager>();
|
|
if (manager == null)
|
|
return false;
|
|
|
|
var userId = currentIdentity.GetUserId<int>();
|
|
var user = await manager.FindByIdAsync(userId);
|
|
if (user == null)
|
|
return false;
|
|
|
|
var sessionId = currentIdentity.FindFirstValue(Constants.Security.SessionIdClaimType);
|
|
if (await manager.ValidateSessionIdAsync(userId, sessionId) == false)
|
|
return false;
|
|
|
|
//we will re-issue the cookie last checked cookie
|
|
cookieManager.AppendResponseCookie(
|
|
owinCtx,
|
|
CookieName,
|
|
DateTimeOffset.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffffffzzz"),
|
|
new CookieOptions
|
|
{
|
|
HttpOnly = true,
|
|
Secure = globalSettings.UseHttps || owinCtx.Request.IsSecure,
|
|
Path = "/"
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
}
|
|
}
|