def normalize_dn(dn): # Capitalize the attribute names as an LDAP server might. # NOTE(blk-u): Special case for this tested value, used with # test_user_id_comma. The call to str2dn here isn't always correct # here, because `dn` is escaped for an LDAP filter. str2dn() normally # works only because there's no special characters in `dn`. if dn == 'cn=Doe\\5c, John,ou=Users,cn=example,cn=com': return 'CN=Doe\\, John,OU=Users,CN=example,CN=com' # NOTE(blk-u): Another special case for this tested value. When a # roleOccupant has an escaped comma, it gets converted to \2C. if dn == 'cn=Doe\\, John,ou=Users,cn=example,cn=com': return 'CN=Doe\\2C John,OU=Users,CN=example,CN=com' try: dn = ldap.dn.str2dn(common.utf8_encode(dn)) except ldap.DECODING_ERROR: # NOTE(amakarov): In case of IDs instead of DNs in group members # they must be handled as regular values. return normalize_value(dn) norm = [] for part in dn: name, val, i = part[0] name = common.utf8_decode(name) name = name.upper() norm.append([(name, val, i)]) return common.utf8_decode(ldap.dn.dn2str(norm))
def add_s(self, dn, modlist): """Add an object with the specified attributes at dn.""" if server_fail: raise ldap.SERVER_DOWN id_attr_in_modlist = False id_attr = self._dn_to_id_attr(dn) id_value = self._dn_to_id_value(dn) # The LDAP API raises a TypeError if attr name is None. for k, dummy_v in modlist: if k is None: raise TypeError('must be string, not None. modlist=%s' % modlist) if k == id_attr: for val in dummy_v: if common.utf8_decode(val) == id_value: id_attr_in_modlist = True if not id_attr_in_modlist: LOG.debug('id_attribute=%(attr)s missing, attributes=%(attrs)s' % {'attr': id_attr, 'attrs': modlist}) raise ldap.NAMING_VIOLATION key = self.key(dn) LOG.debug('add item: dn=%(dn)s, attrs=%(attrs)s', { 'dn': common.utf8_decode(dn), 'attrs': modlist}) if key in self.db: LOG.debug('add item failed: dn=%s is already in store.', common.utf8_decode(dn)) raise ldap.ALREADY_EXISTS(dn) self.db[key] = {k: _internal_attr(k, v) for k, v in modlist} self.db.sync()
def simple_bind_s(self, who='', cred='', serverctrls=None, clientctrls=None): """Provide for compatibility but this method is ignored.""" if server_fail: raise ldap.SERVER_DOWN whos = ['cn=Admin', CONF.ldap.user] if (common.utf8_decode(who) in whos and common.utf8_decode(cred) in ['password', CONF.ldap.password]): return attrs = self.db.get(self.key(who)) if not attrs: LOG.debug('who=%s not found, binding anonymously', common.utf8_decode(who)) db_password = '' if attrs: try: db_password = attrs['userPassword'][0] except (KeyError, IndexError): LOG.debug('bind fail: password for who=%s not found', common.utf8_decode(who)) raise ldap.INAPPROPRIATE_AUTH if cred != common.utf8_encode(db_password): LOG.debug('bind fail: password for who=%s does not match', common.utf8_decode(who)) raise ldap.INVALID_CREDENTIALS
def modify_s(self, dn, modlist): """Modify the object at dn using the attribute list. :param dn: an LDAP DN :param modlist: a list of tuples in the following form: ([MOD_ADD | MOD_DELETE | MOD_REPACE], attribute, value) """ if server_fail: raise ldap.SERVER_DOWN key = self.key(dn) LOG.debug('modify item: dn=%(dn)s attrs=%(attrs)s', { 'dn': common.utf8_decode(dn), 'attrs': modlist}) try: entry = self.db[key] except KeyError: LOG.debug('modify item failed: dn=%s not found.', common.utf8_decode(dn)) raise ldap.NO_SUCH_OBJECT for cmd, k, v in modlist: values = entry.setdefault(k, []) if cmd == ldap.MOD_ADD: v = _internal_attr(k, v) for x in v: if x in values: raise ldap.TYPE_OR_VALUE_EXISTS values += v elif cmd == ldap.MOD_REPLACE: values[:] = _internal_attr(k, v) elif cmd == ldap.MOD_DELETE: if v is None: if not values: LOG.debug('modify item failed: ' 'item has no attribute "%s" to delete', k) raise ldap.NO_SUCH_ATTRIBUTE values[:] = [] else: for val in _internal_attr(k, v): try: values.remove(val) except ValueError: LOG.debug('modify item failed: ' 'item has no attribute "%(k)s" with ' 'value "%(v)s" to delete', { 'k': k, 'v': val}) raise ldap.NO_SUCH_ATTRIBUTE else: LOG.debug('modify item failed: unknown command %s', cmd) raise NotImplementedError('modify_s action %s not' ' implemented' % cmd) self.db[key] = entry self.db.sync()
def delete_ext_s(self, dn, serverctrls, clientctrls=None): """Remove the ldap object at specified dn.""" if server_fail: raise ldap.SERVER_DOWN try: key = self.key(dn) LOG.debug('FakeLdap delete item: dn=%s', common.utf8_decode(dn)) del self.db[key] except KeyError: LOG.debug('delete item failed: dn=%s not found.', common.utf8_decode(dn)) raise ldap.NO_SUCH_OBJECT self.db.sync()
def delete_ext_s(self, dn, serverctrls, clientctrls=None): """Remove the ldap object at specified dn.""" if server_fail: raise ldap.SERVER_DOWN try: if CONTROL_TREEDELETE in [c.controlType for c in serverctrls]: LOG.debug('FakeLdap subtree_delete item: dn=%s', common.utf8_decode(dn)) children = self._getChildren(dn) for c in children: del self.db[c] key = self.key(dn) LOG.debug('FakeLdap delete item: dn=%s', common.utf8_decode(dn)) del self.db[key] except KeyError: LOG.debug('delete item failed: dn=%s not found.', common.utf8_decode(dn)) raise ldap.NO_SUCH_OBJECT self.db.sync()
def test_utf8_conversion(self): value_unicode = u'fäké1' value_utf8 = value_unicode.encode('utf-8') result_utf8 = common_ldap.utf8_encode(value_unicode) self.assertEqual(value_utf8, result_utf8) result_utf8 = common_ldap.utf8_encode(value_utf8) self.assertEqual(value_utf8, result_utf8) result_unicode = common_ldap.utf8_decode(value_utf8) self.assertEqual(value_unicode, result_unicode) result_unicode = common_ldap.utf8_decode(value_unicode) self.assertEqual(value_unicode, result_unicode) self.assertRaises(TypeError, common_ldap.utf8_encode, 100) result_unicode = common_ldap.utf8_decode(100) self.assertEqual(u'100', result_unicode)
def delete_ext_s(self, dn, serverctrls, clientctrls=None): """Remove the ldap object at specified dn.""" if server_fail: raise ldap.SERVER_DOWN try: children = self._getChildren(dn) if children: raise ldap.NOT_ALLOWED_ON_NONLEAF except KeyError: LOG.debug('delete item failed: dn=%s not found.', common.utf8_decode(dn)) raise ldap.NO_SUCH_OBJECT super(FakeLdapNoSubtreeDelete, self).delete_ext_s(dn, serverctrls, clientctrls)
def normalize_value(value): return common.utf8_decode(value)
def dn(self, dn): return common.utf8_decode(dn)
def search_s(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0): """Search for all matching objects under base using the query. Args: base -- dn to search under scope -- search scope (base, subtree, onelevel) filterstr -- filter objects by attrlist -- attrs to return. Returns all attrs if not specified """ if server_fail: raise ldap.SERVER_DOWN if (not filterstr) and (scope != ldap.SCOPE_BASE): raise AssertionError('Search without filter on onelevel or ' 'subtree scope') if scope == ldap.SCOPE_BASE: try: item_dict = self.db[self.key(base)] except KeyError: LOG.debug('search fail: dn not found for SCOPE_BASE') raise ldap.NO_SUCH_OBJECT results = [(base, item_dict)] elif scope == ldap.SCOPE_SUBTREE: # FIXME - LDAP search with SUBTREE scope must return the base # entry, but the code below does _not_. Unfortunately, there are # several tests that depend on this broken behavior, and fail # when the base entry is returned in the search results. The # fix is easy here, just initialize results as above for # the SCOPE_BASE case. # https://bugs.launchpad.net/keystone/+bug/1368772 try: item_dict = self.db[self.key(base)] except KeyError: LOG.debug('search fail: dn not found for SCOPE_SUBTREE') raise ldap.NO_SUCH_OBJECT results = [(base, item_dict)] extraresults = [(k[len(self.__prefix):], v) for k, v in self.db.items() if re.match('%s.*,%s' % (re.escape(self.__prefix), re.escape(self.dn(base))), k)] results.extend(extraresults) elif scope == ldap.SCOPE_ONELEVEL: def get_entries(): base_dn = ldap.dn.str2dn(common.utf8_encode(base)) base_len = len(base_dn) for k, v in self.db.items(): if not k.startswith(self.__prefix): continue k_dn_str = k[len(self.__prefix):] k_dn = ldap.dn.str2dn(common.utf8_encode(k_dn_str)) if len(k_dn) != base_len + 1: continue if k_dn[-base_len:] != base_dn: continue yield (k_dn_str, v) results = list(get_entries()) else: # openldap client/server raises PROTOCOL_ERROR for unexpected scope raise ldap.PROTOCOL_ERROR objects = [] for dn, attrs in results: # filter the objects by filterstr id_attr, id_val, _ = ldap.dn.str2dn(common.utf8_encode(dn))[0][0] id_attr = common.utf8_decode(id_attr) id_val = common.utf8_decode(id_val) match_attrs = attrs.copy() match_attrs[id_attr] = [id_val] attrs_checked = set() if not filterstr or _match_query(common.utf8_decode(filterstr), match_attrs, attrs_checked): if (filterstr and (scope != ldap.SCOPE_BASE) and ('objectclass' not in attrs_checked)): raise AssertionError('No objectClass in search filter') # filter the attributes by attrlist attrs = {k: v for k, v in attrs.items() if not attrlist or k in common.utf8_decode(attrlist)} objects.append((dn, attrs)) return objects
def _dn_to_id_value(self, dn): return common.utf8_decode( ldap.dn.str2dn(common.utf8_encode(dn))[0][0][1])