def test_simple_search(self): dn = 'uid=hugo,cn=users,dc=test,dc=local' server, client = self.create_server_and_client( [ pureldap.LDAPBindResponse(resultCode=0), # for service account ], [ pureldap.LDAPSearchResultEntry(dn, [('someattr', ['somevalue'])]), pureldap.LDAPSearchResultDone(ldaperrors.Success.resultCode), ]) yield client.bind(dn, 'secret') # Assert that Proxy<->Backend uses the correct credentials server.client.assertSent( pureldap.LDAPBindRequest( dn='uid=service,cn=users,dc=test,dc=local', auth='service-secret'), ) # Perform a simple search in the context of the service account entry = LDAPEntry(client, dn) results = yield entry.search('(objectClass=*)', scope=pureldap.LDAP_SCOPE_baseObject) self.assertEqual(len(results), 1) self.assertEqual(len(results[0]['someattr']), 1) (value, ) = results[0]['someattr'] self.assertEqual(value, 'somevalue')
def test_realm_mapping_fails_wrong_password(self): marker = 'markerSecret' realm = 'realmSecret' password = '******' # this is the wrong password! service_dn = 'uid=passthrough,cn=users,dc=test,dc=local' dn = 'uid=hugo,cn=users,dc=test,dc=local' server, client = self.create_server_and_client( [ pureldap.LDAPBindResponse(resultCode=0), # for service account ], [ pureldap.LDAPSearchResultEntry(dn, [('someattr', ['somevalue'])]), pureldap.LDAPSearchResultDone(ldaperrors.Success.resultCode), ]) yield client.bind(service_dn, 'service-secret') # Assert that Proxy<->Backend uses the correct credentials server.client.assertSent( pureldap.LDAPBindRequest(dn=service_dn, auth='service-secret'), ) # Perform a simple search in the context of the service account entry = LDAPEntry(client, dn) r = yield entry.search('(|(objectClass=*)(objectclass=App-%s))' % marker, scope=pureldap.LDAP_SCOPE_baseObject) # sleep a second and then try to bind as hugo time.sleep(0.5) server2, client2 = self.create_server_and_client([ pureldap.LDAPBindResponse( resultCode=0), # for service account (successful hugo bind) ]) d = client2.bind(dn, password) yield self.assertFailure(d, ldaperrors.LDAPInvalidCredentials) self.assertEqual(self.privacyidea.authentication_requests, [('hugo', realm, password, False)]) time.sleep(1) # to clean the reactor
def perform_search(self, dn, filter_object, attributes=(), sizeLimit=0, scope=None): """ Args: dn (str): The base DN for the search to start at filter_object (LDAPFilterSet): Filter to qualify the search against attributes (tuple): Attriutes to get back. Default of empty set means all. `None` gives no attrs sizeLimit (int): Number of results to return. 0 means no limit scope (int): One of LDAP_SCOPE_baseObject = 0 LDAP_SCOPE_singleLevel = 1 LDAP_SCOPE_wholeSubtree = 2 """ entry = LDAPEntry(self, dn) result = yield entry.search( filterObject=filter_object, attributes=attributes, sizeLimit=sizeLimit, scope=scope, ) defer.returnValue(result)
def test_realm_mapping_succeeds_case_sensitive(self): marker = 'markerSecret' password = '******' service_dn = 'uid=passthrough,cn=users,dc=test,dc=local' dn = 'uid=Hugo,cn=users,dc=test,DC=LOCAL' server, client = self.create_server_and_client( [ pureldap.LDAPBindResponse(resultCode=0), # for service account ], [ pureldap.LDAPSearchResultEntry(dn, [('someattr', ['somevalue'])]), pureldap.LDAPSearchResultDone(ldaperrors.Success.resultCode), ]) yield client.bind(service_dn, 'service-secret') # Assert that Proxy<->Backend uses the correct credentials server.client.assertSent( pureldap.LDAPBindRequest(dn=service_dn, auth='service-secret'), ) # Perform a simple search in the context of the service account entry = LDAPEntry(client, dn) r = yield entry.search('(|(objectClass=*)(objectclass=App-%s))' % marker, scope=pureldap.LDAP_SCOPE_baseObject) # sleep half a second and then try to bind as hugo time.sleep(0.5) server2, client2 = self.create_server_and_client([ pureldap.LDAPBindResponse( resultCode=0), # for service account (successful hugo bind) ]) yield client2.bind( dn.lower(), password) # this will work even though the DN has differing case self.assertEqual(self.privacyidea.authentication_requests, [('hugo', 'realmSecret', password, True)]) time.sleep(1) # to clean the reactor
def resolve(self, dn): """ Given a distinguished name, return the login name to be used with privacyIDEA :param dn: distinguished name as string :return: Deferred that fires the login name """ # Perform a LDAP bind, search for an object with the distinguished name *dn* client = yield self.factory.connect_service_account() entry = LDAPEntry(client, dn) try: results = yield entry.search('(objectClass=*)', scope=pureldap.LDAP_SCOPE_baseObject) # Assuming we found one, extract the login name attribute assert len(results) == 1 if self.attribute not in results[0]: log.warn('Unknown lookup attribute: {attribute}', attribute=self.attribute) raise UserMappingError(dn) login_name_set = results[0][self.attribute] assert len(login_name_set) == 1 (login_name, ) = login_name_set defer.returnValue(login_name) except ldaperrors.LDAPNoSuchObject, e: # Apparently, the user could not be found. Raise the appropriate exception. raise UserMappingError(dn)
def perform_search(self, dn, filter_object, attributes=(), sizeLimit=0, scope=None): """ Args: dn (str): The base DN for the search to start at filter_object (LDAPFilterSet): Filter to qualify the search against attributes (tuple): Attriutes to get back. Default of empty set means all. `None` gives no attrs sizeLimit (int): Number of results to return. 0 means no limit scope (int): One of LDAP_SCOPE_baseObject = 0 LDAP_SCOPE_singleLevel = 1 LDAP_SCOPE_wholeSubtree = 2 """ entry = LDAPEntry(self, dn) try: result = yield entry.search( filterObject=filter_object, attributes=attributes, sizeLimit=sizeLimit, scope=scope, ) except ldaperrors.LDAPOperationsError as e: if e.message.decode().startswith( const.LDAP_SUCCESSFUL_BIND_NEEDED_ERROR): log.failure("LDAP search failed") raise ConnectionNotProperlyBoundError( "Search failed because either there was no bind on this connection or there were insufficient privileges with the bound user. If you are attempting to use integrated authentication with SSPI please make sure the server running the Authentication Proxy is domain joined." ) else: raise e defer.returnValue(result)
def test_passthrough_account_search_fails(self): server, client = self.create_server_and_client( [pureldap.LDAPBindResponse(resultCode=0)]) yield client.bind('uid=passthrough,cn=users,dc=test,dc=local', 'some-secret') entry = LDAPEntry(client, 'cn=users,dc=test,dc=local') d = entry.search('(objectClass=*)', scope=pureldap.LDAP_SCOPE_wholeSubtree) yield self.assertFailure(d, ldaperrors.LDAPInsufficientAccessRights)
def isAuthenticated(self, result): base = LDAPEntry(self.client, self.basedn) d = base.search(filterText=self.query, attributes=('*', '+')) base_ops = LDAPEntry(self.client, self.basedn_ops) d_ops = base.search(filterText=self.query_ops, attributes=('*', '+')) d.addCallback(self.gotResults) d_ops.addCallback(self.gotResultsOperations)
def request_page_(client, basedn, page_size=100, cookie='', **search_kwds): control_value = pureber.BERSequence([ pureber.BERInteger(page_size), pureber.BEROctetString(cookie), ]) controls = [('1.2.840.113556.1.4.319', None, control_value)] search_kwds['controls'] = controls search_kwds['return_controls'] = True o = LDAPEntry(client, basedn) results, resp_controls = yield o.search(**search_kwds) cookie = get_paged_search_cookie_(resp_controls) defer.returnValue((results, cookie))
def dn_to_username(self, dn_str: str, client_factory): """ Return a username usable for Duo auth. The return value may include a domain. If necessary, that must be handled with a domain username normalization policy on the Duo integration. Args: dn_str: (str) client_factory (ADClientFactory) Returns: str: The username we want to send to Duo None: If we could not determine the username to send """ try: dn = DistinguishedName(dn_str) except Exception: # At this point we think the DN is actually in a username format # but we'll need some extra searches to verify for sure. # This is technically not legal LDAP to do this, but Active Directory # supports it so we must as well. possible_username = utilities.LdapUsername( dn_str, LdapUsernameOrigin.BIND_DN) try: _ = yield self.validate_ldap_username_for_auth( possible_username, client_factory.search_dn) except NoUserFound as e: log.msg(str(e)) defer.returnValue(None) else: # If our validate call didn't throw an exception that means our "DN" was a valid username # and we can just return that for usage with our call to Duo defer.returnValue(dn_str) entry = LDAPEntry(self, dn) user_filter = yield self.user_filter_object() result = yield entry.search( filterObject=user_filter, attributes=(self.factory.username_attribute, ), ) if len(result) != 1: defer.returnValue(None) attr_set = result[0].get(self.factory.username_attribute) if not attr_set: defer.returnValue(None) defer.returnValue(list(attr_set)[0].decode())
def process_entry(client, args, search_filter, page_size=100, cookie=''): basedn = args.base_dn control_value = pureber.BERSequence([ pureber.BERInteger(page_size), pureber.BEROctetString(cookie), ]) controls = [('1.2.840.113556.1.4.319', None, control_value)] o = LDAPEntry(client, basedn) results, resp_controls = yield o.search(filterText=search_filter, attributes=['dn'], controls=controls, return_controls=True) cookie = get_paged_search_cookie(resp_controls) defer.returnValue((results, cookie))
def process_entry(client, args, search_filter, page_size=100, cookie=''): basedn = args.base_dn control_value = pureber.BERSequence([ pureber.BERInteger(page_size), pureber.BEROctetString(cookie), ]) controls = [('1.2.840.113556.1.4.319', None, control_value)] o = LDAPEntry(client, basedn) results, resp_controls = yield o.search( filterText=search_filter, attributes=['dn'], controls=controls, return_controls=True) cookie = get_paged_search_cookie(resp_controls) defer.returnValue((results, cookie))
def test_user_search_fails(self): dn = 'uid=hugo,cn=users,dc=test,dc=local' server, client = self.create_server_and_client([ # TODO: Would the backend actually answer like that? pureldap.LDAPSearchResultDone( ldaperrors.LDAPInsufficientAccessRights.resultCode), ]) yield client.bind(dn, 'secret') # Assert that there was no traffic between Proxy<->Backend server.client.assertNothingSent() # Try to perform a simple search in the context of the service account entry = LDAPEntry(client, dn) d = entry.search('(objectClass=*)', scope=pureldap.LDAP_SCOPE_baseObject) yield self.assertFailure(d, ldaperrors.LDAPInsufficientAccessRights)
def onConnect(client): # The following arguments may be also specified as unicode strings # but it is recommended to use byte strings for ldaptor objects basedn = b"dc=ldap,dc=com" binddn = b"cn=ldap,ou=user,dc=ldap,dc=com" bindpw = b"123456" query = b"(cn=test)" try: yield client.bind(binddn, bindpw) except Exception as ex: print(ex) raise o = LDAPEntry(client, basedn) results = yield o.search(filterText=query) for entry in results: print(entry.getLDIF())
def onConnect(client): # The following arguments may be also specified as unicode strings # but it is recommended to use byte strings for ldaptor objects basedn = b'dc=example,dc=org' binddn = b'cn=bob,ou=people,dc=example,dc=org' bindpw = b'secret' query = b'(cn=bob)' try: yield client.bind(binddn, bindpw) except Exception as ex: print(ex) raise o = LDAPEntry(client, basedn) results = yield o.search(filterText=query) for entry in results: print(entry.getLDIF())
def transform_result( result: LDAPEntry, desired_attributes: List[str], value_transform: Callable[[LDAPAttribute], str] = encoding_transform, ): """ Turn an LDAPEntry result into a dictionary of attribute values. Args: result (LDAPEntry): an LDAPEntry desired_attributes (list): The desired attributes value_transform (func): a per-value transform function to apply Returns: A dict of {attribute name: [list of attribute values]} for the requested attributes """ result_dict = {} for att in desired_attributes: attset = result.get(att, []) transformed_values = set() for val in list(attset): transformed_values.add(value_transform(val)) result_dict[att] = list(transformed_values) result_dict.pop("userpassword", None) return result_dict
def test_ignores_search_result_reference(self): dn = 'uid=hugo,cn=users,dc=test,dc=local' server, client = self.create_server_and_client( [pureldap.LDAPBindResponse(resultCode=0)], [ pureldap.LDAPSearchResultEntry(dn, [('someattr', ['somevalue'])]), pureldap.LDAPSearchResultReference( ), # NOTE: ldaptor does not really support these pureldap.LDAPSearchResultReference(), pureldap.LDAPSearchResultDone(ldaperrors.Success.resultCode), ]) yield client.bind('uid=passthrough,cn=users,dc=test,dc=local', 'some-secret') entry = LDAPEntry(client, 'cn=users,dc=test,dc=local') r = yield entry.search('(objectClass=*)', scope=pureldap.LDAP_SCOPE_wholeSubtree) self.assertEqual(len(r), 1) self.assertEqual(r[0].dn, dn)
def test_state_reset(self): # Passthrough Bind, User Bind dn = 'uid=hugo,cn=users,dc=test,dc=local' search_response = pureldap.LDAPSearchResultEntry( dn, [('someattr', ['somevalue'])]) server, client = self.create_server_and_client( [pureldap.LDAPBindResponse(resultCode=0)], [ search_response, pureldap.LDAPSearchResultDone(ldaperrors.Success.resultCode), ], [pureldap.LDAPBindResponse(resultCode=0)]) yield client.bind('uid=passthrough,cn=users,dc=test,dc=local', 'some-secret') server.client.assertSent( pureldap.LDAPBindRequest( dn='uid=passthrough,cn=users,dc=test,dc=local', auth='some-secret'), ) # perform a search entry = LDAPEntry(client, dn) r = yield entry.search( '(|(objectClass=*)(objectcLAsS=App-markerSecret))', scope=pureldap.LDAP_SCOPE_baseObject) # check that the state is correct self.assertTrue(server.received_bind_request) self.assertTrue(server.forwarded_passthrough_bind) self.assertEqual(server.search_response_entries, 0) self.assertIsNone(server.last_search_response_entry) yield client.bind(dn, 'secret') self.assertEqual(len(server.client.sent), 2) # Check that the bind requests was sent properly self.assertEqual( server.client.sent[0], pureldap.LDAPBindRequest( dn='uid=passthrough,cn=users,dc=test,dc=local', auth='some-secret')) # check that state is properly reset self.assertTrue(server.received_bind_request) self.assertFalse(server.forwarded_passthrough_bind) self.assertEqual(server.search_response_entries, 0) self.assertIsNone(server.last_search_response_entry)
def test_user_search_fails(self): dn = 'uid=hugo,cn=users,dc=test,dc=local' server, client = self.create_server_and_client( [ pureldap.LDAPBindResponse(resultCode=0), # for service account ], [ pureldap.LDAPSearchResultEntry(dn, [('someattr', ['somevalue'])]), pureldap.LDAPSearchResultDone(ldaperrors.Success.resultCode), ]) yield client.bind(dn, 'secret') # Assert that Proxy<->Backend uses the correct credentials server.client.assertSent( pureldap.LDAPBindRequest( dn='uid=service,cn=users,dc=test,dc=local', auth='service-secret'), ) # Try to perform a simple search in the context of the service account entry = LDAPEntry(client, dn) d = entry.search('(objectClass=*)', scope=pureldap.LDAP_SCOPE_baseObject) yield self.assertFailure(d, ldaperrors.LDAPInsufficientAccessRights)
def test_realm_mapping_fails_fake_search_by_user(self): service_dn = 'uid=passthrough,cn=users,dc=test,dc=local' dn = 'uid=hugo,cn=users,dc=test,dc=local' server, client = self.create_server_and_client( [ pureldap.LDAPBindResponse(resultCode=0), # for service account ], [ pureldap.LDAPSearchResultEntry(dn, [('someattr', ['somevalue'])]), pureldap.LDAPSearchResultDone(ldaperrors.Success.resultCode), ]) yield client.bind(service_dn, 'service-secret') # Assert that Proxy<->Backend uses the correct credentials server.client.assertSent( pureldap.LDAPBindRequest(dn=service_dn, auth='service-secret'), ) # Perform a simple search in the context of the service account entry = LDAPEntry(client, dn) r = yield entry.search( '(|(objectClass=*)(objectcLAsS=App-markerSecret))', scope=pureldap.LDAP_SCOPE_baseObject) # sleep half a second and then try to bind as hugo time.sleep(0.5) server2, client2 = self.create_server_and_client( [ pureldap.LDAPBindResponse( resultCode=0 ), # for service account (successful hugo bind) ], [ pureldap.LDAPSearchResultEntry( dn, [('someattr', ['somevalue'])]), # hugo's search pureldap.LDAPSearchResultDone(ldaperrors.Success.resultCode), ]) yield client2.bind(dn, 'secret') self.assertEqual(self.privacyidea.authentication_requests, [('hugo', 'realmSecret', 'secret', True)]) # Perform another search in hugo's context entry2 = LDAPEntry(client2, dn) r = yield entry2.search( '(|(objectClass=*)(objectcLAsS=App-markerOfficial))', scope=pureldap.LDAP_SCOPE_baseObject) self.assertTrue( server.factory.app_cache.get_cached_marker(dn) in ('markerSecret', None)) time.sleep(1) # to clean the reactor
def onConnect(clientProtocol): o = LDAPEntry(clientProtocol, "dc=fr") results = yield o.search() data = "".join([result.getLDIF() for result in results]) log.msg(f"LDIF formatted results:\n{data}")
def _cb(client, baseDN): e = LDAPEntry(client=client, dn=baseDN) return e
def onConnect(clientProtocol): o = LDAPEntry(clientProtocol, "dc=fr") results = yield o.search() data = u"".join([result.getLDIF() for result in results]) log.msg(u"LDIF formatted results:\n{}".format(data))