def do_ldap_authentication( self, servers, username, password, rikey, base_dns=None, ntlm_domain=None, ntlm_workstation=None, auth_type=const.AD_AUTH_TYPE_NTLM_V2, transport_type=const.AD_TRANSPORT_STARTTLS, ssl_verify_depth=const.DEFAULT_SSL_VERIFY_DEPTH, ssl_verify_hostname=True, ssl_ca_certs=None, timeout=60, username_attributes=None, call_id=None, ): creds = self.get_creds_for_ldap_idp(rikey, auth_type) service_account_username = creds.username service_account_password = creds.password self._verify_ldap_config_args( service_account_username, service_account_password, auth_type, transport_type, ) # Decrypt the end user password using our symmetric key try: password = self.decrypt_password(password) except InvalidToken as e: msg = "Unable to decrypt the password for user: {}. Please check that your CloudSSO enrollment code is correct. Failing the authentication".format( username) log.msg(msg) raise drpc.CallError(ERR_LDAP_PW_DECRYPT_FAILED, { "error": str(e), }) res = yield self.perform_authentication( servers, username, password, service_account_username, service_account_password, base_dns=base_dns, ntlm_domain=ntlm_domain, ntlm_workstation=ntlm_workstation, auth_type=auth_type, transport_type=transport_type, ssl_verify_depth=ssl_verify_depth, ssl_verify_hostname=ssl_verify_hostname, ssl_ca_certs=ssl_ca_certs, timeout=timeout, username_attributes=username_attributes, call_id=call_id, ) defer.returnValue(res)
def get_creds_for_ldap_idp(self, rikey, auth_type): try: creds = self.credential_mapping[rikey] if auth_type == const.AD_AUTH_TYPE_SSPI: creds = ldap_base.ServiceAccountCredential(username=None, password=None) return creds except KeyError as e: log.failure(ERR_LDAP_SSO_MISSING_RIKEY) raise drpc.CallError(ERR_LDAP_SSO_MISSING_RIKEY, { "error": str(e), })
def do_get_proxy_counter(self): log.msg("Reporting proxy counter") try: counter = secret_storage.access_proxy_counter() if self.debug: log.msg( "Found counter value {counter}".format(counter=counter)) except Exception as e: log.failure(ERR_COUNTER_FAILURE) raise drpc.CallError(ERR_COUNTER_FAILURE, { "during": "get_proxy_counter", "error": str(e) }) return { "counter": counter, }
def do_get_proxy_counter(self): log.msg('Reporting proxy counter') try: counter = secret_storage.access_proxy_counter() if self.debug: log.msg( 'Found counter value {counter}'.format(counter=counter)) except Exception as e: log.err(e, ERR_COUNTER_FAILURE) raise drpc.CallError(ERR_COUNTER_FAILURE, { 'during': 'get_proxy_counter', 'error': str(e) }) return { 'counter': counter, }
def do_parallel_workers(self, protocol, unblock_conn_id=None): conn_id = id(protocol) if unblock_conn_id: d = self.parallel_workers.get(unblock_conn_id) if d is None: raise drpc.CallError('bad arg', {'args': ['unblock_conn_id']}) else: d.callback(conn_id) unblocker = conn_id else: d = defer.Deferred() self.parallel_workers[conn_id] = d if self.first_worker is not None: self.first_worker.callback(conn_id) self.first_worker = None unblocker = yield d defer.returnValue({ 'conn_id': conn_id, 'unblocker': unblocker, })
def do_raises(self, error=None, error_args=None): if error: raise drpc.CallError(error, error_args) else: raise Exception('poof')
def do_unserializable(self): raise drpc.CallError(object())
def test_shared_catch_v2(self): with self.assertRaises(shared_call_error): raise drpc_v2.CallError('v2 call error')
def test_v1_catch_v2(self): with self.assertRaises(drpc_v1.CallError): raise drpc_v2.CallError('v2 call error')
def do_ldap_health_check( self, rikey, host, port, base_dns, filter_text=None, attributes=None, ntlm_domain=None, ntlm_workstation=None, auth_type=const.AD_AUTH_TYPE_NTLM_V2, transport_type=const.AD_TRANSPORT_STARTTLS, ssl_verify_depth=const.DEFAULT_SSL_VERIFY_DEPTH, ssl_verify_hostname=True, ssl_ca_certs=None, timeout=60, call_id=None, ): """ Performs a health check against the specified host by binding as the service user and executing a dummy search against each provided base DN to ensure the searches are executed without error. """ # Do this outside of the try/except. If something goes wrong with these # operations, then we should raise that error and not treat it as an # unhealthy result. service_account_credentials = self.get_creds_for_ldap_idp( rikey, auth_type) self._verify_ldap_config_args( service_account_credentials.username, service_account_credentials.password, auth_type, transport_type, ) log.msg(( "Performing health check: " "call_id={call_id} host={host} port={port} rikey={rikey} " "attributes={attributes} base_dns={base_dns} ntlm_domain={ntlm_domain} " "ntlm_workstation={ntlm_workstation} auth_type={auth_type} transport_type={transport_type} " "ssl_verify_depth={ssl_verify_depth} ssl_verify_hostname={ssl_verify_hostname} " "ssl_ca_certs={ssl_ca_certs}").format( call_id=call_id, host=host, port=port, rikey=rikey, attributes=attributes, base_dns=base_dns, ntlm_domain=ntlm_domain, ntlm_workstation=ntlm_workstation, auth_type=auth_type, transport_type=transport_type, ssl_verify_depth=ssl_verify_depth, ssl_verify_hostname=ssl_verify_hostname, ssl_ca_certs=ssl_ca_certs is not None, )) client, timeout_dc = yield self._get_client( host, port, transport_type, ssl_verify_depth, ssl_verify_hostname, ssl_ca_certs, timeout, self.debug, self.is_logging_insecure, ) try: try: # First make sure we can bind as the service user yield client.perform_bind( auth_type=auth_type, dn=service_account_credentials.username, username=service_account_credentials.username, password=service_account_credentials.password, domain=ntlm_domain, workstation=ntlm_workstation, permit_implicit=True, ) except OpenSSL.SSL.Error as e: error_message = util.retrieve_error_string_from_openssl_error( e) if OPENSSL_ERROR_INVALID_CERT in error_message: drpc_error = ERR_TLS_CERT elif OPENSSL_ERROR_INVALID_PROTOCOL in error_message: drpc_error = ERR_TLS_INVALID_PROTOCOL else: drpc_error = ERR_TLS_GENERIC log.failure(drpc_error) raise drpc.CallError(drpc_error, { "error": str(e), }) except ldaperrors.LDAPInvalidCredentials as e: raise drpc.CallError(ERR_LDAP_BIND_INVALID_CREDS, { "error": str(e), }) except Exception as e: # T76043: add better/more specific error handling so we can tell # Gary why the search failed if timeout_dc.active(): log.failure(ldap_base.ERR_LDAP_BIND_FAILED) raise drpc.CallError(ldap_base.ERR_LDAP_BIND_FAILED, { "error": str(e), }) else: log.failure(ldap_base.ERR_LDAP_TIMEOUT) raise drpc.CallError(ldap_base.ERR_LDAP_TIMEOUT, { "during": "bind", "error": str(e), }) # Then execute a search over each provided base DN to make sure that: # 1) The service user has search permissions # 2) Each of the provided base DN's exists filter_obj = (ldapfilter.parseFilter(filter_text.encode("utf-8")) if filter_text else None) try: for base_dn in base_dns: yield client.perform_search( dn=base_dn, filter_object=filter_obj, attributes=attributes, scope=pureldap.LDAP_SCOPE_baseObject, sizeLimit=1, ) except (ldaperrors.LDAPNoSuchObject, ldaperrors.LDAPReferral) as e: # https://ldap.com/ldap-result-code-reference-core-ldapv3-result-codes/#rc-noSuchObject # Object at the requested BaseDN doesn't exist. Since we are searching for just anything at all # this likely means the Base DN is invalid. # https://ldap.com/ldap-result-code-reference-core-ldapv3-result-codes/#rc-referral # We dont support referrals so it's basically an unknown/bad DN log.failure(ERR_LDAP_INVALID_BASE_DN) raise drpc.CallError(ERR_LDAP_INVALID_BASE_DN, { "error": str(e), "base_dn": str(base_dn) }) except ldap.client.ConnectionNotProperlyBoundError as e: log.failure(ldap_base.ERR_LDAP_SEARCH_FAILED_BAD_BIND) raise drpc.CallError(ldap_base.ERR_LDAP_SEARCH_FAILED_BAD_BIND, { "error": str(e), }) except Exception as e: # T76043: add better/more specific error handling so we can tell # Gary why the search failed if timeout_dc.active(): log.failure(ldap_base.ERR_LDAP_SEARCH_FAILED) raise drpc.CallError(ldap_base.ERR_LDAP_SEARCH_FAILED, { "error": str(e), }) else: log.failure(ldap_base.ERR_LDAP_TIMEOUT) raise drpc.CallError( ldap_base.ERR_LDAP_TIMEOUT, { "during": "search", "error": str(e), }, ) finally: if timeout_dc.active(): timeout_dc.cancel() try: client.transport.abortConnection() except Exception: log.failure( "Error cleaning up connection to host {}".format(host)) # If nothing went wrong by this point, then everything's healthy as # far as we can tell. result = { "healthy": True, } log.msg(result) defer.returnValue(result)
def fetch_ldap_attributes_from_server( self, host, port, rikey, desired_attributes, user_dn, ntlm_domain=None, ntlm_workstation=None, auth_type=const.AD_AUTH_TYPE_NTLM_V2, transport_type=const.AD_TRANSPORT_STARTTLS, ssl_verify_depth=const.DEFAULT_SSL_VERIFY_DEPTH, ssl_verify_hostname=True, ssl_ca_certs=None, timeout=60, call_id=None, ): creds = self.get_creds_for_ldap_idp(rikey, auth_type) bind_dn = creds.username bind_pw = creds.password log.msg(( "Performing user attributes fetch: " "call_id={call_id} host={host} port={port} rikey={rikey} " "desired_attributes={desired_attributes} user_dn={user_dn} ntlm_domain={ntlm_domain} " "ntlm_workstation={ntlm_workstation} auth_type={auth_type} transport_type={transport_type} " "ssl_verify_depth={ssl_verify_depth} ssl_verify_hostname={ssl_verify_hostname} " "ssl_ca_certs={ssl_ca_certs}").format( call_id=call_id, host=host, port=port, rikey=rikey, desired_attributes=desired_attributes, user_dn=user_dn, ntlm_domain=ntlm_domain, ntlm_workstation=ntlm_workstation, auth_type=auth_type, transport_type=transport_type, ssl_verify_depth=ssl_verify_depth, ssl_verify_hostname=ssl_verify_hostname, ssl_ca_certs=ssl_ca_certs is not None, )) self._verify_ldap_config_args(bind_dn, bind_pw, auth_type, transport_type) client, timeout_dc = yield self._get_client( host, port, transport_type, ssl_verify_depth, ssl_verify_hostname, ssl_ca_certs, timeout, self.debug, self.is_logging_insecure, ) # Try to do everything network-related try: # Bind as service user, for searching try: yield client.perform_bind( auth_type=auth_type, dn=bind_dn, username=bind_dn, password=bind_pw, domain=ntlm_domain, workstation=ntlm_workstation, permit_implicit=True, ) except Exception as e: if timeout_dc.active(): log.failure(ldap_base.ERR_LDAP_BIND_FAILED) raise drpc.CallError(ldap_base.ERR_LDAP_BIND_FAILED, {"error": six.text_type(e)}) else: log.failure(ldap_base.ERR_LDAP_TIMEOUT) raise drpc.CallError( ldap_base.ERR_LDAP_TIMEOUT, { "during": "bind", "error": six.text_type(e) }, ) # Search for the user try: result = yield client.perform_search( user_dn, None, attributes=desired_attributes, scope=pureldap.LDAP_SCOPE_baseObject, ) if len(result) != 1: log.error(AUTH_INVALID_USER) raise Exception(AUTH_INVALID_USER) result = result[0] except distinguishedname.InvalidRelativeDistinguishedName as irdn: log.failure(ldap_base.ERR_LDAP_BAD_AD_CONFIGURATION) raise drpc.CallError( ldap_base.ERR_LDAP_BAD_AD_CONFIGURATION, {"error": six.text_type(irdn)}, ) except Exception as e: log.failure(ldap_base.ERR_LDAP_SEARCH_FAILED) raise drpc.CallError(ldap_base.ERR_LDAP_SEARCH_FAILED, {"error": six.text_type(e)}) finally: # Clean up networking if timeout_dc.active(): timeout_dc.cancel() try: client.transport.abortConnection() except Exception: pass # At this point, result is a single LDAPEntry, or something has gone wrong and we ideally raised somewhere above encoded_dict = transform_result(result, desired_attributes) logged_atts = transform_result( result, desired_attributes, value_transform=self.to_unicode_ignore_errors) log.msg("For user dn {dn}, found attributes {atts}".format( dn=user_dn, atts=logged_atts)) defer.returnValue(encoded_dict)
def do_fetch_ldap_attributes( self, servers, rikey, desired_attributes, user_dn, ntlm_domain=None, ntlm_workstation=None, auth_type=const.AD_AUTH_TYPE_NTLM_V2, transport_type=const.AD_TRANSPORT_STARTTLS, ssl_verify_depth=const.DEFAULT_SSL_VERIFY_DEPTH, ssl_verify_hostname=True, ssl_ca_certs=None, timeout=60, call_id=None, ): exceptions = {} search_successful = False search_result = {} for server in servers: host = server["hostname"] port = server["port"] try: search_result = yield self.fetch_ldap_attributes_from_server( host=host, port=port, rikey=rikey, desired_attributes=desired_attributes, user_dn=user_dn, ntlm_domain=ntlm_domain, ntlm_workstation=ntlm_workstation, auth_type=auth_type, transport_type=transport_type, ssl_verify_depth=ssl_verify_depth, ssl_verify_hostname=ssl_verify_hostname, ssl_ca_certs=ssl_ca_certs, timeout=timeout, call_id=call_id, ) search_successful = True log.sso_ldap( msg="Fetched LDAP attributes from server", query_type=log.SSO_LDAP_QUERY_TYPE_ATTRIBUTE_FETCH, status=log.SSO_LDAP_QUERY_SUCCEEDED, server=host, port=port, username=user_dn, proxy_key=self.proxy_key, ) break except drpc.CallError as e: log.sso_ldap( msg="Failed to failed to fetch LDAP attributes from server", query_type=log.SSO_LDAP_QUERY_TYPE_ATTRIBUTE_FETCH, status=log.SSO_LDAP_QUERY_FAILED, server=host, port=port, username=user_dn, proxy_key=self.proxy_key, reason=e.error, ) exceptions[host] = { "error": e.error, "error_args": e.error_args } if self.connection_failures_for_all_servers(servers, exceptions): raise drpc.CallError( ERR_LDAP_CANNOT_SERVICE_REQUEST, { "error": str("Failed to communicate with any domain controllers") }, ) defer.returnValue({ "success": search_successful, "attributes": search_result, "msg": FETCH_SUCCESSFUL if search_successful else FETCH_FAILED, "exceptions": exceptions, })
def authenticate_against_server( self, host: str, port: str, username: str, password: str, service_account_username: str, service_account_password: str, base_dns: Optional[List[str]] = None, ntlm_domain: Optional[str] = None, ntlm_workstation: Optional[str] = None, auth_type: str = const.AD_AUTH_TYPE_NTLM_V2, transport_type: str = const.AD_TRANSPORT_STARTTLS, ssl_verify_depth: int = const.DEFAULT_SSL_VERIFY_DEPTH, ssl_verify_hostname: bool = True, ssl_ca_certs: Optional[str] = None, timeout: int = 60, username_attributes: Optional[List[str]] = None, call_id: Optional[int] = None, ): """ * username_attribute: attribute within AD that we will compare the username against. See do_ldap_search for further argument documentation. Returns: tuple(bool, str). - True if the auth was successful, - The full dn of the user who authed """ bind_dn = service_account_username bind_pw = service_account_password # At this point we *should* have a username attribute passed to us. But if we don't have one # we will default to the most popular attribute, samaccountname. This will only work for AD. username_attributes = (username_attributes if username_attributes else ["samaccountname"]) base_dns = base_dns if base_dns else [""] log.msg(( "Performing LDAP authentication: " "call_id={call_id} host={host} port={port} base_dns={base_dns} " "auth_type={auth_type} transport_type={transport_type} " "ssl_verify_depth={ssl_verify_depth} ssl_verify_hostname={ssl_verify_hostname} " "ssl_ca_certs={ssl_ca_certs} username={username} username_attributes={username_attributes}" ).format( call_id=call_id, host=host, port=port, base_dns=base_dns, auth_type=auth_type, transport_type=transport_type, ssl_verify_depth=ssl_verify_depth, ssl_verify_hostname=ssl_verify_hostname, username=username, username_attributes=username_attributes, ssl_ca_certs=ssl_ca_certs is not None, )) # The factory has a username attribute field on it but we will not be using it cl, timeout_dc = yield self._get_client( host, port, transport_type, ssl_verify_depth, ssl_verify_hostname, ssl_ca_certs, timeout, self.debug, self.is_logging_insecure, ) try: # Primary bind. This authenticates us to AD and allows us to # make search queries. try: yield cl.perform_bind( auth_type=auth_type, dn=bind_dn, username=bind_dn, password=bind_pw, domain=ntlm_domain, workstation=ntlm_workstation, permit_implicit=True, ) except Exception as e: if timeout_dc.active(): log.failure(ldap_base.ERR_LDAP_BIND_FAILED) raise drpc.CallError(ldap_base.ERR_LDAP_BIND_FAILED, { "error": str(e), }) else: log.failure(ldap_base.ERR_LDAP_TIMEOUT) raise drpc.CallError(ldap_base.ERR_LDAP_TIMEOUT, { "during": "bind", "error": str(e), }) username_match = dict.fromkeys(username_attributes, username) filterObject = yield cl.user_filter_object( username_matches=username_match) # Search for the user. With AD, the user's cn is their full # name, not username. So, we need to search for the user by comparing # the username to a selection of specific attributes. For example: # sAMAccountName or userprincipalname search_hits: List[BytesLDAPEntry] = [] try: # Always fetch msDS-PrincipalName since we need the user's # sAMAccountName and domain. The username entered by the user # may not be in a valid format for SSPI or NTLM depending on # the username attributes configured, but the sAMAccountName # that's part of msDS-PrincipalName will always work. attributes_to_fetch = { str(attr).lower() for attr in username_attributes } attributes_to_fetch.add("msds-principalname") # Also fetch some helpful attributes for debugging if something goes wrong attributes_to_fetch.add("objectclass") attributes_to_fetch.add("objectcategory") # To check multiple base dns we need to perform a search for each one. If the # combination of all of these searches returns more than 1 unique user we will # fail the auth for base_dn in base_dns: result = yield cl.perform_search( base_dn, filterObject, attributes=tuple(attributes_to_fetch)) search_hits.extend(result) except distinguishedname.InvalidRelativeDistinguishedName as e: log.failure(ldap_base.ERR_LDAP_BAD_AD_CONFIGURATION) raise drpc.CallError(ldap_base.ERR_LDAP_BAD_AD_CONFIGURATION, { "error": str(e), }) except Exception as e: log.failure( "{msg} for {username}", msg=ldap_base.ERR_LDAP_SEARCH_FAILED, username=username, ) raise drpc.CallError(ldap_base.ERR_LDAP_SEARCH_FAILED, { "error": str(e), }) if len(search_hits) == 0: err_msg = AUTH_INVALID_USER err = drpc.CallError(ldap_base.ERR_LDAP_SEARCH_FAILED, { "error": err_msg, }) log.error( "{msg} {username}. Error: {error}", msg=err_msg, username=username, error=err, ) raise err if len(search_hits) > 1: err_msg = AUTH_TOO_MANY_USERS err = drpc.CallError(ldap_base.ERR_LDAP_SEARCH_FAILED, { "error": err_msg, }) log.error( "{msg} while searching for {username}: {users}. Error: {error}", msg=err_msg, username=username, users=[user.dn.getText() for user in search_hits], error=err, ) raise err user_result = search_hits[0] user_full_dn = user_result.dn.getText() # Log out the search result matched_attributes = determine_matched_attributes( username, username_attributes, user_result) log.msg("Found {username} with attributes {atts}".format( username=username, atts=matched_attributes)) # Initialize these as None since Plain binds don't these values bind_username = None user_domain = None # If the auth type is not plain, determine the user's domain and # sAMAccountName by pulling them from msDS-PrincipalName. We need # both in order to do NTLM and SSPI authentications. if auth_type != AD_AUTH_TYPE_PLAIN: msds_principalname_attribute_set = user_result.get( "msDS-PrincipalName") if not msds_principalname_attribute_set: # msDS-PrincipalName must be provided so we can determine # the domain of the user authenticating. Abort the # authentication if the attribute doesn't exist for the user. err = drpc.CallError( ERR_LDAP_MISSING_REQUIRED_ATTRIBUTE, { "error": MSDS_PRINCIPAL_NAME_MISSING.format(username) }, ) log.error( "Error: {error}, User Result: {user_result}", error=err, user_result=user_result, ) raise err msds_principalname = list( user_result.get("msDS-PrincipalName"))[0].decode("utf8") if not msds_principalname or "\\" not in msds_principalname: # This means that msDS-PrincipalName was empty, a form # we can't work with (e.g. SID), or didn't have any # domain information on it. We can't guarantee we'll # log in the correct user without knowing their domain, # so abort the authentication. err = drpc.CallError( ERR_LDAP_INVALID_ATTRIBUTE_VALUE, { "error": INVALID_MSDS_PRINCIPAL_NAME.format(username) }, ) log.error( "Error: {error}, User Result: {user_result}", error=err, user_result=user_result, ) raise err user_domain, bind_username = msds_principalname.split("\\", 1) # Secondary bind. Assuming the user we queried exists, attempt # to bind as them (this is essentially performing an authentication). try: yield cl.perform_bind( auth_type=auth_type, dn=user_full_dn, username=bind_username, password=password, domain=user_domain, workstation=ntlm_workstation, permit_implicit=False, ) except Exception as e: log.msg( "Authentication failed for user {username} ({dn})".format( username=username, dn=user_full_dn)) log.msg(e) defer.returnValue((False, None)) finally: if timeout_dc.active(): timeout_dc.cancel() try: cl.transport.abortConnection() except Exception: pass log.msg("Authentication succeeded for user {username} ({dn})".format( username=username, dn=user_full_dn)) defer.returnValue((True, user_full_dn))
def perform_authentication( self, servers, username, password, service_account_username, service_account_password, base_dns=None, ntlm_domain=None, ntlm_workstation=None, auth_type=const.AD_AUTH_TYPE_NTLM_V2, transport_type=const.AD_TRANSPORT_STARTTLS, ssl_verify_depth=const.DEFAULT_SSL_VERIFY_DEPTH, ssl_verify_hostname=True, ssl_ca_certs=None, timeout=60, username_attributes=None, call_id=None, ): """ Authenticates against the provided servers in random order until a successful authentication occurs or the list of servers has been exhausted. See do_ldap_search for further argument documentation. Args: servers (list): List of dicts, each containing a 'hostname' and 'port' Returns: A dict containing: success (bool): True if the auth was successful msg (str): The message associated with the result of the auth exceptions (list): Dict of CallError keyed on hostname containing any errors that occurred during the authentication process. Note that the presence of errors does not indicate auth failure! For example, the exceptions list will be non-empty if the first server could not be reached but the second server serviced the auth. """ # Assume user authentication never legitimately uses anonymous bind. if not password: defer.returnValue({ "success": False, "msg": AUTH_FAILED, "exceptions": {}, }) exceptions = {} auth_successful = False user_full_dn = None # Try each server in random order for server in servers: host = server["hostname"] port = server["port"] try: auth_successful, user_full_dn = yield self.authenticate_against_server( host=host, port=port, username=username, password=password, service_account_username=service_account_username, service_account_password=service_account_password, base_dns=base_dns, ntlm_domain=ntlm_domain, ntlm_workstation=ntlm_workstation, auth_type=auth_type, transport_type=transport_type, ssl_verify_depth=ssl_verify_depth, ssl_verify_hostname=ssl_verify_hostname, ssl_ca_certs=ssl_ca_certs, timeout=timeout, username_attributes=username_attributes, call_id=call_id, ) # If we reach this line without an exception, the auth was # serviced successfully and we don't need to check any more # servers. if auth_successful: log.sso_ldap( msg="Successful authentication against server", query_type=log.SSO_LDAP_QUERY_TYPE_AUTH, status=log.SSO_LDAP_QUERY_SUCCEEDED, server=host, port=port, username=username, proxy_key=self.proxy_key, ) else: log.sso_ldap( msg="Failed authentication against server", query_type=log.SSO_LDAP_QUERY_TYPE_AUTH, status=log.SSO_LDAP_QUERY_FAILED, server=host, port=port, username=username, proxy_key=self.proxy_key, reason="Invalid credentials", ) break except drpc.CallError as e: # serialize the errors in a similar format to what drpc does log.sso_ldap( msg="Failed authentication against server", query_type=log.SSO_LDAP_QUERY_TYPE_AUTH, status=log.SSO_LDAP_QUERY_FAILED, server=host, port=port, username=username, proxy_key=self.proxy_key, reason=e.error, ) exceptions[host] = { "error": e.error, "error_args": e.error_args, } if self.connection_failures_for_all_servers(servers, exceptions): raise drpc.CallError( ERR_LDAP_CANNOT_SERVICE_REQUEST, { "error": str("Failed to communicate with any domain controllers") }, ) if self.too_many_users_failure_on_all_servers(servers, exceptions): error = drpc.CallError( ERR_LDAP_NON_UNIQUE_USERNAME_ATTR, { "error": "Each domain controller returned more than one user for the provided username attributes." }, ) log.error( "{msg} {username}. Error: {error}", msg=ERR_LDAP_NON_UNIQUE_USERNAME_ATTR, username=username, error=error, ) raise error defer.returnValue({ "success": auth_successful, "user_full_dn": user_full_dn, "msg": AUTH_SUCCEEDED if auth_successful else AUTH_FAILED, "exceptions": exceptions, })