X-Git-Url: http://git.cascardo.info/?p=cascardo%2Fipsilon.git;a=blobdiff_plain;f=ipsilon%2Flogin%2Fauthldap.py;h=6e9afd377e459f847c35cd7e5a040e05b4eb5766;hp=db5836073111359ea9c40388c18264a9eeb7952f;hb=HEAD;hpb=521a28fd446a64c4fa5895e1aa768512249652f6 diff --git a/ipsilon/login/authldap.py b/ipsilon/login/authldap.py index db58360..6e9afd3 100644 --- a/ipsilon/login/authldap.py +++ b/ipsilon/login/authldap.py @@ -1,4 +1,4 @@ -# Copyright (C) 2014 Ipsilon Contributors, see COPYING for license +# Copyright (C) 2014 Ipsilon project Contributors, for license see COPYING from ipsilon.login.common import LoginFormBase, LoginManagerBase, \ LoginManagerInstaller @@ -8,6 +8,30 @@ from ipsilon.util import config as pconfig from ipsilon.info.infoldap import InfoProvider as LDAPInfo import ldap import subprocess +import logging + + +def ldap_connect(server_url, tls): + tls = tls.lower() + tls_req_opt = None + if tls == "never": + tls_req_opt = ldap.OPT_X_TLS_NEVER + elif tls == "demand": + tls_req_opt = ldap.OPT_X_TLS_DEMAND + elif tls == "allow": + tls_req_opt = ldap.OPT_X_TLS_ALLOW + elif tls == "try": + tls_req_opt = ldap.OPT_X_TLS_TRY + if tls_req_opt is not None: + ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, tls_req_opt) + + conn = ldap.initialize(server_url) + + if tls != "notls": + if not server_url.startswith("ldaps"): + conn.start_tls_s() + + return conn class LDAP(LoginFormBase, Log): @@ -17,26 +41,7 @@ class LDAP(LoginFormBase, Log): self.ldap_info = None def _ldap_connect(self): - - tls = self.lm.tls.lower() - tls_req_opt = None - if tls == "never": - tls_req_opt = ldap.OPT_X_TLS_NEVER - elif tls == "demand": - tls_req_opt = ldap.OPT_X_TLS_DEMAND - elif tls == "allow": - tls_req_opt = ldap.OPT_X_TLS_ALLOW - elif tls == "try": - tls_req_opt = ldap.OPT_X_TLS_TRY - if tls_req_opt is not None: - ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, tls_req_opt) - - conn = ldap.initialize(self.lm.server_url) - - if tls != "notls": - if not self.lm.server_url.startswith("ldaps"): - conn.start_tls_s() - return conn + return ldap_connect(self.lm.server_url, self.lm.tls) def _authenticate(self, username, password): @@ -61,21 +66,32 @@ class LDAP(LoginFormBase, Log): username = kwargs.get("login_name") password = kwargs.get("login_password") userattrs = None - authed = False + authok = False errmsg = None if username and password: try: userattrs = self._authenticate(username, password) - authed = True - except Exception, e: # pylint: disable=broad-except + authok = True + except ldap.INVALID_CREDENTIALS as e: errmsg = "Authentication failed" + self.error(errmsg) + except ldap.LDAPError as e: + errmsg = 'Internal system error' + if isinstance(e, ldap.TIMEOUT): + self.error('LDAP request timed out') + else: + desc = e.args[0]['desc'].strip() + info = e.args[0].get('info', '').strip() + self.error("%s: %s %s" % (e.__class__.__name__, + desc, info)) + except Exception as e: # pylint: disable=broad-except + errmsg = 'Internal system error' self.error("Exception raised: [%s]" % repr(e)) else: - errmsg = "Username or password is missing" - self.error(errmsg) + self.error("Username or password is missing") - if authed: + if authok: return self.lm.auth_successful(self.trans, username, 'password', userdata=userattrs) @@ -86,7 +102,6 @@ class LDAP(LoginFormBase, Log): error_username=not username ) self.lm.set_auth_error() - # pylint: disable=star-args return self._template('login/form.html', **context) @@ -190,10 +205,14 @@ class Installer(LoginManagerInstaller): help='LDAP Server Url') group.add_argument('--ldap-bind-dn-template', action='store', help='LDAP Bind DN Template') + group.add_argument('--ldap-tls-level', default='Demand', + choices=['Demand', 'Allow', 'Try', 'Never', + 'NoTLS'], + help='LDAP TLS level') group.add_argument('--ldap-base-dn', action='store', help='LDAP Base DN') - def configure(self, opts): + def configure(self, opts, changes): if opts['ldap'] != 'yes': return @@ -206,11 +225,54 @@ class Installer(LoginManagerInstaller): config = dict() if 'ldap_server_url' in opts: config['server url'] = opts['ldap_server_url'] + else: + logging.error('LDAP Server URL is required') + return False if 'ldap_bind_dn_template' in opts: + try: + opts['ldap_bind_dn_template'] % {'username': 'test'} + except KeyError: + logging.error( + 'Bind DN template does not contain %(username)s' + ) + return False + except ValueError as e: + logging.error( + 'Invalid syntax in Bind DN template: %s ', + e + ) + return False config['bind dn template'] = opts['ldap_bind_dn_template'] - config['tls'] = 'Demand' + if 'ldap_tls_level' in opts and opts['ldap_tls_level'] is not None: + config['tls'] = opts['ldap_tls_level'] + else: + config['tls'] = 'Demand' if 'ldap_base_dn' in opts and opts['ldap_base_dn'] is not None: config['base dn'] = opts['ldap_base_dn'] + test_dn = config['base dn'] + else: + # default set in the config object + test_dn = 'dc=example,dc=com' + + # Test the LDAP connection anonymously + try: + lh = ldap_connect(config['server url'], config['tls']) + lh.simple_bind_s('', '') + lh.search_s(test_dn, ldap.SCOPE_BASE, + attrlist=['objectclasses']) + except ldap.INSUFFICIENT_ACCESS: + logging.warn('Anonymous access not allowed, continuing') + except ldap.UNWILLING_TO_PERFORM: # probably minSSF issue + logging.warn('LDAP server unwilling to perform, expect issues') + except ldap.SERVER_DOWN: + logging.warn('LDAP server is down') + except ldap.NO_SUCH_OBJECT: + logging.error('Base DN not found') + return False + except ldap.LDAPError as e: + logging.error(e) + return False + po.save_plugin_config(config) # Update global config to add login plugin