X-Git-Url: http://git.cascardo.info/?p=cascardo%2Fipsilon.git;a=blobdiff_plain;f=ipsilon%2Finfo%2Finfoldap.py;h=a197157e369f226408c40537be87ae926fc3dd87;hp=70d36d5d6412c1b7b23d59c3dccba1062abdbfc4;hb=HEAD;hpb=62b4656571be6e8671ada295047eac385d330f66 diff --git a/ipsilon/info/infoldap.py b/ipsilon/info/infoldap.py old mode 100755 new mode 100644 index 70d36d5..a197157 --- a/ipsilon/info/infoldap.py +++ b/ipsilon/info/infoldap.py @@ -1,69 +1,64 @@ -#!/usr/bin/python -# -# 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.log import Log +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', -} - - -class InfoProvider(InfoProviderBase, Log): - - def __init__(self): - super(InfoProvider, self).__init__() - self.mapper = InfoMapping() - self.mapper.set_mapping(ldap_mapping) +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 = Policy(ldap_mapping) self.name = 'ldap' self.description = """ Info plugin that uses LDAP to retrieve user data. """ - self._options = { - 'server url': [ - """ The LDAP server url """, - 'string', - 'ldap://example.com' - ], - 'tls': [ - " What TLS level show be required " + - "(Demand, Allow, Try, Never, NoTLS) ", - 'string', - 'Demand' - ], - 'bind dn': [ - """ User DN to bind as, if empty uses anonymous bind. """, - 'string', - 'uid=ipsilon,ou=People,dc=example,dc=com' - ], - 'bind password': [ - """ Password to use for bind operation """, - 'string', - 'Password' - ], - 'user dn template': [ - """ Template to turn username into DN. """, - 'string', - 'uid=%(username)s,ou=People,dc=example,dc=com' - ], - } + self.new_config( + self.name, + pconfig.String( + 'server url', + 'The LDAP server url.', + 'ldap://example.com'), + pconfig.Template( + 'user dn template', + 'Template to turn username into DN.', + 'uid=%(username)s,ou=People,dc=example,dc=com'), + pconfig.Pick( + 'tls', + 'What TLS level show be required', + ['Demand', 'Allow', 'Try', 'Never', 'NoTLS'], + 'Demand'), + pconfig.String( + 'bind dn', + 'DN to bind as, if empty uses anonymous bind.', + 'uid=ipsilon,ou=People,dc=example,dc=com'), + 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 def server_url(self): @@ -85,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() @@ -118,47 +117,79 @@ 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 {} class Installer(InfoProviderInstaller): - def __init__(self): + def __init__(self, *pargs): super(Installer, self).__init__() self.name = 'ldap' + self.pargs = pargs def install_args(self, group): group.add_argument('--info-ldap', choices=['yes', 'no'], default='no', @@ -171,24 +202,23 @@ 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 # Add configuration data to database - po = PluginObject() + po = PluginObject(*self.pargs) po.name = 'ldap' po.wipe_data() - po.wipe_config_values(self.facility) + po.wipe_config_values() config = dict() if 'info_ldap_server_url' in opts: 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: @@ -197,16 +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' - po.save_plugin_config(self.facility, config) - - # Replace global config, only one plugin info can be used - po.name = 'global' - globalconf = po.get_plugin_config(self.facility) - if 'order' in globalconf: - order = globalconf['order'].split(',') + 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: - order = [] - order.append('ldap') - globalconf['order'] = ','.join(order) - po.save_plugin_config(self.facility, globalconf) + 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 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