def test_base(self): # NullCachManager registration provideAdapter(NullCacheManager) # Test main script, could be used by command line with # 'python base.py server port':: old_argv = sys.argv sys.argv = ['base.py', '127.0.0.1', '12345'] self.assertEqual(main(), 'success') sys.argv[-1] = '12346' self.assertEqual(main().args[0], { 'info': 'Transport endpoint is not connected', 'errno': 107, 'desc': "Can't contact LDAP server" }) sys.argv = [] self.assertEqual(main(), 'usage: python base.py [server] [port]') sys.argv = old_argv # Test node.ext.ldap base objects. Test LDAP connectivity self.assertEqual(testLDAPConnectivity('127.0.0.1', 12345), 'success') self.assertEqual(testLDAPConnectivity('127.0.0.1', 12346).args[0], { 'info': 'Transport endpoint is not connected', 'errno': 107, 'desc': u"Can't contact LDAP server"} ) # LDAP credentials host = "127.0.0.1" port = 12345 binddn = "cn=Manager,dc=my-domain,dc=com" bindpw = "secret" props = LDAPProps( server=host, port=port, user=binddn, password=bindpw ) # Create connector. connector = LDAPConnector(props=props) # Create communicator. communicator = LDAPCommunicator(connector) # Bind to directory. communicator.bind() # Search fails if baseDN is not set and not given to search function self.assertEqual(communicator.baseDN, '') err = self.expect_error( ValueError, communicator.search, '(objectClass=*)', SUBTREE ) self.assertEqual(str(err), 'baseDN unset.') # Set base dn and check if previously imported entries are present. communicator.baseDN = 'dc=my-domain,dc=com' res = communicator.search('(objectClass=*)', SUBTREE) self.assertEqual(len(res), 7) # Test search pagination res, cookie = communicator.search( '(objectClass=*)', SUBTREE, page_size=4, cookie='' ) self.assertEqual(len(res), 4) res, cookie = communicator.search( '(objectClass=*)', SUBTREE, page_size=4, cookie=cookie ) self.assertEqual(len(res), 3) self.assertEqual(cookie, b'') # Pagination search fails if cookie but no page size given res, cookie = communicator.search( '(objectClass=*)', SUBTREE, page_size=4, cookie='' ) err = self.expect_error( ValueError, communicator.search, '(objectClass=*)', SUBTREE, cookie=cookie ) self.assertEqual(str(err), 'cookie passed without page_size') # Test inserting entries. entry = { 'cn': b'foo', 'sn': b'bar', 'objectclass': (b'person', b'top'), } dn = 'cn=foo,ou=customer1,ou=customers,dc=my-domain,dc=com' communicator.add(dn, entry) # Now there's one more entry in the directory. res = communicator.search('(objectClass=*)', SUBTREE) self.assertEqual(len(res), 8) # Query added entry directly. res = communicator.search('(cn=foo)', SUBTREE) self.assertEqual(res, [( 'cn=foo,ou=customer1,ou=customers,dc=my-domain,dc=com', {'objectClass': [b'person', b'top'], 'cn': [b'foo'], 'sn': [b'bar']} )]) # Modify this entry and check the result. communicator.modify(res[0][0], [(MOD_REPLACE, 'sn', b'baz')]) res = communicator.search('(cn=foo)', SUBTREE) self.assertEqual(res, [( 'cn=foo,ou=customer1,ou=customers,dc=my-domain,dc=com', {'objectClass': [b'person', b'top'], 'cn': [b'foo'], 'sn': [b'baz']} )]) # Finally delete this entry and check the result. communicator.delete(res[0][0]) self.assertEqual(communicator.search('(cn=foo)', SUBTREE), []) # Unbind from server. communicator.unbind() # Connector using cache. connector = LDAPConnector(props) communicator = LDAPCommunicator(connector) communicator.bind() # Add entry entry = { 'cn': b'foo', 'sn': b'bar', 'objectclass': (b'person', b'top'), } dn = 'cn=foo,ou=customer1,ou=customers,dc=my-domain,dc=com' communicator.add(dn, entry) communicator.baseDN = 'dc=my-domain,dc=com' # Search cached entry. Does not get cached here since no real cache # provider is registered. Thus the nullcacheProviderFactory is used. # But cache API is used anyways:: res = communicator.search('(cn=foo)', SUBTREE) self.assertEqual(res, [( 'cn=foo,ou=customer1,ou=customers,dc=my-domain,dc=com', {'objectClass': [b'person', b'top'], 'cn': [b'foo'], 'sn': [b'bar']} )]) # Delete entry communicator.delete(res[0][0]) res = communicator.search('(cn=foo)', SUBTREE, force_reload=True) self.assertEqual(res, []) communicator.unbind()
class LDAPSession(object): """LDAP Session binds always. """ def __init__(self, props): self._props = props connector = LDAPConnector(props=props) self._communicator = LDAPCommunicator(connector) def checkServerProperties(self): """Test if connection can be established. """ res = testLDAPConnectivity(props=self._props) if res == 'success': return (True, 'OK') else: return (False, res) @property def baseDN(self): baseDN = self._communicator.baseDN return baseDN @baseDN.setter def baseDN(self, baseDN): self._communicator.baseDN = baseDN def ensure_connection(self): """If LDAP directory is down, bind again and retry given function. """ if self._communicator._con is None: self._communicator.bind() def search(self, queryFilter='(objectClass=*)', scope=BASE, baseDN=None, force_reload=False, attrlist=None, attrsonly=0, page_size=None, cookie=None): if not queryFilter: # It makes no sense to really pass these to LDAP, therefore, we # interpret them as "don't filter" which in LDAP terms is # '(objectClass=*)' queryFilter = '(objectClass=*)' self.ensure_connection() res = self._communicator.search( queryFilter, scope, baseDN, force_reload, attrlist, attrsonly, page_size, cookie ) if page_size: res, cookie = res # ActiveDirectory returns entries with dn None, which can be ignored res = [x for x in res if x[0] is not None] if page_size: return res, cookie return res def add(self, dn, data): self.ensure_connection() self._communicator.add(dn, data) def authenticate(self, dn, pw): """Verify credentials, but don't rebind the session to that user """ # Let's bypass connector/communicator until they are sorted out if self._props.ignore_cert: # pragma: no cover ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) con = ldap.initialize( self._props.uri, bytes_mode=False, bytes_strictness='silent' ) # Turning referrals off since they cause problems with MS Active # Directory More info: https://www.python-ldap.org/faq.html#usage con.set_option(ldap.OPT_REFERRALS, 0) try: con.simple_bind_s(dn, pw) except (ldap.INVALID_CREDENTIALS, ldap.UNWILLING_TO_PERFORM): # The UNWILLING_TO_PERFORM event might be thrown, if you query a # local user named ``admin``, but the LDAP server is configured to # deny such queries. Instead of raising an exception, just ignore # this. return False else: return True def modify(self, dn, data, replace=False): """Modify an existing entry in the directory. :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) return result def delete(self, dn): self._communicator.delete(dn) def passwd(self, userdn, oldpw, newpw): self.ensure_connection() result = self._communicator.passwd(userdn, oldpw, newpw) return result def unbind(self): self._communicator.unbind()
class LDAPSession(object): """LDAP Session binds always. all strings must be utf8 encoded! """ def __init__(self, props): self._props = props connector = LDAPConnector(props=props) self._communicator = LDAPCommunicator(connector) def checkServerProperties(self): """Test if connection can be established. """ res = testLDAPConnectivity(props=self._props) if res == 'success': return (True, 'OK') else: return (False, res) @property def baseDN(self): baseDN = self._communicator.baseDN return baseDN @baseDN.setter def baseDN(self, baseDN): """baseDN must be utf8-encoded. """ self._communicator.baseDN = baseDN def ensure_connection(self): """If LDAP directory is down, bind again and retry given function. XXX: * Improve retry logic * Extend LDAPSession object to handle Fallback server(s) """ if self._communicator._con is None: self._communicator.bind() def search(self, queryFilter='(objectClass=*)', scope=BASE, baseDN=None, force_reload=False, attrlist=None, attrsonly=0, page_size=None, cookie=None): if not queryFilter: # It makes no sense to really pass these to LDAP, therefore, we # interpret them as "don't filter" which in LDAP terms is # '(objectClass=*)' queryFilter = '(objectClass=*)' self.ensure_connection() res = self._communicator.search(queryFilter, scope, baseDN, force_reload, attrlist, attrsonly, page_size, cookie) if page_size: res, cookie = res # ActiveDirectory returns entries with dn None, which can be ignored res = filter(lambda x: x[0] is not None, res) if page_size: return res, cookie return res def add(self, dn, data): self.ensure_connection() self._communicator.add(dn, data) def authenticate(self, dn, pw): """Verify credentials, but don't rebind the session to that user """ # Let's bypass connector/communicator until they are sorted out if self._props.ignore_cert: ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) con = ldap.initialize(self._props.uri) # Turning referrals off since they cause problems with MS Active # Directory More info: https://www.python-ldap.org/faq.html#usage con.set_option(ldap.OPT_REFERRALS, 0) try: con.simple_bind_s(dn, pw) except (ldap.INVALID_CREDENTIALS, ldap.UNWILLING_TO_PERFORM): # The UNWILLING_TO_PERFORM event might be thrown, if you query a # local user named ``admin``, but the LDAP server is configured to # deny such queries. Instead of raising an exception, just ignore # this. return False else: return True def modify(self, dn, data, replace=False): """Modify an existing entry in the directory. :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) return result def delete(self, dn): self._communicator.delete(dn) def passwd(self, userdn, oldpw, newpw): self.ensure_connection() result = self._communicator.passwd(userdn, oldpw, newpw) return result def unbind(self): self._communicator.unbind()
class LDAPSession(object): """LDAP Session binds always. all strings must be utf8 encoded! """ def __init__(self, props): self._props = props connector = LDAPConnector(props=props) self._communicator = LDAPCommunicator(connector) def checkServerProperties(self): """Test if connection can be established. """ res = testLDAPConnectivity(props=self._props) if res == 'success': return (True, 'OK') else: return (False, res) def _get_baseDN(self): baseDN = self._communicator.baseDN return baseDN def _set_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. XXX: * Improve retry logic * Extend LDAPSession object to handle Fallback server(s) """ if self._communicator._con is None: self._communicator.bind() def search(self, queryFilter='(objectClass=*)', scope=BASE, baseDN=None, force_reload=False, attrlist=None, attrsonly=0, page_size=None, cookie=None): if not queryFilter: # It makes no sense to really pass these to LDAP, therefore, we # interpret them as "don't filter" which in LDAP terms is # '(objectClass=*)' queryFilter = '(objectClass=*)' self.ensure_connection() res = self._communicator.search(queryFilter, scope, baseDN, force_reload, attrlist, attrsonly, page_size, cookie) if page_size: res, cookie = res # ActiveDirectory returns entries with dn None, which can be ignored res = filter(lambda x: x[0] is not None, res) if page_size: return res, cookie return res def add(self, dn, data): self.ensure_connection() self._communicator.add(dn, data) def authenticate(self, dn, pw): """Verify credentials, but don't rebind the session to that user """ # Let's bypass connector/communicator until they are sorted out if self._props.ignore_cert: ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) con = ldap.initialize(self._props.uri) try: con.simple_bind_s(dn, pw) except (ldap.INVALID_CREDENTIALS, ldap.UNWILLING_TO_PERFORM): # The UNWILLING_TO_PERFORM event might be thrown, if you query a # local user named ``admin``, but the LDAP server is configured to # deny such queries. Instead of raising an exception, just ignore # this. return False else: return True 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. """ self.ensure_connection() result = self._communicator.modify(dn, data) return result def delete(self, dn): self._communicator.delete(dn) def passwd(self, userdn, oldpw, newpw): self.ensure_connection() result = self._communicator.passwd(userdn, oldpw, newpw) return result def unbind(self): self._communicator.unbind()