From 7fc8018956945cfe683a2cf1c53643d800c482bf Mon Sep 17 00:00:00 2001 From: Jose Castro Leon Date: Fri, 27 Jul 2018 15:59:47 +0200 Subject: [PATCH] Allow to specify redirections on single IdP scenarios In scenarios where the cloud operators have only a single Identity Provider, we can have a default redirection to remove unnecessary user clicks and improve user experience. Closes-bug: #1784368 Change-Id: I251703dcaeac43174fbcba7e0658c6f92098b2e0 --- doc/source/configuration/settings.rst | 43 +++++++++++++++++++ openstack_auth/tests/unit/test_auth.py | 29 +++++++++++++ openstack_auth/urls.py | 7 ++- openstack_auth/user.py | 10 +++++ openstack_auth/utils.py | 24 +++++++++++ openstack_auth/views.py | 31 ++++++++++--- .../local/local_settings.py.example | 16 +++++++ openstack_dashboard/settings.py | 3 ++ ...-default-redirection-1acf25d32ac00dd1.yaml | 6 +++ 9 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/websso-default-redirection-1acf25d32ac00dd1.yaml diff --git a/doc/source/configuration/settings.rst b/doc/source/configuration/settings.rst index 7eec3fd337..d12c79143f 100644 --- a/doc/source/configuration/settings.rst +++ b/doc/source/configuration/settings.rst @@ -1522,6 +1522,49 @@ Default: ``"credentials"`` Specifies the default authentication mechanism. When user lands on the login page, this is the first choice they will see. +WEBSSO_DEFAULT_REDIRECT +~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 15.0.0(Stein) + +Default: ``False`` + +Allows to redirect on login to the IdP provider defined on PROTOCOL and REGION +In cases you have a single IdP providing websso, in order to improve user +experience, you can redirect on the login page to the IdP directly by +specifying WEBSSO_DEFAULT_REDIRECT_PROTOCOL and WEBSSO_DEFAULT_REDIRECT_REGION +variables. + +WEBSSO_DEFAULT_REDIRECT_PROTOCOL +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 15.0.0(Stein) + +Default: ``None`` + +Allows to specify the protocol for the IdP to contact if the +WEBSSO_DEFAULT_REDIRECT is set to True + +WEBSSO_DEFAULT_REDIRECT_REGION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 15.0.0(Stein) + +Default: ``OPENSTACK_KEYSTONE_URL`` + +Allows to specify thee region of the IdP to contact if the +WEBSSO_DEFAULT_REDIRECT is set to True + +WEBSSO_DEFAULT_REDIRECT_LOGOUT +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 15.0.0(Stein) + +Default: ``None`` + +Allows to specify a callback to the IdP to cleanup the SSO resources. +Once the user logs out it will redirect to the IdP log out method. + Neutron ------- diff --git a/openstack_auth/tests/unit/test_auth.py b/openstack_auth/tests/unit/test_auth.py index 2dd193ce11..826c5c69e7 100644 --- a/openstack_auth/tests/unit/test_auth.py +++ b/openstack_auth/tests/unit/test_auth.py @@ -1237,4 +1237,33 @@ class OpenStackAuthTestsWebSSO(OpenStackAuthTestsMixin, response = self.client.post(url, form_data) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) + def test_websso_login_default_redirect(self): + origin = 'http://testserver/auth/websso/' + protocol = 'oidc' + redirect_url = ('%s/auth/OS-FEDERATION/websso/%s?origin=%s' % + (settings.OPENSTACK_KEYSTONE_URL, protocol, origin)) + + settings.WEBSSO_DEFAULT_REDIRECT = True + settings.WEBSSO_DEFAULT_REDIRECT_PROTOCOL = 'oidc' + settings.WEBSSO_DEFAULT_REDIRECT_REGION = ( + settings.OPENSTACK_KEYSTONE_URL) + + url = reverse('login') + + # POST to the page and redirect to keystone. + response = self.client.get(url) + self.assertRedirects(response, redirect_url, status_code=302, + target_status_code=404) + + def test_websso_logout_default_redirect(self): + settings.WEBSSO_DEFAULT_REDIRECT = True + settings.WEBSSO_DEFAULT_REDIRECT_LOGOUT = 'http://idptest/logout' + + url = reverse('logout') + + # POST to the page and redirect to logout method from idp. + response = self.client.get(url) + self.assertRedirects(response, settings.WEBSSO_DEFAULT_REDIRECT_LOGOUT, + status_code=302, target_status_code=301) + load_tests = load_tests_apply_scenarios diff --git a/openstack_auth/urls.py b/openstack_auth/urls.py index 12d7cfb0b3..3673f7ecca 100644 --- a/openstack_auth/urls.py +++ b/openstack_auth/urls.py @@ -12,6 +12,7 @@ # limitations under the License. from django.conf.urls import url +from django.views import generic from openstack_auth import utils from openstack_auth import views @@ -33,4 +34,8 @@ urlpatterns = [ ] if utils.is_websso_enabled(): - urlpatterns.append(url(r"^websso/$", views.websso, name='websso')) + urlpatterns += [ + url(r"^websso/$", views.websso, name='websso'), + url(r"^error/$", + generic.TemplateView.as_view(template_name="403.html")) + ] diff --git a/openstack_auth/user.py b/openstack_auth/user.py index dd0f605a21..e5e3283709 100644 --- a/openstack_auth/user.py +++ b/openstack_auth/user.py @@ -38,6 +38,16 @@ def set_session_from_user(request, user): request.user = user +def unset_session_user_variables(request): + request.session['token'] = None + request.session['user_id'] = None + request.session['region_endpoint'] = None + request.session['services_region'] = None + # Update the user object cached in the request + request._cached_user = None + request.user = None + + def create_user_from_token(request, token, endpoint, services_region=None): # if the region is provided, use that, otherwise use the preferred region svc_region = services_region or \ diff --git a/openstack_auth/utils.py b/openstack_auth/utils.py index f17013bd9a..c08cdea3a4 100644 --- a/openstack_auth/utils.py +++ b/openstack_auth/utils.py @@ -146,6 +146,30 @@ def is_websso_enabled(): return websso_enabled and keystonev3_plus +def is_websso_default_redirect(): + """Checks if the websso default redirect is available. + + As with websso, this is only supported in Keystone version 3. + """ + websso_default_redirect = getattr(settings, + 'WEBSSO_DEFAULT_REDIRECT', False) + keystonev3_plus = (get_keystone_version() >= 3) + return websso_default_redirect and keystonev3_plus + + +def get_websso_default_redirect_protocol(): + return getattr(settings, 'WEBSSO_DEFAULT_REDIRECT_PROTOCOL', None) + + +def get_websso_default_redirect_region(): + return getattr(settings, 'WEBSSO_DEFAULT_REDIRECT_REGION', + settings.OPENSTACK_KEYSTONE_URL) + + +def get_websso_default_redirect_logout(): + return getattr(settings, 'WEBSSO_DEFAULT_REDIRECT_LOGOUT', None) + + def build_absolute_uri(request, relative_url): """Ensure absolute_uri are relative to WEBROOT.""" webroot = getattr(settings, 'WEBROOT', '') diff --git a/openstack_auth/views.py b/openstack_auth/views.py index 7f6536f70a..62ed7028cb 100644 --- a/openstack_auth/views.py +++ b/openstack_auth/views.py @@ -55,6 +55,17 @@ LOG = logging.getLogger(__name__) def login(request, template_name=None, extra_context=None, **kwargs): """Logs a user in using the :class:`~openstack_auth.forms.Login` form.""" + # If the user enabled websso and the default redirect + # redirect to the default websso url + if (request.method == 'GET' and utils.is_websso_enabled and + utils.is_websso_default_redirect()): + protocol = utils.get_websso_default_redirect_protocol() + region = utils.get_websso_default_redirect_region() + origin = request.build_absolute_uri('/auth/websso/') + url = ('%s/auth/OS-FEDERATION/websso/%s?origin=%s' % + (region, protocol, origin)) + return shortcuts.redirect(url) + # If the user enabled websso and selects default protocol # from the dropdown, We need to redirect user to the websso url if request.method == 'POST': @@ -151,9 +162,12 @@ def websso(request): request.user = auth.authenticate(request=request, auth_url=auth_url, token=token) except exceptions.KeystoneAuthException as exc: - msg = 'Login failed: %s' % six.text_type(exc) - res = django_http.HttpResponseRedirect(settings.LOGIN_URL) - res.set_cookie('logout_reason', msg, max_age=10) + if utils.is_websso_default_redirect(): + res = django_http.HttpResponseRedirect(settings.LOGIN_ERROR) + else: + msg = 'Login failed: %s' % six.text_type(exc) + res = django_http.HttpResponseRedirect(settings.LOGIN_URL) + res.set_cookie('logout_reason', msg, max_age=10) return res auth_user.set_session_from_user(request, request.user) @@ -178,8 +192,15 @@ def logout(request, login_url=None, **kwargs): LOG.info(msg) """ Securely logs a user out. """ - return django_auth_views.logout_then_login(request, login_url=login_url, - **kwargs) + if (utils.is_websso_enabled and utils.is_websso_default_redirect() and + utils.get_websso_default_redirect_logout()): + auth_user.unset_session_user_variables(request) + return django_http.HttpResponseRedirect( + utils.get_websso_default_redirect_logout()) + else: + return django_auth_views.logout_then_login(request, + login_url=login_url, + **kwargs) @login_required diff --git a/openstack_dashboard/local/local_settings.py.example b/openstack_dashboard/local/local_settings.py.example index 8cdad17b87..9ac3bcdbd2 100644 --- a/openstack_dashboard/local/local_settings.py.example +++ b/openstack_dashboard/local/local_settings.py.example @@ -26,6 +26,7 @@ DEBUG = True WEBROOT = '/' #LOGIN_URL = WEBROOT + 'auth/login/' #LOGOUT_URL = WEBROOT + 'auth/logout/' +#LOGIN_ERROR = WEBROOT + 'auth/error/' # # LOGIN_REDIRECT_URL can be used as an alternative for # HORIZON_CONFIG.user_home, if user_home is not set. @@ -231,6 +232,21 @@ OPENSTACK_KEYSTONE_DEFAULT_ROLE = "_member_" # "acme_saml2": ("acme", "saml2"), #} +# Enables redirection on login to the identity provider defined on +# WEBSSO_DEFAULT_REDIRECT_PROTOCOL and WEBSSO_DEFAULT_REDIRECT_REGION +#WEBSSO_DEFAULT_REDIRECT = False + +# Specifies the protocol to use for default redirection on login +#WEBSSO_DEFAULT_REDIRECT_PROTOCOL = None + +# Specifies the region to which the connection will be established on login +#WEBSSO_DEFAULT_REDIRECT_REGION = OPENSTACK_KEYSTONE_URL + +# Enables redirection on logout to the method specified on the identity provider. +# Once logout the client will be redirected to the address specified in this +# variable. +#WEBSSO_DEFAULT_REDIRECT_LOGOUT = None + # The Keystone Provider drop down uses Keystone to Keystone federation # to switch between Keystone service providers. # Set display name for Identity Provider (dropdown display name) diff --git a/openstack_dashboard/settings.py b/openstack_dashboard/settings.py index 2701527ac7..6283413a5d 100644 --- a/openstack_dashboard/settings.py +++ b/openstack_dashboard/settings.py @@ -54,6 +54,7 @@ SITE_BRANDING = 'OpenStack Dashboard' WEBROOT = '/' LOGIN_URL = None LOGOUT_URL = None +LOGIN_ERROR = None LOGIN_REDIRECT_URL = None MEDIA_ROOT = None MEDIA_URL = None @@ -418,6 +419,8 @@ if LOGIN_URL is None: LOGIN_URL = WEBROOT + 'auth/login/' if LOGOUT_URL is None: LOGOUT_URL = WEBROOT + 'auth/logout/' +if LOGIN_ERROR is None: + LOGIN_ERROR = WEBROOT + 'auth/error/' if LOGIN_REDIRECT_URL is None: LOGIN_REDIRECT_URL = WEBROOT diff --git a/releasenotes/notes/websso-default-redirection-1acf25d32ac00dd1.yaml b/releasenotes/notes/websso-default-redirection-1acf25d32ac00dd1.yaml new file mode 100644 index 0000000000..77000f7703 --- /dev/null +++ b/releasenotes/notes/websso-default-redirection-1acf25d32ac00dd1.yaml @@ -0,0 +1,6 @@ +--- +features: + - Adds the possibility to redirect the login to an identity provider by + default. For that purpose the following variables have been added, + ``WEBSSO_DEFAULT_REDIRECT``, ``WEBSSO_DEFAULT_REDIRECT_PROTOCOL``, + ``WEBSSO_DEFAULT_REDIRECT_REGION`` and ``WEBSSO_DEFAULT_REDIRECT_LOGOUT``.