def _ldap_modify(self): """modifies attributs of self on the ldap directory. """ modlist = list() orgin = self.attributes_factory(name='__attrs__', parent=self) for key in orgin: # MOD_DELETE if not key in self.attrs: moddef = (MOD_DELETE, encode(key), None) modlist.append(moddef) for key in self.attrs: # MOD_ADD value = self.attrs[key] if not self.attrs.is_binary(key): value = encode(value) if key not in orgin: moddef = (MOD_ADD, encode(key), value) modlist.append(moddef) # MOD_REPLACE elif self.attrs[key] != orgin[key]: moddef = (MOD_REPLACE, encode(key), value) modlist.append(moddef) if modlist: self.ldap_session.modify(encode(self.DN), modlist)
def _ldap_add(self): # adds self to the ldap directory. attrs = {} for key, value in self.attrs.items(): if not self.attrs.is_binary(key): value = encode(value) attrs[encode(key)] = value self.ldap_session.add(encode(self.DN), attrs)
def __iter__(self): if self.name is None: return cookie = '' while True: try: res = self.ldap_session.search( scope=ONELEVEL, baseDN=encode(self.DN), attrlist=[''], page_size=self._page_size, cookie=cookie, ) except NO_SUCH_OBJECT: # happens if not persisted yet res = list() if isinstance(res, tuple): res, cookie = res for dn, _ in res: key = decode(explode_dn(dn)[0]) # do not yield if node is supposed to be deleted if key not in self._deleted_children: yield key if not cookie: break # also yield keys of children not persisted yet. for key in self._added_children: yield key
def _ldap_delete(self): """delete self from the ldap-directory. """ self.parent._keys[self.name] = False del self.parent.storage[self.name] del self.parent._keys[self.name] self.ldap_session.delete(encode(self.DN))
def _enumerateUsers(self, exact_match=False, **kw): matches = [] cookie = None while True: res = self.users.search( criteria=encode(kw), attrlist=exact_match and self.users.context.search_attrlist or ['login'], exact_match=exact_match, or_search=not exact_match, page_size=not exact_match and self._ldap_props.page_size or None, cookie=cookie, ) if isinstance(res, tuple): batch_matches, cookie = res else: batch_matches, cookie = res, '' matches += batch_matches if not cookie: break if not matches: raise ValueError('No matches') else: return matches
def _perform(self, function, *args, **kwargs): """Try to perform the given function with the given argument. If LDAP directory is down, bind again and retry given function. XXX: * Improve retry logic in LDAPSession * Extend LDAPSession object to handle Fallback server(s) """ # XXX BUG: # It is complete wrong to encode/ decode in here every string # LDAP handles binary data too and so fetching or setting binary # data fails. We need to refactor this part. args = encode(args) kwargs = encode(kwargs) if self._communicator._con is None: self._communicator.bind() return decode(function(*args, **kwargs))
def setProperties(self, obj, mapping): for id in mapping: assert (id in self._properties) ldapprincipal = self._get_ldap_principal() for id in mapping: self._properties[id] = ldapprincipal.attrs[id] = encode( mapping[id]) try: ldapprincipal.context() except Exception, e: # XXX: specific exception(s) logger.error('LDAPUserPropertySheet.setProperties: %s' % str(e))
def node_attributes(self): rdn = self.request['rdn'] base = self.request['base'] if base == 'users': users = ILDAPUsersConfig(self.plugin) baseDN = users.baseDN else: groups = ILDAPGroupsConfig(self.plugin) baseDN = groups.baseDN root = LDAPNode(baseDN, self.props) node = root[rdn] ret = dict() for key, val in node.attrs.items(): try: if not node.attrs.is_binary(key): ret[encode(key)] = encode(val) else: ret[encode(key)] = "(Binary Data with %d Bytes)" % len(val) except UnicodeDecodeError, e: ret[key.encode('utf-8')] = '! (UnicodeDecodeError)' except Exception, e: ret[key.encode('utf-8')] = '! (Unknown Exception)'
def _calculate_key(self, dn, attrs): if self._key_attr == 'rdn': # explode_dn is ldap world key = explode_dn(encode(dn))[0] else: key = attrs[self._key_attr] if isinstance(key, list): if len(key) != 1: msg = u"Expected one value for '%s' " % (self._key_attr,) msg += u"not %s: '%s'." % (len(key), key) raise KeyError(msg) key = key[0] return decode(key)
def _calculate_key(self, dn, attrs): if self._key_attr == 'rdn': # explode_dn is ldap world key = decode(explode_dn(encode(dn))[0]) else: key = attrs[self._key_attr] if isinstance(key, list): if len(key) != 1: msg = u"Expected one value for '%s' " % (self._key_attr,) msg += u"not %s: '%s'." % (len(key), key) raise KeyError(msg) key = key[0] return key
def _perform(self, function, *args, **kwargs): """Try to perform the given function with the given argument. If LDAP directory is down, bind again and retry given function. XXX: * Improve retry logic in LDAPSession * Extend LDAPSession object to handle Fallback server(s) """ args = encode(args) kwargs = encode(kwargs) if self._communicator._con is None: self._communicator.bind() # XXX: it seems except block is never reached, call of # self._communicator.bind() above already raises error if # communication fails. And why server down? #try: # return decode(function(*args, **kwargs)) #except ldap.SERVER_DOWN: # self._communicator.bind() # return decode(function(*args, **kwargs)) return decode(function(*args, **kwargs))
def node_by_dn(self, dn, strict=False): """Return node from tree by DN. """ root = node = self.root base_dn = root.name if not dn.endswith(base_dn): raise ValueError(u'Invalid base DN') dn = dn[:len(dn) - len(base_dn)].strip(',') for rdn in reversed(explode_dn(encode(dn))): try: node = node[rdn] except KeyError: if strict: raise ValueError(u'Tree contains no node by given DN. ' u'Failed at RDN {}'.format(rdn)) return None return node
def _enumerateGroups(self, exact_match=False, **kw): matches = [] cookie = None while True: res = self.groups.search( criteria=encode(kw), attrlist=None, exact_match=exact_match, page_size=self._ldap_props.page_size, cookie=cookie, ) if isinstance(res, tuple): batch_matches, cookie = res else: batch_matches, cookie = res, '' matches += batch_matches if not cookie: break if not matches: raise ValueError('No matches') else: return matches
def test_encode(self): self.assertEqual( encode( b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xd4' b'\xa0\xff\xff\xaeW\x82\xa9P\xcf8\xaf&\x0e\x00\x00' ), ( b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xd4' b'\xa0\xff\xff\xaeW\x82\xa9P\xcf8\xaf&\x0e\x00\x00' ) ) self.assertEqual(encode(u'\xe4'), b'\xc3\xa4') self.assertEqual(encode([u'\xe4']), [b'\xc3\xa4']) self.assertEqual( encode({u'\xe4': u'\xe4'}), {b'\xc3\xa4': b'\xc3\xa4'} ) self.assertEqual(encode(b'\xc3\xa4'), b'\xc3\xa4') node = BaseNode() node.allow_non_node_childs = True node['foo'] = u'\xe4' self.assertEqual(encode(node), {b'foo': b'\xc3\xa4'})
def search(self, queryFilter=None, criteria=None, attrlist=None, relation=None, relation_node=None, exact_match=False, or_search=False, or_keys=None, or_values=None, page_size=None, cookie=None, get_nodes=False): attrset = set(attrlist or []) attrset.discard('dn') attrset.discard('rdn') # Create queryFilter from all filter definitions # filter for this search ANDed with the default filters defined on self search_filter = LDAPFilter(queryFilter) search_filter &= LDAPDictFilter(criteria, or_search=or_search, or_keys=or_keys, or_values=or_values, ) _filter = LDAPFilter(self.search_filter) _filter &= LDAPDictFilter(self.search_criteria) _filter &= search_filter # relation filters if relation_node is None: relation_node = self relations = [relation, self.search_relation] for relation in relations: if not relation: continue if isinstance(relation, LDAPRelationFilter): _filter &= relation else: _filter &= LDAPRelationFilter(relation_node, relation) # perform the backend search matches = self.ldap_session.search( str(_filter), self.search_scope, baseDN=encode(self.DN), force_reload=self._reload, attrlist=list(attrset), page_size=page_size, cookie=cookie, ) if type(matches) is tuple: matches, cookie = matches # check exact match if exact_match and len(matches) > 1: raise ValueError(u"Exact match asked but result not unique") if exact_match and len(matches) == 0: raise ValueError(u"Exact match asked but result length is zero") # extract key and desired attributes res = [] for dn, attrs in matches: dn = decode(dn) if attrlist is not None: resattr = dict() for k, v in attrs.iteritems(): if k in attrlist: # Check binary binary attribute directly from root # data to avoid initing attrs for a simple search. if k in self.root._binary_attributes: resattr[decode(k)] = v else: resattr[decode(k)] = decode(v) if 'dn' in attrlist: resattr[u'dn'] = dn if 'rdn' in attrlist: rdn = explode_dn(encode(dn))[0] resattr[u'rdn'] = decode(rdn) if get_nodes: res.append((self.node_by_dn(dn, strict=True), resattr)) else: res.append((dn, resattr)) else: if get_nodes: res.append(self.node_by_dn(dn, strict=True)) else: res.append(dn) if cookie is not None: return (res, cookie) return res
def _set_baseDN(self, baseDN): if isinstance(baseDN, str): # make sure its utf8 baseDN = decode(baseDN) baseDN = encode(baseDN) self._communicator.baseDN = baseDN
def search(self, queryFilter=None, criteria=None, attrlist=None, relation=None, relation_node=None, exact_match=False, or_search=False, or_keys=None, or_values=None, page_size=None, cookie=None): attrset = set(attrlist or []) attrset.discard('dn') # fetch also the key attribute if not self._key_attr == 'rdn': attrset.add(self._key_attr) # Create queryFilter from all filter definitions # filter for this search ANDed with the default filters defined on self search_filter = LDAPFilter(queryFilter) search_filter &= LDAPDictFilter(criteria, or_search=or_search, or_keys=or_keys, or_values=or_values, ) _filter = LDAPFilter(self.search_filter) _filter &= LDAPDictFilter(self.search_criteria) _filter &= search_filter # relation filters if relation_node is None: relation_node = self relations = [relation, self.search_relation] for relation in relations: if not relation: continue if isinstance(relation, LDAPRelationFilter): _filter &= relation else: _filter &= LDAPRelationFilter(relation_node, relation) # XXX: Is it really good to filter out entries without the key attr or # would it be better to fail? (see also __iter__ secondary key) if self._key_attr != 'rdn' and self._key_attr not in _filter: _filter &= '(%s=*)' % (self._key_attr,) # perform the backend search matches = self.ldap_session.search( str(_filter), self.search_scope, baseDN=encode(self.DN), force_reload=self._reload, attrlist=list(attrset), page_size=page_size, cookie=cookie, ) if type(matches) is tuple: matches, cookie = matches # XXX: Is ValueError appropriate? # XXX: why do we need to fail at all? shouldn't this be about # substring vs equality match? if exact_match and len(matches) > 1: raise ValueError(u"Exact match asked but result not unique") if exact_match and len(matches) == 0: raise ValueError(u"Exact match asked but result length is zero") # extract key and desired attributes res = [] for dn, attrs in matches: key = self._calculate_key(dn, attrs) if attrlist is not None: resattr = dict() for k, v in attrs.iteritems(): if k in attrlist: if self.attrs.is_binary(k): resattr[decode(k)] = v else: resattr[decode(k)] = decode(v) if 'dn' in attrlist: resattr[u'dn'] = decode(dn) res.append((key, resattr)) else: res.append(key) if cookie is not None: return (res, cookie) return res
def enumerateUsers(self, id=None, login=None, exact_match=False, sort_by=None, max_results=None, **kw): """-> ( user_info_1, ... user_info_N ) o Return mappings for users matching the given criteria. o 'id' or 'login', in combination with 'exact_match' true, will return at most one mapping per supplied ID ('id' and 'login' may be sequences). o If 'exact_match' is False, then 'id' and / or login may be treated by the plugin as "contains" searches (more complicated searches may be supported by some plugins using other keyword arguments). o If 'sort_by' is passed, the results will be sorted accordingly. known valid values are 'id' and 'login' (some plugins may support others). o If 'max_results' is specified, it must be a positive integer, limiting the number of returned mappings. If unspecified, the plugin should return mappings for all users satisfying the criteria. o Minimal keys in the returned mappings: 'id' -- (required) the user ID, which may be different than the login name 'login' -- (required) the login name 'pluginid' -- (required) the plugin ID (as returned by getId()) 'editurl' -- (optional) the URL to a page for updating the mapping's user o Plugin *must* ignore unknown criteria. o Plugin may raise ValueError for invalid criteria. o Insufficiently-specified criteria may have catastrophic scaling issues for some implementations. """ default = tuple() if not self.is_plugin_active(pas_interfaces.IUserEnumerationPlugin): return default # TODO: sort_by in node.ext.ldap if login: if not isinstance(login, basestring): # XXX TODO raise NotImplementedError('sequence is not supported yet.') kw['login'] = login # pas search users gives both login and name if login is meant if "login" in kw and "name" in kw: del kw["name"] if id: if not isinstance(id, basestring): # XXX TODO raise NotImplementedError('sequence is not supported yet.') kw['id'] = id users = self.users if not users: return default if not exact_match: for value in users.principal_attrmap.values(): kw[value] = kw.values()[0] matches = [] cookie = None while True: try: res = users.search( criteria=encode(kw), attrlist=exact_match and users.context.search_attrlist or ['login'], exact_match=exact_match, or_search=not exact_match, page_size=not exact_match and self._ldap_props.page_size or None, cookie=cookie, ) if isinstance(res, tuple): batch_matches, cookie = res else: batch_matches, cookie = res, '' except ValueError: return default matches += batch_matches if not cookie: break if len(matches) >= 100: msg = 'Too many search results. Please, narrow your search.' IStatusMessage(self.REQUEST).add(msg, type='warning') return default pluginid = self.getId() ret = list() for id, attrs in matches: if 'login' not in attrs: continue ret.append({ 'id': id, 'login': attrs['login'][0], 'pluginid': pluginid }) if max_results and len(ret) > max_results: ret = ret[:max_results] return ret
def _ldap_delete(self): # delete self from the ldap-directory. del self.parent.storage[self.name] self.ldap_session.delete(encode(self.DN))
def enumerateGroups(self, id=None, exact_match=False, sort_by=None, max_results=None, **kw): """ -> ( group_info_1, ... group_info_N ) o Return mappings for groups matching the given criteria. o 'id' in combination with 'exact_match' true, will return at most one mapping per supplied ID ('id' and 'login' may be sequences). o If 'exact_match' is False, then 'id' may be treated by the plugin as "contains" searches (more complicated searches may be supported by some plugins using other keyword arguments). o If 'sort_by' is passed, the results will be sorted accordingly. known valid values are 'id' (some plugins may support others). o If 'max_results' is specified, it must be a positive integer, limiting the number of returned mappings. If unspecified, the plugin should return mappings for all groups satisfying the criteria. o Minimal keys in the returned mappings: 'id' -- (required) the group ID 'pluginid' -- (required) the plugin ID (as returned by getId()) 'properties_url' -- (optional) the URL to a page for updating the group's properties. 'members_url' -- (optional) the URL to a page for updating the principals who belong to the group. o Plugin *must* ignore unknown criteria. o Plugin may raise ValueError for invalid critera. o Insufficiently-specified criteria may have catastrophic scaling issues for some implementations. """ default = () if not self.is_plugin_active(pas_interfaces.IGroupEnumerationPlugin): return default groups = self.groups if not groups: return default if id and exact_match: if id in self.getGroupIds(): return ({'id': id, 'pluginid': self.getId()}) else: return default elif id: kw['id'] = id if not kw: # show all matches = groups.ids else: matches = [] cookie = None while True: try: res = groups.search( criteria=encode(kw), attrlist=None, exact_match=exact_match, page_size=self._ldap_props.page_size, cookie=cookie, ) if isinstance(res, tuple): batch_matches, cookie = res else: batch_matches, cookie = res, '' except ValueError: return default matches += batch_matches if not cookie: break if len(matches) >= 100: msg = 'Too many search results. Please, narrow your search.' IStatusMessage(self.REQUEST).add(msg, type='warning') return default if sort_by == 'id': matches = sorted(matches) pluginid = self.getId() ret = list() if exact_match: for id, attrs in matches: ret.append({'id': id, 'pluginid': pluginid}) else: for id in matches: ret.append({'id': id, 'pluginid': pluginid}) if max_results and len(ret) > max_results: ret = ret[:max_results] return ret