From 7b7f7118e85f17eb39b32b39c1f03b7b8e84fb29 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Mon, 23 Mar 2015 16:57:04 +0100 Subject: [PATCH] ldap: update block saved in LDAP users objects with default values (#6784) If a session is older thant the last upgrade then it can happend that user.block miss keys which are now mandatory and have a default value. To be sure those keys exist we update the block with default value each time it is used, i.e. in LDAPUser.get_connection(). --- src/authentic2/backends/ldap_backend.py | 148 +++++++++++++++++--------------- 1 file changed, 78 insertions(+), 70 deletions(-) diff --git a/src/authentic2/backends/ldap_backend.py b/src/authentic2/backends/ldap_backend.py index 5f84545..0f8a7a7 100644 --- a/src/authentic2/backends/ldap_backend.py +++ b/src/authentic2/backends/ldap_backend.py @@ -247,16 +247,20 @@ class LDAPUser(get_user_model()): def has_usable_password(self): return self.block['user_can_change_password'] def get_connection(self): ldap_password = self.get_ldap_password() credentials = () if ldap_password: credentials = (self.dn, ldap_password) + # must be redone if session is older than current code update and new + # options have been added to the setting dictionnary for LDAP + # authentication + update_default(self.block) return get_connection(self.block, credentials=credentials) def get_attributes(self): conn = self.get_connection() return LDAPBackend.get_ldap_attributes(self.block, conn, self.dn) def save(self, *args, **kwargs): if self.transient: @@ -266,16 +270,89 @@ class LDAPUser(get_user_model()): self.pk = self.keep_pk super(LDAPUser, self).save(*args, **kwargs) if hasattr(self, 'keep_pk'): self.pk = pk class LDAPBackendError(RuntimeError): pass +def update_default(block): + '''Add missing key to block based on default values''' + for key in block: + if not key in _VALID_CONFIG_KEYS: + raise ImproperlyConfigured( + ('"{}" : invalid LDAP_AUTH_SETTINGS key, ' + +'available are {}').format(key, _VALID_CONFIG_KEYS)) + + for r in _REQUIRED: + if r not in block: + raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: missing required configuration option %r' % r) + + for d in _DEFAULTS: + if d not in block: + block[d] = _DEFAULTS[d] + else: + if isinstance(_DEFAULTS[d], six.string_types): + if not isinstance(block[d], six.string_types): + raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: ' + 'attribute %r must be a string' % d) + try: + block[d] = str(block[d]) + except UnicodeEncodeError: + raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: ' + 'attribute %r must be a string' % d) + if isinstance(_DEFAULTS[d], bool) and not isinstance(block[d], bool): + raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: ' + 'attribute %r must be a boolean' % d) + if isinstance(_DEFAULTS[d], (list, tuple)) and not isinstance(block[d], (list, tuple)): + raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: ' + 'attribute %r must be a list or a tuple' % d) + if isinstance(_DEFAULTS[d], dict) and not isinstance(block[d], dict): + raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: ' + 'attribute %r must be a dictionary' % d) + if not isinstance(_DEFAULTS[d], bool) and d in _REQUIRED and not block[d]: + raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: ' + 'attribute %r is required but is empty') + for i in _TO_ITERABLE: + if isinstance(block[i], six.string_types): + block[i] = (block[i],) + # lowercase LDAP attribute names + block['external_id_tuples'] = map(lambda t: map(str.lower, map(str, t)), block['external_id_tuples']) + block['attribute_mappings'] = map(lambda t: map(str.lower, map(str, t)), block['attribute_mappings']) + for key in _TO_LOWERCASE: + # we handle strings, list of strings and list of list or tuple whose first element is a string + if isinstance(block[key], six.string_types): + block[key] = str(block[key]).lower() + elif isinstance(block[key], (list, tuple)): + new_seq = [] + for elt in block[key]: + if isinstance(elt, six.string_types): + elt = str(elt).lower() + elif isinstance(elt, (list, tuple)): + elt = list(elt) + elt[0] = str(elt[0]).lower() + elt = tuple(elt) + new_seq.append(elt) + block[key] = tuple(new_seq) + elif isinstance(block[key], dict): + newdict = {} + for subkey in block[key]: + newdict[str(subkey).lower()] = block[key][subkey] + block[key] = newdict + else: + raise NotImplementedError('LDAP setting %r cannot be ' + 'converted to lowercase ' + 'setting, its type is %r' + % (key, type(block[key]))) + # Want to randomize our access, otherwise what's the point of having multiple servers? + block['url'] = list(block['url']) + if block['shuffle_replicas']: + random.shuffle(block['url']) + class LDAPBackend(object): @classmethod @to_list def get_realms(self): config = self.get_config() for block in config: yield block['realm'] @@ -284,86 +361,17 @@ class LDAPBackend(object): if not getattr(settings, 'LDAP_AUTH_SETTINGS', []): return [] if isinstance(settings.LDAP_AUTH_SETTINGS[0], dict): blocks = settings.LDAP_AUTH_SETTINGS else: blocks = (self._parse_simple_config(),) # First get our configuration into a standard format for block in blocks: - for key in block: - if not key in _VALID_CONFIG_KEYS: - raise ImproperlyConfigured( - ('"{}" : invalid LDAP_AUTH_SETTINGS key, ' - +'available are {}').format(key, _VALID_CONFIG_KEYS)) - - for r in _REQUIRED: - if r not in block: - raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: missing required configuration option %r' % r) - - for d in _DEFAULTS: - if d not in block: - block[d] = _DEFAULTS[d] - else: - if isinstance(_DEFAULTS[d], six.string_types): - if not isinstance(block[d], six.string_types): - raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: ' - 'attribute %r must be a string' % d) - try: - block[d] = str(block[d]) - except UnicodeEncodeError: - raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: ' - 'attribute %r must be a string' % d) - if isinstance(_DEFAULTS[d], bool) and not isinstance(block[d], bool): - raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: ' - 'attribute %r must be a boolean' % d) - if isinstance(_DEFAULTS[d], (list, tuple)) and not isinstance(block[d], (list, tuple)): - raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: ' - 'attribute %r must be a list or a tuple' % d) - if isinstance(_DEFAULTS[d], dict) and not isinstance(block[d], dict): - raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: ' - 'attribute %r must be a dictionary' % d) - if not isinstance(_DEFAULTS[d], bool) and d in _REQUIRED and not block[d]: - raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: ' - 'attribute %r is required but is empty') - for i in _TO_ITERABLE: - if isinstance(block[i], six.string_types): - block[i] = (block[i],) - # lowercase LDAP attribute names - block['external_id_tuples'] = map(lambda t: map(str.lower, map(str, t)), block['external_id_tuples']) - block['attribute_mappings'] = map(lambda t: map(str.lower, map(str, t)), block['attribute_mappings']) - for key in _TO_LOWERCASE: - # we handle strings, list of strings and list of list or tuple whose first element is a string - if isinstance(block[key], six.string_types): - block[key] = str(block[key]).lower() - elif isinstance(block[key], (list, tuple)): - new_seq = [] - for elt in block[key]: - if isinstance(elt, six.string_types): - elt = str(elt).lower() - elif isinstance(elt, (list, tuple)): - elt = list(elt) - elt[0] = str(elt[0]).lower() - elt = tuple(elt) - new_seq.append(elt) - block[key] = tuple(new_seq) - elif isinstance(block[key], dict): - newdict = {} - for subkey in block[key]: - newdict[str(subkey).lower()] = block[key][subkey] - block[key] = newdict - else: - raise NotImplementedError('LDAP setting %r cannot be ' - 'converted to lowercase ' - 'setting, its type is %r' - % (key, type(block[key]))) - # Want to randomize our access, otherwise what's the point of having multiple servers? - block['url'] = list(block['url']) - if block['shuffle_replicas']: - random.shuffle(block['url']) + update_default(block) log.debug('got config %r', blocks) return blocks def authenticate(self, username=None, password=None, realm=None): if username is None or password is None: return None config = self.get_config() -- 1.9.1