From 12ee1a52f48f805dabafba94cb3fc05e9f63c5c6 Mon Sep 17 00:00:00 2001
From: Emmanuel Cazenave <ecazenave@entrouvert.com>
Date: Tue, 21 Jun 2022 14:13:28 +0200
Subject: [PATCH] ldap: add options to control authentiction and cron
 provisionning (#60492)

---
 src/authentic2/backends/ldap_backend.py |  8 +++
 tests/test_ldap.py                      | 96 +++++++++++++++++++++++++
 2 files changed, 104 insertions(+)

diff --git a/src/authentic2/backends/ldap_backend.py b/src/authentic2/backends/ldap_backend.py
index 2ba48140..65e55096 100644
--- a/src/authentic2/backends/ldap_backend.py
+++ b/src/authentic2/backends/ldap_backend.py
@@ -543,6 +543,8 @@ class LDAPBackend:
         # https://www.python-ldap.org/en/python-ldap-3.3.0/reference/ldap.html#ldap-controls
         'use_controls': False,
         'ppolicy_dn': '',
+        'authentication': True,
+        'sync_ldap_users': True,
     }
     _REQUIRED = ('url', 'basedn')
     _TO_ITERABLE = ('url', 'groupsu', 'groupstaff', 'groupactive')
@@ -629,6 +631,8 @@ class LDAPBackend:
 
         # Now we can try to authenticate
         for block in config:
+            if block['authentication'] is False:
+                continue
             uid = username
             # if ou is provided, ignore LDAP server for other OU
             if ou:
@@ -1667,6 +1671,8 @@ class LDAPBackend:
         for block in blocks:
             if realm and realm != block['realm']:
                 continue
+            if block['sync_ldap_users'] is False:
+                continue
             count = 0
             try:
                 for user in cls.get_users_for_block(block):
@@ -2012,6 +2018,8 @@ class LDAPBackendPasswordLost(LDAPBackend):
             if not external_id:
                 continue
             for block in config:
+                if block['authentication'] is False:
+                    continue
                 if user_external_id.source != force_text(block['realm']):
                     continue
                 for external_id_tuple in map_text(block['external_id_tuples']):
diff --git a/tests/test_ldap.py b/tests/test_ldap.py
index 9c82e2ef..65ed3e88 100644
--- a/tests/test_ldap.py
+++ b/tests/test_ldap.py
@@ -2493,3 +2493,99 @@ def test_mandatory_role_name_ambiguity_fallback_on_default_ou(db, rf, slapd, cli
     user = User.objects.get()
     role = user.roles.get(name='Ambiguous role')
     assert role.ou == default_ou
+
+
+def test_authenticate_no_authentication(slapd, settings, client, db):
+    settings.LDAP_AUTH_SETTINGS = [
+        {
+            'url': [slapd.ldap_url],
+            'basedn': 'o=ôrga',
+            'use_tls': False,
+            'attributes': ['jpegPhoto'],
+            'authentication': False,
+        }
+    ]
+    result = client.post(
+        '/login/', {'login-password-submit': '1', 'username': USERNAME, 'password': PASS}, follow=True
+    )
+    assert result.status_code == 200
+    assert force_bytes('Étienne Michu') not in result.content
+    assert User.objects.count() == 0
+
+
+def test_get_users_no_sync_ldap_users(slapd, settings, db, monkeypatch, caplog):
+    from django.contrib.auth.models import Group
+
+    settings.LDAP_AUTH_SETTINGS = [
+        {
+            'url': [slapd.ldap_url],
+            'basedn': 'o=ôrga',
+            'use_tls': False,
+            'create_group': True,
+            'group_mapping': [
+                ['cn=group2,o=ôrga', ['Group2']],
+            ],
+            'group_filter': '(&(memberUid={uid})(objectClass=posixGroup))',
+            'group_to_role_mapping': [
+                ['cn=unknown,o=dn', ['Role2']],
+            ],
+            'lookups': ['external_id', 'username'],
+            'sync_ldap_users': False,
+        }
+    ]
+    assert Group.objects.count() == 0
+    assert User.objects.count() == 0
+    users = list(ldap_backend.LDAPBackend.get_users())
+    assert len(users) == 0
+    assert User.objects.count() == 0
+    assert Group.objects.count() == 0
+
+
+def test_deactivate_orphaned_users_when_no_sync_ldap_users(slapd, settings, client, db, app, superuser):
+    settings.LDAP_AUTH_SETTINGS = [
+        {
+            'url': [slapd.ldap_url],
+            'basedn': 'o=ôrga',
+            'use_tls': False,
+        }
+    ]
+    utils.login(app, superuser)
+
+    # create users as a side effect
+    users = list(ldap_backend.LDAPBackend.get_users())
+    block = settings.LDAP_AUTH_SETTINGS[0]
+    assert (
+        ldap_backend.UserExternalId.objects.filter(user__is_active=False, source=block['realm']).count() == 0
+    )
+    resp = app.get('/manage/users/%s/' % users[0].pk)
+    assert 'Deactivated' not in resp.text
+
+    conn = slapd.get_connection_admin()
+    conn.delete_s(DN)
+
+    settings.LDAP_AUTH_SETTINGS = [
+        {
+            'url': [slapd.ldap_url],
+            'basedn': 'o=ôrga',
+            'use_tls': False,
+            'sync_ldap_users': False,
+        }
+    ]
+
+    ldap_backend.LDAPBackend.deactivate_orphaned_users()
+
+    deactivated_user = ldap_backend.UserExternalId.objects.get(
+        user__is_active=False,
+        source=block['realm'],
+        user__deactivation__isnull=False,
+        user__deactivation_reason__startswith='ldap-',
+    )
+    utils.assert_event(
+        'manager.user.deactivation',
+        target_user=deactivated_user.user,
+        reason='ldap-not-present',
+        origin=slapd.ldap_url,
+    )
+    resp = app.get('/manage/users/%s/' % deactivated_user.user.pk)
+    assert 'Deactivated' in resp.text
+    assert 'associated LDAP account does not exist anymore' in resp.text
-- 
2.30.2

