diff --git a/CHANGES.rst b/CHANGES.rst index 2ab84bb..2a09dac 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,19 @@ History 1.0b7 (unreleased) ------------------ +- 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/_node.py b/src/node/ext/ldap/_node.py index 64c77ee..70ae203 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: @@ -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 @@ -631,7 +630,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/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..ce1815a 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. diff --git a/src/node/ext/ldap/ugm/_api.py b/src/node/ext/ldap/ugm/_api.py index 1d33962..e0e22db 100644 --- a/src/node/ext/ldap/ugm/_api.py +++ b/src/node/ext/ldap/ugm/_api.py @@ -586,9 +586,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 +624,29 @@ 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: + try: + 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 + ) + except ValueError as e: + logger.error(str(e)) + return ret + result += chunk + if not cookie: + break + return result + @default @locktree def create(self, pid, **kw): 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 == ''