1 # Copyright (C) 2014 Ipsilon project Contributors, for license see COPYING
3 from ipsilon.info.common import InfoProviderBase
4 from ipsilon.info.common import InfoProviderInstaller
5 from ipsilon.util.plugin import PluginObject
6 from ipsilon.util.policy import Policy
7 from ipsilon.util import config as pconfig
12 # TODO: fetch mapping from configuration
15 ['commonname', 'fullname'],
18 ['destinationindicator', 'country'],
19 ['postalcode', 'postcode'],
21 ['statetorprovincename', 'state'],
22 ['streetaddress', 'street'],
23 ['telephonenumber', 'phone'],
27 class InfoProvider(InfoProviderBase):
29 def __init__(self, *pargs):
30 super(InfoProvider, self).__init__(*pargs)
31 self.mapper = Policy(ldap_mapping)
33 self.description = """
34 Info plugin that uses LDAP to retrieve user data. """
39 'The LDAP server url.',
40 'ldap://example.com'),
43 'Template to turn username into DN.',
44 'uid=%(username)s,ou=People,dc=example,dc=com'),
47 'What TLS level show be required',
48 ['Demand', 'Allow', 'Try', 'Never', 'NoTLS'],
52 'DN to bind as, if empty uses anonymous bind.',
53 'uid=ipsilon,ou=People,dc=example,dc=com'),
56 'Password to use for bind operation'),
59 'The base dn to look for users and groups',
65 return self.get_config_value('server url')
69 return self.get_config_value('tls')
73 return self.get_config_value('bind dn')
76 def bind_password(self):
77 return self.get_config_value('bind password')
80 def user_dn_tmpl(self):
81 return self.get_config_value('user dn template')
85 return self.get_config_value('base dn')
89 tls = self.tls.lower()
92 tls_req_opt = ldap.OPT_X_TLS_NEVER
94 tls_req_opt = ldap.OPT_X_TLS_DEMAND
96 tls_req_opt = ldap.OPT_X_TLS_ALLOW
98 tls_req_opt = ldap.OPT_X_TLS_TRY
99 if tls_req_opt is not None:
100 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, tls_req_opt)
102 conn = ldap.initialize(self.server_url)
105 if not self.server_url.startswith("ldaps"):
108 conn.simple_bind_s(self.bind_dn, self.bind_password)
112 def _get_user_data(self, conn, dn):
113 result = conn.search_s(dn, ldap.SCOPE_BASE)
114 if result is None or result == []:
115 raise Exception('User object could not be found!')
116 elif len(result) > 1:
117 raise Exception('No unique user object could be found!')
119 for name, value in result[0][1].iteritems():
120 if isinstance(value, list) and len(value) == 1:
125 def _get_user_groups(self, conn, base, username):
126 # TODO: fixme to support RFC2307bis schemas
127 results = conn.search_s(base, ldap.SCOPE_SUBTREE,
128 filterstr='memberuid=%s' % username)
129 if results is None or results == []:
130 self.debug('No groups for %s' % username)
135 groups.append(r[1]['cn'][0])
138 def get_user_data_from_conn(self, conn, dn, base, username):
141 ldapattrs = self._get_user_data(conn, dn)
142 self.debug('LDAP attrs for %s: %s' % (dn, ldapattrs))
143 userattrs, extras = self.mapper.map_attributes(ldapattrs)
144 groups = self._get_user_groups(conn, base, username)
146 reply['_groups'] = groups
147 reply['_extras'] = {'ldap': extras}
148 except Exception, e: # pylint: disable=broad-except
149 self.error('Error fetching/mapping LDAP user data: %s' % e)
153 def get_user_attrs(self, user):
155 dn = self.user_dn_tmpl % {'username': user}
156 except ValueError as e:
158 'DN generation failed with template %s, user %s: %s'
159 % (self.user_dn_tmpl, user, e)
162 except Exception as e: # pylint: disable=broad-except
164 'Unhandled error generating DN from %s, user %s: %s'
165 % (self.user_dn_tmpl, user, e)
170 conn = self._ldap_bind()
172 return self.get_user_data_from_conn(conn, dn, base, user)
173 except ldap.LDAPError as e:
175 'LDAP search failed for DN %s on base %s: %s' %
179 except Exception as e: # pylint: disable=broad-except
181 'Unhandled LDAP error for DN %s on base %s: %s' %
187 class Installer(InfoProviderInstaller):
189 def __init__(self, *pargs):
190 super(Installer, self).__init__()
194 def install_args(self, group):
195 group.add_argument('--info-ldap', choices=['yes', 'no'], default='no',
196 help='Use LDAP to populate user attrs')
197 group.add_argument('--info-ldap-server-url', action='store',
198 help='LDAP Server Url')
199 group.add_argument('--info-ldap-bind-dn', action='store',
201 group.add_argument('--info-ldap-bind-pwd', action='store',
202 help='LDAP Bind Password')
203 group.add_argument('--info-ldap-user-dn-template', action='store',
204 help='LDAP User DN Template')
205 group.add_argument('--info-ldap-base-dn', action='store',
208 def configure(self, opts, changes):
209 if opts['info_ldap'] != 'yes':
212 # Add configuration data to database
213 po = PluginObject(*self.pargs)
216 po.wipe_config_values()
218 if 'info_ldap_server_url' in opts:
219 config['server url'] = opts['info_ldap_server_url']
220 elif 'ldap_server_url' in opts:
221 config['server url'] = opts['ldap_server_url']
222 if 'info_ldap_bind_dn' in opts:
223 config['bind dn'] = opts['info_ldap_bind_dn']
224 if 'info_ldap_bind_pwd' in opts:
225 config['bind password'] = opts['info_ldap_bind_pwd']
226 if 'info_ldap_user_dn_template' in opts:
227 config['user dn template'] = opts['info_ldap_user_dn_template']
228 elif 'ldap_bind_dn_template' in opts:
229 config['user dn template'] = opts['ldap_bind_dn_template']
230 if 'info_ldap_tls_level' in opts and opts['info_ldap_tls_level']:
231 config['tls'] = opts['info_ldap_tls_level']
232 elif 'ldap_tls_level' in opts and opts['ldap_tls_level']:
233 config['tls'] = opts['ldap_tls_level']
235 config['tls'] = 'Demand'
236 if 'info_ldap_base_dn' in opts and opts['info_ldap_base_dn']:
237 config['base dn'] = opts['info_ldap_base_dn']
238 elif 'ldap_base_dn' in opts and opts['ldap_base_dn']:
239 config['base dn'] = opts['ldap_base_dn']
240 po.save_plugin_config(config)
242 # Update global config to add info plugin
244 po.save_enabled_state()
246 # For selinux enabled platforms permit httpd to connect to ldap,
249 subprocess.call(['/usr/sbin/setsebool', '-P',
250 'httpd_can_connect_ldap=on'])
251 except Exception: # pylint: disable=broad-except