-#!/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):
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):
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
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)
error_password=not password,
error_username=not username
)
- # pylint: disable=star-args
+ self.lm.set_auth_error()
return self._template('login/form.html', **context)
'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',
@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
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