diff --git a/src/Umbraco.Core/ExpressionHelper.cs b/src/Umbraco.Core/ExpressionHelper.cs index 6aa4ebeaa8..c34c4591b8 100644 --- a/src/Umbraco.Core/ExpressionHelper.cs +++ b/src/Umbraco.Core/ExpressionHelper.cs @@ -177,8 +177,21 @@ namespace Umbraco.Core public static MethodInfo GetMethodInfo(Expression> fromExpression) { if (fromExpression == null) return null; - var body = fromExpression.Body as MethodCallExpression; - return body != null ? body.Method : null; + + MethodCallExpression me; + switch (fromExpression.Body.NodeType) + { + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + var ue = fromExpression.Body as UnaryExpression; + me = ((ue != null) ? ue.Operand : null) as MethodCallExpression; + break; + default: + me = fromExpression.Body as MethodCallExpression; + break; + } + + return me != null ? me.Method : null; } /// diff --git a/src/Umbraco.Web.UI.Client/src/common/security/interceptor.js b/src/Umbraco.Web.UI.Client/src/common/security/interceptor.js index 205088cdfe..05bab31baf 100644 --- a/src/Umbraco.Web.UI.Client/src/common/security/interceptor.js +++ b/src/Umbraco.Web.UI.Client/src/common/security/interceptor.js @@ -11,12 +11,9 @@ angular.module('umbraco.security.interceptor', ['umbraco.security.retryQueue']) //expires. Then we'll update the user in the user service accordingly. var headers = originalResponse.headers(); if (headers["x-umb-user-seconds"]) { - var asNumber = parseFloat(headers["x-umb-user-seconds"]); - if (!isNaN(asNumber)) { - // We must use $injector to get the $http service to prevent circular dependency - var userService = $injector.get('userService'); - userService.setUserTimeout(asNumber); - } + // We must use $injector to get the $http service to prevent circular dependency + var userService = $injector.get('userService'); + userService.setUserTimeout(headers["x-umb-user-seconds"]); } return promise; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index d70b5fe72e..0c468333aa 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -4,6 +4,9 @@ angular.module('umbraco.services') var currentUser = null; var lastUserId = null; var loginDialog = null; + //this tracks the last date/time that the user's remainingAuthSeconds was updated from the server + // this is used so that we know when to go and get the user's remaining seconds directly. + var lastServerTimeoutSet = null; // Redirect to the given url (defaults to '/') function redirect(url) { @@ -44,19 +47,55 @@ angular.module('umbraco.services') throw "The user object is invalid, the remainingAuthSeconds is required."; } currentUser = usr; + lastServerTimeoutSet = new Date(); //start the timer - setCurrentUserTimeout(usr); + countdownUserTimeout(); } - function setCurrentUserTimeout() { + + /** + Method to count down the current user's timeout seconds, + this will continually count down their current remaining seconds every 2 seconds until + there are no more seconds remaining. + */ + function countdownUserTimeout() { $timeout(function () { if (currentUser) { currentUser.remainingAuthSeconds -= 1; - if (currentUser.remainingAuthSeconds > 0) { - //recurse! - setCurrentUserTimeout(); + + //if there are remaining seconds, recurse! + if (currentUser.remainingAuthSeconds > 0) { + + //we need to check when the last time the timeout was set from the server, if + // it has been more than 30 seconds then we'll manually go and retreive it from the + // server - this helps to keep our local countdown in check with the true timeout. + if (lastServerTimeoutSet != null) { + var now = new Date(); + var seconds = (now.getTime() - lastServerTimeoutSet.getTime()) / 1000; + if (seconds > 30) { + //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we + // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. + lastServerTimeoutSet = null; + //now go get it from the server + authResource.getRemainingTimeoutSeconds().then(function (result) { + setUserTimeoutInternal(result); + }); + } + } + + //recurse the countdown! + countdownUserTimeout(); } } - }, 1000);//every second + }, 2000);//every 2 seconds + } + + /** Called to update the current user's timeout */ + function setUserTimeoutInternal(newTimeout) { + var asNumber = parseFloat(newTimeout); + if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) { + currentUser.remainingAuthSeconds = newTimeout; + lastServerTimeoutSet = new Date(); + } } // Register a handler for when an item is added to the retry queue @@ -157,10 +196,9 @@ angular.module('umbraco.services') return deferred.promise; }, + /** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */ setUserTimeout: function(newTimeout) { - if (currentUser && angular.isNumber(newTimeout)) { - currentUser.remainingAuthSeconds = newTimeout; - } + setUserTimeoutInternal(newTimeout); } }; diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 00f8c6f5fc..2d265d44ce 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -175,9 +175,9 @@ namespace Umbraco.Web if (ShouldAuthenticateRequest(req, UmbracoContext.Current.OriginalRequestUrl)) { var ticket = http.GetUmbracoAuthTicket(); - //if there was a ticket, it's not expired, its renewable - or it should not be renewed + //if there was a ticket, it's not expired, - it should not be renewed or its renewable if (ticket != null && ticket.Expired == false - && (http.RenewUmbracoAuthTicket() || ShouldIgnoreTicketRenew(UmbracoContext.Current.OriginalRequestUrl, http))) + && (ShouldIgnoreTicketRenew(UmbracoContext.Current.OriginalRequestUrl, http) || http.RenewUmbracoAuthTicket())) { try { @@ -270,7 +270,7 @@ namespace Umbraco.Web if (IgnoreTicketRenewUrls.Any() == false) { var urlHelper = new UrlHelper(new RequestContext(httpContext, new RouteData())); - var checkSessionUrl = urlHelper.GetUmbracoApiServiceBaseUrl(controller => controller.GetRemainingTimeoutSeconds()); + var checkSessionUrl = urlHelper.GetUmbracoApiService(controller => controller.GetRemainingTimeoutSeconds()); IgnoreTicketRenewUrls.Add(checkSessionUrl); } diff --git a/src/Umbraco.Web/UrlHelperExtensions.cs b/src/Umbraco.Web/UrlHelperExtensions.cs index 4957e0dbb4..357590d530 100644 --- a/src/Umbraco.Web/UrlHelperExtensions.cs +++ b/src/Umbraco.Web/UrlHelperExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Linq.Expressions; +using System.Management.Instrumentation; using System.Web.Mvc; using Umbraco.Core; using Umbraco.Web.Mvc; @@ -55,9 +56,24 @@ namespace Umbraco.Web where T : UmbracoApiController { var method = Umbraco.Core.ExpressionHelper.GetMethodInfo(methodSelector); + if (method == null) + { + throw new MissingMethodException("Could not find the method " + methodSelector + " on type " + typeof(T) + " or the result "); + } return url.GetUmbracoApiService(method.Name).TrimEnd(method.Name); } + public static string GetUmbracoApiService(this UrlHelper url, Expression> methodSelector) + where T : UmbracoApiController + { + var method = Umbraco.Core.ExpressionHelper.GetMethodInfo(methodSelector); + if (method == null) + { + throw new MissingMethodException("Could not find the method " + methodSelector + " on type " + typeof(T) + " or the result "); + } + return url.GetUmbracoApiService(method.Name); + } + /// /// Return the Url for a Web Api service ///