|
Lines 102-110
Link Here
|
| 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 |
|
Lines 119-124
Link Here
|
| 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 |
|
|
Lines 146-151
Link Here
|
| 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 |
|
Lines 222-230
Link Here
|
| 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.""" |
|
Lines 304-369
Link Here
|
| 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') |
|
Lines 375-399
Link Here
|
| 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): |
|
Lines 405-415
Link Here
|
| 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): |
|
Lines 449-463
Link Here
|
| 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): |
|
Lines 548-559
Link Here
|
| 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""" |
|
Lines 640-645
Link Here
|
| 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 |
|
Lines 678-683
Link Here
|
| 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 ): |