From fa57036a717f2fcd8287e49428e5ca818de1f7a1 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Thu, 29 Sep 2022 17:42:13 +0200 Subject: [PATCH 07/10] misc: use hooks to accumulate redirect logout urls (#69720) --- src/authentic2/views.py | 8 +- src/authentic2_auth_fc/apps.py | 39 ++++----- src/authentic2_auth_oidc/apps.py | 116 +++++++++++++-------------- src/authentic2_auth_saml/apps.py | 10 ++- src/authentic2_auth_saml/backends.py | 9 --- 5 files changed, 87 insertions(+), 95 deletions(-) diff --git a/src/authentic2/views.py b/src/authentic2/views.py index fc160474..cfd82b80 100644 --- a/src/authentic2/views.py +++ b/src/authentic2/views.py @@ -591,8 +591,12 @@ def logout_list(request): def redirect_logout_list(request): - '''Return redirect logout links from idp backends''' - return utils_misc.accumulate_from_backends(request, 'redirect_logout_list') + '''Return redirect logout URLs from idp backends or authenticators''' + redirect_logout_list = [] + for urls in hooks.call_hooks('redirect_logout_list', request=request): + if urls: + redirect_logout_list.extend(urls) + return redirect_logout_list def logout(request, next_url=None, do_local=True, check_referer=True): diff --git a/src/authentic2_auth_fc/apps.py b/src/authentic2_auth_fc/apps.py index ff7f8982..2a5ef20b 100644 --- a/src/authentic2_auth_fc/apps.py +++ b/src/authentic2_auth_fc/apps.py @@ -18,31 +18,9 @@ import django.apps from django import template -class Plugin: - def redirect_logout_list(self, request, **kwargs): - from django.urls import reverse - - from . import utils - from .models import FcAuthenticator - - try: - authenticator = FcAuthenticator.objects.get() - except FcAuthenticator.DoesNotExist: - return [] - - url = utils.build_logout_url(request, authenticator.logout_url, next_url=reverse('auth_logout')) - # url is assumed empty if no active session on the OP. - if url: - return [url] - return [] - - class AppConfig(django.apps.AppConfig): name = 'authentic2_auth_fc' - def get_a2_plugin(self): - return Plugin() - def a2_hook_api_modify_serializer(self, view, serializer): from rest_framework import serializers @@ -120,3 +98,20 @@ class AppConfig(django.apps.AppConfig): 'sub': fc_account.sub, } ) + + def a2_hook_redirect_logout_list(self, request, **kwargs): + from django.urls import reverse + + from . import utils + from .models import FcAuthenticator + + try: + authenticator = FcAuthenticator.objects.get() + except FcAuthenticator.DoesNotExist: + return [] + + url = utils.build_logout_url(request, authenticator.logout_url, next_url=reverse('auth_logout')) + # url is assumed empty if no active session on the OP. + if url: + return [url] + return [] diff --git a/src/authentic2_auth_oidc/apps.py b/src/authentic2_auth_oidc/apps.py index aada5d6e..cd234a68 100644 --- a/src/authentic2_auth_oidc/apps.py +++ b/src/authentic2_auth_oidc/apps.py @@ -14,74 +14,16 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import logging + import django.apps +import requests from django import template -class Plugin: - def revoke_token(self, provider, access_token): - import logging - - import requests - - logger = logging.getLogger(__name__) - - url = provider.token_revocation_endpoint - try: - response = requests.post( - url, - auth=(provider.client_id, provider.client_secret), - data={'token': access_token, 'token_type': 'access_token'}, - timeout=10, - ) - except requests.RequestException as e: - logger.warning('failed to revoke access token from OIDC provider %s: %s', provider.issuer, e) - return - try: - response.raise_for_status() - except requests.RequestException as e: - try: - content = response.json() - except ValueError: - content = None - logger.warning( - 'failed to revoke access token from OIDC provider %s: %s, %s', provider.issuer, e, content - ) - return - logger.info('revoked token from OIDC provider %s', provider.issuer) - - def redirect_logout_list(self, request, next=None): - from django.urls import reverse - - from authentic2.utils.misc import make_url - - from .models import OIDCProvider - - tokens = request.session.get('auth_oidc', {}).get('tokens', []) - urls = [] - if tokens: - for token in tokens: - provider = OIDCProvider.objects.get(pk=token['provider_pk']) - # ignore providers wihtout SLO - if not provider.end_session_endpoint: - continue - params = {} - if 'id_token' in token['token_response']: - params['id_token_hint'] = token['token_response']['id_token'] - if 'access_token' in token['token_response'] and provider.token_revocation_endpoint: - self.revoke_token(provider, token['token_response']['access_token']) - params['post_logout_redirect_uri'] = request.build_absolute_uri(reverse('auth_logout')) - urls.append(make_url(provider.end_session_endpoint, params=params)) - return urls - - class AppConfig(django.apps.AppConfig): - name = 'authentic2_auth_oidc' - def get_a2_plugin(self): - return Plugin() - def ready(self): from django.db.models.signals import pre_save @@ -108,3 +50,55 @@ class AppConfig(django.apps.AppConfig): return [ template.loader.get_template('authentic2_auth_oidc/manager_user_sidebar.html').render(context) ] + + def a2_hook_redirect_logout_list(self, request, **kwargs): + from django.urls import reverse + + from authentic2.utils.misc import make_url + + from .models import OIDCProvider + + tokens = request.session.get('auth_oidc', {}).get('tokens', []) + urls = [] + if tokens: + for token in tokens: + provider = OIDCProvider.objects.get(pk=token['provider_pk']) + # ignore providers wihtout SLO + if not provider.end_session_endpoint: + continue + params = {} + if 'id_token' in token['token_response']: + params['id_token_hint'] = token['token_response']['id_token'] + if 'access_token' in token['token_response'] and provider.token_revocation_endpoint: + self._revoke_token(provider, token['token_response']['access_token']) + params['post_logout_redirect_uri'] = request.build_absolute_uri(reverse('auth_logout')) + urls.append(make_url(provider.end_session_endpoint, params=params)) + return urls + + @classmethod + def _revoke_token(cls, provider, access_token): + logger = logging.getLogger(__name__) + + url = provider.token_revocation_endpoint + try: + response = requests.post( + url, + auth=(provider.client_id, provider.client_secret), + data={'token': access_token, 'token_type': 'access_token'}, + timeout=10, + ) + except requests.RequestException as e: + logger.warning('failed to revoke access token from OIDC provider %s: %s', provider.issuer, e) + return + try: + response.raise_for_status() + except requests.RequestException as e: + try: + content = response.json() + except ValueError: + content = None + logger.warning( + 'failed to revoke access token from OIDC provider %s: %s, %s', provider.issuer, e, content + ) + return + logger.info('revoked token from OIDC provider %s', provider.issuer) diff --git a/src/authentic2_auth_saml/apps.py b/src/authentic2_auth_saml/apps.py index 34af2c03..e504287e 100644 --- a/src/authentic2_auth_saml/apps.py +++ b/src/authentic2_auth_saml/apps.py @@ -20,7 +20,6 @@ from mellon.utils import get_idp class AppConfig(django.apps.AppConfig): - name = 'authentic2_auth_saml' def ready(self): @@ -54,3 +53,12 @@ class AppConfig(django.apps.AppConfig): return [ template.loader.get_template('authentic2_auth_saml/manager_user_sidebar.html').render(context) ] + + def a2_hook_redirect_logout_list(self, request, **kwargs): + from mellon.views import logout + + if 'mellon_session' in request.session: + response = logout(request) + if 'Location' in response: + return [response['Location']] + return [] diff --git a/src/authentic2_auth_saml/backends.py b/src/authentic2_auth_saml/backends.py index d376a4de..0d943512 100644 --- a/src/authentic2_auth_saml/backends.py +++ b/src/authentic2_auth_saml/backends.py @@ -38,12 +38,3 @@ class SAMLBackend(BaseSAMLBackend): import lasso return lasso.SAML2_AUTHN_CONTEXT_PREVIOUS_SESSION - - def redirect_logout_list(self, request, next_url=None): - from mellon.views import logout - - if 'mellon_session' in request.session: - response = logout(request) - if 'Location' in response: - return [response['Location']] - return [] -- 2.37.2