diff --git a/etc/refstack.conf.sample b/etc/refstack.conf.sample index af65d549..bb349fe3 100644 --- a/etc/refstack.conf.sample +++ b/etc/refstack.conf.sample @@ -271,6 +271,9 @@ # OpenStackID Auth Server URI. (string value) #openstack_openid_endpoint = https://openstackid.org/accounts/openid2 +# OpenStackID logout URI. (string value) +#openid_logout_endpoint = https://openstackid.org/accounts/user/logout + # Interaction mode. Specifies whether Openstack Id IdP may interact # with the user to determine the outcome of the request. (string # value) diff --git a/refstack-ui/app/app.js b/refstack-ui/app/app.js index 359825cc..be1e3228 100644 --- a/refstack-ui/app/app.js +++ b/refstack-ui/app/app.js @@ -73,6 +73,11 @@ url: '/auth_failure/:message', templateUrl: '/components/home/home.html', controller: 'AuthFailureController as ctrl' + }). + state('logout', { + url: '/logout', + templateUrl: '/components/logout/logout.html', + controller: 'LogoutController as ctrl' }); } @@ -108,10 +113,6 @@ */ function setup($http, $rootScope, $window, $state, refstackApiUrl) { - /** - * This function injects sign in function in all scopes - */ - $rootScope.auth = {}; $rootScope.auth.doSignIn = doSignIn; $rootScope.auth.doSignOut = doSignOut; diff --git a/refstack-ui/app/components/logout/logout.html b/refstack-ui/app/components/logout/logout.html new file mode 100644 index 00000000..38a5c369 --- /dev/null +++ b/refstack-ui/app/components/logout/logout.html @@ -0,0 +1 @@ +<div cg-busy="{promise:ctrl.redirectWait,message:'Logging you out...'}"></div> diff --git a/refstack-ui/app/components/logout/logoutController.js b/refstack-ui/app/components/logout/logoutController.js new file mode 100644 index 00000000..86acb4af --- /dev/null +++ b/refstack-ui/app/components/logout/logoutController.js @@ -0,0 +1,44 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +(function () { + 'use strict'; + + angular + .module('refstackApp') + .controller('LogoutController', LogoutController); + + LogoutController.$inject = [ + '$location', '$window', '$timeout' + ]; + + /** + * Refstack Logout Controller + * This controller handles logging out. In order to fully logout, the + * openstackid_session cookie must also be removed. The way to do that + * is to have the user's browser make a request to the openstackid logout + * page. We do this by placing the logout link as the src for an html + * image. After some time, the user is redirected home. + */ + function LogoutController($location, $window, $timeout) { + var ctrl = this; + + ctrl.openid_logout_url = $location.search().openid_logout; + var img = new Image(0, 0); + img.src = ctrl.openid_logout_url; + ctrl.redirectWait = $timeout(function() { + $window.location.href = '/'; + }, 500); + } +})(); diff --git a/refstack-ui/app/index.html b/refstack-ui/app/index.html index 80a75058..dacb5f8b 100644 --- a/refstack-ui/app/index.html +++ b/refstack-ui/app/index.html @@ -45,6 +45,7 @@ <script src="components/results-report/resultsReportController.js"></script> <script src="components/profile/profileController.js"></script> <script src="components/auth-failure/authFailureController.js"></script> + <script src="components/logout/logoutController.js"></script> <!-- Filters --> <script src="shared/filters.js"></script> diff --git a/refstack-ui/tests/unit/ControllerSpec.js b/refstack-ui/tests/unit/ControllerSpec.js index 784dddf6..ae67fd19 100644 --- a/refstack-ui/tests/unit/ControllerSpec.js +++ b/refstack-ui/tests/unit/ControllerSpec.js @@ -42,6 +42,22 @@ describe('Refstack controllers', function () { }); }); + describe('LogoutController', function () { + var $location, ctrl; + + beforeEach(inject(function ($controller, _$location_) { + $location = _$location_; + $location.url('/logout?openid_logout=some_url'); + ctrl = $controller('LogoutController', {}); + })); + + it('should set the openID logout URL based on query string', + function () { + expect($location.url()).toBe('/logout?openid_logout=some_url'); + expect(ctrl.openid_logout_url).toBe('some_url'); + }); + }); + describe('CapabilitiesController', function () { var ctrl; diff --git a/refstack/api/controllers/auth.py b/refstack/api/controllers/auth.py index 1d8373e9..9e388dad 100644 --- a/refstack/api/controllers/auth.py +++ b/refstack/api/controllers/auth.py @@ -33,6 +33,10 @@ OPENID_OPTS = [ default='https://openstackid.org/accounts/openid2', help='OpenStackID Auth Server URI.' ), + cfg.StrOpt('openid_logout_endpoint', + default='https://openstackid.org/accounts/user/logout', + help='OpenStackID logout URI.' + ), cfg.StrOpt('openid_mode', default='checkid_setup', help='Interaction mode. Specifies whether Openstack Id ' @@ -167,9 +171,15 @@ class AuthController(rest.RestController): pecan.redirect(CONF.ui_url) - @pecan.expose() + @pecan.expose('json') def signout(self): """Handle signout request.""" if api_utils.is_authenticated(): api_utils.delete_params_from_user_session([const.USER_OPENID]) - pecan.redirect(CONF.ui_url) + + params = { + 'openid_logout': CONF.osid.openid_logout_endpoint + } + url = parse.urljoin(CONF.ui_url, + '/#/logout?' + parse.urlencode(params)) + pecan.redirect(url) diff --git a/refstack/tests/unit/test_api.py b/refstack/tests/unit/test_api.py index 0239ac97..10edf7cb 100644 --- a/refstack/tests/unit/test_api.py +++ b/refstack/tests/unit/test_api.py @@ -413,6 +413,8 @@ class AuthControllerTestCase(BaseControllerTestCase): self.CONF = self.useFixture(self.config_fixture).conf self.CONF.set_override('app_dev_mode', True, 'api') self.CONF.set_override('ui_url', 'http://127.0.0.1') + self.CONF.set_override('openid_logout_endpoint', 'http://some-url', + 'osid') @mock.patch('refstack.api.utils.get_user_session') @mock.patch('pecan.redirect', side_effect=webob.exc.HTTPRedirection) @@ -527,7 +529,8 @@ class AuthControllerTestCase(BaseControllerTestCase): const.CSRF_TOKEN: 42 } self.assertRaises(webob.exc.HTTPRedirection, self.controller.signout) - mock_redirect.assert_called_with('http://127.0.0.1') + mock_redirect.assert_called_with('http://127.0.0.1/#/logout?' + 'openid_logout=http%3A%2F%2Fsome-url') self.assertNotIn(const.CSRF_TOKEN, mock_request.environ['beaker.session'])