From 946e92827bcb7a221446f0de7e221090a9211744 Mon Sep 17 00:00:00 2001
From: Paul Marillonnet <pmarillonnet@entrouvert.com>
Date: Thu, 14 Apr 2022 16:29:23 +0200
Subject: [PATCH] ldap_backend: search mandatory roles in default ou when
 ambiguous (#63942)

---
 src/authentic2/backends/ldap_backend.py | 15 ++++++++
 tests/test_ldap.py                      | 50 +++++++++++++++++++++++++
 2 files changed, 65 insertions(+)

diff --git a/src/authentic2/backends/ldap_backend.py b/src/authentic2/backends/ldap_backend.py
index aa4ec4345..7e66388a5 100644
--- a/src/authentic2/backends/ldap_backend.py
+++ b/src/authentic2/backends/ldap_backend.py
@@ -973,6 +973,7 @@ class LDAPBackend:
                 except ValueError:
                     pass
         if slug:
+            default_ou = get_default_ou()
             try:
                 return Role.objects.get(slug=slug, **kwargs), None
             except Role.DoesNotExist:
@@ -982,8 +983,22 @@ class LDAPBackend:
                     error = 'role %r does not exist' % role_id
                 except Role.MultipleObjectsReturned:
                     error = 'multiple objects returned, identifier is imprecise'
+                    if 'ou__slug' not in kwargs:
+                        try:
+                            return Role.objects.get(name=slug, ou=default_ou, **kwargs), None
+                        except Role.DoesNotExist:
+                            error = 'multiple objects returned none of which belongs to default ou, role *name* is ambiguous'
+                        except Role.MultipleObjectsReturned:
+                            pass
             except Role.MultipleObjectsReturned:
                 error = 'multiple objects returned, identifier is imprecise'
+                if 'ou__slug' not in kwargs:
+                    try:
+                        return Role.objects.get(slug=slug, ou=default_ou, **kwargs), None
+                    except Role.DoesNotExist:
+                        error = 'multiple objects returned none of which belongs to default ou, role *slug* is ambiguous'
+                    except Role.MultipleObjectsReturned:
+                        pass
         else:
             error = (
                 'invalid role identifier must be slug, (slug, ou__slug) or (slug, ou__slug, service__slug)'
diff --git a/tests/test_ldap.py b/tests/test_ldap.py
index 3bed7033b..51357d699 100644
--- a/tests/test_ldap.py
+++ b/tests/test_ldap.py
@@ -2337,3 +2337,53 @@ def test_build_external_id_failure_get_users(db, rf, slapd, settings, caplog):
     assert len(caplog.records) == 6
     assert all(record.levelname == 'ERROR' for record in caplog.records)
     assert all('unable to build an external_id' in record.message for record in caplog.records)
+
+
+def test_mandatory_role_slug_ambiguity_fallback_on_default_ou(db, rf, slapd, client, settings, caplog, ou1):
+    settings.LDAP_AUTH_SETTINGS = [
+        {
+            'url': [slapd.ldap_url],
+            'basedn': 'o=ôrga',
+            'use_tls': False,
+            'attributes': ['jpegPhoto'],
+            'set_mandatory_roles': ['ambiguous-role'],
+        }
+    ]
+
+    default_ou = get_default_ou()
+    Role.objects.create(slug='ambiguous-role', ou=default_ou)
+    Role.objects.create(slug='ambiguous-role', ou=ou1)
+    result = client.post(
+        '/login/', {'login-password-submit': '1', 'username': USERNAME, 'password': PASS}, follow=True
+    )
+    assert result.status_code == 200
+    assert force_bytes('Étienne Michu') in result.content
+    assert User.objects.count() == 1
+    user = User.objects.get()
+    role = user.roles.get(slug='ambiguous-role')
+    assert role.ou == default_ou
+
+
+def test_mandatory_role_name_ambiguity_fallback_on_default_ou(db, rf, slapd, client, settings, caplog, ou1):
+    settings.LDAP_AUTH_SETTINGS = [
+        {
+            'url': [slapd.ldap_url],
+            'basedn': 'o=ôrga',
+            'use_tls': False,
+            'attributes': ['jpegPhoto'],
+            'set_mandatory_roles': ['Ambiguous role'],
+        }
+    ]
+
+    default_ou = get_default_ou()
+    Role.objects.create(name='Ambiguous role', ou=default_ou)
+    Role.objects.create(name='Ambiguous role', ou=ou1)
+    result = client.post(
+        '/login/', {'login-password-submit': '1', 'username': USERNAME, 'password': PASS}, follow=True
+    )
+    assert result.status_code == 200
+    assert force_bytes('Étienne Michu') in result.content
+    assert User.objects.count() == 1
+    user = User.objects.get()
+    role = user.roles.get(name='Ambiguous role')
+    assert role.ou == default_ou
-- 
2.35.2

