Skip to content

Commit bcc8de6

Browse files
committed
#337 fixed LDAP authentication when user contains parentheses
1 parent 249e206 commit bcc8de6

4 files changed

Lines changed: 116 additions & 12 deletions

File tree

samples/ldap/bootstrap.ldif

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
dn: ou=People,dc=script-server,dc=net
2+
objectClass: organizationalUnit
3+
ou: People
4+
5+
dn: uid=user1,ou=People,dc=script-server,dc=net
6+
objectClass: inetOrgPerson
7+
objectClass: posixAccount
8+
cn: John Smith
9+
sn: Smith
10+
uid: user1
11+
uidNumber: 1000
12+
gidNumber: 1000
13+
homeDirectory: /home/user1
14+
userPassword: qwerty
15+
16+
dn: uid=user with space,ou=People,dc=script-server,dc=net
17+
objectClass: inetOrgPerson
18+
cn: user with space
19+
sn: Uws
20+
uid: user with space
21+
userPassword: 123 456
22+
23+
dn: uid=user (with brackets),ou=People,dc=script-server,dc=net
24+
objectClass: inetOrgPerson
25+
cn: user with brackets
26+
sn: UwB
27+
uid: user (with brackets)
28+
userPassword: 666
29+
30+
dn: cn=all_users,dc=script-server,dc=net
31+
objectClass: posixGroup
32+
cn: all_users
33+
description: All users group
34+
gidNumber: 10000
35+
memberUid: user1
36+
memberUid: user with space
37+
memberUid: user (with brackets)

samples/ldap/start-ldap-docker.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
3+
docker stop script-server-ldap
4+
docker rm script-server-ldap
5+
6+
set -e
7+
8+
docker run \
9+
--name script-server-ldap \
10+
--env LDAP_ORGANISATION="Script server" \
11+
--env LDAP_DOMAIN="script-server.net" \
12+
--env LDAP_ADMIN_PASSWORD="admin_passw" \
13+
--volume "$PWD"/bootstrap.ldif:/container/service/slapd/assets/config/bootstrap/ldif/50-bootstrap.ldif \
14+
--detach \
15+
osixia/openldap:1.4.0 \
16+
--copy-service \
17+
--loglevel debug

src/auth/auth_ldap.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from ldap3 import Connection, SIMPLE
77
from ldap3.core.exceptions import LDAPAttributeError
8+
from ldap3.utils.conv import escape_filter_chars
89

910
from auth import auth_base
1011
from model import model_helper
@@ -38,19 +39,21 @@ def _resolve_base_dn(full_username):
3839
return ''
3940

4041

41-
def _search(dn, search_filter, attributes, connection):
42-
success = connection.search(dn, search_filter, attributes=attributes)
42+
def _search(dn, search_request, attributes, connection):
43+
search_string = search_request.as_search_string()
44+
45+
success = connection.search(dn, search_string, attributes=attributes)
4346
if not success:
4447
if connection.last_error:
4548
LOGGER.warning('ldap search failed: ' + connection.last_error
46-
+ '. dn:' + dn + ', filter: ' + search_filter)
49+
+ '. dn:' + dn + ', filter: ' + search_string)
4750
return None
4851

4952
return connection.entries
5053

5154

52-
def _load_multiple_entries_values(dn, search_filter, attribute_name, connection):
53-
entries = _search(dn, search_filter, [attribute_name], connection)
55+
def _load_multiple_entries_values(dn, search_request, attribute_name, connection):
56+
entries = _search(dn, search_request, [attribute_name], connection)
5457
if entries is None:
5558
return []
5659

@@ -174,12 +177,13 @@ def _fetch_user_groups(self, user_dn, user_uid, connection):
174177

175178
result = set()
176179

177-
result.update(_load_multiple_entries_values(base_dn, '(member=%s)' % user_dn, 'cn', connection))
180+
result.update(
181+
_load_multiple_entries_values(base_dn, SearchRequest('(member=%s)', user_dn), 'cn', connection))
178182

179183
if user_uid:
180184
result.update(_load_multiple_entries_values(
181185
base_dn,
182-
'(&(objectClass=posixGroup)(memberUid=%s))' % user_uid,
186+
SearchRequest('(&(objectClass=posixGroup)(memberUid=%s))', user_uid),
183187
'cn',
184188
connection))
185189

@@ -191,23 +195,23 @@ def _get_user_ids(self, full_username, connection):
191195
username_lower = full_username.lower()
192196
if ',dc=' in username_lower:
193197
base_dn = username_lower
194-
search_filter = '(objectClass=*)'
198+
search_request = SearchRequest('(objectClass=*)')
195199
elif '@' in full_username:
196-
search_filter = '(userPrincipalName=%s)' % full_username
200+
search_request = SearchRequest('(userPrincipalName=%s)', full_username)
197201
elif '\\' in full_username:
198202
username_index = full_username.rfind('\\') + 1
199203
username = full_username[username_index:]
200-
search_filter = '(sAMAccountName=%s)' % username
204+
search_request = SearchRequest('(sAMAccountName=%s)', username)
201205
else:
202206
LOGGER.warning('Unsupported username pattern for ' + full_username)
203207
return full_username, None
204208

205-
entries = _search(base_dn, search_filter, ['uid'], connection)
209+
entries = _search(base_dn, search_request, ['uid'], connection)
206210
if not entries:
207211
return full_username, None
208212

209213
if len(entries) > 1:
210-
LOGGER.warning('More than one user found by filter: ' + search_filter)
214+
LOGGER.warning('More than one user found by filter: ' + str(search_request))
211215
return full_username, None
212216

213217
entry = entries[0]
@@ -225,3 +229,15 @@ def _set_user_groups(self, user, groups):
225229

226230
new_groups_content = json.dumps(self._user_groups, indent=2)
227231
file_utils.write_file(self._groups_file, new_groups_content)
232+
233+
234+
class SearchRequest:
235+
def __init__(self, template, *variables) -> None:
236+
escaped_vars = [escape_filter_chars(var) for var in variables]
237+
self.search_string = template % tuple(escaped_vars)
238+
239+
def as_search_string(self):
240+
return self.search_string
241+
242+
def __str__(self) -> str:
243+
return self.as_search_string()

src/tests/auth_ldap_test.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,24 @@ def test_load_multiple_groups_by_uid_when_dn_template(self):
143143
groups = self.auth_and_get_groups('user1', auth_wrapper)
144144
self.assertCountEqual(['group1', 'group2', 'group3'], groups)
145145

146+
def test_load_multiple_groups_by_uid_when_not_all_match(self):
147+
auth_wrapper = _LdapAuthenticatorMockWrapper('cn=$username,cn=Users,dc=ldap,dc=test', 'dc=ldap,dc=test')
148+
auth_wrapper.add_posix_user('user1', '1234', 'uid_X')
149+
auth_wrapper.add_posix_group('group1', ['uid_X'])
150+
auth_wrapper.add_posix_group('group2', ['uid_123'])
151+
auth_wrapper.add_posix_group('group3', ['uid_X'])
152+
153+
groups = self.auth_and_get_groups('user1', auth_wrapper)
154+
self.assertCountEqual(['group1', 'group3'], groups)
155+
156+
def test_load_single_group_by_uid_when_dn_has_parenthesss(self):
157+
auth_wrapper = _LdapAuthenticatorMockWrapper('cn=$username,cn=Users,dc=ldap,dc=test', 'dc=ldap,dc=test')
158+
auth_wrapper.add_posix_user('user (1)', '1234', 'uid (X)')
159+
auth_wrapper.add_posix_group('group1', ['uid (X)'])
160+
161+
groups = self.auth_and_get_groups('user (1)', auth_wrapper)
162+
self.assertCountEqual(['group1'], groups)
163+
146164
def test_load_multiple_groups_by_member_and_uid_when_dn_template(self):
147165
auth_wrapper = _LdapAuthenticatorMockWrapper('cn=$username,cn=Users,dc=ldap,dc=test', 'dc=ldap,dc=test')
148166
auth_wrapper.add_posix_user('user1', '1234', 'uid_X')
@@ -169,6 +187,14 @@ def test_load_single_group_by_member_when_sam_account_template(self):
169187
groups = self.auth_and_get_groups('user1', auth_wrapper)
170188
self.assertEqual(['group1'], groups)
171189

190+
def test_load_single_group_by_member_when_sam_account_template_with_parentheses(self):
191+
auth_wrapper = _LdapAuthenticatorMockWrapper('some_domain\\$username', 'dc=some_domain,dc=test')
192+
auth_wrapper.add_user('User (Noname)', '1234', sAMAccountName='user (1)')
193+
auth_wrapper.add_group('group1', ['User (Noname)'])
194+
195+
groups = self.auth_and_get_groups('user (1)', auth_wrapper)
196+
self.assertEqual(['group1'], groups)
197+
172198
def test_load_single_group_by_uid_when_sam_account_template(self):
173199
auth_wrapper = _LdapAuthenticatorMockWrapper('some_domain\\$username', 'dc=some_domain,dc=test')
174200
auth_wrapper.add_posix_user('User Noname', '1234', 'uid_X', sAMAccountName='user1')
@@ -185,6 +211,14 @@ def test_load_single_group_by_member_when_user_principal_template(self):
185211
groups = self.auth_and_get_groups('user1', auth_wrapper)
186212
self.assertEqual(['group1'], groups)
187213

214+
def test_load_single_group_by_member_when_user_principal_template_with_parentheses(self):
215+
auth_wrapper = _LdapAuthenticatorMockWrapper('$username@buggy.net', 'dc=buggy,dc=net')
216+
auth_wrapper.add_user('User (Noname)', '1234', userPrincipalName='user (1)@buggy.net')
217+
auth_wrapper.add_group('group1', ['User (Noname)'])
218+
219+
groups = self.auth_and_get_groups('user (1)', auth_wrapper)
220+
self.assertEqual(['group1'], groups)
221+
188222
def test_cannot_load_group_when_same_principal_names(self):
189223
auth_wrapper = _LdapAuthenticatorMockWrapper('$username@buggy.net', 'dc=buggy,dc=net')
190224
auth_wrapper.add_user('user1', '1234', userPrincipalName='userX@buggy.net')

0 commit comments

Comments
 (0)