diff --git a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs index f608edaca4..4420e1983b 100644 --- a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs +++ b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs @@ -118,33 +118,45 @@ namespace Umbraco.Web.Security.Identity { if (app == null) throw new ArgumentNullException("app"); if (appContext == null) throw new ArgumentNullException("appContext"); - + + var cookieAuthProvider = new BackOfficeCookieAuthenticationProvider + { + // Enables the application to validate the security stamp when the user + // logs in. This is a security feature which is used when you + // change a password or add an external login to your account. + OnValidateIdentity = SecurityStampValidator + .OnValidateIdentity( + TimeSpan.FromMinutes(30), + (manager, user) => user.GenerateUserIdentityAsync(manager), + identity => identity.GetUserId()), + }; + var authOptions = new UmbracoBackOfficeCookieAuthOptions( UmbracoConfig.For.UmbracoSettings().Security, GlobalSettings.TimeOutInMinutes, GlobalSettings.UseSSL) { - Provider = new BackOfficeCookieAuthenticationProvider - { - // Enables the application to validate the security stamp when the user - // logs in. This is a security feature which is used when you - // change a password or add an external login to your account. - OnValidateIdentity = SecurityStampValidator - .OnValidateIdentity( - TimeSpan.FromMinutes(30), - (manager, user) => user.GenerateUserIdentityAsync(manager), - identity => identity.GetUserId()), - } + Provider = cookieAuthProvider }; - + app.UseUmbracoBackOfficeCookieAuthentication(authOptions, appContext); //don't apply if app isnot ready if (appContext.IsUpgrading || appContext.IsConfigured) { + var getSecondsOptions = new UmbracoBackOfficeCookieAuthOptions( + //This defines the explicit path read cookies from for this middleware + new[]{string.Format("{0}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds", GlobalSettings.Path)}, + UmbracoConfig.For.UmbracoSettings().Security, + GlobalSettings.TimeOutInMinutes, + GlobalSettings.UseSSL) + { + Provider = cookieAuthProvider + }; + //This is a custom middleware, we need to return the user's remaining logged in seconds app.Use( - authOptions, + getSecondsOptions, UmbracoConfig.For.UmbracoSettings().Security, app.CreateLogger()); } diff --git a/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs b/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs index c78a918fd9..76bd80037c 100644 --- a/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs +++ b/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs @@ -1,8 +1,11 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Web; using Microsoft.Owin; using Microsoft.Owin.Infrastructure; using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Core.IO; namespace Umbraco.Web.Security.Identity @@ -17,10 +20,19 @@ namespace Umbraco.Web.Security.Identity internal class BackOfficeCookieManager : ChunkingCookieManager, ICookieManager { private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly string[] _explicitPaths; + private readonly string _getRemainingSecondsPath; public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor) + : this(umbracoContextAccessor, null) + { + + } + public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor, IEnumerable explicitPaths) { _umbracoContextAccessor = umbracoContextAccessor; + _explicitPaths = explicitPaths == null ? null : explicitPaths.ToArray(); + _getRemainingSecondsPath = string.Format("{0}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds", GlobalSettings.Path); } /// @@ -64,9 +76,18 @@ namespace Umbraco.Web.Security.Identity var request = ctx.Request; var httpCtx = ctx.TryGetHttpContext(); + //check the explicit paths + if (_explicitPaths != null) + { + return _explicitPaths.Any(x => x.InvariantEquals(request.Uri.AbsolutePath)); + } + + //check user seconds path + if (request.Uri.AbsolutePath.InvariantEquals(_getRemainingSecondsPath)) return false; + if (//check the explicit flag (checkForceAuthTokens && ctx.Get("umbraco-force-auth") != null) - || (checkForceAuthTokens && httpCtx.Success && httpCtx.Result.Items["umbraco-force-auth"] != null) + || (checkForceAuthTokens && httpCtx.Success && httpCtx.Result.Items["umbraco-force-auth"] != null) //check back office || request.Uri.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath) //check installer diff --git a/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs b/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs index 814520a49c..8ec49c4681 100644 --- a/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs +++ b/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs @@ -24,7 +24,6 @@ namespace Umbraco.Web.Security.Identity private readonly UmbracoBackOfficeCookieAuthOptions _authOptions; private readonly ISecuritySection _security; private readonly ILogger _logger; - private const int PersistentLoginSlidingMinutes = 30; public GetUserSecondsMiddleWare( OwinMiddleware next, @@ -44,14 +43,10 @@ namespace Umbraco.Web.Security.Identity { var request = context.Request; var response = context.Response; - - var rootPath = context.Request.PathBase.HasValue - ? context.Request.PathBase.Value.EnsureStartsWith("/").EnsureEndsWith("/") - : "/"; - + if (request.Uri.Scheme.InvariantStartsWith("http") && request.Uri.AbsolutePath.InvariantEquals( - string.Format("{0}{1}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds", rootPath, GlobalSettings.UmbracoMvcArea))) + string.Format("{0}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds", GlobalSettings.Path))) { var cookie = _authOptions.CookieManager.GetRequestCookie(context, _security.AuthCookieName); if (cookie.IsNullOrWhiteSpace() == false) @@ -88,7 +83,8 @@ namespace Umbraco.Web.Security.Identity if (timeRemaining < timeElapsed) { ticket.Properties.IssuedUtc = currentUtc; - ticket.Properties.ExpiresUtc = currentUtc.AddMinutes(PersistentLoginSlidingMinutes); + var timeSpan = expiresUtc.Value.Subtract(issuedUtc.Value); + ticket.Properties.ExpiresUtc = currentUtc.Add(timeSpan); var cookieValue = _authOptions.TicketDataFormat.Protect(ticket); diff --git a/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs b/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs index 397b438f35..2b2ecb4295 100644 --- a/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs +++ b/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs @@ -21,35 +21,13 @@ namespace Umbraco.Web.Security.Identity : this(UmbracoConfig.For.UmbracoSettings().Security, GlobalSettings.TimeOutInMinutes, GlobalSettings.UseSSL) { } - - public CookieOptions CreateRequestCookieOptions(IOwinContext ctx, AuthenticationTicket ticket) - { - if (ctx == null) throw new ArgumentNullException("ctx"); - if (ticket == null) throw new ArgumentNullException("ticket"); - - var cookieOptions = new CookieOptions - { - Path = "/", - Domain = this.CookieDomain ?? null, - Expires = DateTime.Now.AddMinutes(30), - HttpOnly = true, - Secure = this.CookieSecure == CookieSecureOption.Always - || (this.CookieSecure == CookieSecureOption.SameAsRequest && ctx.Request.IsSecure), - }; - - if (ticket.Properties.IsPersistent && ticket.Properties.ExpiresUtc.HasValue) - { - cookieOptions.Expires = ticket.Properties.ExpiresUtc.Value.ToUniversalTime().DateTime; - } - - return cookieOptions; - } - - public UmbracoBackOfficeCookieAuthOptions( - ISecuritySection securitySection, - int loginTimeoutMinutes, - bool forceSsl, - bool useLegacyFormsAuthDataFormat = true) + + public UmbracoBackOfficeCookieAuthOptions( + string[] explicitPaths, + ISecuritySection securitySection, + int loginTimeoutMinutes, + bool forceSsl, + bool useLegacyFormsAuthDataFormat = true) { LoginTimeoutMinutes = loginTimeoutMinutes; AuthenticationType = Constants.Security.BackOfficeAuthenticationType; @@ -57,9 +35,9 @@ namespace Umbraco.Web.Security.Identity if (useLegacyFormsAuthDataFormat) { //If this is not explicitly set it will fall back to the default automatically - TicketDataFormat = new FormsAuthenticationSecureDataFormat(loginTimeoutMinutes); + TicketDataFormat = new FormsAuthenticationSecureDataFormat(loginTimeoutMinutes); } - + SlidingExpiration = true; ExpireTimeSpan = TimeSpan.FromMinutes(LoginTimeoutMinutes); CookieDomain = securitySection.AuthCookieDomain; @@ -69,7 +47,49 @@ namespace Umbraco.Web.Security.Identity CookiePath = "/"; //Custom cookie manager so we can filter requests - CookieManager = new BackOfficeCookieManager(new SingletonUmbracoContextAccessor()); - } + CookieManager = new BackOfficeCookieManager(new SingletonUmbracoContextAccessor(), explicitPaths); + } + + public UmbracoBackOfficeCookieAuthOptions( + ISecuritySection securitySection, + int loginTimeoutMinutes, + bool forceSsl, + bool useLegacyFormsAuthDataFormat = true) + : this(null, securitySection, loginTimeoutMinutes, forceSsl, useLegacyFormsAuthDataFormat) + { + } + + /// + /// Creates the cookie options for saving the auth cookie + /// + /// + /// + /// + public CookieOptions CreateRequestCookieOptions(IOwinContext ctx, AuthenticationTicket ticket) + { + if (ctx == null) throw new ArgumentNullException("ctx"); + if (ticket == null) throw new ArgumentNullException("ticket"); + + var issuedUtc = ticket.Properties.IssuedUtc ?? SystemClock.UtcNow; + var expiresUtc = ticket.Properties.ExpiresUtc ?? issuedUtc.Add(ExpireTimeSpan); + + var cookieOptions = new CookieOptions + { + Path = "/", + Domain = this.CookieDomain ?? null, + HttpOnly = true, + Secure = this.CookieSecure == CookieSecureOption.Always + || (this.CookieSecure == CookieSecureOption.SameAsRequest && ctx.Request.IsSecure), + }; + + if (ticket.Properties.IsPersistent) + { + cookieOptions.Expires = expiresUtc.ToUniversalTime().DateTime; + } + + return cookieOptions; + } + + } } \ No newline at end of file