From 1b6a2c7d5c7cd4cb6a2a985ae482642e8f9dc9f4 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 18 Oct 2016 08:46:41 +0200 Subject: [PATCH] U4-9048 - validate user before retrying queued promises --- .../src/common/security/retryqueue.js | 25 +++++++++++++---- .../common/security/securityinterceptor.js | 25 +++++++++++------ .../src/common/services/user.service.js | 28 +++++++++---------- 3 files changed, 50 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/security/retryqueue.js b/src/Umbraco.Web.UI.Client/src/common/security/retryqueue.js index 28d91dd610..974b8c225e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/security/retryqueue.js +++ b/src/Umbraco.Web.UI.Client/src/common/security/retryqueue.js @@ -5,10 +5,12 @@ angular.module('umbraco.security.retryQueue', []) .factory('securityRetryQueue', ['$q', '$log', function ($q, $log) { var retryQueue = []; + var retryUser = null; + var service = { // The security service puts its own handler in here! onItemAddedCallbacks: [], - + hasMore: function() { return retryQueue.length > 0; }, @@ -23,13 +25,18 @@ angular.module('umbraco.security.retryQueue', []) } }); }, - pushRetryFn: function(reason, retryFn) { + pushRetryFn: function(reason, userName, retryFn) { // The reason parameter is optional - if ( arguments.length === 1) { - retryFn = reason; + if ( arguments.length === 2) { + retryFn = userName; + userName = reason; reason = undefined; } + if ((retryUser && retryUser !== userName) || userName === null) + throw new Error('invalid user'); + retryUser = userName; + // The deferred object that will be resolved or rejected by calling retry or cancel var deferred = $q.defer(); var retryItem = { @@ -59,8 +66,16 @@ angular.module('umbraco.security.retryQueue', []) while(service.hasMore()) { retryQueue.shift().cancel(); } + retryUser = null; }, - retryAll: function() { + retryAll: function (userName) { + + if (retryUser == null) return; + if (retryUser !== userName) { + service.cancelAll(); + return; + } + while(service.hasMore()) { retryQueue.shift().retry(); } diff --git a/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js b/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js index b80754ef67..e47f0663d8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js +++ b/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js @@ -6,7 +6,7 @@ angular.module('umbraco.security.interceptor') return promise.then( function(originalResponse) { // Intercept successful requests - + //Here we'll check if our custom header is in the response which indicates how many seconds the user's session has before it //expires. Then we'll update the user in the user service accordingly. var headers = originalResponse.headers(); @@ -15,11 +15,11 @@ angular.module('umbraco.security.interceptor') var userService = $injector.get('userService'); userService.setUserTimeout(headers["x-umb-user-seconds"]); } - + return promise; }, function(originalResponse) { // Intercept failed requests - + //Here we'll check if we should ignore the error, this will be based on an original header set var headers = originalResponse.config ? originalResponse.config.headers : {}; if (headers["x-umb-ignore-error"] === "ignore") { @@ -36,17 +36,24 @@ angular.module('umbraco.security.interceptor') //A 401 means that the user is not logged in if (originalResponse.status === 401) { - // The request bounced because it was not authorized - add a new request to the retry queue - promise = queue.pushRetryFn('unauthorized-server', function retryRequest() { + var userService = $injector.get('userService'); // see above + + //Associate the user name with the retry to ensure we retry for the right user + promise = userService.getCurrentUser() + .then(function (user) { + var userName = user ? user.name : null; + //The request bounced because it was not authorized - add a new request to the retry queue + return queue.pushRetryFn('unauthorized-server', userName, function retryRequest() { // We must use $injector to get the $http service to prevent circular dependency return $injector.get('$http')(originalResponse.config); + }); }); } else if (originalResponse.status === 404) { //a 404 indicates that the request was not found - this could be due to a non existing url, or it could //be due to accessing a url with a parameter that doesn't exist, either way we should notifiy the user about it - + var errMsg = "The URL returned a 404 (not found):
" + originalResponse.config.url.split('?')[0] + ""; if (originalResponse.data && originalResponse.data.ExceptionMessage) { errMsg += "
with error:
" + originalResponse.data.ExceptionMessage + ""; @@ -58,17 +65,17 @@ angular.module('umbraco.security.interceptor') notifications.error( "Request error", errMsg); - + } else if (originalResponse.status === 403) { //if the status was a 403 it means the user didn't have permission to do what the request was trying to do. - //How do we deal with this now, need to tell the user somehow that they don't have permission to do the thing that was + //How do we deal with this now, need to tell the user somehow that they don't have permission to do the thing that was //requested. We can either deal with this globally here, or we can deal with it globally for individual requests on the umbRequestHelper, // or completely custom for services calling resources. //http://issues.umbraco.org/issue/U4-2749 - //It was decided to just put these messages into the normal status messages. + //It was decided to just put these messages into the normal status messages. var msg = "Unauthorized access to URL:
" + originalResponse.config.url.split('?')[0] + ""; if (originalResponse.config.data) { 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 dffda24f1d..c759169752 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 @@ -31,7 +31,7 @@ angular.module('umbraco.services') loginDialog = null; if (success) { - securityRetryQueue.retryAll(); + securityRetryQueue.retryAll(currentUser.name); } else { securityRetryQueue.cancelAll(); @@ -39,9 +39,9 @@ angular.module('umbraco.services') } } - /** - This methods will set the current user when it is resolved and - will then start the counter to count in-memory how many seconds they have + /** + This methods will set the current user when it is resolved and + will then start the counter to count in-memory how many seconds they have remaining on the auth session */ function setCurrentUser(usr) { @@ -54,8 +54,8 @@ angular.module('umbraco.services') countdownUserTimeout(); } - /** - Method to count down the current user's timeout seconds, + /** + Method to count down the current user's timeout seconds, this will continually count down their current remaining seconds every 5 seconds until there are no more seconds remaining. */ @@ -70,8 +70,8 @@ angular.module('umbraco.services') //if there are more than 30 remaining seconds, recurse! if (currentUser.remainingAuthSeconds > 30) { - //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 retrieve it from the + //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 retrieve it from the // server - this helps to keep our local countdown in check with the true timeout. if (lastServerTimeoutSet != null) { var now = new Date(); @@ -79,7 +79,7 @@ angular.module('umbraco.services') 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 + //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; @@ -98,7 +98,7 @@ angular.module('umbraco.services') } else { - //we are either timed out or very close to timing out so we need to show the login dialog. + //we are either timed out or very close to timing out so we need to show the login dialog. if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) { //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) angularHelper.safeApply($rootScope, function () { @@ -109,14 +109,14 @@ angular.module('umbraco.services') } finally { userAuthExpired(); - } + } }); } else { //we've got less than 30 seconds remaining so let's check the server if (lastServerTimeoutSet != null) { - //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we + //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; @@ -211,8 +211,8 @@ angular.module('umbraco.services') return result; }); }, - - /** Logs the user out + + /** Logs the user out */ logout: function () {