|
102 |
MACHINE_WRITE = 'ldap_machine_write' |
102 |
MACHINE_WRITE = 'ldap_machine_write' |
103 |
ADMIN_WRITE = 'ldap_admin_write' |
103 |
ADMIN_WRITE = 'ldap_admin_write' |
104 |
|
104 |
|
|
|
105 |
|
105 |
class LDAP_ConnectionError( Exception ): |
106 |
class LDAP_ConnectionError( Exception ): |
106 |
pass |
107 |
pass |
107 |
|
108 |
|
|
|
109 |
|
108 |
def open_ldap_connection( binddn, bindpw, ldap_server ): |
110 |
def open_ldap_connection( binddn, bindpw, ldap_server ): |
109 |
'''Opens a new LDAP connection using the given user LDAP DN and |
111 |
'''Opens a new LDAP connection using the given user LDAP DN and |
110 |
password. The connection is open to the given server or if None the |
112 |
password. The connection is open to the given server or if None the |
|
119 |
|
121 |
|
120 |
return lo |
122 |
return lo |
121 |
|
123 |
|
|
|
124 |
|
122 |
def get_ldap_connections( connection_types, force = False ): |
125 |
def get_ldap_connections( connection_types, force = False ): |
123 |
global _ldap_connections, _user_dn, _password |
126 |
global _ldap_connections, _user_dn, _password |
124 |
|
127 |
|
|
146 |
|
149 |
|
147 |
return connections |
150 |
return connections |
148 |
|
151 |
|
|
|
152 |
|
149 |
def LDAP_Connection( *connection_types ): |
153 |
def LDAP_Connection( *connection_types ): |
150 |
"""This decorator function provides an open LDAP connection that can |
154 |
"""This decorator function provides an open LDAP connection that can |
151 |
be accessed via the variable ldap_connection and a valid position |
155 |
be accessed via the variable ldap_connection and a valid position |
|
222 |
return wraps( func )( wrapper_func ) |
226 |
return wraps( func )( wrapper_func ) |
223 |
return inner_wrapper |
227 |
return inner_wrapper |
224 |
|
228 |
|
|
|
229 |
|
225 |
class LicenseError( Exception ): |
230 |
class LicenseError( Exception ): |
226 |
pass |
231 |
pass |
227 |
|
232 |
|
|
|
233 |
|
228 |
@LDAP_Connection(MACHINE_READ, MACHINE_WRITE) |
234 |
@LDAP_Connection(MACHINE_READ, MACHINE_WRITE) |
229 |
def check_license(ldap_machine_read = None, ldap_machine_write = None, ldap_position = None, search_base = None ): |
235 |
def check_license(ldap_machine_read = None, ldap_machine_write = None, ldap_position = None, search_base = None ): |
230 |
"""Checks the license cases and throws exceptions accordingly. The logic is copied from the UDM UMC module.""" |
236 |
"""Checks the license cases and throws exceptions accordingly. The logic is copied from the UDM UMC module.""" |
|
304 |
except udm_errors.licenseGPLversion: |
310 |
except udm_errors.licenseGPLversion: |
305 |
raise LicenseError(_('Your license status could not be validated. Thus, you are not eligible to support and maintenance. If you have bought a license, please contact Univention or your Univention partner.')) |
311 |
raise LicenseError(_('Your license status could not be validated. Thus, you are not eligible to support and maintenance. If you have bought a license, please contact Univention or your Univention partner.')) |
306 |
|
312 |
|
|
|
313 |
|
307 |
def _init_search_base(ldap_connection, force=False): |
314 |
def _init_search_base(ldap_connection, force=False): |
|
|
315 |
# initiate the list of available schools and set the default search base |
308 |
global _search_base |
316 |
global _search_base |
309 |
|
317 |
|
310 |
if _search_base and not force: |
318 |
if _search_base and not force: |
311 |
# search base has already been initiated... we are done |
319 |
# search base has already been initiated... we are done |
312 |
return |
320 |
return |
313 |
|
321 |
|
314 |
# initiate the list of available schools and set the default search base |
322 |
availableSchools = {} |
315 |
if ldap_connection.binddn.find('ou=') > 0: |
323 |
schoolname = None |
|
|
324 |
school_dn = None |
325 |
|
326 |
school_dn = getOUDN(ldap_connection.binddn) |
327 |
if school_dn: |
328 |
MODULE.info('LDAP_Connection: user account is bound to school.') |
316 |
# we got an OU in the user DN -> school teacher or assistent |
329 |
# we got an OU in the user DN -> school teacher or assistent |
317 |
# restrict the visibility to current school |
330 |
# restrict the visibility to current school |
318 |
# (note that there can be schools with a DN such as ou=25g18,ou=25,dc=...) |
331 |
# (note that there can be schools with a DN such as ou=25g18,ou=25,dc=...) |
319 |
schoolDN = ldap_connection.binddn[ldap_connection.binddn.find('ou='):] |
332 |
schoolname = SchoolSearchBase.getOU(school_dn) |
320 |
school = (ldap_connection.explodeDn( schoolDN, 1 )[0], ) # note: school is a tuple with one element |
333 |
availableSchools = {schoolname: school_dn} |
321 |
_search_base = SchoolSearchBase(school, school[0], schoolDN) |
|
|
322 |
MODULE.info('LDAP_Connection: setting schoolDN: %s' % _search_base.schoolDN) |
323 |
else: |
334 |
else: |
324 |
MODULE.warn( 'LDAP_Connection: unable to identify ou of this account - showing all OUs!' ) |
335 |
MODULE.warn('LDAP_Connection: unable to identify ou of this account - showing all OUs!') |
325 |
#_ouswitchenabled = True |
336 |
|
326 |
oulist = ucr.get('ucsschool/local/oulist') |
337 |
oulist = ucr.get('ucsschool/local/oulist') |
327 |
availableSchools = [] |
338 |
district_mode = ucs.is_true('ucsschool/ldap/district/enable') |
|
|
339 |
searchscope = 'sub' if district_mode else 'one' |
340 |
|
341 |
# get a list of available OUs via UDM module container/ou |
342 |
availableSchools = udm_modules.lookup( |
343 |
'container/ou', None, ldap_connection, |
344 |
filter='objectClass=ucsschoolOrganizationalUnit', |
345 |
scope=searchscope, superordinate=None, |
346 |
base=ucr.get('ldap/base') |
347 |
) |
348 |
availableSchools = ((ou['name'], ou.dn) for ou in availableSchools) |
349 |
|
328 |
if oulist: |
350 |
if oulist: |
329 |
# OU list override via UCR variable (it can be necessary to adjust the list of |
351 |
# OU list override via UCR variable (it can be necessary to adjust the list of |
330 |
# visible schools on specific systems manually) |
352 |
# visible schools on specific systems manually) |
331 |
availableSchools = [ x.strip() for x in oulist.split(',') ] |
353 |
MODULE.info('LDAP_Connection: availableSchools overridden by UCR variable ucsschool/local/oulist') |
332 |
MODULE.info( 'LDAP_Connection: availableSchools overridden by UCR variable ucsschool/local/oulist') |
354 |
oulist = oulist.split(',') |
333 |
else: |
355 |
availableSchools = ((name, dn) for name, dn in availableSchools if name in oulist) |
334 |
# get a list of available OUs via UDM module container/ou |
|
|
335 |
availableSchools = udm_modules.lookup( |
336 |
'container/ou', None, ldap_connection, 'objectClass=ucsschoolOrganizationalUnit', |
337 |
scope='one', superordinate=None, |
338 |
base=ucr.get('ldap/base') |
339 |
) |
340 |
availableSchools = [ou['name'] for ou in availableSchools] |
341 |
|
356 |
|
342 |
# use the first available OU as default search base |
357 |
availableSchools = dict(availableSchools) |
|
|
358 |
|
343 |
if not availableSchools: |
359 |
if not availableSchools: |
344 |
MODULE.warn('LDAP_Connection: ERROR, COULD NOT FIND ANY OU!!!') |
360 |
MODULE.warn('LDAP_Connection: ERROR, COULD NOT FIND ANY OU!!!') |
345 |
_search_base = SchoolSearchBase(['']) |
|
|
346 |
else: |
347 |
MODULE.info( 'LDAP_Connection: availableSchools=%s' % availableSchools ) |
348 |
_search_base = SchoolSearchBase(availableSchools) |
349 |
|
361 |
|
|
|
362 |
MODULE.info('LDAP_Connection: availableSchools=%s; school=%s; school_dn=%s' % (availableSchools, schoolname, school_dn)) |
363 |
_search_base = SchoolSearchBase(availableSchools, schoolname, school_dn) |
364 |
|
365 |
|
350 |
class SchoolSearchBase(object): |
366 |
class SchoolSearchBase(object): |
351 |
"""This class serves a wrapper for all the different search bases (users, |
367 |
"""This class serves a wrapper for all the different search bases (users, |
352 |
groups, students, teachers etc.). It is initiated with a particular ou. |
368 |
groups, students, teachers etc.). It is initiated with a particular ou. |
353 |
The class is inteded for read access only, instead of switching the |
369 |
The class is inteded for read access only, instead of switching the |
354 |
search base, a new instance can simply be created. |
370 |
search base, a new instance can simply be created. |
355 |
""" |
371 |
""" |
356 |
def __init__( self, availableSchools, school = None, dn = None, ldapBase = None ): |
372 |
def __init__(self, availableSchools, school=None, dn=None, ldapBase=None): |
357 |
if ldapBase: |
373 |
if ldapBase: |
358 |
self._ldapBase = ldapBase |
374 |
self._ldapBase = ldapBase |
359 |
else: |
375 |
else: |
360 |
self._ldapBase = ucr.get('ldap/base') |
376 |
self._ldapBase = ucr.get('ldap/base') |
361 |
|
377 |
|
362 |
self._availableSchools = availableSchools |
378 |
self._availableSchools = availableSchools |
363 |
self._school = school or availableSchools[0] |
379 |
# self._school = school or availableSchools[0] |
364 |
# FIXME: search for OU to get correct dn |
380 |
# # FIXME: search for OU to get correct dn |
365 |
self._schoolDN = dn or 'ou=%s,%s' % (self.school, self._ldapBase ) |
381 |
# self._schoolDN = dn or 'ou=%s,%s' % (self.school, self._ldapBase ) |
366 |
|
382 |
|
|
|
383 |
self._school = school |
384 |
if not self._school and availableSchools: |
385 |
self._school = availableSchools.keys()[0] |
386 |
|
387 |
if dn: # school DN is given |
388 |
self._schoolDN = dn |
389 |
else: |
390 |
# school DN is not given, try to guess it from the dict of all schools |
391 |
if self.school in availableSchools: |
392 |
self._schoolDN = availableSchools[self.school] |
393 |
else: |
394 |
# should not happen... use a poor man's fallback |
395 |
MODULE.error('Could not find corresponding school DN for schoolOU "%s"!' % self.school) |
396 |
self._schoolDN = 'ou=%s,%s' % (self.school, self._ldapBase ) |
397 |
|
367 |
# prefixes |
398 |
# prefixes |
368 |
self._containerAdmins = ucr.get('ucsschool/ldap/default/container/admins', 'admins') |
399 |
self._containerAdmins = ucr.get('ucsschool/ldap/default/container/admins', 'admins') |
369 |
self._containerStudents = ucr.get('ucsschool/ldap/default/container/pupils', 'schueler') |
400 |
self._containerStudents = ucr.get('ucsschool/ldap/default/container/pupils', 'schueler') |
|
375 |
self._examUserContainerName = ucr.get('ucsschool/ldap/default/container/exam', 'examusers') |
406 |
self._examUserContainerName = ucr.get('ucsschool/ldap/default/container/exam', 'examusers') |
376 |
self._examGroupNameTemplate = ucr.get('ucsschool/ldap/default/groupname/exam', 'OU%(ou)s-Klassenarbeit') |
407 |
self._examGroupNameTemplate = ucr.get('ucsschool/ldap/default/groupname/exam', 'OU%(ou)s-Klassenarbeit') |
377 |
|
408 |
|
378 |
@staticmethod |
409 |
@classmethod |
379 |
def getOU(dn): |
410 |
def getOU(cls, dn): |
380 |
'''Return the school OU for a given DN.''' |
411 |
"""Return the school OU for a given DN. |
381 |
if dn.find('ou=') < 0: |
|
|
382 |
# no 'ou=' in the string |
383 |
return None |
384 |
schoolDN = dn[dn.find('ou='):] |
385 |
school = univention.uldap.explodeDn( schoolDN, 1 )[0] |
386 |
return school |
387 |
|
412 |
|
|
|
413 |
>>> getOU('uid=a,fou=bar,Ou=dc1,oU=dc,dc=foo,dc=bar') |
414 |
'dc1' |
415 |
""" |
416 |
school_dn = cls.getOUDN(dn) |
417 |
if school_dn: |
418 |
return univention.uldap.explodeDn(school_dn, True)[0] |
419 |
|
420 |
@classmethod |
421 |
def getOUDN(cls, dn): |
422 |
"""Return the School OU-DN part for a given DN. |
423 |
|
424 |
>>> getOUDN('uid=a,fou=bar,Ou=dc1,oU=dc,dc=foo,dc=bar') |
425 |
'Ou=dc1,oU=dc,dc=foo,dc=bar' |
426 |
>>> getOUDN('ou=dc1,ou=dc,dc=foo,dc=bar') |
427 |
'ou=dc1,ou=dc,dc=foo,dc=bar' |
428 |
""" |
429 |
match = cls.getOUDN.RE_OU.search(dn) |
430 |
if match: |
431 |
return match.group(1) |
432 |
getOUDN.RE_OU = re.compile('(?:^|,)(ou=.*)$', re.I) |
433 |
|
388 |
@property |
434 |
@property |
389 |
def availableSchools(self): |
435 |
def availableSchools(self): |
390 |
return self._availableSchools |
436 |
return self._availableSchools |
391 |
|
437 |
|
392 |
@property |
438 |
@property |
393 |
def allSchoolBases(self): |
439 |
def allSchoolBases(self): |
|
|
440 |
"""returns a generator containing a SchoolSearchBase for every available school""" |
394 |
availableSchools = self.availableSchools |
441 |
availableSchools = self.availableSchools |
395 |
for school in availableSchools: |
442 |
for school, school_dn in availableSchools.iteritems(): |
396 |
yield self.__class__(availableSchools, school, None, self._ldapBase) |
443 |
yield self.__class__(availableSchools, school, school_dn, self._ldapBase) |
397 |
|
444 |
|
398 |
@property |
445 |
@property |
399 |
def school(self): |
446 |
def school(self): |
|
405 |
|
452 |
|
406 |
@property |
453 |
@property |
407 |
def users(self): |
454 |
def users(self): |
408 |
return "cn=users,%s" % self.schoolDN |
455 |
return "cn=users,%s" % (self.schoolDN,) |
409 |
|
456 |
|
410 |
@property |
457 |
@property |
411 |
def groups(self): |
458 |
def groups(self): |
412 |
return "cn=groups,%s" % self.schoolDN |
459 |
return "cn=groups,%s" % (self.schoolDN,) |
413 |
|
460 |
|
414 |
@property |
461 |
@property |
415 |
def workgroups(self): |
462 |
def workgroups(self): |
|
449 |
|
496 |
|
450 |
@property |
497 |
@property |
451 |
def shares(self): |
498 |
def shares(self): |
452 |
return "cn=shares,%s" % self.schoolDN |
499 |
return "cn=shares,%s" % (self.schoolDN,) |
453 |
|
500 |
|
454 |
@property |
501 |
@property |
455 |
def printers(self): |
502 |
def printers(self): |
456 |
return "cn=printers,%s" % self.schoolDN |
503 |
return "cn=printers,%s" % (self.schoolDN,) |
457 |
|
504 |
|
458 |
@property |
505 |
@property |
459 |
def computers(self): |
506 |
def computers(self): |
460 |
return "cn=computers,%s" % self.schoolDN |
507 |
return "cn=computers,%s" % (self.schoolDN,) |
461 |
|
508 |
|
462 |
@property |
509 |
@property |
463 |
def examUsers(self): |
510 |
def examUsers(self): |
|
548 |
|
595 |
|
549 |
# make sure that at least one school OU |
596 |
# make sure that at least one school OU |
550 |
msg = '' |
597 |
msg = '' |
551 |
if not search_base.availableSchools[0]: |
598 |
if not search_base.availableSchools: |
552 |
request.status = MODULE_ERR |
599 |
request.status = MODULE_ERR |
553 |
msg = _('Could not find any school. You have to create a school before continuing. Use the \'Add school\' UMC module to create one.') |
600 |
msg = _("Could not find any school. You have to create a school before continuing. Use the 'Add school' UMC module to create one.") |
554 |
|
601 |
|
555 |
# return list of school OUs |
602 |
# return list of school OUs |
556 |
self.finished(request.id, search_base.availableSchools, msg) |
603 |
self.finished(request.id, search_base.availableSchools.keys(), msg) |
557 |
|
604 |
|
558 |
def _groups( self, ldap_connection, school, ldap_base, pattern = None, scope = 'sub' ): |
605 |
def _groups( self, ldap_connection, school, ldap_base, pattern = None, scope = 'sub' ): |
559 |
"""Returns a list of all groups of the given school""" |
606 |
"""Returns a list of all groups of the given school""" |
|
640 |
|
687 |
|
641 |
return userresult |
688 |
return userresult |
642 |
|
689 |
|
|
|
690 |
|
643 |
class LDAP_Filter: |
691 |
class LDAP_Filter: |
644 |
|
692 |
|
645 |
@staticmethod |
693 |
@staticmethod |
|
678 |
|
726 |
|
679 |
return '(&%s)' % ''.join( expressions ) |
727 |
return '(&%s)' % ''.join( expressions ) |
680 |
|
728 |
|
|
|
729 |
|
681 |
class Display: |
730 |
class Display: |
682 |
@staticmethod |
731 |
@staticmethod |
683 |
def user( udm_object ): |
732 |
def user( udm_object ): |