39 # search scopes |
39 # search scopes |
40 BASE = ldap.SCOPE_BASE |
40 BASE = ldap.SCOPE_BASE |
41 ONELEVEL = ldap.SCOPE_ONELEVEL |
41 ONELEVEL = ldap.SCOPE_ONELEVEL |
42 SUBTREE = ldap.SCOPE_SUBTREE |
42 SUBTREE = ldap.SCOPE_SUBTREE |
43 |
43 |
44 # XXX only for edition ?? |
44 # map ldap protocol to their standard port |
45 ## password encryption possibilities |
45 PROTO_PORT = {'ldap': 389, |
46 #ENCRYPTIONS = ('SHA', 'CRYPT', 'MD5', 'CLEAR') # , 'SSHA' |
46 'ldaps': 636, |
47 |
47 'ldapi': None, |
48 # mode identifier : (port, protocol) |
48 } |
49 MODES = { |
|
50 0: (389, 'ldap'), |
|
51 1: (636, 'ldaps'), |
|
52 2: (0, 'ldapi'), |
|
53 } |
|
54 |
49 |
55 |
50 |
56 class LDAPUserSource(AbstractSource): |
51 class LDAPUserSource(AbstractSource): |
57 """LDAP read-only CWUser source""" |
52 """LDAP read-only CWUser source""" |
58 support_entities = {'CWUser': False} |
53 support_entities = {'CWUser': False} |
59 |
|
60 port = None |
|
61 |
|
62 cnx_mode = 0 |
|
63 cnx_dn = '' |
|
64 cnx_pwd = '' |
|
65 |
54 |
66 options = ( |
55 options = ( |
67 ('host', |
56 ('host', |
68 {'type' : 'string', |
57 {'type' : 'string', |
69 'default': 'ldap', |
58 'default': 'ldap', |
70 'help': 'ldap host', |
59 'help': 'ldap host. It may contains port information using \ |
71 'group': 'ldap-source', 'inputlevel': 1, |
60 <host>:<port> notation.', |
72 }), |
61 'group': 'ldap-source', 'inputlevel': 1, |
|
62 }), |
|
63 ('protocol', |
|
64 {'type' : 'choice', |
|
65 'default': 'ldap', |
|
66 'choices': ('ldap', 'ldaps', 'ldapi'), |
|
67 'help': 'ldap protocol', |
|
68 'group': 'ldap-source', 'inputlevel': 1, |
|
69 }), |
|
70 |
|
71 ('auth-mode', |
|
72 {'type' : 'choice', |
|
73 'default': 'simple', |
|
74 'choices': ('simple', 'cram_md5', 'digest_md5', 'gssapi'), |
|
75 'help': 'authentication mode used to authenticate user to the ldap.', |
|
76 'group': 'ldap-source', 'inputlevel': 1, |
|
77 }), |
|
78 ('auth-realm', |
|
79 {'type' : 'string', |
|
80 'default': None, |
|
81 'help': 'realm to use when using gssapp/kerberos authentication.', |
|
82 'group': 'ldap-source', 'inputlevel': 1, |
|
83 }), |
|
84 |
|
85 ('data-cnx-dn', |
|
86 {'type' : 'string', |
|
87 'default': '', |
|
88 'help': 'user dn to use to open data connection to the ldap (eg used \ |
|
89 to respond to rql queries).', |
|
90 'group': 'ldap-source', 'inputlevel': 1, |
|
91 }), |
|
92 ('data-cnx-password', |
|
93 {'type' : 'string', |
|
94 'default': '', |
|
95 'help': 'password to use to open data connection to the ldap (eg used to respond to rql queries).', |
|
96 'group': 'ldap-source', 'inputlevel': 1, |
|
97 }), |
|
98 |
73 ('user-base-dn', |
99 ('user-base-dn', |
74 {'type' : 'string', |
100 {'type' : 'string', |
75 'default': 'ou=People,dc=logilab,dc=fr', |
101 'default': 'ou=People,dc=logilab,dc=fr', |
76 'help': 'base DN to lookup for users', |
102 'help': 'base DN to lookup for users', |
77 'group': 'ldap-source', 'inputlevel': 0, |
103 'group': 'ldap-source', 'inputlevel': 0, |
127 |
153 |
128 def __init__(self, repo, appschema, source_config, *args, **kwargs): |
154 def __init__(self, repo, appschema, source_config, *args, **kwargs): |
129 AbstractSource.__init__(self, repo, appschema, source_config, |
155 AbstractSource.__init__(self, repo, appschema, source_config, |
130 *args, **kwargs) |
156 *args, **kwargs) |
131 self.host = source_config['host'] |
157 self.host = source_config['host'] |
|
158 self.protocol = source_config.get('protocol', 'ldap') |
|
159 self.authmode = source_config.get('auth-mode', 'simple') |
|
160 self._authenticate = getattr(self, '_auth_%s' % self.authmode) |
|
161 self.cnx_dn = source_config.get('data-cnx-dn') or '' |
|
162 self.cnx_pwd = source_config.get('data-cnx-password') or '' |
|
163 self.user_base_scope = globals()[source_config['user-scope']] |
132 self.user_base_dn = source_config['user-base-dn'] |
164 self.user_base_dn = source_config['user-base-dn'] |
133 self.user_base_scope = globals()[source_config['user-scope']] |
165 self.user_base_scope = globals()[source_config['user-scope']] |
134 self.user_classes = splitstrip(source_config['user-classes']) |
166 self.user_classes = splitstrip(source_config['user-classes']) |
135 self.user_login_attr = source_config['user-login-attr'] |
167 self.user_login_attr = source_config['user-login-attr'] |
136 self.user_default_groups = splitstrip(source_config['user-default-group']) |
168 self.user_default_groups = splitstrip(source_config['user-default-group']) |
223 except IndexError: |
255 except IndexError: |
224 # no such user |
256 # no such user |
225 raise AuthenticationError() |
257 raise AuthenticationError() |
226 # check password by establishing a (unused) connection |
258 # check password by establishing a (unused) connection |
227 try: |
259 try: |
228 self._connect(user['dn'], password) |
260 self._connect(user, password) |
229 except: |
261 except Exception, ex: |
|
262 self.info('while trying to authenticate %s: %s', user, ex) |
230 # Something went wrong, most likely bad credentials |
263 # Something went wrong, most likely bad credentials |
231 raise AuthenticationError() |
264 raise AuthenticationError() |
232 return self.extid2eid(user['dn'], 'CWUser', session) |
265 return self.extid2eid(user['dn'], 'CWUser', session) |
233 |
266 |
234 def ldap_name(self, var): |
267 def ldap_name(self, var): |
366 result = trfunc.apply(result) |
399 result = trfunc.apply(result) |
367 #print '--> ldap result', result |
400 #print '--> ldap result', result |
368 return result |
401 return result |
369 |
402 |
370 |
403 |
371 def _connect(self, userdn=None, userpwd=None): |
404 def _connect(self, user=None, userpwd=None): |
372 port, protocol = MODES[self.cnx_mode] |
405 if self.protocol == 'ldapi': |
373 if protocol == 'ldapi': |
|
374 hostport = self.host |
406 hostport = self.host |
|
407 elif not ':' in self.host: |
|
408 hostport = '%s:%s' % (self.host, PROTO_PORT[self.protocol]) |
375 else: |
409 else: |
376 hostport = '%s:%s' % (self.host, self.port or port) |
410 hostport = self.host |
377 self.info('connecting %s://%s as %s', protocol, hostport, |
411 self.info('connecting %s://%s as %s', self.protocol, hostport, |
378 userdn or 'anonymous') |
412 user and user['dn'] or 'anonymous') |
379 url = LDAPUrl(urlscheme=protocol, hostport=hostport) |
413 url = LDAPUrl(urlscheme=self.protocol, hostport=hostport) |
380 conn = ReconnectLDAPObject(url.initializeUrl()) |
414 conn = ReconnectLDAPObject(url.initializeUrl()) |
381 # Set the protocol version - version 3 is preferred |
415 # Set the protocol version - version 3 is preferred |
382 try: |
416 try: |
383 conn.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3) |
417 conn.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3) |
384 except ldap.LDAPError: # Invalid protocol version, fall back safely |
418 except ldap.LDAPError: # Invalid protocol version, fall back safely |
389 #except ldap.LDAPError: # Cannot set referrals, so do nothing |
423 #except ldap.LDAPError: # Cannot set referrals, so do nothing |
390 # pass |
424 # pass |
391 #conn.set_option(ldap.OPT_NETWORK_TIMEOUT, conn_timeout) |
425 #conn.set_option(ldap.OPT_NETWORK_TIMEOUT, conn_timeout) |
392 #conn.timeout = op_timeout |
426 #conn.timeout = op_timeout |
393 # Now bind with the credentials given. Let exceptions propagate out. |
427 # Now bind with the credentials given. Let exceptions propagate out. |
394 if userdn is None: |
428 if user is None: |
|
429 # no user specified, we want to initialize the 'data' connection, |
395 assert self._conn is None |
430 assert self._conn is None |
396 self._conn = conn |
431 self._conn = conn |
397 userdn = self.cnx_dn |
432 # XXX always use simple bind for data connection |
398 userpwd = self.cnx_pwd |
433 if not self.cnx_dn: |
399 conn.simple_bind_s(userdn, userpwd) |
434 conn.simple_bind_s(self.cnx_dn, self.cnx_pwd) |
|
435 else: |
|
436 self._authenticate(conn, {'dn': self.cnx_dn}, self.cnx_pwd) |
|
437 else: |
|
438 # user specified, we want to check user/password, no need to return |
|
439 # the connection which will be thrown out |
|
440 self._authenticate(conn, user, userpwd) |
400 return conn |
441 return conn |
|
442 |
|
443 def _auth_simple(self, conn, user, userpwd): |
|
444 conn.simple_bind_s(user['dn'], userpwd) |
|
445 |
|
446 def _auth_cram_md5(self, conn, user, userpwd): |
|
447 from ldap import sasl |
|
448 auth_token = sasl.cram_md5(user['dn'], userpwd) |
|
449 conn.sasl_interactive_bind_s("", auth_tokens) |
|
450 |
|
451 def _auth_digest_md5(self, conn, user, userpwd): |
|
452 from ldap import sasl |
|
453 auth_token = sasl.digest_md5(user['dn'], userpwd) |
|
454 conn.sasl_interactive_bind_s("", auth_tokens) |
|
455 |
|
456 def _auth_gssapi(self, conn, user, userpwd): |
|
457 # print XXX not proper sasl/gssapi |
|
458 from ldap import sasl |
|
459 import kerberos |
|
460 if not kerberos.checkPassword(user[self.user_login_attr], userpwd): |
|
461 raise Exception('BAD login / mdp') |
|
462 #conn.sasl_interactive_bind_s("", auth_tokens) |
401 |
463 |
402 def _search(self, session, base, scope, |
464 def _search(self, session, base, scope, |
403 searchstr='(objectClass=*)', attrs=()): |
465 searchstr='(objectClass=*)', attrs=()): |
404 """make an ldap query""" |
466 """make an ldap query""" |
405 cnx = session.pool.connection(self.uri).cnx |
467 cnx = session.pool.connection(self.uri).cnx |