From 4a0470f0ef25c5b4cb10b758096049e29e6400e7 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Thu, 5 Feb 2015 11:51:04 -0500 Subject: [PATCH] Delete token on logout --- assets/app/index.html | 1 + assets/app/scripts/app.js | 1 + assets/app/scripts/controllers/util/logout.js | 30 ++-- assets/app/scripts/services/auth.js | 78 +++++++--- assets/app/scripts/services/data.js | 66 +++++++-- assets/app/scripts/services/logout.js | 28 ++++ assets/app/views/util/logout.html | 4 +- assets/test/spec/controllers/project.js | 1 + assets/test/spec/controllers/projects.js | 1 + pkg/assets/bindata.go | 135 ++++++++++++++---- 10 files changed, 280 insertions(+), 65 deletions(-) create mode 100644 assets/app/scripts/services/logout.js diff --git a/assets/app/index.html b/assets/app/index.html index a02e2a3e28bc..25dd45abd5ed 100644 --- a/assets/app/index.html +++ b/assets/app/index.html @@ -112,6 +112,7 @@ + diff --git a/assets/app/scripts/app.js b/assets/app/scripts/app.js index c80fb38ae6de..60bc334dc15e 100644 --- a/assets/app/scripts/app.js +++ b/assets/app/scripts/app.js @@ -135,6 +135,7 @@ angular $httpProvider.interceptors.push('AuthInterceptor'); AuthServiceProvider.LoginService('RedirectLoginService'); + AuthServiceProvider.LogoutService('DeleteTokenLogoutService'); // TODO: fall back to cookie store when localStorage is unavailable (see known issues at http://caniuse.com/#feat=namevalue-storage) AuthServiceProvider.UserStore('LocalStorageUserStore'); diff --git a/assets/app/scripts/controllers/util/logout.js b/assets/app/scripts/controllers/util/logout.js index fbd10e20f7f5..6d22134cc179 100644 --- a/assets/app/scripts/controllers/util/logout.js +++ b/assets/app/scripts/controllers/util/logout.js @@ -8,13 +8,27 @@ * Controller of the openshiftConsole */ angular.module('openshiftConsole') - .controller('LogoutController', function ($rootScope, $scope, AuthService) { - // If clearing the user results in a change from authenticated to unauthenticated, force the page in response - AuthService.onUserChanged(function(){ - console.log("LogoutController - user changed, reloading the page"); - window.location.reload(false); - }); + .controller('LogoutController', function ($scope, $log, AuthService) { + $log.debug("LogoutController"); - // TODO: actually run the logout flow, delete the token, etc - AuthService.setUser(null, null); + if (AuthService.isLoggedIn()) { + $log.debug("LogoutController, logged in, initiating logout"); + $scope.logoutMessage = "Logging out..."; + + AuthService.startLogout().finally(function(){ + // Make sure the logout completed + if (AuthService.isLoggedIn()) { + $log.debug("LogoutController, logout failed, still logged in"); + $scope.logoutMessage = 'You could not be logged out. Return to the console.'; + } else { + // TODO: redirect to configurable logout destination + $log.debug("LogoutController, logout completed, reloading the page"); + window.location.reload(false); + } + }); + } else { + // TODO: redirect to configurable logout destination + $log.debug("LogoutController, not logged in, logout complete"); + $scope.logoutMessage = 'You are logged out. Return to the console.'; + } }); diff --git a/assets/app/scripts/services/auth.js b/assets/app/scripts/services/auth.js index 23d69ba5f381..5f8870bc2ed7 100644 --- a/assets/app/scripts/services/auth.js +++ b/assets/app/scripts/services/auth.js @@ -40,34 +40,45 @@ angular.module('openshiftConsole') } return _loginService; }; + var _logoutService = ""; + this.LogoutService = function(logoutServiceName) { + if (logoutServiceName) { + _logoutService = logoutServiceName; + } + return _logoutService; + }; - this.$get = function($q, $injector, $rootScope) { + var loadService = function(injector, name, setter) { + if (!name) { + throw setter + " not set"; + } else if (angular.isString(name)) { + return injector.get(name); + } else { + return injector.invoke(name); + } + }; + + this.$get = function($q, $injector, $log, $rootScope) { if (debug) { console.log('AuthServiceProvider.$get', arguments); } var _loginCallbacks = $.Callbacks(); + var _logoutCallbacks = $.Callbacks(); var _userChangedCallbacks = $.Callbacks(); var _loginPromise = null; + var _logoutPromise = null; - var userStore; - if (!_userStore) { - throw "AuthServiceProvider.UserStore() not set"; - } else if (angular.isString(_userStore)) { - userStore = $injector.get(_userStore); - } else { - userStore = $injector.invoke(_userStore); - } - - var loginService; - if (!_loginService) { - throw "AuthServiceProvider.LoginService() not set"; - } else if (angular.isString(_loginService)) { - loginService = $injector.get(_loginService); - } else { - loginService = $injector.invoke(_loginService); - } + var userStore = loadService($injector, _userStore, "AuthServiceProvider.UserStore()"); + var loginService = loadService($injector, _loginService, "AuthServiceProvider.LoginService()"); + var logoutService = loadService($injector, _logoutService, "AuthServiceProvider.LogoutService()"); return { + // Returns true if currently logged in. + isLoggedIn: function() { + return !!userStore.getUser(); + }, + + // Returns a promise of a user, which is resolved with a logged in user. Triggers a login if needed. withUser: function() { var user = userStore.getUser(); if (user) { @@ -144,11 +155,42 @@ angular.module('openshiftConsole') }); return _loginPromise; }, + + startLogout: function() { + if (_logoutPromise) { + if (debug) { console.log("Logout already in progress"); } + return _logoutPromise; + } + var self = this; + var user = userStore.getUser(); + var token = userStore.getToken(); + var wasLoggedIn = this.isLoggedIn(); + _logoutPromise = logoutService.logout(user, token).then(function() { + if (debug) { console.log("Logout service success"); } + }).catch(function(err) { + if (debug) { console.log("Logout service error", err); } + }).finally(function() { + // Clear the user and token + self.setUser(null, null); + // Make sure isLoggedIn() returns false before we fire logout callbacks + var isLoggedIn = self.isLoggedIn(); + // Only fire logout callbacks if we transitioned from a logged in state to a logged out state + if (wasLoggedIn && !isLoggedIn) { + _logoutCallbacks.fire(); + } + _logoutPromise = null; + }); + return _logoutPromise; + }, // TODO: add a way to unregister once we start doing in-page logins onLogin: function(callback) { _loginCallbacks.add(callback); }, + // TODO: add a way to unregister once we start doing in-page logouts + onLogout: function(callback) { + _logoutCallbacks.add(callback); + }, // TODO: add a way to unregister once we start doing in-page user changes onUserChanged: function(callback) { _userChangedCallbacks.add(callback); diff --git a/assets/app/scripts/services/data.js b/assets/app/scripts/services/data.js index f5945aaf33af..e5cb9e489b84 100644 --- a/assets/app/scripts/services/data.js +++ b/assets/app/scripts/services/data.js @@ -155,6 +155,54 @@ angular.module('openshiftConsole') } }; +// type: API type (e.g. "pods") +// name: API name, the unique name for the object +// context: API context (e.g. {project: "..."}) +// opts: http - options to pass to the inner $http call +// Returns a promise resolved with response data or rejected with {data:..., status:..., headers:..., config:...} when the delete call completes. + DataService.prototype.delete = function(type, name, context, opts) { + opts = opts || {}; + var deferred = $q.defer(); + if (context.projectPromise && type !== "projects") { + var self = this; + context.projectPromise.done(function(project) { + $http(angular.extend({ + method: 'DELETE', + url: self._urlForType(type, name, context, false, {namespace: project.metadata.name}) + }, opts.http || {})) + .success(function(data, status, headerFunc, config, statusText) { + deferred.resolve(data); + }) + .error(function(data, status, headers, config) { + deferred.reject({ + data: data, + status: status, + headers: headers, + config: config + }); + }); + }); + } + else { + $http(angular.extend({ + method: 'DELETE', + url: this._urlForType(type, name, context) + }, opts.http || {})) + .success(function(data, status, headerFunc, config, statusText) { + deferred.resolve(data); + }) + .error(function(data, status, headers, config) { + deferred.reject({ + data: data, + status: status, + headers: headers, + config: config + }); + }); + } + return deferred.promise; + }; + // type: API type (e.g. "pods") // name: API name, the unique name for the object // context: API context (e.g. {project: "..."}) @@ -547,14 +595,16 @@ angular.module('openshiftConsole') // an introspection endpoint that would give us this mapping // https://github.com/openshift/origin/issues/230 var SERVER_TYPE_MAP = { - builds : apicfg.openshift, - deploymentConfigs : apicfg.openshift, - images : apicfg.openshift, - projects : apicfg.openshift, - users: apicfg.openshift, - pods : apicfg.k8s, - services : apicfg.k8s, - replicationControllers: apicfg.k8s + builds: apicfg.openshift, + deploymentConfigs: apicfg.openshift, + images: apicfg.openshift, + oAuthAccessTokens: apicfg.openshift, + projects: apicfg.openshift, + users: apicfg.openshift, + + pods: apicfg.k8s, + replicationControllers: apicfg.k8s, + services: apicfg.k8s }; DataService.prototype._urlForType = function(type, id, context, isWebsocket, params) { diff --git a/assets/app/scripts/services/logout.js b/assets/app/scripts/services/logout.js new file mode 100644 index 000000000000..e7ed9d8c505e --- /dev/null +++ b/assets/app/scripts/services/logout.js @@ -0,0 +1,28 @@ +// Logout strategies +angular.module('openshiftConsole') +.provider('DeleteTokenLogoutService', function() { + + var debug = true; + + this.$get = function($q, $injector) { + return { + logout: function(user, token) { + if (debug) { console.log("DeleteTokenLogoutService.logout()", user, token); } + + // If we don't have a token, we're done + if (!token) { + if (debug) { console.log("DeleteTokenLogoutService, no token, returning immediately"); } + return $q.when({}); + } + + // Lazily get the data service. Can't explicitly depend on it or we get circular dependencies. + var DataService = $injector.get('DataService'); + // Use the token to delete the token + // Never trigger a login when deleting our token + var opts = {http: {auth: {token: token, triggerLogin: false}}}; + // TODO: Change this to return a promise that "succeeds" even if the token delete fails? + return DataService.delete("oAuthAccessTokens", token, {}, opts); + }, + }; + }; +}); diff --git a/assets/app/views/util/logout.html b/assets/app/views/util/logout.html index be6b27649c4e..4f93da120988 100644 --- a/assets/app/views/util/logout.html +++ b/assets/app/views/util/logout.html @@ -1,8 +1,6 @@

Log out

-
- You have been logged out. Return to the console. -
+
diff --git a/assets/test/spec/controllers/project.js b/assets/test/spec/controllers/project.js index 6011fed1499f..66eda45fb9f9 100644 --- a/assets/test/spec/controllers/project.js +++ b/assets/test/spec/controllers/project.js @@ -27,6 +27,7 @@ describe('Controller: ProjectController', function () { angular.module('openshiftConsole').config(function(AuthServiceProvider) { AuthServiceProvider.LoginService('RedirectLoginService'); + AuthServiceProvider.LogoutService('DeleteTokenLogoutService'); AuthServiceProvider.UserStore('MemoryUserStore'); }); diff --git a/assets/test/spec/controllers/projects.js b/assets/test/spec/controllers/projects.js index 560d255b139f..bbc85860133a 100644 --- a/assets/test/spec/controllers/projects.js +++ b/assets/test/spec/controllers/projects.js @@ -27,6 +27,7 @@ describe('Controller: ProjectsController', function () { angular.module('openshiftConsole').config(function(AuthServiceProvider) { AuthServiceProvider.LoginService('RedirectLoginService'); + AuthServiceProvider.LogoutService('DeleteTokenLogoutService'); AuthServiceProvider.UserStore('MemoryUserStore'); }); diff --git a/pkg/assets/bindata.go b/pkg/assets/bindata.go index 300819221154..cf6b84421ac4 100644 --- a/pkg/assets/bindata.go +++ b/pkg/assets/bindata.go @@ -12472,7 +12472,7 @@ redirectTo:"/" }); } ]).config([ "$httpProvider", "AuthServiceProvider", "RedirectLoginServiceProvider", function(a, b, c) { if (window.OPENSHIFT_CONFIG) { -a.interceptors.push("AuthInterceptor"), b.LoginService("RedirectLoginService"), b.UserStore("LocalStorageUserStore"); +a.interceptors.push("AuthInterceptor"), b.LoginService("RedirectLoginService"), b.LogoutService("DeleteTokenLogoutService"), b.UserStore("LocalStorageUserStore"); var d = window.OPENSHIFT_CONFIG.auth; c.OAuthClientID(d.oauth_client_id), c.OAuthAuthorizeURI(d.oauth_authorize_uri), c.OAuthRedirectURI(d.oauth_redirect_base + "/oauth"); } @@ -12612,24 +12612,32 @@ return a && (b = a), b; var c = ""; this.LoginService = function(a) { return a && (c = a), c; -}, this.$get = [ "$q", "$injector", "$rootScope", function(d, e, f) { +}; +var d = ""; +this.LogoutService = function(a) { +return a && (d = a), d; +}; +var e = function(a, b, c) { +if (b) return angular.isString(b) ? a.get(b) :a.invoke(b); +throw c + " not set"; +}; +this.$get = [ "$q", "$injector", "$log", "$rootScope", function(f, g, h, i) { a && console.log("AuthServiceProvider.$get", arguments); -var g, h = $.Callbacks(), i = $.Callbacks(), j = null; -if (!b) throw "AuthServiceProvider.UserStore() not set"; -g = angular.isString(b) ? e.get(b) :e.invoke(b); -var k; -if (!c) throw "AuthServiceProvider.LoginService() not set"; -return k = angular.isString(c) ? e.get(c) :e.invoke(c), { +var j = $.Callbacks(), k = $.Callbacks(), l = $.Callbacks(), m = null, n = null, o = e(g, b, "AuthServiceProvider.UserStore()"), p = e(g, c, "AuthServiceProvider.LoginService()"), q = e(g, d, "AuthServiceProvider.LogoutService()"); +return { +isLoggedIn:function() { +return !!o.getUser(); +}, withUser:function() { -var b = g.getUser(); -return b ? (f.user = b, a && console.log("AuthService.withUser()", b), d.when(b)) :(a && console.log("AuthService.withUser(), calling startLogin()"), this.startLogin()); +var b = o.getUser(); +return b ? (i.user = b, a && console.log("AuthService.withUser()", b), f.when(b)) :(a && console.log("AuthService.withUser(), calling startLogin()"), this.startLogin()); }, setUser:function(b, c) { a && console.log("AuthService.setUser()", b, c); -var d = g.getUser(); -g.setUser(b), g.setToken(c), f.user = b; -var e = d && d.metadata && d.metadata.name, h = b && b.metadata && b.metadata.name; -e != h && (a && console.log("AuthService.setUser(), user changed", d, b), i.fire(b)); +var d = o.getUser(); +o.setUser(b), o.setToken(c), i.user = b; +var e = d && d.metadata && d.metadata.name, f = b && b.metadata && b.metadata.name; +e != f && (a && console.log("AuthService.setUser(), user changed", d, b), l.fire(b)); }, requestRequiresAuth:function(b) { var c = b.url.toString().indexOf("api/") > 0; @@ -12637,26 +12645,42 @@ return a && c && console.log("AuthService.requestRequiresAuth()", b.url.toString }, addAuthToRequest:function(b) { var c = ""; -return b && b.auth && b.auth.token ? (c = b.auth.token, a && console.log("AuthService.addAuthToRequest(), using token from request config", c)) :(c = g.getToken(), a && console.log("AuthService.addAuthToRequest(), using token from user store", c)), c ? ("WATCH" == b.method ? (b.url = URI(b.url).addQuery({ +return b && b.auth && b.auth.token ? (c = b.auth.token, a && console.log("AuthService.addAuthToRequest(), using token from request config", c)) :(c = o.getToken(), a && console.log("AuthService.addAuthToRequest(), using token from user store", c)), c ? ("WATCH" == b.method ? (b.url = URI(b.url).addQuery({ access_token:c }).toString(), a && console.log("AuthService.addAuthToRequest(), added token param", b.url)) :(b.headers.Authorization = "Bearer " + c, a && console.log("AuthService.addAuthToRequest(), added token header", b.headers.Authorization)), !0) :(a && console.log("AuthService.addAuthToRequest(), no token available"), !1); }, startLogin:function() { -if (j) return a && console.log("Login already in progress"), j; +if (m) return a && console.log("Login already in progress"), m; var b = this; -return j = k.login().then(function(a, c) { -b.setUser(a, c), h.fire(a); +return m = p.login().then(function(a, c) { +b.setUser(a, c), j.fire(a); })["catch"](function(a) { console.log(a); })["finally"](function() { -j = null; +m = null; +}); +}, +startLogout:function() { +if (n) return a && console.log("Logout already in progress"), n; +var b = this, c = o.getUser(), d = o.getToken(), e = this.isLoggedIn(); +return n = q.logout(c, d).then(function() { +a && console.log("Logout service success"); +})["catch"](function(b) { +a && console.log("Logout service error", b); +})["finally"](function() { +b.setUser(null, null); +var a = b.isLoggedIn(); +e && !a && k.fire(), n = null; }); }, onLogin:function(a) { -h.add(a); +j.add(a); +}, +onLogout:function(a) { +k.add(a); }, onUserChanged:function(a) { -i.add(a); +l.add(a); } }; } ]; @@ -12718,6 +12742,42 @@ c[k] || (c[k] = {}), e ? "DELETED" == d ? delete c[k][l][i] :(c[k][l] || (c[k][l f.prototype.list = function(a, b, c) { var d = this._listCallbacks(a, b); d.add(c), this._watchInFlight(a, b) && this._resourceVersion(a, b) ? (d.fire(this._data(a, b)), d.empty()) :this._listInFlight(a, b) || this._startListOp(a, b); +}, f.prototype["delete"] = function(b, c, e, f) { +f = f || {}; +var g = d.defer(); +if (e.projectPromise && "projects" !== b) { +var h = this; +e.projectPromise.done(function(d) { +a(angular.extend({ +method:"DELETE", +url:h._urlForType(b, c, e, !1, { +namespace:d.metadata.name +}) +}, f.http || {})).success(function(a) { +g.resolve(a); +}).error(function(a, b, c, d) { +g.reject({ +data:a, +status:b, +headers:c, +config:d +}); +}); +}); +} else a(angular.extend({ +method:"DELETE", +url:this._urlForType(b, c, e) +}, f.http || {})).success(function(a) { +g.resolve(a); +}).error(function(a, b, c, d) { +g.reject({ +data:a, +status:b, +headers:c, +config:d +}); +}); +return g.promise; }, f.prototype.get = function(b, e, f, g) { g = g || {}; var h = !!g.force; @@ -12880,11 +12940,12 @@ var m = { builds:l.openshift, deploymentConfigs:l.openshift, images:l.openshift, +oAuthAccessTokens:l.openshift, projects:l.openshift, users:l.openshift, pods:l.k8s, -services:l.k8s, -replicationControllers:l.k8s +replicationControllers:l.k8s, +services:l.k8s }; return f.prototype._urlForType = function(a, b, c, d, e) { var f; @@ -12945,6 +13006,24 @@ return f.when({}); } }; } ]; +}), angular.module("openshiftConsole").provider("DeleteTokenLogoutService", function() { +var a = !0; +this.$get = [ "$q", "$injector", function(b, c) { +return { +logout:function(d, e) { +if (a && console.log("DeleteTokenLogoutService.logout()", d, e), !e) return a && console.log("DeleteTokenLogoutService, no token, returning immediately"), b.when({}); +var f = c.get("DataService"), g = { +http:{ +auth:{ +token:e, +triggerLogin:!1 +} +} +}; +return f["delete"]("oAuthAccessTokens", e, {}, g); +} +}; +} ]; }), angular.module("openshiftConsole").factory("LabelFilter", [ function() { function a() { this._conjuncts = {}; @@ -13371,10 +13450,10 @@ default: a.errorMessage = "An error has occurred"; } b.error_description && (a.errorDetails = b.error_description); -} ]), angular.module("openshiftConsole").controller("LogoutController", [ "$rootScope", "$scope", "AuthService", function(a, b, c) { -c.onUserChanged(function() { -console.log("LogoutController - user changed, reloading the page"), window.location.reload(!1); -}), c.setUser(null, null); +} ]), angular.module("openshiftConsole").controller("LogoutController", [ "$scope", "$log", "AuthService", function(a, b, c) { +b.debug("LogoutController"), c.isLoggedIn() ? (b.debug("LogoutController, logged in, initiating logout"), a.logoutMessage = "Logging out...", c.startLogout()["finally"](function() { +c.isLoggedIn() ? (b.debug("LogoutController, logout failed, still logged in"), a.logoutMessage = 'You could not be logged out. Return to the console.') :(b.debug("LogoutController, logout completed, reloading the page"), window.location.reload(!1)); +})) :(b.debug("LogoutController, not logged in, logout complete"), a.logoutMessage = 'You are logged out. Return to the console.'); } ]), angular.module("openshiftConsole").directive("relativeTimestamp", function() { return { restrict:"E", @@ -55382,7 +55461,7 @@ func views_util_error_html() ([]byte, error) { return _views_util_error_html, nil } -var _views_util_logout_html = []byte(`

Log out

You have been logged out. Return to the console.
`) +var _views_util_logout_html = []byte(`

Log out

`) func views_util_logout_html() ([]byte, error) { return _views_util_logout_html, nil