Index: ucs-school-umc-distribution/umc/python/distribution/util.py =================================================================== --- ucs-school-umc-distribution/umc/python/distribution/util.py (Revision 49277) +++ ucs-school-umc-distribution/umc/python/distribution/util.py (Arbeitskopie) @@ -227,7 +227,7 @@ # initiate a new search base using the ou in the group schoolDN = entryObj.dn[entryObj.dn.find('ou='):] school = ldap_connection.explodeDn(schoolDN, 1)[0] - _search_base = SchoolSearchBase(school, school, schoolDN) + _search_base = SchoolSearchBase({school: schoolDN}, school, schoolDN) # open group object name_pattern = re.compile('^%s-' % (re.escape(_search_base.school)), flags=re.I) Index: ucs-school-lib/python/schoolldap.py =================================================================== --- ucs-school-lib/python/schoolldap.py (Revision 49277) +++ ucs-school-lib/python/schoolldap.py (Arbeitskopie) @@ -102,9 +102,11 @@ MACHINE_WRITE = 'ldap_machine_write' ADMIN_WRITE = 'ldap_admin_write' + class LDAP_ConnectionError( Exception ): pass + def open_ldap_connection( binddn, bindpw, ldap_server ): '''Opens a new LDAP connection using the given user LDAP DN and password. The connection is open to the given server or if None the @@ -119,6 +121,7 @@ return lo + def get_ldap_connections( connection_types, force = False ): global _ldap_connections, _user_dn, _password @@ -146,6 +149,7 @@ return connections + def LDAP_Connection( *connection_types ): """This decorator function provides an open LDAP connection that can be accessed via the variable ldap_connection and a valid position @@ -222,9 +226,11 @@ return wraps( func )( wrapper_func ) return inner_wrapper + class LicenseError( Exception ): pass + @LDAP_Connection(MACHINE_READ, MACHINE_WRITE) def check_license(ldap_machine_read = None, ldap_machine_write = None, ldap_position = None, search_base = None ): """Checks the license cases and throws exceptions accordingly. The logic is copied from the UDM UMC module.""" @@ -304,66 +310,91 @@ except udm_errors.licenseGPLversion: 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.')) + def _init_search_base(ldap_connection, force=False): + # initiate the list of available schools and set the default search base global _search_base if _search_base and not force: # search base has already been initiated... we are done return - # initiate the list of available schools and set the default search base - if ldap_connection.binddn.find('ou=') > 0: + availableSchools = {} + schoolname = None + school_dn = None + + school_dn = getOUDN(ldap_connection.binddn) + if school_dn: + MODULE.info('LDAP_Connection: user account is bound to school.') # we got an OU in the user DN -> school teacher or assistent # restrict the visibility to current school # (note that there can be schools with a DN such as ou=25g18,ou=25,dc=...) - schoolDN = ldap_connection.binddn[ldap_connection.binddn.find('ou='):] - school = (ldap_connection.explodeDn( schoolDN, 1 )[0], ) # note: school is a tuple with one element - _search_base = SchoolSearchBase(school, school[0], schoolDN) - MODULE.info('LDAP_Connection: setting schoolDN: %s' % _search_base.schoolDN) + schoolname = SchoolSearchBase.getOU(school_dn) + availableSchools = {schoolname: school_dn} else: - MODULE.warn( 'LDAP_Connection: unable to identify ou of this account - showing all OUs!' ) - #_ouswitchenabled = True + MODULE.warn('LDAP_Connection: unable to identify ou of this account - showing all OUs!') + oulist = ucr.get('ucsschool/local/oulist') - availableSchools = [] + district_mode = ucs.is_true('ucsschool/ldap/district/enable') + searchscope = 'sub' if district_mode else 'one' + + # get a list of available OUs via UDM module container/ou + availableSchools = udm_modules.lookup( + 'container/ou', None, ldap_connection, + filter='objectClass=ucsschoolOrganizationalUnit', + scope=searchscope, superordinate=None, + base=ucr.get('ldap/base') + ) + availableSchools = ((ou['name'], ou.dn) for ou in availableSchools) + if oulist: # OU list override via UCR variable (it can be necessary to adjust the list of # visible schools on specific systems manually) - availableSchools = [ x.strip() for x in oulist.split(',') ] - MODULE.info( 'LDAP_Connection: availableSchools overridden by UCR variable ucsschool/local/oulist') - else: - # get a list of available OUs via UDM module container/ou - availableSchools = udm_modules.lookup( - 'container/ou', None, ldap_connection, 'objectClass=ucsschoolOrganizationalUnit', - scope='one', superordinate=None, - base=ucr.get('ldap/base') - ) - availableSchools = [ou['name'] for ou in availableSchools] + MODULE.info('LDAP_Connection: availableSchools overridden by UCR variable ucsschool/local/oulist') + oulist = oulist.split(',') + availableSchools = ((name, dn) for name, dn in availableSchools if name in oulist) - # use the first available OU as default search base + availableSchools = dict(availableSchools) + if not availableSchools: MODULE.warn('LDAP_Connection: ERROR, COULD NOT FIND ANY OU!!!') - _search_base = SchoolSearchBase(['']) - else: - MODULE.info( 'LDAP_Connection: availableSchools=%s' % availableSchools ) - _search_base = SchoolSearchBase(availableSchools) + MODULE.info('LDAP_Connection: availableSchools=%s; school=%s; school_dn=%s' % (availableSchools, schoolname, school_dn)) + _search_base = SchoolSearchBase(availableSchools, schoolname, school_dn) + + class SchoolSearchBase(object): """This class serves a wrapper for all the different search bases (users, groups, students, teachers etc.). It is initiated with a particular ou. The class is inteded for read access only, instead of switching the search base, a new instance can simply be created. """ - def __init__( self, availableSchools, school = None, dn = None, ldapBase = None ): + def __init__(self, availableSchools, school=None, dn=None, ldapBase=None): if ldapBase: self._ldapBase = ldapBase else: self._ldapBase = ucr.get('ldap/base') self._availableSchools = availableSchools - self._school = school or availableSchools[0] - # FIXME: search for OU to get correct dn - self._schoolDN = dn or 'ou=%s,%s' % (self.school, self._ldapBase ) +# self._school = school or availableSchools[0] +# # FIXME: search for OU to get correct dn +# self._schoolDN = dn or 'ou=%s,%s' % (self.school, self._ldapBase ) + self._school = school + if not self._school and availableSchools: + self._school = availableSchools.keys()[0] + + if dn: # school DN is given + self._schoolDN = dn + else: + # school DN is not given, try to guess it from the dict of all schools + if self.school in availableSchools: + self._schoolDN = availableSchools[self.school] + else: + # should not happen... use a poor man's fallback + MODULE.error('Could not find corresponding school DN for schoolOU "%s"!' % self.school) + self._schoolDN = 'ou=%s,%s' % (self.school, self._ldapBase ) + # prefixes self._containerAdmins = ucr.get('ucsschool/ldap/default/container/admins', 'admins') self._containerStudents = ucr.get('ucsschool/ldap/default/container/pupils', 'schueler') @@ -375,25 +406,41 @@ self._examUserContainerName = ucr.get('ucsschool/ldap/default/container/exam', 'examusers') self._examGroupNameTemplate = ucr.get('ucsschool/ldap/default/groupname/exam', 'OU%(ou)s-Klassenarbeit') - @staticmethod - def getOU(dn): - '''Return the school OU for a given DN.''' - if dn.find('ou=') < 0: - # no 'ou=' in the string - return None - schoolDN = dn[dn.find('ou='):] - school = univention.uldap.explodeDn( schoolDN, 1 )[0] - return school + @classmethod + def getOU(cls, dn): + """Return the school OU for a given DN. + >>> getOU('uid=a,fou=bar,Ou=dc1,oU=dc,dc=foo,dc=bar') + 'dc1' + """ + school_dn = cls.getOUDN(dn) + if school_dn: + return univention.uldap.explodeDn(school_dn, True)[0] + + @classmethod + def getOUDN(cls, dn): + """Return the School OU-DN part for a given DN. + + >>> getOUDN('uid=a,fou=bar,Ou=dc1,oU=dc,dc=foo,dc=bar') + 'Ou=dc1,oU=dc,dc=foo,dc=bar' + >>> getOUDN('ou=dc1,ou=dc,dc=foo,dc=bar') + 'ou=dc1,ou=dc,dc=foo,dc=bar' + """ + match = cls.getOUDN.RE_OU.search(dn) + if match: + return match.group(1) + getOUDN.RE_OU = re.compile('(?:^|,)(ou=.*)$', re.I) + @property def availableSchools(self): return self._availableSchools @property def allSchoolBases(self): + """returns a generator containing a SchoolSearchBase for every available school""" availableSchools = self.availableSchools - for school in availableSchools: - yield self.__class__(availableSchools, school, None, self._ldapBase) + for school, school_dn in availableSchools.iteritems(): + yield self.__class__(availableSchools, school, school_dn, self._ldapBase) @property def school(self): @@ -405,11 +452,11 @@ @property def users(self): - return "cn=users,%s" % self.schoolDN + return "cn=users,%s" % (self.schoolDN,) @property def groups(self): - return "cn=groups,%s" % self.schoolDN + return "cn=groups,%s" % (self.schoolDN,) @property def workgroups(self): @@ -449,15 +496,15 @@ @property def shares(self): - return "cn=shares,%s" % self.schoolDN + return "cn=shares,%s" % (self.schoolDN,) @property def printers(self): - return "cn=printers,%s" % self.schoolDN + return "cn=printers,%s" % (self.schoolDN,) @property def computers(self): - return "cn=computers,%s" % self.schoolDN + return "cn=computers,%s" % (self.schoolDN,) @property def examUsers(self): @@ -548,12 +595,12 @@ # make sure that at least one school OU msg = '' - if not search_base.availableSchools[0]: + if not search_base.availableSchools: request.status = MODULE_ERR - msg = _('Could not find any school. You have to create a school before continuing. Use the \'Add school\' UMC module to create one.') + msg = _("Could not find any school. You have to create a school before continuing. Use the 'Add school' UMC module to create one.") # return list of school OUs - self.finished(request.id, search_base.availableSchools, msg) + self.finished(request.id, search_base.availableSchools.keys(), msg) def _groups( self, ldap_connection, school, ldap_base, pattern = None, scope = 'sub' ): """Returns a list of all groups of the given school""" @@ -640,6 +687,7 @@ return userresult + class LDAP_Filter: @staticmethod @@ -678,6 +726,7 @@ return '(&%s)' % ''.join( expressions ) + class Display: @staticmethod def user( udm_object ): Index: ucs-school-umc-installer/umc/python/schoolinstaller/__init__.py =================================================================== --- ucs-school-umc-installer/umc/python/schoolinstaller/__init__.py (Revision 49277) +++ ucs-school-umc-installer/umc/python/schoolinstaller/__init__.py (Arbeitskopie) @@ -568,7 +568,7 @@ result = udm_modules.lookup('container/ou', None, lo, base=ucrMaster.get('ldap/base'), scope='sub', filter='name=%s' % schoolOU) if result: # OU already exists... find all joined slave systems in the ou - searchBase = SchoolSearchBase([schoolOU], ldapBase=ucrMaster.get('ldap/base')) + searchBase = SchoolSearchBase({schoolOU: result[0].dn}, ldapBase=ucrMaster.get('ldap/base')) slaves = udm_modules.lookup('computers/domaincontroller_slave', None, lo, base=searchBase.computers, scope='sub', filter='service=LDAP') # make sure that no joined DC slave is the main DC for this school Index: ucs-school-umc-computerroom/umc/python/computerroom/__init__.py =================================================================== --- ucs-school-umc-computerroom/umc/python/computerroom/__init__.py (Revision 49277) +++ ucs-school-umc-computerroom/umc/python/computerroom/__init__.py (Arbeitskopie) @@ -289,10 +289,9 @@ # match the corresponding school OU school = None - roomParts = explodeDn(roomDN) - for ischool in search_base.availableSchools: - if ('ou=%s' % ischool) in roomParts: - # match + for ischool, ischool_dn in search_base.availableSchools.iteritems(): + pattern = re.compile('.*%s$' % (re.escape(ischool_dn)), re.I) + if pattern.match(roomDN): school = ischool break else: Index: ucs-school-umc-csv-import/umc/python/schoolcsvimport/util.py =================================================================== --- ucs-school-umc-csv-import/umc/python/schoolcsvimport/util.py (Revision 49277) +++ ucs-school-umc-csv-import/umc/python/schoolcsvimport/util.py (Arbeitskopie) @@ -227,7 +227,7 @@ @classmethod def get_search_base(cls, school): if school not in cls._search_base_cache: - cls._search_base_cache[school] = SchoolSearchBase([], school) + cls._search_base_cache[school] = SchoolSearchBase({}, school) return cls._search_base_cache[school] @classmethod @@ -657,7 +657,7 @@ @classmethod def get_search_base(cls, school): if school not in cls._search_base_cache: - cls._search_base_cache[school] = SchoolSearchBase([], school) + cls._search_base_cache[school] = SchoolSearchBase({}, school) return cls._search_base_cache[school] class Group(UCSSchoolHelperAbstractClass):