{ "info": { "author": "BlueDynamics Alliance", "author_email": "dev@bluedynamics.com", "bugtrack_url": null, "classifiers": [ "Operating System :: OS Independent", "Programming Language :: Python" ], "description": "Overview\n========\n\n``node.ext.ldap`` is a LDAP convenience library for LDAP communication based on\n`python-ldap `_ (version 2.4 or later)\nand `node `_.\n\nThe package contains base configuration and communication objects, a LDAP node\nobject and a LDAP node based user and group management implementation utilizing\n`node.ext.ugm `_.\n\n.. _`RFC 2251`: http://www.ietf.org/rfc/rfc2251.txt\n\nThis package is the successor of\n`bda.ldap `_.\n\n.. contents::\n :depth: 2\n\n\nUsage\n=====\n\n\nLDAP Properties\n---------------\n\nTo define connection properties for LDAP use ``node.ext.ldap.LDAPProps``\nobject::\n\n >>> from node.ext.ldap import LDAPProps \n >>> props = LDAPProps(uri='ldap://localhost:12345/',\n ... user='cn=Manager,dc=my-domain,dc=com',\n ... password='secret',\n ... cache=False)\n\nTest server connectivity with ``node.ext.ldap.testLDAPConnectivity``::\n\n >>> from node.ext.ldap import testLDAPConnectivity\n >>> testLDAPConnectivity(props=props)\n 'success'\n\n\nLDAP Connection\n---------------\n\nFor handling LDAP connections, ``node.ext.ldap.LDAPConnector`` is used. It\nexpects a ``LDAPProps`` instance in the constructor. Normally there is no\nneed to instantiate this object directly, this happens during creation of\nhigher abstractions, see below::\n\n >>> from node.ext.ldap import LDAPConnector\n >>> connector = LDAPConnector(props=props)\n >>> connector\n \n\nCalling ``bind`` creates and returns the LDAP connection::\n\n >>> connector.bind()\n \n\nCalling ``unbind`` destroys the connection::\n\n >>> connector.unbind()\n\n\nLDAP Communication\n------------------\n\nFor communicating with an LDAP server, ``node.ext.ldap.LDAPCommunicator`` is\nused. It provides all the basic functions needed to search and modify the\ndirectory.\n\n``LDAPCommunicator`` expects a ``LDAPConnector`` instance at creation time::\n\n >>> from node.ext.ldap import LDAPCommunicator\n >>> communicator = LDAPCommunicator(connector)\n >>> communicator\n \n\nBind to server::\n\n >>> communicator.bind()\n\nAdding directory entry::\n\n >>> communicator.add(\n ... 'cn=foo,ou=demo,dc=my-domain,dc=com',\n ... {\n ... 'cn': 'foo',\n ... 'sn': 'Mustermann',\n ... 'userPassword': 'secret',\n ... 'objectClass': ['person'],\n ... })\n\nSet default search DN::\n\n >>> communicator.baseDN = 'ou=demo,dc=my-domain,dc=com'\n\nSearch in directory::\n\n >>> import node.ext.ldap\n >>> communicator.search('(objectClass=person)', node.ext.ldap.SUBTREE)\n [('cn=foo,ou=demo,dc=my-domain,dc=com', \n {'objectClass': ['person'], \n 'userPassword': ['secret'], \n 'cn': ['foo'], \n 'sn': ['Mustermann']})]\n\nModify directory entry::\n\n >>> from ldap import MOD_REPLACE\n >>> communicator.modify('cn=foo,ou=demo,dc=my-domain,dc=com',\n ... [(MOD_REPLACE, 'sn', 'Musterfrau')])\n \n >>> communicator.search('(objectClass=person)',\n ... node.ext.ldap.SUBTREE,\n ... attrlist=['cn'])\n [('cn=foo,ou=demo,dc=my-domain,dc=com', \n {'cn': ['foo']})]\n\nChange the password of a directory entry which represents a user::\n\n >>> communicator.passwd(\n ... 'cn=foo,ou=demo,dc=my-domain,dc=com', 'secret', '12345')\n \n >>> communicator.search('(objectClass=person)',\n ... node.ext.ldap.SUBTREE,\n ... attrlist=['userPassword'])\n [('cn=foo,ou=demo,dc=my-domain,dc=com', \n {'userPassword': ['{SSHA}...']})]\n\nDelete directory entry::\n\n >>> communicator.delete('cn=foo,ou=demo,dc=my-domain,dc=com')\n \n >>> communicator.search('(objectClass=person)', node.ext.ldap.SUBTREE)\n []\n\nClose connection::\n\n >>> communicator.unbind()\n\n\nLDAP Session\n------------\n\nA more convenient way for dealing with LDAP is provided by\n``node.ext.ldap.LDAPSession``. It basically provides the same functionality\nas ``LDAPCommunicator``, but automatically creates the connectivity objects\nand checks the connection state before performing actions.\n\nInstantiate ``LDAPSession`` object. Expects ``LDAPProps`` instance::\n\n >>> from node.ext.ldap import LDAPSession\n >>> session = LDAPSession(props)\n\nLDAP session has a convenience to check given properties::\n\n >>> session.checkServerProperties()\n (True, 'OK')\n\nSet default search DN for session::\n\n >>> session.baseDN = 'ou=demo,dc=my-domain,dc=com'\n\nSearch in directory::\n\n >>> session.search()\n [('ou=demo,dc=my-domain,dc=com', \n {'objectClass': ['top', 'organizationalUnit'], \n 'ou': ['demo'], \n 'description': ['Demo organizational unit']})]\n\nAdd directory entry::\n\n >>> session.add(\n ... 'cn=foo,ou=demo,dc=my-domain,dc=com',\n ... {\n ... 'cn': 'foo',\n ... 'sn': 'Mustermann',\n ... 'userPassword': 'secret',\n ... 'objectClass': ['person'],\n ... })\n\nChange the password of a directory entry which represents a user::\n\n >>> session.passwd('cn=foo,ou=demo,dc=my-domain,dc=com', 'secret', '12345')\n\nAuthenticate a specific user::\n\n >>> session.authenticate('cn=foo,ou=demo,dc=my-domain,dc=com', '12345')\n True\n\nModify directory entry::\n \n >>> session.modify('cn=foo,ou=demo,dc=my-domain,dc=com',\n ... [(MOD_REPLACE, 'sn', 'Musterfrau')])\n \n >>> session.search('(objectClass=person)',\n ... node.ext.ldap.SUBTREE,\n ... attrlist=['cn'])\n [('cn=foo,ou=demo,dc=my-domain,dc=com', {'cn': ['foo']})]\n\nDelete directory entry::\n\n >>> session.delete('cn=foo,ou=demo,dc=my-domain,dc=com')\n >>> session.search('(objectClass=person)', node.ext.ldap.SUBTREE)\n []\n\nClose session::\n\n >>> session.unbind()\n\n\nLDAP Nodes\n----------\n\nOne can deal with LDAP entries as node objects. Therefor\n``node.ext.ldap.LDAPNode`` is used. To get a clue of the complete\nnode API, see `node `_ package.\n\nCreate a LDAP node. The root Node expects the base DN and a ``LDAPProps``\ninstance::\n\n >>> from node.ext.ldap import LDAPNode\n >>> root = LDAPNode('ou=demo,dc=my-domain,dc=com', props=props)\n\nEvery LDAP node has a DN and a RDN::\n\n >>> root.DN\n u'ou=demo,dc=my-domain,dc=com'\n \n >>> root.rdn_attr\n u'ou'\n\nDirectory entry has no children yet::\n\n >>> root.keys()\n []\n \nAdd children to root node::\n\n >>> person = LDAPNode()\n >>> person.attrs['objectClass'] = ['person']\n >>> person.attrs['sn'] = 'Mustermann'\n >>> person.attrs['userPassword'] = 'secret'\n >>> root['cn=person1'] = person\n \n >>> person = LDAPNode()\n >>> person.attrs['objectClass'] = ['person']\n >>> person.attrs['sn'] = 'Musterfrau'\n >>> person.attrs['userPassword'] = 'secret'\n >>> root['cn=person2'] = person\n\nIf the RDN attribute was not set during node creation, it is computed from\nnode key and set automatically::\n\n >>> person.attrs['cn']\n u'person2'\n\nSome might fetch children DN's by key from LDAP node. This only works for\nexisting children::\n\n >>> root.child_dn('cn=person1')\n u'cn=person1,ou=demo,dc=my-domain,dc=com'\n \n >>> root.child_dn('cn=person99')\n Traceback (most recent call last):\n ...\n KeyError: 'cn=person99'\n\nHave a look at the tree::\n\n >>> root.printtree()\n \n \n \n\nThe entries have not been written to the directory yet. When modifying a LDAP\nnode tree, everything happens im memory. Persisting is done by calling the\ntree, or a part of it. You can check sync state of a node with its ``changed``\nflag. If changed is ``True`` it means either that the node attributes or node\nchildren has changed::\n\n >>> root.changed\n True\n \n >>> root()\n >>> root.changed\n False\n\nModify a LDAP node::\n\n >>> person = root['cn=person1']\n\nModify existing attribute::\n\n >>> person.attrs['sn'] = 'Mustermensch'\n\nAdd new attribute::\n\n >>> person.attrs['description'] = 'Mustermensch description'\n >>> person()\n\nDelete an attribute::\n\n >>> del person.attrs['description']\n >>> person()\n\nDelete LDAP node::\n\n >>> del root['cn=person2']\n >>> root()\n >>> root.printtree()\n \n \n\n\nSearching LDAP\n--------------\n\nAdd some users and groups we'll search for::\n\n >>> for i in range(2, 6):\n ... node = LDAPNode()\n ... node.attrs['objectClass'] = ['person']\n ... node.attrs['sn'] = 'Surname %s' % i\n ... node.attrs['userPassword'] = 'secret%s' % i\n ... node.attrs['description'] = 'group1'\n ... root['cn=person%s' % i] = node\n \n >>> node = LDAPNode()\n >>> node.attrs['objectClass'] = ['groupOfNames']\n >>> node.attrs['member'] = [\n ... root.child_dn('cn=person1'),\n ... root.child_dn('cn=person2'),\n ... ]\n ... node.attrs['description'] = 'IT'\n >>> root['cn=group1'] = node\n \n >>> node = LDAPNode()\n >>> node.attrs['objectClass'] = ['groupOfNames']\n >>> node.attrs['member'] = [\n ... root.child_dn('cn=person4'),\n ... root.child_dn('cn=person5'),\n ... ]\n >>> root['cn=group2'] = node\n \n >>> root()\n >>> root.printtree()\n \n \n \n \n \n \n \n \n\nFor defining search criteria LDAP filters are used, which can be combined by\nbool operators '&' and '|'::\n\n >>> from node.ext.ldap import LDAPFilter\n >>> filter = LDAPFilter('(objectClass=person)')\n >>> filter |= LDAPFilter('(objectClass=groupOfNames)')\n >>> root.search(queryFilter=filter)\n [u'cn=person1', \n u'cn=person2', \n u'cn=person3', \n u'cn=person4', \n u'cn=person5', \n u'cn=group1', \n u'cn=group2']\n\nDefine multiple criteria LDAP filter::\n\n >>> from node.ext.ldap import LDAPDictFilter\n >>> filter = LDAPDictFilter({'objectClass': ['person'], 'cn': 'person1'})\n >>> root.search(queryFilter=filter)\n [u'cn=person1']\n\nDefine a relation LDAP filter. In this case we build a relation between group\n'cn' and person 'description'::\n\n >>> from node.ext.ldap import LDAPRelationFilter\n >>> filter = LDAPRelationFilter(root['cn=group1'], 'cn:description')\n >>> root.search(queryFilter=filter)\n [u'cn=person2', \n u'cn=person3', \n u'cn=person4', \n u'cn=person5']\n\nDifferent LDAP filter types can be combined::\n\n >>> filter &= LDAPFilter('(cn=person2)')\n >>> str(filter) \n '(&(description=group1)(cn=person2))'\n\nThe following keyword arguments are accepted by ``LDAPNode.search``. If multiple keywords are\nused, combine search criteria with '&' where appropriate:\n\n**queryFilter**\n Either a LDAP filter instance or a string. If given argument is string type,\n a ``LDAPFilter`` instance is created.\n\n**criteria**\n A dictionary containing search criteria. A ``LDAPDictFilter`` instance is\n created.\n\n**attrlist**\n List of attribute names to return.\n\n**relation**\n Either ``LDAPRelationFilter`` instance or a string defining the relation.\n If given argument is string type, a ``LDAPRelationFilter`` instance is\n created.\n\n**relation_node**\n In combination with ``relation`` argument, when given as string, use\n ``relation_node`` instead of self for filter creation.\n\n**exact_match**\n Flag whether 1-length result is expected. Raises an error if empty result\n or more than one entry found.\n\n**or_search**\n In combination with ``criteria``, this parameter is passed to the creation\n of LDAPDictFilter controlling whether to combine criteria with '&' or '|'.\n\nYou can define search defaults on the node which are always considered when\ncalling ``search`` on this node. If set, they are always '&' combined with\nany (optional) passed filters.\n\nDefine the default search scope::\n\n >>> from node.ext.ldap import SUBTREE\n >>> root.search_scope = SUBTREE\n\nDefine default search filter, could be of type LDAPFilter, LDAPDictFilter,\nLDAPRelationFilter or string::\n\n >>> root.search_filter = LDAPFilter('objectClass=groupOfNames')\n >>> root.search()\n [u'cn=group1', u'cn=group2']\n\n >>> root.search_filter = None\n\nDefine default search criteria as dict::\n \n >>> root.search_criteria = {'objectClass': 'person'}\n >>> root.search()\n [u'cn=person1', \n u'cn=person2', \n u'cn=person3', \n u'cn=person4', \n u'cn=person5']\n\nDefine default search relation::\n\n >>> root.search_relation = \\\n ... LDAPRelationFilter(root['cn=group1'], 'cn:description')\n >>> root.search()\n [u'cn=person2', \n u'cn=person3', \n u'cn=person4', \n u'cn=person5']\n\nAgain, like with the keyword arguments, multiple defined defaults are '&'\ncombined::\n\n # empty result, there are no groups with group 'cn' as 'description' \n >>> root.search_criteria = {'objectClass': 'group'}\n >>> root.search()\n []\n\n\nUser and Group management\n-------------------------\n\nLDAP is often used to manage Authentication, thus ``node.ext.ldap`` provides\nan API for User and Group management. The API follows the contract of\n`node.ext.ugm `_::\n\n >>> from node.ext.ldap import ONELEVEL\n >>> from node.ext.ldap.ugm import (\n ... UsersConfig,\n ... GroupsConfig,\n ... RolesConfig,\n ... Ugm,\n ... )\n\nInstantiate users, groups and roles configuration. They are based on\n``PrincipalsConfig`` class and expect this settings:\n\n**baseDN**\n Principals container base DN.\n\n**attrmap**\n Principals Attribute map as ``odict.odict``. This object must contain the\n mapping between reserved keys and the real LDAP attribute, as well as\n mappings to all accessible attributes for principal nodes if instantiated\n in strict mode, see below.\n\n**scope**\n Search scope for principals.\n\n**queryFilter**\n Search Query filter for principals\n\n**objectClasses**\n Object classes used for creation of new principals. For some objectClasses\n default value callbacks are registered, which are used to generate default\n values for mandatory attributes if not already set on principal vessel node.\n\n**defaults**\n Dict like object containing default values for principal creation. A value\n could either be static or a callable accepting the principals node and the\n new principal id as arguments. This defaults take precedence to defaults\n detected via set object classes.\n\n**strict**\n Define whether all available principal attributes must be declared in attmap,\n or only reserved ones. Defaults to True.\n\n**memberOfSupport**\n Flag whether to use 'memberOf' attribute (AD) or memberOf overlay\n (openldap) for Group membership resolution where appropriate.\n\nReserved attrmap keys for Users, Groups and roles:\n\n**id**\n The attribute containing the user id (mandatory).\n\n**rdn**\n The attribute representing the RDN of the node (mandatory)\n XXX: get rid of, should be detected automatically\n\nReserved attrmap keys for Users:\n\n**login**\n Alternative login name attribute (optional)\n\nCreate config objects::\n\n >>> ucfg = UsersConfig(\n ... baseDN='ou=demo,dc=my-domain,dc=com',\n ... attrmap={\n ... 'id': 'cn',\n ... 'rdn': 'cn',\n ... 'login': 'sn',\n ... },\n ... scope=ONELEVEL,\n ... queryFilter='(objectClass=person)',\n ... objectClasses=['person'],\n ... defaults={},\n ... strict=False,\n ... )\n \n >>> gcfg = GroupsConfig(\n ... baseDN='ou=demo,dc=my-domain,dc=com',\n ... attrmap={\n ... 'id': 'cn',\n ... 'rdn': 'cn',\n ... },\n ... scope=ONELEVEL,\n ... queryFilter='(objectClass=groupOfNames)',\n ... objectClasses=['groupOfNames'],\n ... defaults={},\n ... strict=False,\n ... memberOfSupport=False,\n ... )\n\nRoles are represented in LDAP like groups. Note, if groups and roles are mixed\nup in the same container, make sure that query filter fits. For our demo,\ndifferent group object classes are used. Anyway, in real world it might be\nworth considering a seperate container for roles::\n\n >>> rcfg = GroupsConfig(\n ... baseDN='ou=demo,dc=my-domain,dc=com',\n ... attrmap={\n ... 'id': 'cn',\n ... 'rdn': 'cn',\n ... },\n ... scope=ONELEVEL,\n ... queryFilter='(objectClass=groupOfUniqueNames)',\n ... objectClasses=['groupOfUniqueNames'],\n ... defaults={},\n ... strict=False,\n ... )\n\nInstantiate ``Ugm`` object::\n\n >>> ugm = Ugm(props=props, ucfg=ucfg, gcfg=gcfg, rcfg=rcfg)\n >>> ugm\n \n\nThe Ugm object has 2 children, the users container and the groups container.\nThe are accessible via node API, but also on ``users`` respective ``groups``\nattribute::\n\n >>> ugm.keys()\n ['users', 'groups']\n \n >>> ugm.users\n \n \n >>> ugm.groups\n \n\nFetch user::\n\n >>> user = ugm.users['person1']\n >>> user\n \n\nUser attributes. Reserved keys are available on user attributes::\n\n >>> user.attrs['id']\n u'person1'\n \n >>> user.attrs['login']\n u'Mustermensch'\n\n'login' maps to 'sn'::\n\n >>> user.attrs['sn']\n u'Mustermensch'\n\n >>> user.attrs['login'] = u'Mustermensch1'\n >>> user.attrs['sn']\n u'Mustermensch1'\n\n >>> user.attrs['description'] = 'Some description'\n >>> user()\n\nCheck user credentials::\n\n >>> user.authenticate('secret')\n True\n\nChange user password::\n\n >>> user.passwd('secret', 'newsecret')\n >>> user.authenticate('newsecret')\n True\n\nGroups user is member of::\n\n >>> user.groups\n []\n\nAdd new User::\n\n >>> user = ugm.users.create('person99', sn='Person 99')\n >>> user()\n \n >>> ugm.users.keys()\n [u'person1', \n u'person2', \n u'person3', \n u'person4', \n u'person5', \n u'person99']\n\nDelete User::\n\n >>> del ugm.users['person99']\n >>> ugm.users()\n >>> ugm.users.keys()\n [u'person1', \n u'person2', \n u'person3', \n u'person4', \n u'person5']\n\nFetch Group::\n\n >>> group = ugm.groups['group1']\n\nGroup members::\n\n >>> group.member_ids\n [u'person1', u'person2']\n \n >>> group.users\n [, ] \n\nAdd group member::\n\n >>> group.add('person3')\n >>> group.member_ids\n [u'person1', u'person2', u'person3']\n \nDelete group member::\n\n >>> del group['person3']\n >>> group.member_ids\n [u'person1', u'person2']\n\nGroup attribute manipulation works the same way as on user objects.\n\nManage roles for users and groups. Roles can be queried, added and removed via\nugm or principal object. Fetch a user::\n\n >>> user = ugm.users['person1']\n\nAdd role for user via ugm::\n\n >>> ugm.add_role('viewer', user)\n\nAdd role for user directly::\n\n >>> user.add_role('editor')\n\nQuery roles for user via ugm::\n\n >>> ugm.roles(user)\n [u'viewer', u'editor']\n\nQuery roles directly::\n\n >>> user.roles\n [u'viewer', u'editor']\n\nCall UGM to persist roles::\n\n >>> ugm()\n\nDelete role via ugm::\n\n >>> ugm.remove_role('viewer', user)\n >>> user.roles\n [u'editor']\n\nDelete role directly::\n\n >>> user.remove_role('editor')\n >>> user.roles\n []\n\nCall UGM to persist roles::\n\n >>> ugm()\n\nSame with group. Fetch a group::\n\n >>> group = ugm.groups['group1']\n\nAdd roles::\n \n >>> ugm.add_role('viewer', group)\n >>> group.add_role('editor')\n \n >>> ugm.roles(group)\n [u'viewer', u'editor']\n \n >>> group.roles\n [u'viewer', u'editor']\n \n >>> ugm()\n\nRemove roles::\n\n >>> ugm.remove_role('viewer', group)\n >>> group.remove_role('editor')\n >>> group.roles\n []\n \n >>> ugm()\n\n\nCharacter Encoding\n------------------\n\nLDAP (v3 at least, `RFC 2251`_) uses ``utf-8`` string encoding only.\n``LDAPNode`` does the encoding for you. Consider it a bug, if you receive\nanything else than unicode from ``LDAPNode``, except attributes configured as\nbinary. The ``LDAPSession``, ``LDAPConnector`` and ``LDAPCommunicator`` are\nencoding-neutral, they do no decoding or encoding.\n\nUnicode strings you pass to nodes or sessions are automatically encoded as uft8\nfor LDAP, except if configured binary. If you feed them ordinary strings they are\ndecoded as utf8 and reencoded as utf8 to make sure they are utf8 or compatible,\ne.g. ascii.\n\nIf you have an LDAP server that does not use utf8, monkey-patch\n``node.ext.ldap._node.CHARACTER_ENCODING``.\n\n\nCaching Support\n---------------\n\n``node.ext.ldap`` can cache LDAP searches using ``bda.cache``. You need\nto provide a cache factory utility in you application in order to make caching\nwork. If you don't, ``node.ext.ldap`` falls back to use ``bda.cache.NullCache``,\nwhich does not cache anything and is just an API placeholder.\n\nTo provide a cache based on ``Memcached`` install memcached server and\nconfigure it. Then you need to provide the factory utility::\n\n >>> # Dummy registry.\n >>> from zope.component import registry\n >>> components = registry.Components('comps')\n \n >>> from node.ext.ldap.cache import MemcachedProviderFactory\n >>> cache_factory = MemcachedProviderFactory()\n >>> components.registerUtility(cache_factory)\n \nIn case of multiple memcached backends on various IPs and ports initialization\nof the factory looks like this:: \n\n >>> # Dummy registry.\n >>> components = registry.Components('comps')\n \n >>> cache_factory = MemcachedProviderFactory(servers=['10.0.0.10:22122',\n ... '10.0.0.11:22322'])\n >>> components.registerUtility(cache_factory)\n\n\nDependencies\n------------\n\n- python-ldap\n- smbpasswd\n- argparse\n- plumber\n- node\n- node.ext.ugm\n- bda.cache\n\n\nNotes on python-ldap\n--------------------\n\nThere are different compile issues on different platforms. If you experience\nproblems with ``python-ldap``, make sure it is available in the python\nenvironment you run buildout in, so it won't be fetched and built by buildout\nitself.\n\n\nTest Coverage\n-------------\n\nSummary of the test coverage report::\n\n lines cov% module\n 7 100% node.ext.ldap.__init__\n 457 99% node.ext.ldap._node\n 146 99% node.ext.ldap.base\n 13 100% node.ext.ldap.cache\n 18 100% node.ext.ldap.events\n 129 100% node.ext.ldap.filter\n 61 100% node.ext.ldap.interfaces\n 49 100% node.ext.ldap.properties\n 38 97% node.ext.ldap.schema\n 6 100% node.ext.ldap.scope\n 60 100% node.ext.ldap.session\n 438 98% node.ext.ldap.testing.__init__\n 28 100% node.ext.ldap.tests\n 1 100% node.ext.ldap.ugm.__init__\n 707 97% node.ext.ldap.ugm._api\n 21 100% node.ext.ldap.ugm.defaults\n 35 100% node.ext.ldap.ugm.posix\n 29 96% node.ext.ldap.ugm.samba\n 21 100% node.ext.ldap.ugm.shadow\n\n\nTODO\n====\n\n- TLS/SSL Support. in ``LDAPConnector``\n could be useful: python-ldap's class SmartLDAPObject(ReconnectLDAPObject) -\n Mainly the __init__() method does some smarter things like negotiating the\n LDAP protocol version and calling LDAPObject.start_tls_s().\n XXX: SmartLDAPObject has been removed from the most recent python-ldap,\n because of being too buggy.\n\n- define what our retry logic should look like, re-think function of session,\n communicator and connector. (check ldap.ldapobject.ReconnectLDAPObject)\n ideas: more complex retry logic with fallback servers, eg. try immediately\n again, if that fails use backup server, then start to probe other server\n after a timespan, report status of ldap servers, preferred server,\n equal servers, load balancing; Are there ldap load balancers to recommend?\n\n- consider ``search_st`` with timeout.\n\n- investigate ``ReconnectLDAPObject.set_cache_options``\n\n- check/implement silent sort on only the keys ``LDAPNode.sortonkeys``\n\n- parse ldap schema to identify binary attributes, also\n further types like BOOL and multivalued, overrides must be possible.\n\n- node.ext.ldap.filter unicode/utf-8\n\n- auto-detection of rdn attribute.\n\n- interactive configuration showing live how many users/groups are found with\n the current config and what a selected user/group would look like\n\n- Scope SUBTREE for Principals containers is not tested properly yet.\n Especially ``__getitem__`` needs a little love.\n\n- Configuration validation for UGM. Add some checks in ``Ugm.__init__`` which\n tries to block stupid configuration.\n\n\nContributors\n============\n\n- Robert Niederreiter \n\n- Florian Friesdorf \n\n- Jens Klein \n\n- Georg Bernhard \n\n- Johannes Raggam \n\n- Daniel Widerin \n\nHistory\n=======\n\n0.9.7\n-----\n\n- Added possibility to hook external LDIF layer for testldap server via\n buildout configuration.\n [rnix]\n\n- Update openldap version in buildout configs.\n [rnix]\n\n\n0.9.6\n-----\n\n- Add new property to allow disable ``check_duplicates``.\n This avoids following Exception when connecting ldap servers with\n non-unique attributes used as keys. [saily]\n ::\n\n Traceback (most recent call last):\n ...\n RuntimeError: Key not unique: =''.\n\n- ensure attrlist values are strings\n [rnix, 2013-12-03]\n\n\n0.9.5\n-----\n\n- Add ``expired`` property to ``node.ext.ldap.ugm._api.LDAPUser``.\n [rnix, 2012-12-17]\n\n- Introduce ``node.ext.ldap.ugm._api.calculate_expired`` helper function.\n [rnix, 2012-12-17]\n\n- Lookup ``expired`` attribut from LDAP in\n ``node.ext.ldap.ugm._api.LDAPUser.authenticate``.\n [rnix, 2012-12-17]\n\n\n0.9.4\n-----\n\n- Encode DN in ``node.ext.ldap._node.LDAPStorage._ldap_modify``.\n [rnix, 2012-11-08]\n\n- Encode DN in ``node.ext.ldap._node.LDAPStorage._ldap_delete``.\n [rnix, 2012-11-08]\n\n- Encode DN in ``node.ext.ldap.ugm._api.LDAPUsers.passwd``.\n [rnix, 2012-11-08]\n\n- Encode DN in ``node.ext.ldap.ugm._api.LDAPUsers.authenticate``.\n [rnix, 2012-11-07]\n\n- Encode ``baseDN`` in ``LDAPPrincipal.member_of_attr``.\n [rnix, 2012-11-06]\n\n- Encode ``baseDN`` in ``AttributesBehavior.load``.\n [rnix, 2012-11-06]\n\n- Python 2.7 compatibility.\n [rnix, 2012-10-16]\n\n- PEP-8.\n [rnix, 2012-10-16]\n\n- Fix ``LDAPPrincipals.idbydn`` handling UTF-8 DN's properly.\n [rnix, 2012-10-16]\n\n- Rename parts to behaviors.\n [rnix, 2012-07-29]\n\n- adopt to ``node`` 0.9.8.\n [rnix, 2012-07-29]\n\n- Adopt to ``plumber`` 1.2.\n [rnix, 2012-07-29]\n\n- Do not convert cookie to unicode in ``LDAPSession.search``. Cookie value is\n no utf-8 string but octet string as described in\n http://tools.ietf.org/html/rfc2696.html.\n [rnix, 2012-07-27]\n\n- Add ``User.group_ids``.\n [rnix, 2012-07-26]\n\n\n0.9.3\n-----\n\n- Fix schema to not bind to test BaseDN only and make binding deferred.\n [jensens, 2012-05-30]\n\n\n0.9.2\n-----\n\n- Remove ``escape_queries`` property from\n ``node.ext.ldap.properties.LDAPProps``.\n [rnix, 2012-05-18]\n\n- Use ``zope.interface.implementer`` instead of ``zope.interface.implements``.\n [rnix, 2012-05-18]\n\n- Structural object class ``inetOrgPerson`` instead of ``account`` on posix\n users and groups related test LDIF's\n [rnix, 2012-04-23]\n\n- session no longer magically decodes everything and prevents binary data from\n being fetched from ldap. LDAP-Node has semantic knowledge to determine binary\n data LDAP-Node converts all non binary data and all keys to unicode.\n [jensens, 2012-04-04]\n\n- or_values and or_keys for finer control of filter criteria\n [iElectric, chaoflow, 2012-03-24]\n\n- support paged searching\n [iElectric, chaoflow, 2012-03-24]\n\n\n0.9.1\n-----\n\n- added is_multivalued to properties and modified node to use this list instead\n of the static list. prepare for binary attributes.\n [jensens, 2012-03-19]\n\n- added schema_info to node.\n [jensens, 2012-03-19]\n\n- ``shadowInactive`` defaults to ``0``.\n [rnix, 2012-03-06]\n\n- Introduce ``expiresAttr`` and ``expiresUnit`` in principals config.\n Considered in ``Users.authenticate``.\n [rnix, 2012-02-11]\n\n- Do not throw ``KeyError`` if secondary key set but attribute not found on\n entry. In case, skip entry.\n [rnix, 2012-02-10]\n\n- Force unicode ids and keys in UGM API.\n [rnix, 2012-01-23]\n\n- Add unicode support for filters.\n [rnix, 2012-01-23]\n\n- Add ``LDAPUsers.id_for_login``.\n [rnix, 2012-01-18]\n\n- Implement memberOf Support for openldap memberof overlay and AD memberOf\n behavior.\n [rnix, 2011-11-07]\n\n- Add ``LDAPProps.escape_queries`` for ActiveDirectory.\n [rnix, 2011-11-06]\n\n- Add group object class to member attribute mapping for ActiveDirectory.\n [rnix, 2011-11-06]\n\n- Make testlayer and testldap more flexible for usage outside this package.\n [jensens, 2010-09-30]\n\n\n0.9\n---\n\n- refactor form ``bda.ldap``.\n [rnix, chaoflow]\n\nLicense\n=======\n\nCopyright (c) 2006-2014, BlueDynamics Alliance, Austria, Germany, Swizerland\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this \n list of conditions and the following disclaimer.\n* Redistributions in binary form must reproduce the above copyright notice, this \n list of conditions and the following disclaimer in the documentation and/or \n other materials provided with the distribution.\n* Neither the name of the BlueDynamics Alliance nor the names of its \n contributors may be used to endorse or promote products derived from this \n software without specific prior written permission.\n \nTHIS SOFTWARE IS PROVIDED BY BlueDynamics Alliance ``AS IS`` AND ANY\nEXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL BlueDynamics Alliance BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/bluedynamics/node.ext.ldap", "keywords": "", "license": "Simplified BSD", "maintainer": null, "maintainer_email": null, "name": "node.ext.ldap", "package_url": "https://pypi.org/project/node.ext.ldap/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/node.ext.ldap/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/bluedynamics/node.ext.ldap" }, "release_url": "https://pypi.org/project/node.ext.ldap/0.9.7/", "requires_dist": null, "requires_python": null, "summary": "Node based LDAP support", "version": "0.9.7" }, "last_serial": 5798790, "releases": { "0.9": [ { "comment_text": "", "digests": { "md5": "936fbe8b9c486c4c525bafb87a4bcd16", "sha256": "80b29f59397350218787155fac2570c96d3fcdd0be89dacace85214cdc1a4ebf" }, "downloads": -1, "filename": "node.ext.ldap-0.9.tar.gz", "has_sig": false, "md5_digest": "936fbe8b9c486c4c525bafb87a4bcd16", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1875178, "upload_time": "2011-09-20T17:17:57", "url": "https://files.pythonhosted.org/packages/1d/b0/19babc9b7d62bff9441111061ed3b35a255c8144fc3a556b0c9ca0bb066c/node.ext.ldap-0.9.tar.gz" } ], "0.9.1": [ { "comment_text": "", "digests": { "md5": "7eedc01b347364cd50e9c41049cb687a", "sha256": "e4fc92412477d6e2f858717cd70526c0d829f1af7e5b78e65c8b7a0ac07d0226" }, "downloads": -1, "filename": "node.ext.ldap-0.9.1.tar.gz", "has_sig": false, "md5_digest": "7eedc01b347364cd50e9c41049cb687a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1865540, "upload_time": "2012-03-20T10:38:49", "url": "https://files.pythonhosted.org/packages/ab/55/012d4fcafd2946638d4b9f163933b8841a808a027b11ede3dcb2a3429ed6/node.ext.ldap-0.9.1.tar.gz" } ], "0.9.1pre2": [], "0.9.2": [ { "comment_text": "", "digests": { "md5": "98e422eaab6ea045b14c47059421569a", "sha256": "562c3b78c87bfed017ae961b327ba3c4291bbabaeeb1d217c77942dd23762f3a" }, "downloads": -1, "filename": "node.ext.ldap-0.9.2.tar.gz", "has_sig": false, "md5_digest": "98e422eaab6ea045b14c47059421569a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1880181, "upload_time": "2012-05-30T11:17:57", "url": "https://files.pythonhosted.org/packages/6d/3b/0786c817ff7ad6a18a5ccc4a40895f655d6e969cb4a4424a28fb732becdd/node.ext.ldap-0.9.2.tar.gz" } ], "0.9.3": [ { "comment_text": "", "digests": { "md5": "7e12055266eed268bcc90383507a10cb", "sha256": "0c7381707fbbb2630230ebcee1a1f1c8be9094f568181859b5c562dc72697469" }, "downloads": -1, "filename": "node.ext.ldap-0.9.3.tar.gz", "has_sig": false, "md5_digest": "7e12055266eed268bcc90383507a10cb", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1880840, "upload_time": "2012-05-31T12:42:02", "url": "https://files.pythonhosted.org/packages/3b/b1/7abf96d2bd428fe9626d3fbf08e844428834990cd13f2fd8b0bba1a703dc/node.ext.ldap-0.9.3.tar.gz" } ], "0.9.4": [ { "comment_text": "", "digests": { "md5": "abe2fce4a0c14df6d7cf3c888ff79c0f", "sha256": "45ccc3bd93787cc62d3c5e4a712275b2e097ba4debe55a7c909aae27ef423588" }, "downloads": -1, "filename": "node.ext.ldap-0.9.4.tar.gz", "has_sig": false, "md5_digest": "abe2fce4a0c14df6d7cf3c888ff79c0f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1880823, "upload_time": "2012-12-05T11:20:45", "url": "https://files.pythonhosted.org/packages/73/d8/2f79f5b310bf574c468c8c64e6dd4fe6e2769abff45e45cd4e927327d2b3/node.ext.ldap-0.9.4.tar.gz" } ], "0.9.5": [ { "comment_text": "", "digests": { "md5": "4a8f6aa02121bf66aaceeaf9f8ded23b", "sha256": "169a9bc9a4f2eed9bf35480bfb095156cb4a5774b2989074c92ebe09ffbd6328" }, "downloads": -1, "filename": "node.ext.ldap-0.9.5.tar.gz", "has_sig": false, "md5_digest": "4a8f6aa02121bf66aaceeaf9f8ded23b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1881829, "upload_time": "2013-02-24T15:17:57", "url": "https://files.pythonhosted.org/packages/7b/6c/11ca5b978914604ab35dfd7e6cf6adc2d43353af52476348ecd2d4242573/node.ext.ldap-0.9.5.tar.gz" } ], "0.9.6": [ { "comment_text": "", "digests": { "md5": "0c171d588c1833198bc75f95201bb41d", "sha256": "82a74b88e2e4478eaa7f74be9d008ae95ca8bad5a6afe9cabe80317321bbd331" }, "downloads": -1, "filename": "node.ext.ldap-0.9.6.zip", "has_sig": false, "md5_digest": "0c171d588c1833198bc75f95201bb41d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1988940, "upload_time": "2014-03-13T09:38:08", "url": "https://files.pythonhosted.org/packages/4f/87/9f8b13ea8d4849b653f92b662c7286e6f5d841643cb41cc6465e2f0f6099/node.ext.ldap-0.9.6.zip" } ], "0.9.7": [ { "comment_text": "", "digests": { "md5": "3b8df9b23cdffa2649071fe98dde6368", "sha256": "cdb2bef454632e7ce186f2eac180393bca49e07f6298ac8bcf1a0c88d173e02d" }, "downloads": -1, "filename": "node.ext.ldap-0.9.7.tar.gz", "has_sig": false, "md5_digest": "3b8df9b23cdffa2649071fe98dde6368", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1882417, "upload_time": "2014-05-13T16:41:57", "url": "https://files.pythonhosted.org/packages/8f/20/264188ed7b97e8d895628e7f05ea7b0a7de91afc63a25a491acef7b0e435/node.ext.ldap-0.9.7.tar.gz" } ], "1.0b1": [ { "comment_text": "", "digests": { "md5": "484d050ce4d51872c14df0e91f1ed7cb", "sha256": "5156073c3403479f6b3484960fc8deead1018ff88f322e73a20aa3335385b195" }, "downloads": -1, "filename": "node.ext.ldap-1.0b1.tar.gz", "has_sig": false, "md5_digest": "484d050ce4d51872c14df0e91f1ed7cb", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1881855, "upload_time": "2015-12-31T16:53:19", "url": "https://files.pythonhosted.org/packages/ca/81/d79a279010615f0f2b7b6644abd39acad09a70fd2e25d14c43e08f73e611/node.ext.ldap-1.0b1.tar.gz" } ], "1.0b10": [ { "comment_text": "", "digests": { "md5": "38ecf5aea81e672588db2b8e3af35503", "sha256": "9260be406e40b259e466a9faa174e56e432c5594da49a0b77cf377d4f0463021" }, "downloads": -1, "filename": "node.ext.ldap-1.0b10.tar.gz", "has_sig": false, "md5_digest": "38ecf5aea81e672588db2b8e3af35503", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1896861, "upload_time": "2019-06-30T11:49:12", "url": "https://files.pythonhosted.org/packages/7f/84/07d05671817b3b1366313c997f48b13acb37202719e3d470fd30e7014a0f/node.ext.ldap-1.0b10.tar.gz" } ], "1.0b11": [ { "comment_text": "", "digests": { "md5": "6eb3c65ec8ca7b6e26a4afa3ae52da8a", "sha256": "37db421b3669c1d61edf0bc0b42320c004ac5379e60379635728d35313fdd325" }, "downloads": -1, "filename": "node.ext.ldap-1.0b11.tar.gz", "has_sig": false, "md5_digest": "6eb3c65ec8ca7b6e26a4afa3ae52da8a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1897340, "upload_time": "2019-09-08T09:04:47", "url": "https://files.pythonhosted.org/packages/e3/66/fb80699c379470e26ab3c3cb32ecf9efa3aa91042c1134e782a2a128ace2/node.ext.ldap-1.0b11.tar.gz" } ], "1.0b2": [ { "comment_text": "", "digests": { "md5": "346d5b813ad3c4b6686960c48c5cce12", "sha256": "bb25a94faa62a55049c45325b44e8c2025540212d910f857622d7b574aac06af" }, "downloads": -1, "filename": "node.ext.ldap-1.0b2.tar.gz", "has_sig": false, "md5_digest": "346d5b813ad3c4b6686960c48c5cce12", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1884108, "upload_time": "2016-09-09T14:01:18", "url": "https://files.pythonhosted.org/packages/d4/0c/74669140b5718fcd4b01a179738fd948f5757d4362f6eb7c3d6145035b4c/node.ext.ldap-1.0b2.tar.gz" } ], "1.0b3": [ { "comment_text": "", "digests": { "md5": "4f0511a0b3e92bd1d87644437a8b5cd9", "sha256": "be46d2dfb28ac67d38f81079af6ce0d736d9339c41acab7f4f001a846c613819" }, "downloads": -1, "filename": "node.ext.ldap-1.0b3.tar.gz", "has_sig": false, "md5_digest": "4f0511a0b3e92bd1d87644437a8b5cd9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1885773, "upload_time": "2016-10-18T16:41:52", "url": "https://files.pythonhosted.org/packages/51/38/f9c5524f98971c4b92f5c2df0f34ac3ee4becb18d3216db3ec392994cade/node.ext.ldap-1.0b3.tar.gz" } ], "1.0b4": [ { "comment_text": "", "digests": { "md5": "a76d4fc68d964d6e381f08ffc574211e", "sha256": "976283f3018d37626baccce32278d0fafa5221bdf68220f4e83dbba1648a9230" }, "downloads": -1, "filename": "node.ext.ldap-1.0b4.tar.gz", "has_sig": false, "md5_digest": "a76d4fc68d964d6e381f08ffc574211e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1889053, "upload_time": "2017-06-07T12:21:11", "url": "https://files.pythonhosted.org/packages/06/86/7fe7c01918eb11107153ff161d168419147d218e0bc18ba506138afb06e6/node.ext.ldap-1.0b4.tar.gz" } ], "1.0b5": [ { "comment_text": "", "digests": { "md5": "24d2ec27ea2395566165d4ef84e71c08", "sha256": "c26bf0a1a7f55fb04025d43d89ed6019e47ee99960825f0621048ce65d82505e" }, "downloads": -1, "filename": "node.ext.ldap-1.0b5.tar.gz", "has_sig": false, "md5_digest": "24d2ec27ea2395566165d4ef84e71c08", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1888158, "upload_time": "2017-10-27T17:23:09", "url": "https://files.pythonhosted.org/packages/79/5e/3dc507ef11ab77a269214939360266f86fe15e770b66b7ba9794189eb442/node.ext.ldap-1.0b5.tar.gz" } ], "1.0b6": [ { "comment_text": "", "digests": { "md5": "9d6b9aaf81fd454149aebb8f0fc2ec84", "sha256": "12464018ba3caa074141957e1472cea235f599f33c4b5de119c6081907d14857" }, "downloads": -1, "filename": "node.ext.ldap-1.0b6.tar.gz", "has_sig": false, "md5_digest": "9d6b9aaf81fd454149aebb8f0fc2ec84", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1888435, "upload_time": "2017-10-27T18:13:51", "url": "https://files.pythonhosted.org/packages/f3/c8/5fa611de0c189bd8956652ae834273e0eb42e967b742f05d0d10645221dd/node.ext.ldap-1.0b6.tar.gz" } ], "1.0b7": [ { "comment_text": "", "digests": { "md5": "d373b60da9d6af82f3fe3ef675c2c466", "sha256": "97ad7463e069da39854291342d3fd1a34d375ea7e02aaecdfb57267bf40d335e" }, "downloads": -1, "filename": "node.ext.ldap-1.0b7.tar.gz", "has_sig": false, "md5_digest": "d373b60da9d6af82f3fe3ef675c2c466", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1891532, "upload_time": "2017-12-15T11:45:28", "url": "https://files.pythonhosted.org/packages/ac/3a/972e21836f39bc269a3954128a66b54dbc46ee637cbe725a184085da4db1/node.ext.ldap-1.0b7.tar.gz" } ], "1.0b8": [ { "comment_text": "", "digests": { "md5": "4f97a22b3006ac3f4103317568b1dcc5", "sha256": "57c78a01b38751bc189afe8fc9cd366621e134b25b7f66365d43a298553e1793" }, "downloads": -1, "filename": "node.ext.ldap-1.0b8.tar.gz", "has_sig": false, "md5_digest": "4f97a22b3006ac3f4103317568b1dcc5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1892048, "upload_time": "2018-10-22T09:05:39", "url": "https://files.pythonhosted.org/packages/3b/03/0f184117bbc3c7aea028cc65bd584ee963d5bd78e390c110b704f0c48641/node.ext.ldap-1.0b8.tar.gz" } ], "1.0b9": [ { "comment_text": "", "digests": { "md5": "237f19a1eb2bed2810a092d15dc6cfde", "sha256": "1edcfff3b5de75ba8c46a19f02ff9d68c3b54cb56e4c87c27273e074b4c10ffc" }, "downloads": -1, "filename": "node.ext.ldap-1.0b9.tar.gz", "has_sig": false, "md5_digest": "237f19a1eb2bed2810a092d15dc6cfde", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1887711, "upload_time": "2019-05-07T09:05:28", "url": "https://files.pythonhosted.org/packages/20/71/f9a208806290d1a7e7801bf2b6039972506810157ee42218ecd94164ddd3/node.ext.ldap-1.0b9.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "3b8df9b23cdffa2649071fe98dde6368", "sha256": "cdb2bef454632e7ce186f2eac180393bca49e07f6298ac8bcf1a0c88d173e02d" }, "downloads": -1, "filename": "node.ext.ldap-0.9.7.tar.gz", "has_sig": false, "md5_digest": "3b8df9b23cdffa2649071fe98dde6368", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1882417, "upload_time": "2014-05-13T16:41:57", "url": "https://files.pythonhosted.org/packages/8f/20/264188ed7b97e8d895628e7f05ea7b0a7de91afc63a25a491acef7b0e435/node.ext.ldap-0.9.7.tar.gz" } ] }