diff --git a/src/Umbraco.Core/Models/DataTypeDefinition.cs b/src/Umbraco.Core/Models/DataTypeDefinition.cs
index e2a226af3d..5d5b9490a5 100644
--- a/src/Umbraco.Core/Models/DataTypeDefinition.cs
+++ b/src/Umbraco.Core/Models/DataTypeDefinition.cs
@@ -268,11 +268,6 @@ namespace Umbraco.Core.Models
get { return _additionalData; }
}
- ///
- /// Some entities may expose additional data that other's might not, this custom data will be available in this collection
- ///
- public IDictionary AdditionalData { get; private set; }
-
internal override void AddingEntity()
{
base.AddingEntity();
diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs
index 1975fdc5db..e3e2abf42f 100644
--- a/src/Umbraco.Web/Editors/AuthenticationController.cs
+++ b/src/Umbraco.Web/Editors/AuthenticationController.cs
@@ -64,27 +64,7 @@ namespace Umbraco.Web.Editors
return _userManager;
}
}
-
- ///
- /// This is a special method that will return the current users' remaining session seconds, the reason
- /// it is special is because this route is ignored in the UmbracoModule so that the auth ticket doesn't get
- /// updated with a new timeout.
- ///
- ///
- [WebApi.UmbracoAuthorize]
- [ValidateAngularAntiForgeryToken]
- public double GetRemainingTimeoutSeconds()
- {
- var httpContextAttempt = TryGetHttpContext();
- if (httpContextAttempt.Success)
- {
- return httpContextAttempt.Result.GetRemainingAuthSeconds();
- }
-
- //we need an http context
- throw new NotSupportedException("An HttpContext is required for this request");
- }
-
+
[WebApi.UmbracoAuthorize]
[ValidateAngularAntiForgeryToken]
public async Task PostUnLinkLogin(UnLinkLoginModel unlinkLoginModel)
@@ -225,8 +205,16 @@ namespace Umbraco.Web.Editors
owinContext.Authentication.SignOut(Core.Constants.Security.BackOfficeExternalAuthenticationType);
+ var nowUtc = DateTime.Now.ToUniversalTime();
+
owinContext.Authentication.SignIn(
- new AuthenticationProperties() { IsPersistent = isPersistent },
+ new AuthenticationProperties()
+ {
+ IsPersistent = isPersistent,
+ AllowRefresh = true,
+ IssuedUtc = nowUtc,
+ ExpiresUtc = nowUtc.AddMinutes(GlobalSettings.TimeOutInMinutes)
+ },
await user.GenerateUserIdentityAsync(UserManager));
}
diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs
index c25d0e50c0..8e3e05d100 100644
--- a/src/Umbraco.Web/Editors/BackOfficeController.cs
+++ b/src/Umbraco.Web/Editors/BackOfficeController.cs
@@ -493,9 +493,17 @@ namespace Umbraco.Web.Editors
private async Task SignInAsync(BackOfficeIdentityUser user, bool isPersistent)
{
OwinContext.Authentication.SignOut(Core.Constants.Security.BackOfficeExternalAuthenticationType);
-
+
+ var nowUtc = DateTime.Now.ToUniversalTime();
+
OwinContext.Authentication.SignIn(
- new AuthenticationProperties() {IsPersistent = isPersistent},
+ new AuthenticationProperties()
+ {
+ IsPersistent = isPersistent,
+ AllowRefresh = true,
+ IssuedUtc = nowUtc,
+ ExpiresUtc = nowUtc.AddMinutes(GlobalSettings.TimeOutInMinutes)
+ },
await user.GenerateUserIdentityAsync(UserManager));
}
diff --git a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs
index c812384965..a9a884a2e7 100644
--- a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs
+++ b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
@@ -12,6 +11,7 @@ using Microsoft.Owin.Security.Cookies;
using Owin;
using Umbraco.Core;
using Umbraco.Core.Configuration;
+using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Security;
@@ -28,7 +28,7 @@ namespace Umbraco.Web.Security.Identity
public static void SetUmbracoLoggerFactory(this IAppBuilder app)
{
app.SetLoggerFactory(new OwinLoggerFactory());
- }
+ }
#region Backoffice
@@ -38,8 +38,8 @@ namespace Umbraco.Web.Security.Identity
///
///
///
- public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app,
- ApplicationContext appContext,
+ public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app,
+ ApplicationContext appContext,
MembershipProviderBase userMembershipProvider)
{
if (appContext == null) throw new ArgumentNullException("appContext");
@@ -93,7 +93,7 @@ namespace Umbraco.Web.Security.Identity
public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app,
ApplicationContext appContext,
Func, IOwinContext, TManager> userManager)
- where TManager : BackOfficeUserManager
+ where TManager : BackOfficeUserManager
where TUser : BackOfficeIdentityUser
{
if (appContext == null) throw new ArgumentNullException("appContext");
@@ -120,13 +120,13 @@ namespace Umbraco.Web.Security.Identity
//Don't proceed if the app is not ready
if (appContext.IsUpgrading == false && appContext.IsConfigured == false) return app;
- app.UseCookieAuthentication(new UmbracoBackOfficeCookieAuthOptions(
- UmbracoConfig.For.UmbracoSettings().Security,
- GlobalSettings.TimeOutInMinutes,
- GlobalSettings.UseSSL)
+ var authOptions = new UmbracoBackOfficeCookieAuthOptions(
+ UmbracoConfig.For.UmbracoSettings().Security,
+ GlobalSettings.TimeOutInMinutes,
+ GlobalSettings.UseSSL)
{
Provider = new CookieAuthenticationProvider
- {
+ {
// 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.
@@ -136,7 +136,12 @@ namespace Umbraco.Web.Security.Identity
(manager, user) => user.GenerateUserIdentityAsync(manager),
identity => identity.GetUserId())
}
- });
+ };
+
+ //This is a custom middleware, we need to return the user's remaining logged in seconds
+ app.Use(authOptions, UmbracoConfig.For.UmbracoSettings().Security);
+
+ app.UseCookieAuthentication(authOptions);
return app;
}
@@ -171,7 +176,7 @@ namespace Umbraco.Web.Security.Identity
});
return app;
- }
+ }
#endregion
}
diff --git a/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs b/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs
index 7eedf2ecb0..841c94c80c 100644
--- a/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs
+++ b/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs
@@ -32,7 +32,7 @@ namespace Umbraco.Web.Security.Identity
{
return null;
}
-
+
return UmbracoModule.ShouldAuthenticateRequest(
context.HttpContextFromOwinContext().Request,
_umbracoContextAccessor.Value.OriginalRequestUrl) == false
@@ -41,5 +41,6 @@ namespace Umbraco.Web.Security.Identity
//Return the default implementation
: base.GetRequestCookie(context, key);
}
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Security/Identity/FormsAuthenticationSecureDataFormat.cs b/src/Umbraco.Web/Security/Identity/FormsAuthenticationSecureDataFormat.cs
index ba46bba881..77e0fe9faf 100644
--- a/src/Umbraco.Web/Security/Identity/FormsAuthenticationSecureDataFormat.cs
+++ b/src/Umbraco.Web/Security/Identity/FormsAuthenticationSecureDataFormat.cs
@@ -27,12 +27,16 @@ namespace Umbraco.Web.Security.Identity
{
var backofficeIdentity = (UmbracoBackOfficeIdentity)data.Identity;
var userDataString = JsonConvert.SerializeObject(backofficeIdentity.UserData);
-
+
var ticket = new FormsAuthenticationTicket(
5,
data.Identity.Name,
- data.Properties.IssuedUtc.HasValue ? data.Properties.IssuedUtc.Value.LocalDateTime : DateTime.Now,
- data.Properties.ExpiresUtc.HasValue ? data.Properties.ExpiresUtc.Value.LocalDateTime : DateTime.Now.AddMinutes(_loginTimeoutMinutes),
+ data.Properties.IssuedUtc.HasValue
+ ? data.Properties.IssuedUtc.Value.LocalDateTime
+ : DateTime.Now,
+ data.Properties.ExpiresUtc.HasValue
+ ? data.Properties.ExpiresUtc.Value.LocalDateTime
+ : DateTime.Now.AddMinutes(_loginTimeoutMinutes),
data.Properties.IsPersistent,
userDataString,
"/"
@@ -65,7 +69,8 @@ namespace Umbraco.Web.Security.Identity
{
ExpiresUtc = decrypt.Expiration.ToUniversalTime(),
IssuedUtc = decrypt.IssueDate.ToUniversalTime(),
- IsPersistent = decrypt.IsPersistent
+ IsPersistent = decrypt.IsPersistent,
+ AllowRefresh = true
});
return ticket;
diff --git a/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs b/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs
new file mode 100644
index 0000000000..eeebe13f3e
--- /dev/null
+++ b/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Globalization;
+using System.Threading.Tasks;
+using Microsoft.Owin;
+using Microsoft.Owin.Security.Cookies;
+using Umbraco.Core;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Configuration.UmbracoSettings;
+
+namespace Umbraco.Web.Security.Identity
+{
+ ///
+ /// Custom middleware to return the remaining seconds the user has before they are logged out
+ ///
+ ///
+ /// This is quite a custom request because in most situations we just want to return the seconds and don't want
+ /// to renew the auth ticket, however if KeepUserLoggedIn is true, then we do want to renew the auth ticket for
+ /// this request!
+ ///
+ internal class GetUserSecondsMiddleWare : OwinMiddleware
+ {
+ private readonly UmbracoBackOfficeCookieAuthOptions _authOptions;
+ private readonly ISecuritySection _security;
+
+ public GetUserSecondsMiddleWare(
+ OwinMiddleware next,
+ UmbracoBackOfficeCookieAuthOptions authOptions,
+ ISecuritySection security)
+ : base(next)
+ {
+ _authOptions = authOptions;
+ _security = security;
+ }
+
+ public override async Task Invoke(IOwinContext context)
+ {
+ 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)))
+ {
+ var cookie = _authOptions.CookieManager.GetRequestCookie(context, _security.AuthCookieName);
+ if (cookie.IsNullOrWhiteSpace() == false)
+ {
+ var ticket = _authOptions.TicketDataFormat.Unprotect(cookie);
+ if (ticket != null)
+ {
+ var remainingSeconds = ticket.Properties.ExpiresUtc.HasValue
+ ? (ticket.Properties.ExpiresUtc.Value - DateTime.Now.ToUniversalTime()).TotalSeconds
+ : 0;
+
+ response.ContentType = "application/json; charset=utf-8";
+ response.StatusCode = 200;
+ response.Headers.Add("Cache-Control", new[] { "no-cache" });
+ response.Headers.Add("Pragma", new[] { "no-cache" });
+ response.Headers.Add("Expires", new[] { "-1" });
+ response.Headers.Add("Date", new[] { DateTime.Now.ToUniversalTime().ToString("R") });
+
+ //Ok, so here we need to check if we want to process/renew the auth ticket for each
+ // of these requests. If that is the case, the user will really never be logged out until they
+ // close their browser (there will be edge cases of that, especially when debugging)
+ if (_security.KeepUserLoggedIn)
+ {
+ var utcNow = DateTime.Now.ToUniversalTime();
+ ticket.Properties.IssuedUtc = utcNow;
+ ticket.Properties.ExpiresUtc = utcNow.AddMinutes(_authOptions.LoginTimeoutMinutes);
+
+ var cookieValue = _authOptions.TicketDataFormat.Protect(ticket);
+
+ var cookieOptions = new CookieOptions
+ {
+ Path = "/",
+ Domain = _authOptions.CookieDomain,
+ Expires = DateTime.Now.AddMinutes(_authOptions.LoginTimeoutMinutes),
+ HttpOnly = true,
+ Secure = _authOptions.CookieSecure == CookieSecureOption.Always
+ || (_authOptions.CookieSecure == CookieSecureOption.SameAsRequest && request.Uri.Scheme.InvariantEquals("https")),
+ };
+
+ _authOptions.CookieManager.AppendResponseCookie(
+ context,
+ _authOptions.CookieName,
+ cookieValue,
+ cookieOptions);
+ }
+
+ await response.WriteAsync(remainingSeconds.ToString(CultureInfo.InvariantCulture));
+ return;
+ }
+ }
+ response.StatusCode = 401;
+ }
+ else if (Next != null)
+ {
+ await Next.Invoke(context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs b/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs
index 1751a57462..9a55e42b51 100644
--- a/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs
+++ b/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs
@@ -1,4 +1,5 @@
-using System.Security.Claims;
+using System;
+using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Owin.Security.Cookies;
using Umbraco.Core;
@@ -12,6 +13,8 @@ namespace Umbraco.Web.Security.Identity
///
public sealed class UmbracoBackOfficeCookieAuthOptions : CookieAuthenticationOptions
{
+ public int LoginTimeoutMinutes { get; private set; }
+
public UmbracoBackOfficeCookieAuthOptions()
: this(UmbracoConfig.For.UmbracoSettings().Security, GlobalSettings.TimeOutInMinutes, GlobalSettings.UseSSL)
{
@@ -23,6 +26,7 @@ namespace Umbraco.Web.Security.Identity
bool forceSsl,
bool useLegacyFormsAuthDataFormat = true)
{
+ LoginTimeoutMinutes = loginTimeoutMinutes;
AuthenticationType = Constants.Security.BackOfficeAuthenticationType;
if (useLegacyFormsAuthDataFormat)
@@ -30,12 +34,13 @@ namespace Umbraco.Web.Security.Identity
//If this is not explicitly set it will fall back to the default automatically
TicketDataFormat = new FormsAuthenticationSecureDataFormat(loginTimeoutMinutes);
}
-
+
+ SlidingExpiration = true;
+ ExpireTimeSpan = TimeSpan.FromMinutes(GlobalSettings.TimeOutInMinutes);
CookieDomain = securitySection.AuthCookieDomain;
CookieName = securitySection.AuthCookieName;
CookieHttpOnly = true;
CookieSecure = forceSsl ? CookieSecureOption.Always : CookieSecureOption.SameAsRequest;
-
CookiePath = "/";
//Custom cookie manager so we can filter requests
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index f7b877be35..0f1cf90ed6 100644
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -299,6 +299,7 @@
+
diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs
index cd85e82686..f93347aa20 100644
--- a/src/Umbraco.Web/UmbracoModule.cs
+++ b/src/Umbraco.Web/UmbracoModule.cs
@@ -199,41 +199,41 @@ namespace Umbraco.Web
return false;
}
- private static readonly ConcurrentHashSet IgnoreTicketRenewUrls = new ConcurrentHashSet();
- ///
- /// Determines if the authentication ticket should be renewed with a new timeout
- ///
- ///
- ///
- ///
- ///
- /// We do not want to renew the ticket when we are checking for the user's remaining timeout unless -
- /// UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn == true
- ///
- internal static bool ShouldIgnoreTicketRenew(Uri url, HttpContextBase httpContext)
- {
- //this setting will renew the ticket for all requests.
- if (UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn)
- {
- return false;
- }
+ //private static readonly ConcurrentHashSet IgnoreTicketRenewUrls = new ConcurrentHashSet();
+ /////
+ ///// Determines if the authentication ticket should be renewed with a new timeout
+ /////
+ /////
+ /////
+ /////
+ /////
+ ///// We do not want to renew the ticket when we are checking for the user's remaining timeout unless -
+ ///// UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn == true
+ /////
+ //internal static bool ShouldIgnoreTicketRenew(Uri url, HttpContextBase httpContext)
+ //{
+ // //this setting will renew the ticket for all requests.
+ // if (UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn)
+ // {
+ // return false;
+ // }
- //initialize the ignore ticket urls - we don't need to lock this, it's concurrent and a hashset
- // we don't want to have to gen the url each request so this will speed things up a teeny bit.
- if (IgnoreTicketRenewUrls.Any() == false)
- {
- var urlHelper = new UrlHelper(new RequestContext(httpContext, new RouteData()));
- var checkSessionUrl = urlHelper.GetUmbracoApiService(controller => controller.GetRemainingTimeoutSeconds());
- IgnoreTicketRenewUrls.Add(checkSessionUrl);
- }
+ // //initialize the ignore ticket urls - we don't need to lock this, it's concurrent and a hashset
+ // // we don't want to have to gen the url each request so this will speed things up a teeny bit.
+ // if (IgnoreTicketRenewUrls.Any() == false)
+ // {
+ // var urlHelper = new UrlHelper(new RequestContext(httpContext, new RouteData()));
+ // var checkSessionUrl = urlHelper.GetUmbracoApiService(controller => controller.GetRemainingTimeoutSeconds());
+ // IgnoreTicketRenewUrls.Add(checkSessionUrl);
+ // }
- if (IgnoreTicketRenewUrls.Any(x => url.AbsolutePath.StartsWith(x)))
- {
- return true;
- }
+ // if (IgnoreTicketRenewUrls.Any(x => url.AbsolutePath.StartsWith(x)))
+ // {
+ // return true;
+ // }
- return false;
- }
+ // return false;
+ //}
///
/// Checks the current request and ensures that it is routable based on the structure of the request and URI
diff --git a/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs b/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs
index 4517464553..028c835d90 100644
--- a/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs
+++ b/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs
@@ -4,6 +4,7 @@ using Umbraco.Core;
namespace Umbraco.Web.WebApi
{
+
///
/// Ensures authorization is successful for a back office user
///