diff --git a/CHANGES.rst b/CHANGES.rst index 2ab84bb..1ac7b07 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,23 @@ History 1.0b7 (unreleased) ------------------ +- Do not catch ``ValueError`` in + ``node.ext.ldap._node.LDAPStorage.batched_search``. + [rnix] + +- Use property decorators for ``node.ext.ldap._node.LDAPStorage.changed`` + and ``node.ext.ldap.session.LDAPSession.baseDN``. + [rnix] + +- Fix signature of ``node.ext.ldap.interfaces.ILDAPStorage.search`` to match + the actual implementation in ``node.ext.ldap._node.LDAPStorage.search``. + [rnix] + +- Fix signature of ``node.ext.ldap.ugm.LDAPPrincipals.search`` according to + ``node.ext.ugm.interfaces.IPrincipals.search``. The implementation exposed + LDAP related arguments and has been renamed to ``raw_search``. + [rnix] + - Add ``exists`` property to ``LDAPStorage``. [rnix] diff --git a/src/node/ext/ldap/__init__.py b/src/node/ext/ldap/__init__.py index 39fdac0..1899a98 100644 --- a/src/node/ext/ldap/__init__.py +++ b/src/node/ext/ldap/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# do not change import order from node.ext.ldap.scope import BASE from node.ext.ldap.scope import ONELEVEL from node.ext.ldap.scope import SUBTREE diff --git a/src/node/ext/ldap/_node.py b/src/node/ext/ldap/_node.py index 64c77ee..a254ebe 100644 --- a/src/node/ext/ldap/_node.py +++ b/src/node/ext/ldap/_node.py @@ -56,7 +56,7 @@ def __init__(_next, self, name=None, parent=None): @default def load(self): ldap_node = self.parent - # nothong to load + # nothing to load if not ldap_node.name \ or not ldap_node.ldap_session \ or ldap_node._action == ACTION_ADD: @@ -83,10 +83,10 @@ def load(self): ) # result length must be 1 if len(entry) != 1: - raise RuntimeError( # pragma NO COVERAGE - u"Fatal. Expected entry does not exist " # pragma NO COVERAGE - u"or more than one entry found" # pragma NO COVERAGE - ) # pragma NO COVERAGE + raise RuntimeError( #pragma NO COVERAGE + u"Fatal. Expected entry does not exist " #pragma NO COVERAGE + u"or more than one entry found" #pragma NO COVERAGE + ) #pragma NO COVERAGE # read attributes from result and set to self attrs = entry[0][1] for key, item in attrs.items(): @@ -140,7 +140,8 @@ def is_multivalued(self, name): return name in self.parent.root._multivalued_attributes -AttributesBehavior = LDAPAttributesBehavior # B/C +# B/C +AttributesBehavior = LDAPAttributesBehavior deprecated('AttributesBehavior', """ ``node.ext.ldap._node.AttributesBehavior`` is deprecated as of node.ext.ldap 1.0 and will be removed in node.ext.ldap 1.1. Use @@ -164,11 +165,8 @@ def __init__(self, name=None, props=None): """LDAP Node expects ``name`` and ``props`` arguments for the root LDAP Node or nothing for children. - name - Initial base DN for the root LDAP Node. - - props - ``node.ext.ldap.LDAPProps`` object. + :param name: Initial base DN for the root LDAP Node. + :param props: ``node.ext.ldap.LDAPProps`` instance. """ if (name and not props) or (props and not name): raise ValueError(u"Wrong initialization.") @@ -380,11 +378,14 @@ def DN(self): def rdn_attr(self): return self.name and self.name.split('=')[0] or None - def _get_changed(self): + @property + def changed(self): return self._changed - def _set_changed(self, value): - """Set the changed flag + @default + @changed.setter + def changed(self, value): + """Set the changed flag. Set: - if self.attrs are changed (attrs set us) @@ -424,8 +425,6 @@ def _set_changed(self, value): if self._changed is not oldval and self.parent is not None: self.parent.changed = self._changed - changed = default(property(_get_changed, _set_changed)) - @default def child_dn(self, key): # return child DN for key @@ -444,7 +443,7 @@ def exists(self): ) # this probably never happens if len(res) != 1: - raise RuntimeError() + raise RuntimeError() #pragma NO COVERAGE return True except NO_SUCH_OBJECT: return False @@ -560,13 +559,10 @@ def batched_search(self, page_size=None, search_func=None, **kw): cookie = None kw['page_size'] = page_size while True: - try: - kw['cookie'] = cookie - matches, cookie = search_func(**kw) - for item in matches: - yield item - except ValueError: - break + kw['cookie'] = cookie + matches, cookie = search_func(**kw) + for item in matches: + yield item if not cookie: break @@ -631,7 +627,6 @@ 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 key not in self.attrs: diff --git a/src/node/ext/ldap/base.py b/src/node/ext/ldap/base.py index fba35d0..39926f2 100644 --- a/src/node/ext/ldap/base.py +++ b/src/node/ext/ldap/base.py @@ -5,7 +5,6 @@ from node.ext.ldap.interfaces import ICacheProviderFactory from node.ext.ldap.properties import LDAPProps from zope.component import queryUtility - import hashlib import ldap import logging @@ -17,14 +16,11 @@ def testLDAPConnectivity(server=None, port=None, props=None): """Function to test the availability of the LDAP Server. - server - Server IP or name - - port - LDAP port - - props - LDAPProps object. If given, server and port are ignored. + :param server: Server IP or name + :param port: LDAP port + :param props: LDAPProps object. If given, server and port are ignored. + :return object: Either string 'success' if connectivity, otherwise ldap + error instance. """ if props is None: props = LDAPProps(server=server, port=port) @@ -39,7 +35,10 @@ def testLDAPConnectivity(server=None, port=None, props=None): def md5digest(key): - """abbrev to create a md5 hex digest + """Abbrev to create a md5 hex digest. + + :param key: Key to create a md5 hex digest for. + :return digest: hex digest. """ m = hashlib.md5() m.update(key) @@ -69,7 +68,9 @@ class LDAPConnector(object): """ def __init__(self, props=None): - """Initialize LDAPConnector. + """Initialize LDAP connector. + + :param props: ``LDAPServerProperties`` instance. """ self.protocol = ldap.VERSION3 self._uri = props.uri @@ -96,7 +97,7 @@ def bind(self): if self._start_tls: # ignore in tests for now. nevertheless provide a test environment # for TLS and SSL later - self._con.start_tls_s() # pragma NO COVERAGE + self._con.start_tls_s() #pragma NO COVERAGE self._con.simple_bind_s(self._bindDN, self._bindPW) return self._con @@ -116,9 +117,9 @@ class LDAPCommunicator(object): """ def __init__(self, connector): - """ - connector - LDAPConnector instance. + """Initialize LDAP communicator. + + :param connector: ``LDAPConnector`` instance. """ self.baseDN = '' self._connector = connector @@ -162,36 +163,21 @@ def search(self, queryFilter, scope, baseDN=None, page_size=None, cookie=None): """Search the directory. - queryFilter - LDAP query filter - - scope - LDAP search scope - - baseDN - Search base. Defaults to ``self.baseDN`` - - force_reload - Force reload of result if cache enabled. - - attrlist - LDAP attrlist to query. - - attrsonly - Flag whether to return only attribute names, without corresponding - values. - - page_size - Number of items per page, when doing pagination. - - cookie - Cookie string returned by previous search with pagination. + :param queryFilter: LDAP query filter + :param scope: LDAP search scope + :param baseDN: Search base. Defaults to ``self.baseDN`` + :param force_reload: Force reload of result if cache enabled. + :param attrlist: LDAP attrlist to query. + :param attrsonly: Flag whether to return only attribute names, without + corresponding values. + :param page_size: Number of items per page, when doing pagination. + :param cookie: Cookie string returned by previous search with + pagination. """ if baseDN is None: baseDN = self.baseDN if not baseDN: raise ValueError(u"baseDN unset.") - if page_size: if cookie is None: cookie = '' @@ -209,7 +195,6 @@ def _search(baseDN, scope, queryFilter, # in case we do pagination of results if type(attrlist) in (list, tuple): attrlist = [str(_) for _ in attrlist] - msgid = self._con.search_ext( baseDN, scope, @@ -225,7 +210,6 @@ def _search(baseDN, scope, queryFilter, return results, pctrls[0].cookie else: return results - args = [baseDN, scope, queryFilter, attrlist, attrsonly, serverctrls] if self._cache: key_items = [ @@ -251,11 +235,8 @@ def _search(baseDN, scope, queryFilter, def add(self, dn, data): """Insert an entry into directory. - dn - adding DN - - data - dict containing key/value pairs of entry attributes + :param dn: Adding DN + :param data: Dict containing key/value pairs of entry attributes """ attributes = [(k, v) for k, v in data.items()] self._con.add_s(dn, attributes) diff --git a/src/node/ext/ldap/base.rst b/src/node/ext/ldap/base.rst index 33e5625..14d5fcd 100644 --- a/src/node/ext/ldap/base.rst +++ b/src/node/ext/ldap/base.rst @@ -28,7 +28,7 @@ LDAP credentials:: ... server=host, ... port=port, ... user=binddn, - ... password=bindpw, + ... password=bindpw ... ) Test main script, could be used by command line with @@ -94,6 +94,31 @@ Set base dn and check if previously imported entries are present.:: >>> len(res) 7 +Test search pagination:: + + >>> res, cookie = communicator.search( + ... '(objectClass=*)', SUBTREE, page_size=4, cookie='') + >>> len(res) + 4 + + >>> res, cookie = communicator.search( + ... '(objectClass=*)', SUBTREE, page_size=4, cookie=cookie) + + >>> len(res) + 3 + + >>> cookie + '' + +Pagination search fails if cookie but no page size given:: + + >>> res, cookie = communicator.search( + ... '(objectClass=*)', SUBTREE, page_size=4, cookie='') + >>> communicator.search('(objectClass=*)', SUBTREE, cookie=cookie) + Traceback (most recent call last): + ... + ValueError: cookie passed without page_size + Test inserting entries.:: >>> entry = { diff --git a/src/node/ext/ldap/filter.py b/src/node/ext/ldap/filter.py index 3012002..eab6448 100644 --- a/src/node/ext/ldap/filter.py +++ b/src/node/ext/ldap/filter.py @@ -58,8 +58,7 @@ def __or__(self, other): return LDAPFilter(res) def __contains__(self, attr): - attr = '(%s=' % (attr,) - return attr in self._filter + return self._filter.find('({}='.format(attr)) > -1 def __str__(self): return self._filter and self._filter or '' @@ -100,34 +99,21 @@ def __str__(self): """turn relation string into ldap filter string """ dictionary = dict() - parsedRelation = dict() for pair in self.relation.split('|'): k, _, v = pair.partition(':') if k not in parsedRelation: parsedRelation[k] = list() parsedRelation[k].append(v) - existing = [x for x in self.gattrs] for k, vals in parsedRelation.items(): for v in vals: - if ( - str(v) == '' or + if (str(v) == '' or str(k) == '' or - str(k) not in existing - ): + str(k) not in existing): continue dictionary[str(v)] = self.gattrs[str(k)] - self.dictionary = dictionary - - # this "if/else creates an unused result and has no effect - # _filter = LDAPFilter() - # if len(dictionary) is 1: - # _filter = LDAPFilter(self.relation) - # else: - # _filter = dict_to_filter(parsedRelation, self.or_search) - if self.dictionary: return str(dict_to_filter(self.dictionary, self.or_search)) return '' diff --git a/src/node/ext/ldap/filter.rst b/src/node/ext/ldap/filter.rst index 990489c..2d8031e 100644 --- a/src/node/ext/ldap/filter.rst +++ b/src/node/ext/ldap/filter.rst @@ -52,6 +52,12 @@ another LDAPFilter, a string or a None type.:: >>> foo LDAPFilter('(a=ä)') + >>> 'a' in foo + True + + >>> 'objectClass' in foo + False + >>> filter = LDAPFilter('(objectClass=person)') >>> filter |= LDAPFilter('(objectClass=some)') >>> filter @@ -69,6 +75,9 @@ another LDAPFilter, a string or a None type.:: >>> filter LDAPFilter('(|(objectClass=personä)(objectClass=someä))') + >>> 'objectClass' in filter + True + LDAPDictFilter -------------- @@ -133,6 +142,13 @@ from relations.:: >>> node.attrs['someUid'] = u'123\xe4' >>> node.attrs['someName'] = 'Name' + >>> rel_filter = LDAPRelationFilter(node, '') + >>> rel_filter + LDAPRelationFilter('') + + >>> str(rel_filter) + '' + >>> rel_filter = LDAPRelationFilter(node, 'someUid:otherUid') >>> rel_filter LDAPRelationFilter('(otherUid=123ä)') diff --git a/src/node/ext/ldap/interfaces.py b/src/node/ext/ldap/interfaces.py index 857f889..10a0633 100644 --- a/src/node/ext/ldap/interfaces.py +++ b/src/node/ext/ldap/interfaces.py @@ -23,84 +23,87 @@ class ILDAPProps(Interface): """LDAP properties configuration interface. """ - uri = Attribute(u'LDAP URI') + uri = Attribute('LDAP URI') - user = Attribute(u'LDAP User') + user = Attribute('LDAP User') - password = Attribute(u'Bind Password') + password = Attribute('Bind Password') - cache = Attribute(u'Flag wether to use cache or not') + cache = Attribute('Flag wether to use cache or not') - timeout = Attribute(u'Timeout in seconds') + timeout = Attribute('Timeout in seconds') - start_tls = Attribute(u'TLS enabled') + start_tls = Attribute('TLS enabled') - ignore_cert = Attribute(u'Ignore TLS/SSL certificate errors') + ignore_cert = Attribute('Ignore TLS/SSL certificate errors') - tls_cacertfile = Attribute(u'Name of CA Cert file') + tls_cacertfile = Attribute('Name of CA Cert file') - tls_cacertdir = Attribute(u'Path to CA Cert directory') # unused + # XXX + # tls_cacertdir = Attribute('Path to CA Cert directory') - tls_clcertfile = Attribute(u'Name of CL Cert file') # unused + # XXX + # tls_clcertfile = Attribute('Name of CL Cert file') - tls_clkeyfile = Attribute(u'Path to CL key file') # unused + # XXX + # tls_clkeyfile = Attribute('Path to CL key file') - retry_max = Attribute(u'Retry count') + retry_max = Attribute('Retry count') - retry_delay = Attribute(u'Retry delay in seconds') + retry_delay = Attribute('Retry delay in seconds') - multivalued_attributes = Attribute(u'Attributes considered multi valued') + multivalued_attributes = Attribute('Attributes considered multi valued') - binary_attributes = Attribute(u'Attributes considered binary') + binary_attributes = Attribute('Attributes considered binary') - page_size = Attribute(u'Page size for LDAP queries.') + page_size = Attribute('Page size for LDAP queries.') class ILDAPPrincipalsConfig(Interface): """LDAP principals configuration interface. """ - baseDN = Attribute(u'Principals base DN') + baseDN = Attribute('Principals base DN') - attrmap = Attribute(u'Principals Attribute map as ``odict.odict``') + attrmap = Attribute('Principals Attribute map as ``odict.odict``') - scope = Attribute(u'Search scope for principals') + scope = Attribute('Search scope for principals') - queryFilter = Attribute(u'Search Query filter for principals') + queryFilter = Attribute('Search Query filter for principals') # XXX - # member_relation = Attribute(u'Optional member relation to be used to ' - # u'speed up groups search, i.e. ' - # u''uid:memberUid'') + # member_relation = Attribute('Optional member relation to be used to ' + # 'speed up groups search, i.e. ' + # ''uid:memberUid'') - objectClasses = Attribute(u'Object classes for new principals.') + objectClasses = Attribute('Object classes for new principals.') defaults = Attribute( - u'Dict like object containing default values for principal creation.' - u'A value could either be static or a callable. This defaults take' - u'precedence to defaults detected via set object classes.' + 'Dict like object containing default values for principal creation.' + 'A value could either be static or a callable. This defaults take' + 'precedence to defaults detected via set object classes.' ) strict = Attribute( - u'Flag whether to initialize Aliaser for LDAP attributes in strict ' - u'mode. Defaults to True.' + 'Flag whether to initialize Aliaser for LDAP attributes in strict ' + 'mode. Defaults to True.' ) memberOfSupport = Attribute( - u'Flag whether to use "memberOf" attribute (AD) or memberOf overlay ' - u'(openldap) for Group membership resolution where appropriate.' + 'Flag whether to use "memberOf" attribute (AD) or memberOf overlay ' + '(openldap) for Group membership resolution where appropriate.' ) # XXX: currently expiresAttr only gets considered for user authentication # group and role expiration is not implemented yet. expiresAttr = Attribute( - u'Attribute containing an expiration timestamp from epoch in UTC. ' - u'If None, entry never expires.' + 'Attribute containing an expiration timestamp from epoch in UTC. ' + 'If None, entry never expires.' ) expiresUnit = Attribute( - u'Expiration unit. Either ``node.ext.ldap.ugm.EXPIRATION_DAYS`` or ' - u'``EXPIRATION_SECONDS``. defaults to days.' + 'Expiration unit. Either ``node.ext.ldap.ugm.EXPIRATION_DAYS`` or ' + '``EXPIRATION_SECONDS``. Defaults to days.' ) @@ -118,35 +121,39 @@ class ILDAPStorage(IStorage): """A LDAP Node. """ - ldap_session = Attribute( - u'``node.ext.ldap.session.LDAPSession`` instance.' - ) + ldap_session = Attribute('``node.ext.ldap.session.LDAPSession`` instance.') + + DN = Attribute('LDAP object DN.') - DN = Attribute(u'LDAP object DN.') + rdn_attr = Attribute('RDN attribute name.') - # rdn_attr = Attribute(u'RDN attribute name.') + changed = Attribute('Flag whether node has been modified.') - changed = Attribute(u'Flag whether node has been modified.') + search_scope = Attribute('Default child search scope') - search_scope = Attribute(u'Default child search scope') + search_filter = Attribute('Default child search filter') - search_filter = Attribute(u'Default child search filter') + search_criteria = Attribute('Default child search criteria') - search_criteria = Attribute(u'Default child search criteria') + search_relation = Attribute('Default child search relation') - search_relation = Attribute(u'Default child search relation') + child_factory = Attribute('Factory used for child node instanciation.') child_defaults = Attribute( - u'Default child attributes. Will be set to all children attributes' - u'on __setitem__ if not present yet.' + 'Default child attributes. Will be set to all children attributes' + 'on ``__setitem__`` if not present yet.' ) def child_dn(key): """Return child DN for ``key``. + + :param key: Child key. """ - def search(queryFilter=None, criteria=None, relation=None, - attrlist=None, exact_match=False, or_search=False): + def search(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): """Search the directors. All search criteria are additive and will be ``&``ed. ``queryFilter`` @@ -154,37 +161,42 @@ def search(queryFilter=None, criteria=None, relation=None, ``self.search_filter``, ``self.search_criteria`` and ``self.search_relation``. - Returns a list of matching keys if ``attrlist`` is None, otherwise a - list of 2-tuples containing (key, attrdict). - - queryFilter - ldap queryFilter, e.g. ``(objectClass=foo)``, as string or - LDAPFilter instance. - - criteria - dictionary of attribute value(s) (string or list of string) - - relation - the nodes we search has a relation to us. A relation is defined as - a string of attribute pairs: - `` = ':'``. - The value of these attributes must match for relation to match. - Multiple pairs can be or-joined with. - - attrlist - Normally a list of keys is returned. By defining attrlist the - return format will be ``[(key, {attr1: [value1, ...]}), ...]``. To - get this format without any attributs, i.e. empty dicts in the - tuples, specify an empty attrlist. In addition to the normal ldap - attributes you can also the request the DN to be included. DN is - also the only value in result set as string instead of list. - - exact_match - raise ValueError if not one match, return format is a single key or - tuple, if attrlist is specified. - - or_search - flag whether criteria should be ORer or ANDed. defaults to False. + The search result is a list of matching keys if ``attrlist`` is None, + otherwise a list of 2-tuples containing (key, attrdict). If + ``get_nodes`` is True, the result is either a list of nodes or a list + of 2-tuples containing (node, attrdict). + + :param queryFilter: LDAP queryFilter, e.g. ``(objectClass=foo)``, as + string or ``LDAPFilter`` instance. + :param criteria: Dictionary of attribute value(s) (string or list of + string) + :param attrlist: Normally a list of keys is returned. By defining + attrlist the return format will be + ``[(key, {attr1: [value1, ...]}), ...]``. To get this format + without any attributs, i.e. empty dicts in the tuples, specify an + empty attrlist. In addition to the normal LDAP attributes you can + also the request the DN to be included. DN is also the only value + in result set as string instead of list. + :param relation: The nodes we search has a relation to us. A relation + is defined as a string of attribute pairs + `` = ':'``. The value of these + attributes must match for relation to match. Multiple pairs can be + or-joined with. + :param relation_node: Node instance used to create the relation filter. + If not defined, ``self`` is used. + :param exact_match: Raise ``ValueError`` if not one match, return + format is a single key or tuple, if attrlist is specified. + :param or_search: Flag whether criteria should be OR-ed or AND-ed. + Defaults to False. + :param or_keys: Flag whether criteria keys should be OR-ed or AND-ed. + Overrides and defaults to ``or_search``. + :param or_values: Flag whether criteria values should be OR-ed or + AND-ed. Overrides and defaults to ``or_search``. + :param page_size: LDAP pagination search size. + :param cookie: LDAP pagination search cookie. + :param get_nodes: Flag whether to return LDAP nodes in search result. + :return result: If no page size defined, return value is the result, + otherwise a tuple containing (cookie, result). """ diff --git a/src/node/ext/ldap/properties.py b/src/node/ext/ldap/properties.py index 5d35d48..b0a1ab8 100644 --- a/src/node/ext/ldap/properties.py +++ b/src/node/ext/ldap/properties.py @@ -55,84 +55,48 @@ def __init__( retry_delay=10.0, multivalued_attributes=MULTIVALUED_DEFAULTS, binary_attributes=BINARY_DEFAULTS, - page_size=1000 - ): + page_size=1000): """Take the connection properties as arguments. - SSL/TLS still unsupported + SSL/TLS still unsupported. - server - DEPRECATED use uri! servername, defaults to 'localhost' - - port - DEPRECATED uss uri! server port, defaults to 389 - - user - username to bind, defaults to '' - - password - password to bind, defaults to '' - - cache - Bool wether to enable caching or not, defaults to True - - timeout - Cache timeout in seconds. only takes affect if cache is enabled. - - uri - overrides server/port, forget about server and port, use + :param server: DEPRECATED use uri! servername, defaults to 'localhost' + :param port: DEPRECATED uss uri! server port, defaults to 389 + :param user: Username to bind, defaults to '' + :param password: Password to bind, defaults to '' + :param cache: Bool wether to enable caching or not, defaults to True + :param timeout: Cache timeout in seconds. only takes affect if cache is + enabled. + :param uri: Overrides server/port, forget about server and port, use this to specify how to access the ldap server, eg: - ldapi:///path/to/socket - ldap://: (will try start_tls, which you can enforce, see start_tls) - ldaps://: - - start_tls - Determines if StartTLS extended operation is tried on + :param start_tls: Determines if StartTLS extended operation is tried on a LDAPv3 server, if the LDAP URL scheme is ldap:. If LDAP URL scheme is not 'ldap:' (e.g. 'ldaps:' or 'ldapi:') this parameter is ignored. 0 - Don't use StartTLS ext op 1 - Try StartTLS ext op but proceed when unavailable 2 - Try StartTLS ext op and re-raise exception if it fails - - ignore_cert - Ignore TLS/SSL certificate errors. Useful for self-signed - certificates. Defaults to False - - tls_cacertfile - Provide a specific CA Certifcate file. This is needed if the - CA is not in the default CA keyring (i.e. with self-signed - certificates). Under Windows its possible that python-ldap lib does - recognize the system keyring. - - tls_cacertdir + :param ignore_cert: Ignore TLS/SSL certificate errors. Useful for + self-signed certificates. Defaults to False + :param tls_cacertfile: Provide a specific CA Certifcate file. This is + needed if the CA is not in the default CA keyring (i.e. with + self-signed certificates). Under Windows its possible that + python-ldap lib does recognize the system keyring. + :param tls_cacertdir: Not yet + :param tls_clcertfile: Not yet + :param tls_clkeyfile: Not yet + :param retry_max: Maximum count of reconnect trials. Not yet + :param retry_delay: Time span to wait between two reconnect trials. Not yet - - tls_clcertfile - Not yet - - tls_clkeyfile - Not yet - - retry_max - Maximum count of reconnect trials - Not yet - - retry_delay - Time span to wait between two reconnect trials - Not yet - - multivalued_attributes - Set of attributes names considered as multivalued to be returned - as list. - - binary_attributes - Set of attributes names considered as binary. + :param multivalued_attributes: Set of attributes names considered as + multivalued to be returned as list. + :param binary_attributes: Set of attributes names considered as binary. (no unicode conversion) - - page_size - page size for LDAP search requests, defaults to 1000. + :param page_size: Oage size for LDAP search requests, defaults to 1000. Number of objects requested at once. In iterations after this number of objects a new search query is sent for the next batch using returned the LDAP cookie. @@ -159,4 +123,5 @@ def __init__( self.binary_attributes = binary_attributes self.page_size = page_size +# B/C LDAPProps = LDAPServerProperties diff --git a/src/node/ext/ldap/session.py b/src/node/ext/ldap/session.py index fe753d4..65b9c3c 100644 --- a/src/node/ext/ldap/session.py +++ b/src/node/ext/ldap/session.py @@ -26,17 +26,17 @@ def checkServerProperties(self): else: return (False, res) - def _get_baseDN(self): + @property + def baseDN(self): baseDN = self._communicator.baseDN return baseDN - def _set_baseDN(self, baseDN): + @baseDN.setter + def baseDN(self, baseDN): """baseDN must be utf8-encoded. """ self._communicator.baseDN = baseDN - baseDN = property(_get_baseDN, _set_baseDN) - def ensure_connection(self): """If LDAP directory is down, bind again and retry given function. @@ -94,17 +94,12 @@ def authenticate(self, dn, pw): def modify(self, dn, data, replace=False): """Modify an existing entry in the directory. - dn - Modification DN - - #data - # either list of 3 tuples (look at - # node.ext.ldap.base.LDAPCommunicator.modify for details), or - # a dictionary representing the entry or parts of the entry. - # XXX: dicts not yet - - replace - if set to True, replace entry at DN entirely with data. + :param dn: Modification DN + :param data: Either list of 3 tuples (look at + ``node.ext.ldap.base.LDAPCommunicator.modify`` for details), or a + dictionary representing the entry or parts of the entry. + XXX: dicts not yet + :param replace: If set to True, replace entry at DN entirely with data. """ self.ensure_connection() result = self._communicator.modify(dn, data) diff --git a/src/node/ext/ldap/tests.py b/src/node/ext/ldap/tests.py index 9fc2339..1548401 100644 --- a/src/node/ext/ldap/tests.py +++ b/src/node/ext/ldap/tests.py @@ -55,4 +55,4 @@ def test_suite(): return suite if __name__ == '__main__': - unittest.main(defaultTest='test_suite') # pragma NO COVERAGE + unittest.main(defaultTest='test_suite') #pragma NO COVERAGE diff --git a/src/node/ext/ldap/ugm/_api.py b/src/node/ext/ldap/ugm/_api.py index 1d33962..07fdbd7 100644 --- a/src/node/ext/ldap/ugm/_api.py +++ b/src/node/ext/ldap/ugm/_api.py @@ -114,11 +114,8 @@ class PrincipalAliasedAttributes(object): def __init__(self, context, aliaser=None): """ - context - the node whose children to alias - - aliaser - the aliaser to be used + :param context: The node whose children to alias + :param aliaser: The aliaser to be used """ self.__name__ = context.name self.__parent__ = None @@ -586,9 +583,9 @@ def _unalias_dict(self, dct): return unaliased_dct @default - def search(self, criteria=None, attrlist=None, - exact_match=False, or_search=False, or_keys=None, - or_values=None, page_size=None, cookie=None): + def raw_search(self, criteria=None, attrlist=None, + exact_match=False, or_search=False, or_keys=None, + or_values=None, page_size=None, cookie=None): search_attrlist = [self._key_attr] if attrlist is not None and self._key_attr not in attrlist: search_attrlist += attrlist @@ -624,6 +621,25 @@ def search(self, criteria=None, attrlist=None, return results, cookie return results + @default + def search(self, criteria=None, attrlist=None, + exact_match=False, or_search=False): + result = [] + cookie = None + while True: + chunk, cookie = self.raw_search( + criteria=criteria, + attrlist=attrlist, + exact_match=exact_match, + or_search=or_search, + page_size=self.context.ldap_session._props.page_size, + cookie=cookie + ) + result += chunk + if not cookie: + break + return result + @default @locktree def create(self, pid, **kw): @@ -992,23 +1008,12 @@ class LDAPUgm(UgmBase): def __init__(self, name=None, parent=None, props=None, ucfg=None, gcfg=None, rcfg=None): """ - name - node name - - parent - node parent - - props - LDAPProps - - ucfg - UsersConfig - - gcfg - GroupsConfig - - rcfg - RolesConfig + :param name: Node name. + :param parent: Node parent. + :param props: LDAPProps instance. + :param ucfg: UsersConfig instance. + :param gcfg: GroupsConfig instance. + :param rcfg: RolesConfig instance. """ self.__name__ = name self.__parent__ = parent diff --git a/src/node/ext/ldap/ugm/principals.rst b/src/node/ext/ldap/ugm/principals.rst index f4facac..4dbb448 100644 --- a/src/node/ext/ldap/ugm/principals.rst +++ b/src/node/ext/ldap/ugm/principals.rst @@ -260,13 +260,14 @@ Search for users:: >>> users.search(criteria=dict(sn=schmidt.attrs['sn']), attrlist=['login']) [(u'Schmidt', {'login': [u'user3']})] -Paginated search for users:: +By default, search function is paginated. To control the LDAP search behavior +in more detail, ``raw_search`` can be used:: - >>> results, cookie = users.search(page_size=3, cookie='') + >>> results, cookie = users.raw_search(page_size=3, cookie='') >>> results [u'Meier', u'M\xfcller', u'Schmidt'] - >>> results, cookie = users.search(page_size=3, cookie=cookie) + >>> results, cookie = users.raw_search(page_size=3, cookie=cookie) >>> results [u'Umhauer'] >>> assert cookie == ''