X-Git-Url: http://git.cascardo.info/?p=cascardo%2Fipsilon.git;a=blobdiff_plain;f=ipsilon%2Finfo%2Finfoldap.py;h=a197157e369f226408c40537be87ae926fc3dd87;hp=6ba5b0dcb7031f8c80ff6f3817007b2723e32f79;hb=HEAD;hpb=45cb73a21a90084818c3057e362ef9459f1600f3 diff --git a/ipsilon/info/infoldap.py b/ipsilon/info/infoldap.py index 6ba5b0d..a197157 100644 --- a/ipsilon/info/infoldap.py +++ b/ipsilon/info/infoldap.py @@ -1,36 +1,34 @@ -# Copyright (C) 2014 Ipsilon Project Contributors -# -# See the file named COPYING for the project license +# Copyright (C) 2014 Ipsilon project Contributors, for license see COPYING from ipsilon.info.common import InfoProviderBase from ipsilon.info.common import InfoProviderInstaller -from ipsilon.info.common import InfoMapping from ipsilon.util.plugin import PluginObject +from ipsilon.util.policy import Policy from ipsilon.util import config as pconfig import ldap +import subprocess # TODO: fetch mapping from configuration -ldap_mapping = { - 'cn': 'fullname', - 'commonname': 'fullname', - 'sn': 'surname', - 'mail': 'email', - 'destinationindicator': 'country', - 'postalcode': 'postcode', - 'st': 'state', - 'statetorprovincename': 'state', - 'streetaddress': 'street', - 'telephonenumber': 'phone', -} +ldap_mapping = [ + ['cn', 'fullname'], + ['commonname', 'fullname'], + ['sn', 'surname'], + ['mail', 'email'], + ['destinationindicator', 'country'], + ['postalcode', 'postcode'], + ['st', 'state'], + ['statetorprovincename', 'state'], + ['streetaddress', 'street'], + ['telephonenumber', 'phone'], +] class InfoProvider(InfoProviderBase): def __init__(self, *pargs): super(InfoProvider, self).__init__(*pargs) - self.mapper = InfoMapping() - self.mapper.set_mapping(ldap_mapping) + self.mapper = Policy(ldap_mapping) self.name = 'ldap' self.description = """ Info plugin that uses LDAP to retrieve user data. """ @@ -56,6 +54,10 @@ Info plugin that uses LDAP to retrieve user data. """ pconfig.String( 'bind password', 'Password to use for bind operation'), + pconfig.String( + 'base dn', + 'The base dn to look for users and groups', + 'dc=example,dc=com'), ) @property @@ -78,6 +80,10 @@ Info plugin that uses LDAP to retrieve user data. """ def user_dn_tmpl(self): return self.get_config_value('user dn template') + @property + def base_dn(self): + return self.get_config_value('base dn') + def _ldap_bind(self): tls = self.tls.lower() @@ -111,39 +117,70 @@ Info plugin that uses LDAP to retrieve user data. """ raise Exception('No unique user object could be found!') data = dict() for name, value in result[0][1].iteritems(): - if type(value) is list and len(value) == 1: + if isinstance(value, list) and len(value) == 1: value = value[0] data[name] = value return data - def _get_user_groups(self, conn, dn, ldapattrs): + def _get_user_groups(self, conn, base, username): # TODO: fixme to support RFC2307bis schemas - if 'memberuid' in ldapattrs: - return ldapattrs['memberuid'] - else: + results = conn.search_s(base, ldap.SCOPE_SUBTREE, + filterstr='memberuid=%s' % username) + if results is None or results == []: + self.debug('No groups for %s' % username) return [] + groups = [] + for r in results: + if 'cn' in r[1]: + groups.append(r[1]['cn'][0]) + return groups - def get_user_data_from_conn(self, conn, dn): + def get_user_data_from_conn(self, conn, dn, base, username): reply = dict() try: ldapattrs = self._get_user_data(conn, dn) - userattrs, extras = self.mapper.map_attrs(ldapattrs) - groups = self._get_user_groups(conn, dn, ldapattrs) - reply['userdata'] = userattrs - reply['groups'] = groups - reply['extras'] = {'ldap': extras} + self.debug('LDAP attrs for %s: %s' % (dn, ldapattrs)) + userattrs, extras = self.mapper.map_attributes(ldapattrs) + groups = self._get_user_groups(conn, base, username) + reply = userattrs + reply['_groups'] = groups + reply['_extras'] = {'ldap': extras} except Exception, e: # pylint: disable=broad-except - self.error(e) + self.error('Error fetching/mapping LDAP user data: %s' % e) return reply def get_user_attrs(self, user): try: - conn = self._ldap_bind() dn = self.user_dn_tmpl % {'username': user} - return self.get_user_data_from_conn(conn, dn) - except Exception, e: # pylint: disable=broad-except - self.error(e) + except ValueError as e: + self.error( + 'DN generation failed with template %s, user %s: %s' + % (self.user_dn_tmpl, user, e) + ) + return {} + except Exception as e: # pylint: disable=broad-except + self.error( + 'Unhandled error generating DN from %s, user %s: %s' + % (self.user_dn_tmpl, user, e) + ) + return {} + + try: + conn = self._ldap_bind() + base = self.base_dn + return self.get_user_data_from_conn(conn, dn, base, user) + except ldap.LDAPError as e: + self.error( + 'LDAP search failed for DN %s on base %s: %s' % + (dn, base, e) + ) + return {} + except Exception as e: # pylint: disable=broad-except + self.error( + 'Unhandled LDAP error for DN %s on base %s: %s' % + (dn, base, e) + ) return {} @@ -165,8 +202,10 @@ class Installer(InfoProviderInstaller): help='LDAP Bind Password') group.add_argument('--info-ldap-user-dn-template', action='store', help='LDAP User DN Template') + group.add_argument('--info-ldap-base-dn', action='store', + help='LDAP Base DN') - def configure(self, opts): + def configure(self, opts, changes): if opts['info_ldap'] != 'yes': return @@ -180,9 +219,6 @@ class Installer(InfoProviderInstaller): config['server url'] = opts['info_ldap_server_url'] elif 'ldap_server_url' in opts: config['server url'] = opts['ldap_server_url'] - config = {'bind dn': opts['info_ldap_bind_dn']} - config = {'bind password': opts['info_ldap_bind_pwd']} - config = {'user dn template': opts['info_ldap_user_dn_template']} if 'info_ldap_bind_dn' in opts: config['bind dn'] = opts['info_ldap_bind_dn'] if 'info_ldap_bind_pwd' in opts: @@ -191,9 +227,26 @@ class Installer(InfoProviderInstaller): config['user dn template'] = opts['info_ldap_user_dn_template'] elif 'ldap_bind_dn_template' in opts: config['user dn template'] = opts['ldap_bind_dn_template'] - config['tls'] = 'Demand' + if 'info_ldap_tls_level' in opts and opts['info_ldap_tls_level']: + config['tls'] = opts['info_ldap_tls_level'] + elif 'ldap_tls_level' in opts and opts['ldap_tls_level']: + config['tls'] = opts['ldap_tls_level'] + else: + config['tls'] = 'Demand' + if 'info_ldap_base_dn' in opts and opts['info_ldap_base_dn']: + config['base dn'] = opts['info_ldap_base_dn'] + elif 'ldap_base_dn' in opts and opts['ldap_base_dn']: + config['base dn'] = opts['ldap_base_dn'] po.save_plugin_config(config) - # Update global config to add login plugin + # Update global config to add info 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