From 22e9dd81c1f096c4928d770a7bc1f8f93a455936 Mon Sep 17 00:00:00 2001 From: wluyima Date: Mon, 1 Jul 2024 11:49:45 +0300 Subject: [PATCH] When a user logs out they should also be logged out of the authentication provider #12 Implemented post provider logout callback handler #12 --- ftw/oidcauth/browser/configure.zcml | 7 +++++ ftw/oidcauth/browser/oidc.py | 49 +++++++++++++++++++++++++++++ ftw/oidcauth/plugin.py | 2 ++ ftw/oidcauth/www/config.zpt | 7 +++++ 4 files changed, 65 insertions(+) diff --git a/ftw/oidcauth/browser/configure.zcml b/ftw/oidcauth/browser/configure.zcml index 84d3a8a..8300fe7 100644 --- a/ftw/oidcauth/browser/configure.zcml +++ b/ftw/oidcauth/browser/configure.zcml @@ -11,4 +11,11 @@ permission="zope.Public" /> + diff --git a/ftw/oidcauth/browser/oidc.py b/ftw/oidcauth/browser/oidc.py index 20600a2..e3162d5 100644 --- a/ftw/oidcauth/browser/oidc.py +++ b/ftw/oidcauth/browser/oidc.py @@ -1,7 +1,10 @@ +from Products.CMFPlone.browser.login.logout import LogoutView from Products.Five import BrowserView from ftw.oidcauth.browser.oidc_tools import OIDCClientAuthentication from ftw.oidcauth.errors import OIDCBaseError +from six.moves.urllib.parse import parse_qsl, urlencode from zExceptions import NotFound as zNotFound +from zope.component import getMultiAdapter from zope.interface import implements from zope.publisher.interfaces import IPublishTraverse from zope.publisher.interfaces import NotFound @@ -23,6 +26,8 @@ def publishTraverse(self, request, name): if self.method is None: if name == 'callback': self.method = name + elif name == 'logout': + self.method = name else: raise NotFound(self, name, request) else: @@ -32,6 +37,8 @@ def publishTraverse(self, request, name): def __call__(self): if self.method == 'callback': self.callback() + elif self.method == 'logout': + self.logout() else: raise zNotFound() @@ -58,3 +65,45 @@ def set_error_response(self, status, message): response.setHeader('Content-Type', 'text/plain') response.setStatus(status, lock=1) response.setBody(message, lock=1) + + def logout(self): + p = OIDCClientAuthentication.get_oidc_plugin() + base_url = get_base_url(self.context, self.request) + original_redirect = self.request.get('redirect') + redirect = base_url + if original_redirect: + if original_redirect.startswith("http:") or original_redirect.startswith("https:"): + redirect = original_redirect + else: + redirect = base_url + original_redirect + + logout_base_url = p.end_session_endpoint + params = {} + if "?" in p.end_session_endpoint: + url_parts = p.end_session_endpoint.split("?") + logout_base_url = url_parts[0] + params.update(dict(parse_qsl(url_parts[1]))) + params["client_id"] = p.client_id + params["post_logout_redirect_uri"] = redirect + logout_url = "{}?{}".format(logout_base_url, urlencode(params)) + self.request.response.redirect(logout_url) + + +class OIDCLogoutView(LogoutView): + def __call__(self): + if OIDCClientAuthentication.get_oidc_plugin().end_session_endpoint: + base_url = get_base_url(self.context, self.request) + next_ = self.request.get('next') + oidc_logout = base_url + '/oidc/logout' + if next_ is None or not next_.startswith(oidc_logout): + if next_: + oidc_logout = "{}?{}".format(oidc_logout, urlencode({'redirect': next_})) + redirect = "{}?{}".format(base_url + '/logout', urlencode({'next': oidc_logout})) + self.request.response.redirect(redirect) + return + + super(OIDCLogoutView, self).__call__() + + +def get_base_url(context, request): + return getMultiAdapter((context, request), name="plone_portal_state").navigation_root_url() diff --git a/ftw/oidcauth/plugin.py b/ftw/oidcauth/plugin.py index b326733..55bf009 100644 --- a/ftw/oidcauth/plugin.py +++ b/ftw/oidcauth/plugin.py @@ -84,6 +84,7 @@ def __init__(self, id, title=None): self.token_endpoint = None self.user_endpoint = None self.jwks_endpoint = None + self.end_session_endpoint = None self._auto_provisioning_enabled = True self.properties_mapping = json.dumps({ "userid": "sub", @@ -228,6 +229,7 @@ def manage_updateConfig(self, REQUEST): self.token_endpoint = REQUEST.form.get('token-endpoint') self.user_endpoint = REQUEST.form.get('user-endpoint') self.jwks_endpoint = REQUEST.form.get('jwks-endpoint') + self.end_session_endpoint = REQUEST.form.get('end-session-endpoint') self._auto_provisioning_enabled = REQUEST.form.get('auto-provisioning-enabled') roles = REQUEST.form.get('roles') diff --git a/ftw/oidcauth/www/config.zpt b/ftw/oidcauth/www/config.zpt index c07461b..53b5890 100644 --- a/ftw/oidcauth/www/config.zpt +++ b/ftw/oidcauth/www/config.zpt @@ -81,6 +81,13 @@ Error: + +
End Session Endpoint
+
+ + +
Enable Auto Provisioning