From 8e128142f9428cbad51eed16f83770aaadff56dd Mon Sep 17 00:00:00 2001
From: Emmanuel Cazenave <ecazenave@entrouvert.com>
Date: Mon, 22 Jun 2020 15:55:44 +0200
Subject: [PATCH] remove authentic2_auth_oidc from plugin system (#44322)

---
 setup.py                             |  1 -
 src/authentic2/settings.py           | 10 ++--
 src/authentic2/urls.py               |  8 +++-
 src/authentic2/utils/__init__.py     |  9 ++++
 src/authentic2_auth_oidc/__init__.py | 64 +------------------------
 src/authentic2_auth_oidc/apps.py     | 71 ++++++++++++++++++++++++++++
 6 files changed, 95 insertions(+), 68 deletions(-)
 create mode 100644 src/authentic2_auth_oidc/apps.py

diff --git a/setup.py b/setup.py
index bede39a1..74d61806 100755
--- a/setup.py
+++ b/setup.py
@@ -169,7 +169,6 @@ setup(name="authentic2",
       },
       entry_points={
           'authentic2.plugin': [
-              'authentic2-auth-oidc = authentic2_auth_oidc:Plugin',
               'authentic2-idp-cas = authentic2_idp_cas:Plugin',
               'authentic2-idp-oidc = authentic2_idp_oidc:Plugin',
               'authentic2-provisionning-ldap = authentic2_provisionning_ldap:Plugin',
diff --git a/src/authentic2/settings.py b/src/authentic2/settings.py
index a192dabb..833c9f0c 100644
--- a/src/authentic2/settings.py
+++ b/src/authentic2/settings.py
@@ -130,6 +130,7 @@ INSTALLED_APPS = (
     'django_tables2',
     'mellon',
     'authentic2_auth_saml',
+    'authentic2_auth_oidc',
     'authentic2.nonce',
     'authentic2.saml',
     'authentic2.idp',
@@ -158,6 +159,7 @@ AUTHENTICATION_BACKENDS = (
     'authentic2.backends.models_backend.DummyModelBackend',
     'django_rbac.backends.DjangoRBACBackend',
     'authentic2_auth_saml.backends.SAMLBackend',
+    'authentic2_auth_oidc.backends.OIDCBackend',
 )
 AUTHENTICATION_BACKENDS = plugins.register_plugins_authentication_backends(AUTHENTICATION_BACKENDS)
 CSRF_FAILURE_VIEW = 'authentic2.views.csrf_failure_view'
@@ -176,9 +178,11 @@ ACCOUNT_ACTIVATION_DAYS = 2
 # Authentication settings
 ###########################
 AUTH_USER_MODEL = 'custom_user.User'
-AUTH_FRONTENDS = ('authentic2_auth_saml.authenticators.SAMLAuthenticator',) + \
-    plugins.register_plugins_authenticators((
-        'authentic2.authenticators.LoginPasswordAuthenticator',))
+AUTH_FRONTENDS = (
+    'authentic2_auth_saml.authenticators.SAMLAuthenticator',
+    'authentic2_auth_oidc.authenticators.OIDCAuthenticator',
+) + plugins.register_plugins_authenticators((
+    'authentic2.authenticators.LoginPasswordAuthenticator',))
 
 ###########################
 # RBAC settings
diff --git a/src/authentic2/urls.py b/src/authentic2/urls.py
index 276ba837..fa002184 100644
--- a/src/authentic2/urls.py
+++ b/src/authentic2/urls.py
@@ -25,6 +25,7 @@ from django.views.static import serve as media_serve
 
 from . import plugins, views
 from authentic2.decorators import setting_enabled, required, lasso_required
+import authentic2_auth_oidc.urls
 import authentic2_auth_saml.urls
 import authentic2.idp.saml.app_settings
 
@@ -156,4 +157,9 @@ authentic2_idp_saml_urls = required(
     ),
     [url(r'^idp/saml2/', include('authentic2.idp.saml.urls'))]
 )
-urlpatterns = authentic2_auth_saml.urls.urlpatterns + authentic2_idp_saml_urls + urlpatterns
+urlpatterns = (
+    authentic2_auth_oidc.urls.urlpatterns +
+    authentic2_auth_saml.urls.urlpatterns +
+    authentic2_idp_saml_urls +
+    urlpatterns
+)
diff --git a/src/authentic2/utils/__init__.py b/src/authentic2/utils/__init__.py
index 25179b0e..f4dbe0b7 100644
--- a/src/authentic2/utils/__init__.py
+++ b/src/authentic2/utils/__init__.py
@@ -28,6 +28,7 @@ from itertools import islice, chain, count
 
 from importlib import import_module
 
+import django.apps
 from django.conf import settings
 from django.http import HttpResponseRedirect, HttpResponse
 from django.core.exceptions import ImproperlyConfigured
@@ -143,6 +144,14 @@ def accumulate_from_backends(request, method_name, **kwargs):
             method = getattr(plugin, method_name)
             if callable(method):
                 list += method(request, **kwargs)
+
+    # now try app config
+    for app_config in django.apps.apps.get_app_configs():
+        if hasattr(app_config, method_name):
+            method = getattr(app_config, method_name)
+            if callable(method):
+                list += method(request, **kwargs)
+
     return list
 
 
diff --git a/src/authentic2_auth_oidc/__init__.py b/src/authentic2_auth_oidc/__init__.py
index d8ecd5ca..ebb55275 100644
--- a/src/authentic2_auth_oidc/__init__.py
+++ b/src/authentic2_auth_oidc/__init__.py
@@ -14,66 +14,4 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-
-class Plugin(object):
-    def get_before_urls(self):
-        from . import urls
-        return urls.urlpatterns
-
-    def get_apps(self):
-        return [__name__]
-
-    def get_authentication_backends(self):
-        return ['authentic2_auth_oidc.backends.OIDCBackend']
-
-    def get_authenticators(self):
-        return ['authentic2_auth_oidc.authenticators.OIDCAuthenticator']
-
-    def redirect_logout_list(self, request, next=None):
-        from django.urls import reverse
-        from authentic2.utils 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
-
-    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(u'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(u'failed to revoke access token from OIDC provider %s: %s, %s',
-                           provider.issuer, e, content)
-            return
-        logger.info(u'revoked token from OIDC provider %s', provider.issuer)
+default_app_config = 'authentic2_auth_oidc.apps.AppConfig'
diff --git a/src/authentic2_auth_oidc/apps.py b/src/authentic2_auth_oidc/apps.py
new file mode 100644
index 00000000..5d9a73c3
--- /dev/null
+++ b/src/authentic2_auth_oidc/apps.py
@@ -0,0 +1,71 @@
+# authentic2 - versatile identity manager
+# Copyright (C) 2010-2020 Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import django.apps
+
+
+class AppConfig(django.apps.AppConfig):
+
+    name = 'authentic2_auth_oidc'
+
+    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(u'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(u'failed to revoke access token from OIDC provider %s: %s, %s',
+                           provider.issuer, e, content)
+            return
+        logger.info(u'revoked token from OIDC provider %s', provider.issuer)
+
+    def redirect_logout_list(self, request, next=None):
+        from django.urls import reverse
+        from authentic2.utils 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
-- 
2.20.1

