From b8f1384fe992d338d227658ad637e3efbfc8393f Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Mon, 11 Feb 2019 17:13:12 +0100 Subject: [PATCH 2/2] ldap: allow provisionning of all user attributes (fixes #30535) --- src/authentic2/backends/ldap_backend.py | 25 ++++++++++++ tests/test_ldap.py | 51 +++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/src/authentic2/backends/ldap_backend.py b/src/authentic2/backends/ldap_backend.py index a64a7416..91852a1a 100644 --- a/src/authentic2/backends/ldap_backend.py +++ b/src/authentic2/backends/ldap_backend.py @@ -316,6 +316,8 @@ class LDAPBackend(object): 'connect_with_user_credentials': True, # can reset password 'can_reset_password': False, + # mapping from LDAP attributes to User attributes + 'user_attributes': [], } _REQUIRED = ('url', 'basedn') _TO_ITERABLE = ('url', 'groupsu', 'groupstaff', 'groupactive') @@ -498,6 +500,7 @@ class LDAPBackend(object): return None def populate_user_attributes(self, user, block, attributes): + # map legacy attributes (columns from Django user model) for legacy_attribute, legacy_field in (('email', 'email_field'), ('first_name', 'fname_field'), ('last_name', 'lname_field')): @@ -511,6 +514,24 @@ class LDAPBackend(object): if getattr(user, legacy_attribute) != value: setattr(user, legacy_attribute, value) user._changed = True + # map new style attributes + user_attributes = {} + for mapping in block.get('user_attributes', []): + from_ldap = mapping.get('from_ldap') + to_user = mapping.get('to_user') + if not from_ldap or not to_user: + continue + if not attributes.get(from_ldap): + user_attributes[to_user] = '' + else: + user_attributes[to_user] = attributes[from_ldap][0] + for name in user_attributes: + value = getattr(user.attributes, name, None) + if value != user_attributes[name]: + if not user.pk: + user.save() + setattr(user.attributes, name, user_attributes[name]) + user._changed = True def populate_admin_flags_by_group(self, user, block, group_dns): '''Attribute admin flags based on groups. @@ -745,6 +766,10 @@ class LDAPBackend(object): external_id_tuple)) for from_at, to_at in block['attribute_mappings']: attributes.add(to_at) + for mapping in block['user_attributes']: + from_ldap = mapping.get('from_ldap') + if from_ldap: + attributes.add(from_ldap) return list(set(map(str.lower, map(str, attributes)))) @classmethod diff --git a/tests/test_ldap.py b/tests/test_ldap.py index 9fd4e325..738bc26c 100644 --- a/tests/test_ldap.py +++ b/tests/test_ldap.py @@ -79,6 +79,7 @@ uid: {uid} cn: Étienne Michu sn: Michu gn: Étienne +l: Paris mail: etienne.michu@example.net dn: cn=group1,o=ôrga @@ -94,6 +95,7 @@ uid: michu{i} cn: Étienne Michu sn: Michu gn: Étienne +l: locality{i} mail: etienne.michu@example.net '''.format(i=i, password=PASS)) @@ -649,3 +651,52 @@ def test_tls(db, tls_slapd, settings, client): assert result.status_code == 200 assert 'Étienne Michu' in str(result) assert 'name="username"' not in str(result) + + +def test_user_attributes(slapd, settings, client, db): + settings.LDAP_AUTH_SETTINGS = [{ + 'url': [slapd.ldap_url], + 'basedn': u'o=ôrga', + 'use_tls': False, + 'user_attributes': [ + { + 'from_ldap': 'l', + 'to_user': 'locality', + }, + ] + }] + + # create a locality attribute + models.Attribute.objects.create( + label='locality', + name='locality', + kind='string', + required=False, + user_visible=True, + user_editable=False, + asked_on_registration=False, + multiple=False) + + client.post('/login/', + { + 'login-password-submit': '1', + 'username': USERNAME, + 'password': PASS + }, + follow=True) + username = u'%s@ldap' % USERNAME + user = User.objects.get(username=username) + assert user.attributes.locality == u'Paris' + client.session.flush() + for i in range(5): + client.post('/login/', + { + 'login-password-submit': '1', + 'username': 'michu%s' % i, + 'password': PASS, + }, + follow=True) + username = u'michu%s@ldap' % i + user = User.objects.get(username=username) + assert user.attributes.locality == u'locality%s' % i + client.session.flush() -- 2.20.1