4
4
import ldap3
5
5
from jupyterhub .auth import Authenticator
6
6
from ldap3 .utils .conv import escape_filter_chars
7
+ from ldap3 .utils .dn import escape_rdn
7
8
from traitlets import Bool , Int , List , Unicode , Union , UseEnum , observe , validate
8
9
9
10
@@ -162,20 +163,17 @@ def _validate_bind_dn_template(self, proposal):
162
163
help = "List of attributes to be searched" ,
163
164
)
164
165
165
- # FIXME: Use something other than this? THIS IS LAME, akin to websites restricting things you
166
- # can use in usernames / passwords to protect from SQL injection!
167
166
valid_username_regex = Unicode (
168
167
r"^[a-z][.a-z0-9_-]*$" ,
169
168
config = True ,
170
169
help = """
171
- Regex for validating usernames - those that do not match this regex will be rejected.
172
-
173
- This is primarily used as a measure against LDAP injection, which has fatal security
174
- considerations. The default works for most LDAP installations, but some users might need
175
- to modify it to fit their custom installs. If you are modifying it, be sure to understand
176
- the implications of allowing additional characters in usernames and what that means for
177
- LDAP injection issues. See https://www.owasp.org/index.php/LDAP_injection for an overview
178
- of LDAP injection.
170
+ Regex for validating usernames - those that do not match this regex will
171
+ be rejected.
172
+
173
+ This config was primarily introduced to prevent LDAP injection
174
+ (https://www.owasp.org/index.php/LDAP_injection), but that is since 2.0
175
+ being mitigated by escaping all sensitive characters when interacting
176
+ with the LDAP server.
179
177
""" ,
180
178
)
181
179
@@ -246,7 +244,8 @@ def _validate_bind_dn_template(self, proposal):
246
244
default_value = None ,
247
245
allow_none = True ,
248
246
help = """
249
- Technical account for user lookup, if `lookup_dn` is set to True.
247
+ DN for a technical user account allowed to search for information about
248
+ provided username, if `lookup_dn` is set to True.
250
249
251
250
If both lookup_dn_search_user and lookup_dn_search_password are None, then anonymous LDAP query will be done.
252
251
""" ,
@@ -278,13 +277,16 @@ def _validate_bind_dn_template(self, proposal):
278
277
False ,
279
278
config = True ,
280
279
help = """
281
- If set to True, escape special chars in userdn when authenticating in LDAP.
282
-
283
- On some LDAP servers, when userdn contains chars like '(', ')', '\' authentication may fail when those chars
284
- are not escaped.
280
+ Removed in 2.0, configuring this no longer has any effect.
285
281
""" ,
286
282
)
287
283
284
+ @observe ("escape_userdn" )
285
+ def _observe_escape_userdn (self , change ):
286
+ self .log .warning (
287
+ "LDAPAuthenticator.escape_userdn was removed in 2.0 and no longer has any effect."
288
+ )
289
+
288
290
search_filter = Unicode (
289
291
config = True , help = "LDAP3 Search Filter whose results are allowed access"
290
292
)
@@ -310,16 +312,13 @@ def resolve_username(self, username_supplied_by_user):
310
312
Resolves a username supplied by a user to the a user DN when lookup_dn
311
313
is True.
312
314
"""
313
- search_dn = self .lookup_dn_search_user
314
- if self .escape_userdn :
315
- search_dn = escape_filter_chars (search_dn )
316
315
conn = self .get_connection (
317
- userdn = search_dn ,
316
+ userdn = self . lookup_dn_search_user ,
318
317
password = self .lookup_dn_search_password ,
319
318
)
320
319
if not conn .bind ():
321
320
self .log .warning (
322
- f"Failed to connect to LDAP server with search user '{ search_dn } '"
321
+ f"Failed to connect to LDAP server with search user '{ self . lookup_dn_search_user } '"
323
322
)
324
323
return (None , None )
325
324
@@ -441,17 +440,12 @@ async def authenticate(self, handler, data):
441
440
username , resolved_dn = self .resolve_username (username )
442
441
if not username :
443
442
return None
444
- if str (self .lookup_dn_user_dn_attribute ).upper () == "CN" :
445
- # Only escape commas if the lookup attribute is CN
446
- username = re .subn (r"([^\\])," , r"\1\," , username )[0 ]
447
443
if not bind_dn_template :
448
444
bind_dn_template = [resolved_dn ]
449
445
450
446
is_bound = False
451
447
for dn in bind_dn_template :
452
- userdn = dn .format (username = username )
453
- if self .escape_userdn :
454
- userdn = escape_filter_chars (userdn )
448
+ userdn = dn .format (username = escape_rdn (username ))
455
449
self .log .debug (f"Attempting to bind { username } with { userdn } " )
456
450
msg = "Status of user bind {username} with {userdn} : {is_bound}"
457
451
try :
0 commit comments