From 73df42523da8c6b317d65189bae620c8f31a6027 Mon Sep 17 00:00:00 2001 From: Paul Marillonnet Date: Tue, 17 Oct 2017 16:17:43 +0200 Subject: [PATCH] WIP ldap_backend : fix encoding errors during user synchronization (#19168) --- src/authentic2/backends/ldap_backend.py | 20 +++++++++++++++ tests/test_ldap.py | 45 +++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/src/authentic2/backends/ldap_backend.py b/src/authentic2/backends/ldap_backend.py index 80f1baa7..1d36150c 100644 --- a/src/authentic2/backends/ldap_backend.py +++ b/src/authentic2/backends/ldap_backend.py @@ -319,6 +319,9 @@ class LDAPBackend(object): # First get our configuration into a standard format for block in blocks: cls.update_default(block) + # python-ldap needs UTF-8 encoded strings + if isinstance(block.get('base_dn'), unicode): + block['base_dn'] = block['base_dn'].encode('utf-8') log.debug('got config %r', blocks) return blocks @@ -349,6 +352,11 @@ class LDAPBackend(object): utf8_username = smart_bytes(username) utf8_password = smart_bytes(password) + # python-ldap needs UTF-8 encoded strings + for dn_subelement in ('basedn', 'user_basedn', 'user_dn_template'): + if isinstance(block.get(dn_subelement), unicode): + block[dn_sublement] = block[dn_subelement].encode('utf-8') + for conn in self.get_connections(block): authz_ids = [] user_basedn = block.get('user_basedn') or block['basedn'] @@ -522,6 +530,9 @@ class LDAPBackend(object): '''Retrieve group DNs from the LDAP by attributes (memberOf) or by filter. ''' + # python-ldap needs UTF-8 encoded strings + if isinstance(block.get('group_base_dn'), unicode): + block['group_base_dn'] = block['group_base_dn'].encode('utf-8') group_base_dn = block.get('group_basedn', block['basedn']) member_of_attribute = block['member_of_attribute'] group_filter = block['group_filter'] @@ -840,6 +851,9 @@ class LDAPBackend(object): if conn is None: logger.warning(u'unable to synchronize with LDAP servers %r', block['url']) continue + # python-ldap needs UTF-8 encoded strings. + if isinstance(block.get('user_basedn'), unicode): + block['user_basedn'] = block['user_basedn'].encode('utf-8') user_basedn = block.get('user_basedn') or block['basedn'] user_filter = block['sync_ldap_users_filter'] or block['user_filter'] user_filter = user_filter.replace('%s', '*') @@ -950,6 +964,9 @@ class LDAPBackend(object): auth = handler_class(*sasl_params) conn.sasl_interactive_bind_s(who, auth) elif block['binddn'] and block['bindpw']: + # python-ldap needs UTF-8 encoded strings + if isinstance(block.get('binddn'), unicode): + block['binddn'] = block['binddn'].encode('utf-8') conn.bind_s(block['binddn'], block['bindpw']) yield conn except ldap.INVALID_CREDENTIALS: @@ -1072,6 +1089,9 @@ class LDAPBackendPasswordLost(LDAPBackend): results = conn.search_s(dn, ldap.SCOPE_BASE) else: ldap_filter = self.external_id_to_filter(external_id, external_id_tuple) + # python-ldap needs UTF-8 encoded strings + if isinstance(block.get('basedn'), unicode): + block['basedn'] = block['basedn'].encode('utf-8') results = conn.search_s(block['basedn'], ldap.SCOPE_SUBTREE, ldap_filter) if not results: diff --git a/tests/test_ldap.py b/tests/test_ldap.py index 34d10146..d52aada6 100644 --- a/tests/test_ldap.py +++ b/tests/test_ldap.py @@ -23,6 +23,11 @@ CN = 'Étienne Michu' DN = 'cn=%s,o=orga' % escape_dn_chars(CN) PASS = 'passé' +USERNAME_ACCENTS = u'étienne.michü' +UID_ACCENTS = 'étienne.michü' +CN_ACCENTS = 'Étienne Michü' +DN_ACCENTS = 'cn=%s,o=orga' % (escape_dn_chars(CN_ACCENTS)) + @pytest.fixture def slapd(request): @@ -41,6 +46,20 @@ objectClass: groupOfNames member: {dn} '''.format(dn=DN, uid=UID, password=PASS)) + slapd.add_ldif('''dn: {dn} +objectClass: inetOrgPerson +userPassword: {password} +uid: {uid} +cn: Étienne Michü +sn: Michü +gn: Étienne +mail: etienne.michu@example.net + +dn: cn=group3,o=orga +objectClass: groupOfNames +member: {dn} + +'''.format(dn=DN_ACCENTS, uid=UID_ACCENTS, password=PASS)) for i in range(100): slapd.add_ldif('''dn: uid=michu{i},o=orga objectClass: inetOrgPerson @@ -101,6 +120,32 @@ def test_simple(slapd, settings, client): @pytest.mark.django_db +def test_accents_in_dn(slapd, settings, client): + settings.LDAP_AUTH_SETTINGS = [{ + 'url': [slapd.ldap_url], + 'basedn': 'o=entité1', + 'use_tls': False, + }] + result = client.post('/login/', {'login-password-submit': '1', + 'username': USERNAME_ACCENTS, + 'password': PASS}, follow=True) + assert result.status_code == 200 + assert u'Étienne Michü' in unicode(str(result), 'utf-8') + User = get_user_model() + assert User.objects.count() == 1 + user = User.objects.get() + assert user.username == u'%s@ldap' % USERNAME_ACCENTS + assert user.first_name == u'Étienne' + assert user.last_name == 'Michü' + assert user.is_active is True + assert user.is_superuser is False + assert user.is_staff is False + assert user.groups.count() == 0 + assert not user.check_password(PASS) + assert 'password' not in client.session['ldap-data'] + + +@pytest.mark.django_db def test_simple_with_binddn(slapd, settings, client): settings.LDAP_AUTH_SETTINGS = [{ 'url': [slapd.ldap_url], -- 2.11.0