247 |
247 |
def has_usable_password(self):
|
248 |
248 |
return self.block['user_can_change_password']
|
249 |
249 |
|
250 |
250 |
def get_connection(self):
|
251 |
251 |
ldap_password = self.get_ldap_password()
|
252 |
252 |
credentials = ()
|
253 |
253 |
if ldap_password:
|
254 |
254 |
credentials = (self.dn, ldap_password)
|
|
255 |
# must be redone if session is older than current code update and new
|
|
256 |
# options have been added to the setting dictionnary for LDAP
|
|
257 |
# authentication
|
|
258 |
update_default(self.block)
|
255 |
259 |
return get_connection(self.block, credentials=credentials)
|
256 |
260 |
|
257 |
261 |
def get_attributes(self):
|
258 |
262 |
conn = self.get_connection()
|
259 |
263 |
return LDAPBackend.get_ldap_attributes(self.block, conn, self.dn)
|
260 |
264 |
|
261 |
265 |
def save(self, *args, **kwargs):
|
262 |
266 |
if self.transient:
|
... | ... | |
266 |
270 |
self.pk = self.keep_pk
|
267 |
271 |
super(LDAPUser, self).save(*args, **kwargs)
|
268 |
272 |
if hasattr(self, 'keep_pk'):
|
269 |
273 |
self.pk = pk
|
270 |
274 |
|
271 |
275 |
class LDAPBackendError(RuntimeError):
|
272 |
276 |
pass
|
273 |
277 |
|
|
278 |
def update_default(block):
|
|
279 |
'''Add missing key to block based on default values'''
|
|
280 |
for key in block:
|
|
281 |
if not key in _VALID_CONFIG_KEYS:
|
|
282 |
raise ImproperlyConfigured(
|
|
283 |
('"{}" : invalid LDAP_AUTH_SETTINGS key, '
|
|
284 |
+'available are {}').format(key, _VALID_CONFIG_KEYS))
|
|
285 |
|
|
286 |
for r in _REQUIRED:
|
|
287 |
if r not in block:
|
|
288 |
raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: missing required configuration option %r' % r)
|
|
289 |
|
|
290 |
for d in _DEFAULTS:
|
|
291 |
if d not in block:
|
|
292 |
block[d] = _DEFAULTS[d]
|
|
293 |
else:
|
|
294 |
if isinstance(_DEFAULTS[d], six.string_types):
|
|
295 |
if not isinstance(block[d], six.string_types):
|
|
296 |
raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: '
|
|
297 |
'attribute %r must be a string' % d)
|
|
298 |
try:
|
|
299 |
block[d] = str(block[d])
|
|
300 |
except UnicodeEncodeError:
|
|
301 |
raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: '
|
|
302 |
'attribute %r must be a string' % d)
|
|
303 |
if isinstance(_DEFAULTS[d], bool) and not isinstance(block[d], bool):
|
|
304 |
raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: '
|
|
305 |
'attribute %r must be a boolean' % d)
|
|
306 |
if isinstance(_DEFAULTS[d], (list, tuple)) and not isinstance(block[d], (list, tuple)):
|
|
307 |
raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: '
|
|
308 |
'attribute %r must be a list or a tuple' % d)
|
|
309 |
if isinstance(_DEFAULTS[d], dict) and not isinstance(block[d], dict):
|
|
310 |
raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: '
|
|
311 |
'attribute %r must be a dictionary' % d)
|
|
312 |
if not isinstance(_DEFAULTS[d], bool) and d in _REQUIRED and not block[d]:
|
|
313 |
raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: '
|
|
314 |
'attribute %r is required but is empty')
|
|
315 |
for i in _TO_ITERABLE:
|
|
316 |
if isinstance(block[i], six.string_types):
|
|
317 |
block[i] = (block[i],)
|
|
318 |
# lowercase LDAP attribute names
|
|
319 |
block['external_id_tuples'] = map(lambda t: map(str.lower, map(str, t)), block['external_id_tuples'])
|
|
320 |
block['attribute_mappings'] = map(lambda t: map(str.lower, map(str, t)), block['attribute_mappings'])
|
|
321 |
for key in _TO_LOWERCASE:
|
|
322 |
# we handle strings, list of strings and list of list or tuple whose first element is a string
|
|
323 |
if isinstance(block[key], six.string_types):
|
|
324 |
block[key] = str(block[key]).lower()
|
|
325 |
elif isinstance(block[key], (list, tuple)):
|
|
326 |
new_seq = []
|
|
327 |
for elt in block[key]:
|
|
328 |
if isinstance(elt, six.string_types):
|
|
329 |
elt = str(elt).lower()
|
|
330 |
elif isinstance(elt, (list, tuple)):
|
|
331 |
elt = list(elt)
|
|
332 |
elt[0] = str(elt[0]).lower()
|
|
333 |
elt = tuple(elt)
|
|
334 |
new_seq.append(elt)
|
|
335 |
block[key] = tuple(new_seq)
|
|
336 |
elif isinstance(block[key], dict):
|
|
337 |
newdict = {}
|
|
338 |
for subkey in block[key]:
|
|
339 |
newdict[str(subkey).lower()] = block[key][subkey]
|
|
340 |
block[key] = newdict
|
|
341 |
else:
|
|
342 |
raise NotImplementedError('LDAP setting %r cannot be '
|
|
343 |
'converted to lowercase '
|
|
344 |
'setting, its type is %r'
|
|
345 |
% (key, type(block[key])))
|
|
346 |
# Want to randomize our access, otherwise what's the point of having multiple servers?
|
|
347 |
block['url'] = list(block['url'])
|
|
348 |
if block['shuffle_replicas']:
|
|
349 |
random.shuffle(block['url'])
|
|
350 |
|
274 |
351 |
class LDAPBackend(object):
|
275 |
352 |
@classmethod
|
276 |
353 |
@to_list
|
277 |
354 |
def get_realms(self):
|
278 |
355 |
config = self.get_config()
|
279 |
356 |
for block in config:
|
280 |
357 |
yield block['realm']
|
281 |
358 |
|
... | ... | |
284 |
361 |
if not getattr(settings, 'LDAP_AUTH_SETTINGS', []):
|
285 |
362 |
return []
|
286 |
363 |
if isinstance(settings.LDAP_AUTH_SETTINGS[0], dict):
|
287 |
364 |
blocks = settings.LDAP_AUTH_SETTINGS
|
288 |
365 |
else:
|
289 |
366 |
blocks = (self._parse_simple_config(),)
|
290 |
367 |
# First get our configuration into a standard format
|
291 |
368 |
for block in blocks:
|
292 |
|
for key in block:
|
293 |
|
if not key in _VALID_CONFIG_KEYS:
|
294 |
|
raise ImproperlyConfigured(
|
295 |
|
('"{}" : invalid LDAP_AUTH_SETTINGS key, '
|
296 |
|
+'available are {}').format(key, _VALID_CONFIG_KEYS))
|
297 |
|
|
298 |
|
for r in _REQUIRED:
|
299 |
|
if r not in block:
|
300 |
|
raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: missing required configuration option %r' % r)
|
301 |
|
|
302 |
|
for d in _DEFAULTS:
|
303 |
|
if d not in block:
|
304 |
|
block[d] = _DEFAULTS[d]
|
305 |
|
else:
|
306 |
|
if isinstance(_DEFAULTS[d], six.string_types):
|
307 |
|
if not isinstance(block[d], six.string_types):
|
308 |
|
raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: '
|
309 |
|
'attribute %r must be a string' % d)
|
310 |
|
try:
|
311 |
|
block[d] = str(block[d])
|
312 |
|
except UnicodeEncodeError:
|
313 |
|
raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: '
|
314 |
|
'attribute %r must be a string' % d)
|
315 |
|
if isinstance(_DEFAULTS[d], bool) and not isinstance(block[d], bool):
|
316 |
|
raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: '
|
317 |
|
'attribute %r must be a boolean' % d)
|
318 |
|
if isinstance(_DEFAULTS[d], (list, tuple)) and not isinstance(block[d], (list, tuple)):
|
319 |
|
raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: '
|
320 |
|
'attribute %r must be a list or a tuple' % d)
|
321 |
|
if isinstance(_DEFAULTS[d], dict) and not isinstance(block[d], dict):
|
322 |
|
raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: '
|
323 |
|
'attribute %r must be a dictionary' % d)
|
324 |
|
if not isinstance(_DEFAULTS[d], bool) and d in _REQUIRED and not block[d]:
|
325 |
|
raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: '
|
326 |
|
'attribute %r is required but is empty')
|
327 |
|
for i in _TO_ITERABLE:
|
328 |
|
if isinstance(block[i], six.string_types):
|
329 |
|
block[i] = (block[i],)
|
330 |
|
# lowercase LDAP attribute names
|
331 |
|
block['external_id_tuples'] = map(lambda t: map(str.lower, map(str, t)), block['external_id_tuples'])
|
332 |
|
block['attribute_mappings'] = map(lambda t: map(str.lower, map(str, t)), block['attribute_mappings'])
|
333 |
|
for key in _TO_LOWERCASE:
|
334 |
|
# we handle strings, list of strings and list of list or tuple whose first element is a string
|
335 |
|
if isinstance(block[key], six.string_types):
|
336 |
|
block[key] = str(block[key]).lower()
|
337 |
|
elif isinstance(block[key], (list, tuple)):
|
338 |
|
new_seq = []
|
339 |
|
for elt in block[key]:
|
340 |
|
if isinstance(elt, six.string_types):
|
341 |
|
elt = str(elt).lower()
|
342 |
|
elif isinstance(elt, (list, tuple)):
|
343 |
|
elt = list(elt)
|
344 |
|
elt[0] = str(elt[0]).lower()
|
345 |
|
elt = tuple(elt)
|
346 |
|
new_seq.append(elt)
|
347 |
|
block[key] = tuple(new_seq)
|
348 |
|
elif isinstance(block[key], dict):
|
349 |
|
newdict = {}
|
350 |
|
for subkey in block[key]:
|
351 |
|
newdict[str(subkey).lower()] = block[key][subkey]
|
352 |
|
block[key] = newdict
|
353 |
|
else:
|
354 |
|
raise NotImplementedError('LDAP setting %r cannot be '
|
355 |
|
'converted to lowercase '
|
356 |
|
'setting, its type is %r'
|
357 |
|
% (key, type(block[key])))
|
358 |
|
# Want to randomize our access, otherwise what's the point of having multiple servers?
|
359 |
|
block['url'] = list(block['url'])
|
360 |
|
if block['shuffle_replicas']:
|
361 |
|
random.shuffle(block['url'])
|
|
369 |
update_default(block)
|
362 |
370 |
log.debug('got config %r', blocks)
|
363 |
371 |
return blocks
|
364 |
372 |
|
365 |
373 |
def authenticate(self, username=None, password=None, realm=None):
|
366 |
374 |
if username is None or password is None:
|
367 |
375 |
return None
|
368 |
376 |
|
369 |
377 |
config = self.get_config()
|
370 |
|
-
|