32 FOR A PARTICULAR PURPOSE. |
32 FOR A PARTICULAR PURPOSE. |
33 """ |
33 """ |
34 from __future__ import division |
34 from __future__ import division |
35 from base64 import b64decode |
35 from base64 import b64decode |
36 |
36 |
37 from logilab.common.textutils import splitstrip |
|
38 from rql.nodes import Relation, VariableRef, Constant, Function |
|
39 |
|
40 import ldap |
37 import ldap |
41 from ldap.ldapobject import ReconnectLDAPObject |
38 from ldap.ldapobject import ReconnectLDAPObject |
42 from ldap.filter import filter_format, escape_filter_chars |
39 from ldap.filter import filter_format, escape_filter_chars |
43 from ldapurl import LDAPUrl |
40 from ldapurl import LDAPUrl |
44 |
41 |
45 from logilab.common.configuration import time_validator |
42 from rql.nodes import Relation, VariableRef, Constant, Function |
|
43 |
46 from cubicweb import AuthenticationError, UnknownEid, RepositoryError |
44 from cubicweb import AuthenticationError, UnknownEid, RepositoryError |
47 from cubicweb.server.utils import cartesian_product |
45 from cubicweb.server.utils import cartesian_product |
48 from cubicweb.server.sources import (AbstractSource, TrFunc, GlobTrFunc, |
46 from cubicweb.server.sources import (AbstractSource, TrFunc, GlobTrFunc, |
49 ConnectionWrapper, TimedCache) |
47 ConnectionWrapper, TimedCache) |
50 |
48 |
166 'group': 'ldap-source', 'level': 3, |
164 'group': 'ldap-source', 'level': 3, |
167 }), |
165 }), |
168 |
166 |
169 ) |
167 ) |
170 |
168 |
171 def __init__(self, repo, source_config, *args, **kwargs): |
169 def __init__(self, repo, source_config, eid=None): |
172 AbstractSource.__init__(self, repo, source_config, *args, **kwargs) |
170 AbstractSource.__init__(self, repo, source_config, eid) |
173 self.host = source_config['host'] |
171 self.update_config(None, self.check_conf_dict(eid, source_config)) |
174 self.protocol = source_config.get('protocol', 'ldap') |
172 self._conn = None |
175 self.authmode = source_config.get('auth-mode', 'simple') |
173 |
|
174 def update_config(self, source_entity, typedconfig): |
|
175 """update configuration from source entity. `typedconfig` is config |
|
176 properly typed with defaults set |
|
177 """ |
|
178 self.host = typedconfig['host'] |
|
179 self.protocol = typedconfig['protocol'] |
|
180 self.authmode = typedconfig['auth-mode'] |
176 self._authenticate = getattr(self, '_auth_%s' % self.authmode) |
181 self._authenticate = getattr(self, '_auth_%s' % self.authmode) |
177 self.cnx_dn = source_config.get('data-cnx-dn') or '' |
182 self.cnx_dn = typedconfig['data-cnx-dn'] |
178 self.cnx_pwd = source_config.get('data-cnx-password') or '' |
183 self.cnx_pwd = typedconfig['data-cnx-password'] |
179 self.user_base_scope = globals()[source_config['user-scope']] |
184 self.user_base_dn = str(typedconfig['user-base-dn']) |
180 self.user_base_dn = str(source_config['user-base-dn']) |
185 self.user_base_scope = globals()[typedconfig['user-scope']] |
181 self.user_base_scope = globals()[source_config['user-scope']] |
186 self.user_login_attr = typedconfig['user-login-attr'] |
182 self.user_classes = splitstrip(source_config['user-classes']) |
187 self.user_default_groups = typedconfig['user-default-group'] |
183 self.user_login_attr = source_config['user-login-attr'] |
188 self.user_attrs = typedconfig['user-attrs-map'] |
184 self.user_default_groups = splitstrip(source_config['user-default-group']) |
|
185 self.user_attrs = dict(v.split(':', 1) for v in splitstrip(source_config['user-attrs-map'])) |
|
186 self.user_filter = source_config.get('user-filter') |
|
187 self.user_rev_attrs = {'eid': 'dn'} |
189 self.user_rev_attrs = {'eid': 'dn'} |
188 for ldapattr, cwattr in self.user_attrs.items(): |
190 for ldapattr, cwattr in self.user_attrs.items(): |
189 self.user_rev_attrs[cwattr] = ldapattr |
191 self.user_rev_attrs[cwattr] = ldapattr |
190 self.base_filters = self._make_base_filters() |
192 self.base_filters = [filter_format('(%s=%s)', ('objectClass', o)) |
191 self._conn = None |
193 for o in typedconfig['user-classes']] |
192 self._cache = {} |
194 if typedconfig['user-filter']: |
193 # ttlm is in minutes! |
195 self.base_filters.append(typedconfig['user-filter']) |
194 self._cache_ttl = time_validator(None, None, |
196 self._interval = typedconfig['synchronization-interval'] |
195 source_config.get('cache-life-time', 2*60*60)) |
197 self._cache_ttl = max(71, typedconfig['cache-life-time']) |
196 self._cache_ttl = max(71, self._cache_ttl) |
198 self.reset_caches() |
197 self._query_cache = TimedCache(self._cache_ttl) |
|
198 # interval is in seconds ! |
|
199 self._interval = time_validator(None, None, |
|
200 source_config.get('synchronization-interval', |
|
201 24*60*60)) |
|
202 |
|
203 def _make_base_filters(self): |
|
204 filters = [filter_format('(%s=%s)', ('objectClass', o)) |
|
205 for o in self.user_classes] |
|
206 if self.user_filter: |
|
207 filters += [self.user_filter] |
|
208 return filters |
|
209 |
199 |
210 def reset_caches(self): |
200 def reset_caches(self): |
211 """method called during test to reset potential source caches""" |
201 """method called during test to reset potential source caches""" |
212 self._cache = {} |
202 self._cache = {} |
213 self._query_cache = TimedCache(self._cache_ttl) |
203 self._query_cache = TimedCache(self._cache_ttl) |
298 # On Windows + ADAM this would have succeeded (!!!) |
288 # On Windows + ADAM this would have succeeded (!!!) |
299 # You get Authenticated as: 'NT AUTHORITY\ANONYMOUS LOGON'. |
289 # You get Authenticated as: 'NT AUTHORITY\ANONYMOUS LOGON'. |
300 # we really really don't want that |
290 # we really really don't want that |
301 raise AuthenticationError() |
291 raise AuthenticationError() |
302 searchfilter = [filter_format('(%s=%s)', (self.user_login_attr, login))] |
292 searchfilter = [filter_format('(%s=%s)', (self.user_login_attr, login))] |
303 searchfilter.extend(self._make_base_filters()) |
293 searchfilter.extend(self.base_filters) |
304 searchstr = '(&%s)' % ''.join(searchfilter) |
294 searchstr = '(&%s)' % ''.join(searchfilter) |
305 # first search the user |
295 # first search the user |
306 try: |
296 try: |
307 user = self._search(session, self.user_base_dn, |
297 user = self._search(session, self.user_base_dn, |
308 self.user_base_scope, searchstr)[0] |
298 self.user_base_scope, searchstr)[0] |