From 154ac3c1e70c8ef17172ac60b2e3105f7b4a2340 Mon Sep 17 00:00:00 2001
From: Serghei Mihai <smihai@entrouvert.com>
Date: Fri, 8 Mar 2019 14:57:45 +0100
Subject: [PATCH 2/2] auth: separate OIDC providers in blocks on login page
 (#31259)

---
 src/authentic2/views.py                       | 29 +++++++++++++------
 src/authentic2_auth_oidc/authenticators.py    | 10 +++++--
 .../templates/authentic2_auth_oidc/login.html | 11 +++----
 tests/test_auth_oidc.py                       | 27 +++++++++++++++++
 4 files changed, 59 insertions(+), 18 deletions(-)

diff --git a/src/authentic2/views.py b/src/authentic2/views.py
index cb87834d..063a42c7 100644
--- a/src/authentic2/views.py
+++ b/src/authentic2/views.py
@@ -320,19 +320,30 @@ def login(request, template_name='authentic2/login.html',
                 block['form'] = form_class()
             blocks.append(block)
         else:  # New frontends API
+            auth_blocks = []
             parameters = {'request': request,
                           'context': context}
-            block = utils.get_authenticator_method(authenticator, 'login', parameters)
+            # check if the authenticator has multiple instances
+            if hasattr(authenticator, 'instances'):
+                for instance_id, instance in authenticator.instances:
+                    parameters['instance'] = instance
+                    block = utils.get_authenticator_method(authenticator, 'login', parameters)
+                    # update block id in order to separate instances
+                    block['id'] = instance_id
+                    auth_blocks.append(block)
+            else:
+                auth_blocks.append(utils.get_authenticator_method(authenticator, 'login', parameters))
             # If a login frontend method returns an HttpResponse with a status code != 200
             # this response is returned.
-            if block:
-                if block['status_code'] != 200:
-                    return block['response']
-                blocks.append(block)
-        if hasattr(authenticator, 'is_hidden'):
-            blocks[-1]['is_hidden'] = authenticator.is_hidden(request)
-        else:
-            blocks[-1]['is_hidden'] = False
+            for block in auth_blocks:
+                if block:
+                    if block['status_code'] != 200:
+                        return block['response']
+                    blocks.append(block)
+                if hasattr(authenticator, 'is_hidden'):
+                    blocks[-1]['is_hidden'] = authenticator.is_hidden(request)
+                else:
+                    blocks[-1]['is_hidden'] = False
 
     # Old frontends API
     for block in blocks:
diff --git a/src/authentic2_auth_oidc/authenticators.py b/src/authentic2_auth_oidc/authenticators.py
index bbc0516d..47b10633 100644
--- a/src/authentic2_auth_oidc/authenticators.py
+++ b/src/authentic2_auth_oidc/authenticators.py
@@ -30,7 +30,13 @@ class OIDCAuthenticator(object):
     def id(self):
         return 'oidc'
 
+    @property
+    def instances(self):
+        for p in utils.get_providers(shown=True):
+            yield (p.slug, p)
+
     def login(self, request, *args, **kwargs):
         context = kwargs.get('context', {})
-        context['providers'] = utils.get_providers(shown=True)
-        return render(request, 'authentic2_auth_oidc/login.html', context)
+        if kwargs.get('instance'):
+            context['provider'] = kwargs['instance']
+            return render(request, 'authentic2_auth_oidc/login.html', context)
diff --git a/src/authentic2_auth_oidc/templates/authentic2_auth_oidc/login.html b/src/authentic2_auth_oidc/templates/authentic2_auth_oidc/login.html
index 094abfb2..1aa0a07e 100644
--- a/src/authentic2_auth_oidc/templates/authentic2_auth_oidc/login.html
+++ b/src/authentic2_auth_oidc/templates/authentic2_auth_oidc/login.html
@@ -1,7 +1,4 @@
-{% for provider in providers %}
-  <p id="oidc-p-{% firstof provider.slug provider.name|slugify %}">
-    <a id="oidc-a-{% firstof provider.slug  provider.name|slugify %}"
-       href="{% url "oidc-login" pk=provider.pk %}?next={{ request.GET.next|urlencode }}">{{ provider.name }}</a>
-  </p>
-{% endfor %}
-
+<p id="oidc-p-{% firstof provider.slug provider.name|slugify %}">
+  <a id="oidc-a-{% firstof provider.slug  provider.name|slugify %}"
+     href="{% url "oidc-login" pk=provider.pk %}?next={{ request.GET.next|urlencode }}">{{ provider.name }}</a>
+</p>
diff --git a/tests/test_auth_oidc.py b/tests/test_auth_oidc.py
index dbb55eef..f4f5a876 100644
--- a/tests/test_auth_oidc.py
+++ b/tests/test_auth_oidc.py
@@ -283,6 +283,33 @@ def check_simple_qs(qs):
     return qs
 
 
+def test_providers_on_login_page(oidc_provider, app):
+    response = app.get('/login/')
+    # two frontends should be present on login page
+    assert response.pyquery('p#oidc-p-oididp')
+    provider = OIDCProvider.objects.create(
+        ou=get_default_ou(),
+        name='OIDCIDP 2',
+        slug='oidcidp-2',
+        issuer='https://idp2.example.com/',
+        authorization_endpoint='https://idp2.example.com/authorize',
+        token_endpoint='https://idp2.example.com/token',
+        end_session_endpoint='https://idp2.example.com/logout',
+        userinfo_endpoint='https://idp*é.example.com/user_info',
+        token_revocation_endpoint='https://idp2.example.com/revoke',
+        max_auth_age=10,
+        strategy=OIDCProvider.STRATEGY_CREATE,
+        jwkset_json=None,
+        idtoken_algo=OIDCProvider.ALGO_RSA,
+        claims_parameter_supported=False
+    )
+
+    response = app.get('/login/')
+    assert response.pyquery('p#oidc-p-oididp')
+    assert response.pyquery('p#oidc-p-oidcidp-2')
+    provider.delete()
+
+
 def test_sso(app, caplog, code, oidc_provider, oidc_provider_jwkset, login_url, login_callback_url, hooks):
     OU = get_ou_model()
     cassis = OU.objects.create(name='Cassis', slug='cassis')
-- 
2.23.0

