diff --git a/rhodecode/apps/admin/tests/test_admin_auth_settings.py b/rhodecode/apps/admin/tests/test_admin_auth_settings.py --- a/rhodecode/apps/admin/tests/test_admin_auth_settings.py +++ b/rhodecode/apps/admin/tests/test_admin_auth_settings.py @@ -86,6 +86,7 @@ class TestAuthSettingsView(object): 'host': 'dc.example.com', 'port': '999', + 'timeout': 3600, 'tls_kind': 'PLAIN', 'tls_reqcert': 'NEVER', diff --git a/rhodecode/authentication/plugins/auth_ldap.py b/rhodecode/authentication/plugins/auth_ldap.py --- a/rhodecode/authentication/plugins/auth_ldap.py +++ b/rhodecode/authentication/plugins/auth_ldap.py @@ -22,6 +22,7 @@ RhodeCode authentication plugin for LDAP """ +import socket import re import colander import logging @@ -90,6 +91,16 @@ class LdapSettingsSchema(AuthnPluginSett title=_('Port'), validator=colander.Range(min=0, max=65536), widget='int') + + timeout = colander.SchemaNode( + colander.Int(), + default=60 * 5, + description=_('Timeout for LDAP connection'), + preparer=strip_whitespace, + title=_('Connection timeout'), + validator=colander.Range(min=1), + widget='int') + dn_user = colander.SchemaNode( colander.String(), default='', @@ -191,19 +202,47 @@ class LdapSettingsSchema(AuthnPluginSett class AuthLdap(object): def _build_servers(self): + def host_resolver(host, port): + """ + Main work for this function is to prevent ldap connection issues, + and detect them early using a "greenified" sockets + """ + host = host.strip() + + log.info('Resolving LDAP host %s', host) + try: + ip = socket.gethostbyname(host) + log.info('Got LDAP server %s ip %s', host, ip) + except Exception: + raise LdapConnectionError( + 'Failed to resolve host: `{}`'.format(host)) + + log.info('Checking LDAP IP access %s', ip) + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.connect((ip, int(port))) + s.shutdown(socket.SHUT_RD) + except Exception: + raise LdapConnectionError( + 'Failed to connect to host: `{}:{}`'.format(host, port)) + + return '{}:{}'.format(host, port) + + port = self.LDAP_SERVER_PORT return ', '.join( - ["{}://{}:{}".format( - self.ldap_server_type, host.strip(), self.LDAP_SERVER_PORT) + ["{}://{}".format( + self.ldap_server_type, host_resolver(host, port)) for host in self.SERVER_ADDRESSES]) def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='', tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3, search_scope='SUBTREE', attr_login='uid', - ldap_filter=''): + ldap_filter='', timeout=None): if ldap == Missing: raise LdapImportError("Missing or incompatible ldap library") self.debug = False + self.timeout = timeout or 60 * 5 self.ldap_version = ldap_version self.ldap_server_type = 'ldap' @@ -231,22 +270,25 @@ class AuthLdap(object): self.LDAP_FILTER = safe_str(ldap_filter) def _get_ldap_conn(self): + log.debug('initializing LDAP connection to:%s', self.LDAP_SERVER) + if self.debug: ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255) if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'): - ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, - '/etc/openldap/cacerts') - ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF) - ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON) - ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 60 * 10) - ldap.set_option(ldap.OPT_TIMEOUT, 60 * 10) - + ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts') if self.TLS_KIND != 'PLAIN': ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT) - log.debug('initializing LDAP connection to:%s', self.LDAP_SERVER) + ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF) + ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON) + + # init connection now ldap_conn = ldap.initialize(self.LDAP_SERVER) + ldap_conn.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout) + ldap_conn.set_option(ldap.OPT_TIMEOUT, self.timeout) + ldap_conn.timeout = self.timeout + if self.ldap_version == 2: ldap_conn.protocol = ldap.VERSION2 else: @@ -446,6 +488,7 @@ class RhodeCodeAuthPlugin(RhodeCodeExter 'attr_login': settings.get('attr_login'), 'ldap_version': 3, 'ldap_filter': settings.get('filter'), + 'timeout': settings.get('timeout') } ldap_attrs = self.try_dynamic_binding(username, password, ldap_args) @@ -496,3 +539,4 @@ class RhodeCodeAuthPlugin(RhodeCodeExter except (Exception,): log.exception("Other exception") return None +