From b2c5d7270e2dfbfe2f1d5f31410ec11d60f97375 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 1 Nov 2013 15:37:59 +1100 Subject: [PATCH] Fixes: U4-3286 Using a custom aspx page that inherits from UmbracoEnsuredPage seems to log you out - moves the authentication/ticket logic to one central place, now for all base page validation requests if the ticket is not already there it will attempt to authentication the request. This only occurs when a page is being loaded that requires back office authentication but is not part of the umbraco back office route (so packages mainly) --- .../Security/AuthenticationExtensions.cs | 120 +++++++++++++++++- src/Umbraco.Web/Security/WebSecurity.cs | 2 +- src/Umbraco.Web/UmbracoModule.cs | 41 +----- .../BasePages/BasePage.cs | 2 +- 4 files changed, 119 insertions(+), 46 deletions(-) diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs index 2081ecd3dc..71ab76e082 100644 --- a/src/Umbraco.Core/Security/AuthenticationExtensions.cs +++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs @@ -1,4 +1,6 @@ using System; +using System.Security.Principal; +using System.Threading; using System.Web; using System.Web.Security; using Newtonsoft.Json; @@ -11,14 +13,109 @@ namespace Umbraco.Core.Security /// internal static class AuthenticationExtensions { - public static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContextBase http) + /// + /// This will check the ticket to see if it is valid, if it is it will set the current thread's user and culture + /// + /// + /// + /// If true will attempt to renew the ticket + public static bool AuthenticateCurrentRequest(this HttpContextBase http, FormsAuthenticationTicket ticket, bool renewTicket) { - return http.User.Identity as UmbracoBackOfficeIdentity; + if (http == null) throw new ArgumentNullException("http"); + + //if there was a ticket, it's not expired, - it should not be renewed or its renewable + if (ticket != null && ticket.Expired == false && (renewTicket == false || http.RenewUmbracoAuthTicket())) + { + try + { + //create the Umbraco user identity + var identity = new UmbracoBackOfficeIdentity(ticket); + + //set the principal object + var principal = new GenericPrincipal(identity, identity.Roles); + + //It is actually not good enough to set this on the current app Context and the thread, it also needs + // to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually + // an underlying fault of asp.net not propogating the User correctly. + if (HttpContext.Current != null) + { + HttpContext.Current.User = principal; + } + http.User = principal; + Thread.CurrentPrincipal = principal; + + //This is a back office request, we will also set the culture/ui culture + Thread.CurrentThread.CurrentCulture = + Thread.CurrentThread.CurrentUICulture = + new System.Globalization.CultureInfo(identity.Culture); + + return true; + } + catch (Exception ex) + { + if (ex is FormatException || ex is JsonReaderException) + { + //this will occur if the cookie data is invalid + http.UmbracoLogout(); + } + else + { + throw; + } + + } + } + + return false; } - internal static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContext http) + + /// + /// This will return the current back office identity. + /// + /// + /// + /// If set to true and a back office identity is not found and not authenticated, this will attempt to authenticate the + /// request just as is done in the Umbraco module and then set the current identity if it is valid + /// + /// + /// Returns the current back office identity if an admin is authenticated otherwise null + /// + public static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContextBase http, bool authenticateRequestIfNotFound) { - return new HttpContextWrapper(http).GetCurrentIdentity(); + if (http == null) throw new ArgumentNullException("http"); + var identity = http.User.Identity as UmbracoBackOfficeIdentity; + if (identity != null) return identity; + if (authenticateRequestIfNotFound == false) return null; + //even if authenticateRequestIfNotFound is true we cannot continue if the request is actually authenticated + // which would mean something strange is going on that it is not an umbraco identity. + if (http.User.Identity.IsAuthenticated) return null; + + //now we just need to try to authenticate the current request + var ticket = http.GetUmbracoAuthTicket(); + if (http.AuthenticateCurrentRequest(ticket, true)) + { + //now we 'should have an umbraco identity + return http.User.Identity as UmbracoBackOfficeIdentity; + } + return null; + } + + /// + /// This will return the current back office identity. + /// + /// + /// + /// If set to true and a back office identity is not found and not authenticated, this will attempt to authenticate the + /// request just as is done in the Umbraco module and then set the current identity if it is valid + /// + /// + /// Returns the current back office identity if an admin is authenticated otherwise null + /// + internal static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContext http, bool authenticateRequestIfNotFound) + { + if (http == null) throw new ArgumentNullException("http"); + return new HttpContextWrapper(http).GetCurrentIdentity(authenticateRequestIfNotFound); } /// @@ -26,11 +123,13 @@ namespace Umbraco.Core.Security /// public static void UmbracoLogout(this HttpContextBase http) { + if (http == null) throw new ArgumentNullException("http"); Logout(http, UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName); } internal static void UmbracoLogout(this HttpContext http) { + if (http == null) throw new ArgumentNullException("http"); new HttpContextWrapper(http).UmbracoLogout(); } @@ -41,6 +140,7 @@ namespace Umbraco.Core.Security /// public static bool RenewUmbracoAuthTicket(this HttpContextBase http) { + if (http == null) throw new ArgumentNullException("http"); return RenewAuthTicket(http, UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName, UmbracoConfig.For.UmbracoSettings().Security.AuthCookieDomain); @@ -48,6 +148,7 @@ namespace Umbraco.Core.Security internal static bool RenewUmbracoAuthTicket(this HttpContext http) { + if (http == null) throw new ArgumentNullException("http"); return new HttpContextWrapper(http).RenewUmbracoAuthTicket(); } @@ -58,6 +159,8 @@ namespace Umbraco.Core.Security /// public static FormsAuthenticationTicket CreateUmbracoAuthTicket(this HttpContextBase http, UserData userdata) { + if (http == null) throw new ArgumentNullException("http"); + if (userdata == null) throw new ArgumentNullException("userdata"); var userDataString = JsonConvert.SerializeObject(userdata); return CreateAuthTicketAndCookie( http, @@ -74,6 +177,8 @@ namespace Umbraco.Core.Security internal static FormsAuthenticationTicket CreateUmbracoAuthTicket(this HttpContext http, UserData userdata) { + if (http == null) throw new ArgumentNullException("http"); + if (userdata == null) throw new ArgumentNullException("userdata"); return new HttpContextWrapper(http).CreateUmbracoAuthTicket(userdata); } @@ -84,6 +189,7 @@ namespace Umbraco.Core.Security /// public static double GetRemainingAuthSeconds(this HttpContextBase http) { + if (http == null) throw new ArgumentNullException("http"); var ticket = http.GetUmbracoAuthTicket(); return ticket.GetRemainingAuthSeconds(); } @@ -111,11 +217,13 @@ namespace Umbraco.Core.Security /// public static FormsAuthenticationTicket GetUmbracoAuthTicket(this HttpContextBase http) { + if (http == null) throw new ArgumentNullException("http"); return GetAuthTicket(http, UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName); } internal static FormsAuthenticationTicket GetUmbracoAuthTicket(this HttpContext http) { + if (http == null) throw new ArgumentNullException("http"); return new HttpContextWrapper(http).GetUmbracoAuthTicket(); } @@ -126,6 +234,7 @@ namespace Umbraco.Core.Security /// private static void Logout(this HttpContextBase http, string cookieName) { + if (http == null) throw new ArgumentNullException("http"); //remove from the request http.Request.Cookies.Remove(cookieName); @@ -145,6 +254,7 @@ namespace Umbraco.Core.Security private static FormsAuthenticationTicket GetAuthTicket(this HttpContextBase http, string cookieName) { + if (http == null) throw new ArgumentNullException("http"); var formsCookie = http.Request.Cookies[cookieName]; if (formsCookie == null) { @@ -172,6 +282,7 @@ namespace Umbraco.Core.Security /// true if there was a ticket to renew otherwise false if there was no ticket private static bool RenewAuthTicket(this HttpContextBase http, string cookieName, string cookieDomain) { + if (http == null) throw new ArgumentNullException("http"); //get the ticket var ticket = GetAuthTicket(http, cookieName); //renew the ticket @@ -219,6 +330,7 @@ namespace Umbraco.Core.Security string cookieName, string cookieDomain) { + if (http == null) throw new ArgumentNullException("http"); // Create a new ticket used for authentication var ticket = new FormsAuthenticationTicket( 4, diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index b44a80c832..657691dbe8 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -386,7 +386,7 @@ namespace Umbraco.Web.Security /// public int GetUserId() { - var identity = _httpContext.GetCurrentIdentity(); + var identity = _httpContext.GetCurrentIdentity(true); if (identity == null) return -1; return Convert.ToInt32(identity.Id); diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 473fdc082e..3ee5be98b4 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -178,47 +178,8 @@ namespace Umbraco.Web if (ShouldAuthenticateRequest(req, UmbracoContext.Current.OriginalRequestUrl)) { var ticket = http.GetUmbracoAuthTicket(); - //if there was a ticket, it's not expired, - it should not be renewed or its renewable - if (ticket != null && ticket.Expired == false - && (ShouldIgnoreTicketRenew(UmbracoContext.Current.OriginalRequestUrl, http) || http.RenewUmbracoAuthTicket())) - { - try - { - //create the Umbraco user identity - var identity = new UmbracoBackOfficeIdentity(ticket); - //set the principal object - var principal = new GenericPrincipal(identity, identity.Roles); - - //It is actually not good enough to set this on the current app Context and the thread, it also needs - // to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually - // an underlying fault of asp.net not propogating the User correctly. - if (HttpContext.Current != null) - { - HttpContext.Current.User = principal; - } - app.Context.User = principal; - Thread.CurrentPrincipal = principal; - - //This is a back office request, we will also set the culture/ui culture - Thread.CurrentThread.CurrentCulture = - Thread.CurrentThread.CurrentUICulture = - new System.Globalization.CultureInfo(identity.Culture); - } - catch (Exception ex) - { - if (ex is FormatException || ex is JsonReaderException) - { - //this will occur if the cookie data is invalid - http.UmbracoLogout(); - } - else - { - throw; - } - - } - } + http.AuthenticateCurrentRequest(ticket, ShouldIgnoreTicketRenew(UmbracoContext.Current.OriginalRequestUrl, http) == false); } } diff --git a/src/umbraco.businesslogic/BasePages/BasePage.cs b/src/umbraco.businesslogic/BasePages/BasePage.cs index a9727fb41c..73df8927e3 100644 --- a/src/umbraco.businesslogic/BasePages/BasePage.cs +++ b/src/umbraco.businesslogic/BasePages/BasePage.cs @@ -181,7 +181,7 @@ namespace umbraco.BasePages /// public static int GetUserId() { - var identity = HttpContext.Current.GetCurrentIdentity(); + var identity = HttpContext.Current.GetCurrentIdentity(true); if (identity == null) return -1; return Convert.ToInt32(identity.Id);