X-Git-Url: http://git.cascardo.eti.br/?a=blobdiff_plain;f=ipsilon%2Flogin%2Fauthldap.py;h=6e9afd377e459f847c35cd7e5a040e05b4eb5766;hb=7ba361e006e7cc178132a103fc62403409689516;hp=06dac0924ce0619b874607bb6a072db5ed484821;hpb=b7b80c5c0fc1895e85aae3acbfcbbc593a42697f;p=cascardo%2Fipsilon.git diff --git a/ipsilon/login/authldap.py b/ipsilon/login/authldap.py old mode 100755 new mode 100644 index 06dac09..6e9afd3 --- a/ipsilon/login/authldap.py +++ b/ipsilon/login/authldap.py @@ -1,13 +1,37 @@ -#!/usr/bin/python -# -# 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 +from ipsilon.login.common import LoginFormBase, LoginManagerBase, \ + LoginManagerInstaller from ipsilon.util.plugin import PluginObject from ipsilon.util.log import Log 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): @@ -49,9 +54,11 @@ class LDAP(LoginFormBase, Log): self.lm.info = None if not self.ldap_info: - self.ldap_info = LDAPInfo() + self.ldap_info = LDAPInfo(self._site) - return self.ldap_info.get_user_data_from_conn(conn, dn) + base = self.lm.base_dn + return self.ldap_info.get_user_data_from_conn(conn, dn, base, + username) return None @@ -59,29 +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: - userdata = self._authenticate(username, password) - if userdata: - userattrs = dict() - for d, v in userdata.get('userdata', {}).items(): - userattrs[d] = v - if 'groups' in userdata: - userattrs['groups'] = userdata['groups'] - if 'extras' in userdata: - userattrs['extras'] = userdata['extras'] - authed = True - except Exception, e: # pylint: disable=broad-except + userattrs = self._authenticate(username, password) + 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) @@ -91,7 +101,7 @@ class LDAP(LoginFormBase, Log): error_password=not password, error_username=not username ) - # pylint: disable=star-args + self.lm.set_auth_error() return self._template('login/form.html', **context) @@ -117,6 +127,10 @@ authentication. """ 'bind dn template', 'Template to turn username into DN.', 'uid=%(username)s,ou=People,dc=example,dc=com'), + pconfig.String( + 'base dn', + 'The base dn to look for users and groups', + 'dc=example,dc=com'), pconfig.Condition( 'get user info', 'Get user info via ldap using user credentials', @@ -162,33 +176,43 @@ authentication. """ @property def get_user_info(self): - return (self.get_config_value('get user info').lower() == 'yes') + return self.get_config_value('get user info') @property def bind_dn_tmpl(self): return self.get_config_value('bind dn template') + @property + def base_dn(self): + return self.get_config_value('base dn') + def get_tree(self, site): self.page = LDAP(site, self, 'login/ldap') return self.page -class Installer(object): +class Installer(LoginManagerInstaller): def __init__(self, *pargs): + super(Installer, self).__init__() self.name = 'ldap' - self.ptype = 'login' self.pargs = pargs def install_args(self, group): group.add_argument('--ldap', choices=['yes', 'no'], default='no', - help='Configure PAM authentication') + help='Configure LDAP authentication') group.add_argument('--ldap-server-url', action='store', help='LDAP Server Url') group.add_argument('--ldap-bind-dn-template', action='store', help='LDAP Bind DN Template') - - def configure(self, opts): + 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, changes): if opts['ldap'] != 'yes': return @@ -201,11 +225,64 @@ class Installer(object): 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 po.is_enabled = True po.save_enabled_state() + + # For selinux enabled platforms permit httpd to connect to ldap, + # ignore if it fails + try: + subprocess.call(['/usr/sbin/setsebool', '-P', + 'httpd_can_connect_ldap=on']) + except Exception: # pylint: disable=broad-except + pass