def ldap_search_has_results(bound_client, search_dn_str, filter_object): """ Determine if an ldap filter will return any results. The exact count will not be determined, just whether the search will return zero or 1+ results. Args: bound_client: An ldaptor client that's already connected and bound as a search user search_dn_str (str): the base DN to search from filter_object: the filter text for the search, in LDAP filter format Returns: LdapSearchResult: the result of the test """ search_dn = DistinguishedName(search_dn_str) try: search_results = yield bound_client.perform_search(search_dn, filter_object, sizeLimit=1) except Exception as e: result = LdapSearchResult(False, search_dn_str, filter_object, exception=e) else: if len(search_results) > 0: result = LdapSearchResult(True, search_dn_str, filter_object) else: result = LdapSearchResult(False, search_dn_str, filter_object) defer.returnValue(result)
def main(dns): l = [] resolver = client.Resolver("/etc/resolv.conf") for dnString in dns: dn = DistinguishedName(stringValue=dnString) domain = dn.getDomainName() d = resolver.lookupService("_ldap._tcp.%s" % domain) l.append(d) d.addCallback(printAnswer, dnString) d.addErrback(errback) dl = DeferredList(l) dl.addBoth(lambda dummy: reactor.callLater(0, reactor.stop)) reactor.run() sys.exit(exitStatus)
def _is_under_exempt_ous(self, user_identifier, identifier_location): """Returns true if the user is found to be in one of the configured exempt groups Args: user_identifier: Username or DN to lookup identifier_location: One of utilities.LDAP_USERNAME_FROM_*. Explain where the username came from. DN field of the LDAP packet, NTLM username field, etc. """ if not self.exempt_ous: defer.returnValue(False) user_identifier = user_identifier.lower() try: dn = DistinguishedName(user_identifier) except Exception: # Response DNs shouldn't be invalid but a request's DN # field may be a NetBIOS (domain\username), a UPN (username@domain), a samAccountName (username), or a common name (username)". # Implementing exempt_ous for such binds requires an additional search # in order to get the DN. log.msg( "Bind Request did not have a full DN. Attempting to lookup full DN for {}".format( user_identifier ) ) possible_username = utilities.LdapUsername( user_identifier, identifier_location ) try: dn = yield self.username_to_dn(possible_username) except DnLookupFailed as e: log.msg( "Failed to lookup full DN. Marking as non-exempt by default. Error: {}".format( e ) ) defer.returnValue(False) defer.returnValue(any([base_dn.contains(dn) for base_dn in self.exempt_ous]))
def normalize_dn(dn): """ Transforms a `DistinguishedName` into a `DistinguishedName` with the casing normalized. """ return DistinguishedName( tuple( RelativeDistinguishedName(r.getText().lower()) for r in dn.listOfRDNs))
def test_parseOptions_multiple(self): """ It can have have multiple --service-location options and they are indexed using the base DN. """ sut = ServiceLocationOptionsImplementation() sut.parseOptions(options=[ '--service-location', 'dc=example,dc=com:127.0.0.1', '--service-location', 'dc=example,dc=org:172.0.0.1', ]) base_com = DistinguishedName('dc=example,dc=com') base_org = DistinguishedName('dc=example,dc=org') value_com = sut.opts['service-location'][base_com] value_org = sut.opts['service-location'][base_org] self.assertEqual(('127.0.0.1', None), value_com) self.assertEqual(('172.0.0.1', None), value_org)
def test_parseOptions_multiple(self): """ It can have have multiple --service-location options and they are indexed using the base DN. """ sut = ServiceLocationOptionsImplementation() sut.parseOptions(options=[ "--service-location", "dc=example,dc=com:127.0.0.1", "--service-location", "dc=example,dc=org:172.0.0.1", ]) base_com = DistinguishedName("dc=example,dc=com") base_org = DistinguishedName("dc=example,dc=org") value_com = sut.opts["service-location"][base_com] value_org = sut.opts["service-location"][base_org] self.assertEqual(("127.0.0.1", None), value_com) self.assertEqual(("172.0.0.1", None), value_org)
def test_parseOptions_single(self): """ It can have a single --service-location option. """ sut = ServiceLocationOptionsImplementation() sut.parseOptions(options=[ '--service-location', 'dc=example,dc=com:127.0.0.1:1234']) base = DistinguishedName('dc=example,dc=com') value = sut.opts['service-location'][base] self.assertEqual(('127.0.0.1', '1234'), value)
def test_parseOptions_single(self): """ It can have a single --service-location option. """ sut = ServiceLocationOptionsImplementation() sut.parseOptions( options=["--service-location", "dc=example,dc=com:127.0.0.1:1234"]) base = DistinguishedName("dc=example,dc=com") value = sut.opts["service-location"][base] self.assertEqual(("127.0.0.1", "1234"), value)
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 __init__( self, factors, primary_ator, failmode, exempt_primary_bind=True, exempt_ous=None, allow_searches_after_bind=True, allow_unlimited_binds=False, **kwargs ): """ Duo LDAP server with secondary auth factor pre-selected by admin. factors: comma-separated list of factor names like "auto,push". exempt_primary_bind: If True, Duo auth will not be performed for the first bind request in a session. Otherwise, it will be performed for every successful bind. exempt_ous: List of base DNs (DistinguishedName objects or strings). Duo auth will not be performed for any bind request with a DN under one of the listed trees. resolving_bind_request: True if a valid BindRequest has been received and a BindResponse has not yet been sent back. Note: there is currently no support for self-enrollment here! """ if exempt_ous is None: exempt_ous = [] super(DuoAutoLdapServer, self).__init__(**kwargs) self.primary_ator = primary_ator self.factors = factors self.failmode = failmode self.exempt_primary_bind = exempt_primary_bind self.exempt_ous = [DistinguishedName(str(dn).lower()) for dn in exempt_ous] self.no_more_binds = False self.allow_searches_after_bind = allow_searches_after_bind self.allow_unlimited_binds = allow_unlimited_binds self.resolving_bind_request = False
def _is_under_exempt_ous(self, dn_str): if not self.exempt_ous: defer.returnValue(False) dn_str = dn_str.lower() try: dn = DistinguishedName(dn_str) except Exception: # Response DNs shouldn't be invalid but a request's DN # field may be a "NetBIOS domain\username". Implementing # exempt_ous for such binds requires an additional search # in order to get the DN. log.msg( "Bind Request did not have a full DN. Attempting to lookup full DN for {}" .format(dn_str)) try: dn = yield self.username_to_dn(dn_str) except DnLookupFailed as e: log.msg( "Failed to lookup full DN. Marking as non-exempt by default. Error: {}" .format(e)) defer.returnValue(False) defer.returnValue( any([base_dn.contains(dn) for base_dn in self.exempt_ous]))