diff --git a/assets/app/scripts/controllers/util/oauth.js b/assets/app/scripts/controllers/util/oauth.js index 5753a176ccba..cc391b4d2ce0 100644 --- a/assets/app/scripts/controllers/util/oauth.js +++ b/assets/app/scripts/controllers/util/oauth.js @@ -8,13 +8,22 @@ * Controller of the openshiftConsole */ angular.module('openshiftConsole') - .controller('OAuthController', function ($location, $q, RedirectLoginService, DataService, AuthService, Logger) { + .controller('OAuthController', function ($scope, $location, $q, RedirectLoginService, DataService, AuthService, Logger) { var authLogger = Logger.get("auth"); + // Initialize to a no-op function. + // Needed to let the view confirm a login when the state is unverified. + $scope.completeLogin = function(){}; + $scope.cancelLogin = function() { + $location.replace(); + $location.url("./"); + }; + RedirectLoginService.finish() .then(function(data) { var token = data.token; var then = data.then; + var verified = data.verified; var ttl = data.ttl; // Try to fetch the user @@ -25,21 +34,41 @@ angular.module('openshiftConsole') .then(function(user) { // Set the new user and token in the auth service authLogger.log("OAuthController, got user", user); - AuthService.setUser(user, token, ttl); - // Redirect to original destination (or default to '/') - var destination = then || './'; - if (URI(destination).is('absolute')) { - authLogger.log("OAuthController, invalid absolute redirect", destination); - destination = './'; + $scope.completeLogin = function() { + // Persist the user + AuthService.setUser(user, token, ttl); + + // Redirect to original destination (or default to './') + var destination = then || './'; + if (URI(destination).is('absolute')) { + authLogger.log("OAuthController, invalid absolute redirect", destination); + destination = './'; + } + authLogger.log("OAuthController, redirecting", destination); + $location.replace(); + $location.url(destination); + }; + + if (verified) { + // Automatically complete + $scope.completeLogin(); + } else { + // Require the UI to prompt + $scope.confirmUser = user; + + // Additionally, give the UI info about the user being overridden + var currentUser = AuthService.UserStore().getUser(); + if (currentUser && currentUser.metadata.name !== user.metadata.name) { + $scope.overriddenUser = currentUser; + } } - authLogger.log("OAuthController, redirecting", destination); - $location.url(destination); }) .catch(function(rejection) { // Handle an API error response fetching the user var redirect = URI('error').query({error: 'user_fetch_failed'}).toString(); authLogger.error("OAuthController, error fetching user", rejection, "redirecting", redirect); + $location.replace(); $location.url(redirect); }); @@ -51,6 +80,7 @@ angular.module('openshiftConsole') error_uri: rejection.error_uri || "" }).toString(); authLogger.error("OAuthController, error", rejection, "redirecting", redirect); + $location.replace(); $location.url(redirect); }); diff --git a/assets/app/scripts/services/login.js b/assets/app/scripts/services/login.js index a2e4d1100a4d..5bcbc4ca0749 100644 --- a/assets/app/scripts/services/login.js +++ b/assets/app/scripts/services/login.js @@ -29,6 +29,70 @@ angular.module('openshiftConsole') this.$get = function($location, $q, Logger) { var authLogger = Logger.get("auth"); + var getRandomInts = function(length) { + var randomValues; + + if (window.crypto && window.Uint32Array) { + try { + var r = new Uint32Array(length); + window.crypto.getRandomValues(r); + randomValues = []; + for (var j=0; j < length; j++) { + randomValues.push(r[j]); + } + } catch(e) { + authLogger.debug("RedirectLoginService.getRandomInts: ", e); + randomValues = null; + } + } + + if (!randomValues) { + randomValues = []; + for (var i=0; i < length; i++) { + randomValues.push(Math.floor(Math.random() * 4294967296)); + } + } + + return randomValues; + }; + + var nonceKey = "RedirectLoginService.nonce"; + var makeState = function(then) { + var nonce = String(new Date().getTime()) + "-" + getRandomInts(8).join(""); + try { + window.localStorage[nonceKey] = nonce; + } catch(e) { + authLogger.log("RedirectLoginService.makeState, localStorage error: ", e); + } + return JSON.stringify({then: then, nonce:nonce}); + }; + var parseState = function(state) { + var retval = { + then: null, + verified: false + }; + + var nonce = ""; + try { + nonce = window.localStorage[nonceKey]; + window.localStorage.removeItem(nonceKey); + } catch(e) { + authLogger.log("RedirectLoginService.parseState, localStorage error: ", e); + } + + try { + var data = state ? JSON.parse(state) : {}; + if (data && data.nonce && nonce && data.nonce === nonce) { + retval.verified = true; + retval.then = data.then; + } + } catch(e) { + authLogger.error("RedirectLoginService.parseState, state error: ", e); + } + authLogger.error("RedirectLoginService.parseState", retval); + return retval; + }; + return { // Returns a promise that resolves with {user:{...}, token:'...', ttl:X}, or rejects with {error:'...'[,error_description:'...',error_uri:'...']} login: function() { @@ -49,7 +113,7 @@ angular.module('openshiftConsole') uri.query({ client_id: _oauth_client_id, response_type: 'token', - state: returnUri.toString(), + state: makeState(returnUri.toString()), redirect_uri: _oauth_redirect_uri }); authLogger.log("RedirectLoginService.login(), redirecting", uri.toString()); @@ -59,7 +123,7 @@ angular.module('openshiftConsole') }, // Parses oauth callback parameters from window.location - // Returns a promise that resolves with {token:'...',then:'...'}, or rejects with {error:'...'[,error_description:'...',error_uri:'...']} + // Returns a promise that resolves with {token:'...',then:'...',verified:true|false}, or rejects with {error:'...'[,error_description:'...',error_uri:'...']} // If no token and no error is present, resolves with {} // Example error codes: https://tools.ietf.org/html/rfc6749#section-5.2 finish: function() { @@ -71,12 +135,12 @@ angular.module('openshiftConsole') var fragmentParams = new URI("?" + u.fragment()).query(true); authLogger.log("RedirectLoginService.finish()", queryParams, fragmentParams); - // Error codes can come in query params or fragment params - // Handle an error response from the OAuth server + // Error codes can come in query params or fragment params + // Handle an error response from the OAuth server var error = queryParams.error || fragmentParams.error; - if (error) { - var error_description = queryParams.error_description || fragmentParams.error_description; - var error_uri = queryParams.error_uri || fragmentParams.error_uri; + if (error) { + var error_description = queryParams.error_description || fragmentParams.error_description; + var error_uri = queryParams.error_uri || fragmentParams.error_uri; authLogger.log("RedirectLoginService.finish(), error", error, error_description, error_uri); return $q.reject({ error: error, @@ -85,13 +149,16 @@ angular.module('openshiftConsole') }); } + var stateData = parseState(fragmentParams.state); + // Handle an access_token response if (fragmentParams.access_token && (fragmentParams.token_type || "").toLowerCase() === "bearer") { var deferred = $q.defer(); deferred.resolve({ token: fragmentParams.access_token, ttl: fragmentParams.expires_in, - then: fragmentParams.state + then: stateData.state, + verified: stateData.verified }); return deferred.promise; } diff --git a/assets/app/views/util/oauth.html b/assets/app/views/util/oauth.html index 2856de7ff0f9..10758fe236cf 100644 --- a/assets/app/views/util/oauth.html +++ b/assets/app/views/util/oauth.html @@ -2,12 +2,26 @@