Source code for ucsschool.lib.schoolldap

#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# UCS@school python lib
#
# Copyright 2007-2021 Univention GmbH
#
# http://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <http://www.gnu.org/licenses/>.

import re
from typing import Dict, Optional, Pattern, Sequence

from ldap.dn import dn2str, escape_dn_chars, explode_dn, str2dn

from univention.config_registry import ConfigRegistry


[docs]class SchoolSearchBase(object): """Deprecated: don't use position to identify user objects""" ucr = None # type: ConfigRegistry _regex_cache = {} # type: Dict[str, Pattern] # prefixes _containerAdmins = "" _containerStudents = "" _containerStaff = "" _containerTeachersAndStaff = "" _containerTeachers = "" _containerClass = "" _containerRooms = "" _examUserContainerName = "" _examGroupNameTemplate = "" group_prefix_students = "" group_prefix_teachers = "" group_prefix_admins = "" group_prefix_staff = "" def __init__(self, availableSchools, school=None, dn=None, ldapBase=None): # type: (Sequence[str], Optional[str], Optional[str], Optional[str]) -> None if not self.ucr: self._load_ucr() self._ldapBase = ldapBase or self.ucr.get("ldap/base") from ucsschool.lib.models.school import School self._school = school or availableSchools[0] self._schoolDN = dn or School.cache(self.school).dn if not self._containerAdmins: self._load_containers_and_prefixes() @classmethod def _load_containers_and_prefixes(cls): # type: () -> None if not cls.ucr: cls._load_ucr() cls._containerAdmins = cls.ucr.get("ucsschool/ldap/default/container/admins", "admins") cls._containerStudents = cls.ucr.get("ucsschool/ldap/default/container/pupils", "schueler") cls._containerStaff = cls.ucr.get("ucsschool/ldap/default/container/staff", "mitarbeiter") cls._containerTeachersAndStaff = cls.ucr.get( "ucsschool/ldap/default/container/teachers-and-staff", "lehrer und mitarbeiter" ) cls._containerTeachers = cls.ucr.get("ucsschool/ldap/default/container/teachers", "lehrer") cls._containerClass = cls.ucr.get("ucsschool/ldap/default/container/class", "klassen") cls._containerRooms = cls.ucr.get("ucsschool/ldap/default/container/rooms", "raeume") cls._examUserContainerName = cls.ucr.get("ucsschool/ldap/default/container/exam", "examusers") cls._examGroupNameTemplate = cls.ucr.get( "ucsschool/ldap/default/groupname/exam", "OU%(ou)s-Klassenarbeit" ) cls.group_prefix_students = cls.ucr.get("ucsschool/ldap/default/groupprefix/pupils", "schueler-") cls.group_prefix_teachers = cls.ucr.get("ucsschool/ldap/default/groupprefix/teachers", "lehrer-") cls.group_prefix_admins = cls.ucr.get("ucsschool/ldap/default/groupprefix/admins", "admins-") cls.group_prefix_staff = cls.ucr.get("ucsschool/ldap/default/groupprefix/staff", "mitarbeiter-") @classmethod def _load_ucr(cls): # type: () -> ConfigRegistry cls.ucr = ConfigRegistry() cls.ucr.load() return cls.ucr
[docs] @classmethod def getOU(cls, dn): # type: (str) -> str """Return the school OU for a given DN. >>> SchoolSearchBase.getOU('uid=a,fou=bar,Ou=dc1,oU=dc,dc=foo,dc=bar') 'dc1' """ try: return next(val for x in str2dn(dn) for attr, val, z in x if attr.lower() == "ou") except StopIteration: pass
[docs] @classmethod def getOUDN(cls, dn): # type: (str) -> str """Return the School OU-DN part for a given DN. >>> SchoolSearchBase.getOUDN('uid=a,fou=bar,Ou=dc1,oU=dc,dc=foo,dc=bar') 'Ou=dc1,oU=dc,dc=foo,dc=bar' >>> SchoolSearchBase.getOUDN('ou=dc1,ou=dc,dc=foo,dc=bar') 'ou=dc1,ou=dc,dc=foo,dc=bar' >>> SchoolSearchBase.getOUDN('dc=foo,dc=bar') 'dc=foo,dc=bar' """ sdn = str2dn(dn) index = 0 for part in sdn: if any(x[0].lower() == "ou" for x in part): break index += 1 else: return dn return dn2str(sdn[index:])
@property def dhcp(self): # type: () -> str return "cn=dhcp,%s" % self.schoolDN @property def policies(self): # type: () -> str return "cn=policies,%s" % self.schoolDN @property def networks(self): # type: () -> str return "cn=networks,%s" % self.schoolDN @property def school(self): # type: () -> str return self._school @property def schoolDN(self): # type: () -> str return self._schoolDN @property def users(self): # type: () -> str return "cn=users,%s" % self.schoolDN @property def groups(self): # type: () -> str return "cn=groups,%s" % self.schoolDN @property def students_group(self): # type: () -> str return "cn=%s%s,cn=groups,%s" % ( escape_dn_chars(self.group_prefix_students), escape_dn_chars(self.school.lower()), self.schoolDN, ) @property def teachers_group(self): # type: () -> str return "cn=%s%s,cn=groups,%s" % ( escape_dn_chars(self.group_prefix_teachers), escape_dn_chars(self.school.lower()), self.schoolDN, ) @property def staff_group(self): # type: () -> str return "cn=%s%s,cn=groups,%s" % ( escape_dn_chars(self.group_prefix_staff), escape_dn_chars(self.school.lower()), self.schoolDN, ) @property def admins_group(self): # type: () -> str return "cn=%s%s,cn=ouadmins,cn=groups,%s" % ( escape_dn_chars(self.group_prefix_admins), escape_dn_chars(self.school.lower()), self._ldapBase, ) @property def workgroups(self): # type: () -> str return "cn=%s,cn=groups,%s" % (escape_dn_chars(self._containerStudents), self.schoolDN) @property def classes(self): # type: () -> str return "cn=%s,cn=%s,cn=groups,%s" % ( escape_dn_chars(self._containerClass), escape_dn_chars(self._containerStudents), self.schoolDN, ) @property def rooms(self): # type: () -> str return "cn=%s,cn=groups,%s" % (escape_dn_chars(self._containerRooms), self.schoolDN) @property def students(self): # type: () -> str return "cn=%s,cn=users,%s" % (escape_dn_chars(self._containerStudents), self.schoolDN) @property def teachers(self): # type: () -> str return "cn=%s,cn=users,%s" % (escape_dn_chars(self._containerTeachers), self.schoolDN) @property def teachersAndStaff(self): # type: () -> str return "cn=%s,cn=users,%s" % (escape_dn_chars(self._containerTeachersAndStaff), self.schoolDN) @property def staff(self): # type: () -> str return "cn=%s,cn=users,%s" % (escape_dn_chars(self._containerStaff), self.schoolDN) @property def admins(self): # type: () -> str return "cn=%s,cn=users,%s" % (escape_dn_chars(self._containerAdmins), self.schoolDN) @property def classShares(self): # type: () -> str return "cn=%s,cn=shares,%s" % (escape_dn_chars(self._containerClass), self.schoolDN) @property def shares(self): # type: () -> str return "cn=shares,%s" % self.schoolDN @property def printers(self): # type: () -> str return "cn=printers,%s" % self.schoolDN @property def computers(self): # type: () -> str return "cn=computers,%s" % self.schoolDN @property def examUsers(self): # type: () -> str return "cn=%s,%s" % (escape_dn_chars(self._examUserContainerName), self.schoolDN) @property def globalGroupContainer(self): # type: () -> str return "cn=ouadmins,cn=groups,%s" % (self._ldapBase,) @property def educationalDCGroup(self): # type: () -> str return "cn=OU%s-DC-Edukativnetz,cn=ucsschool,cn=groups,%s" % ( escape_dn_chars(self.school), self._ldapBase, ) @property def educationalMemberGroup(self): # type: () -> str return "cn=OU%s-Member-Edukativnetz,cn=ucsschool,cn=groups,%s" % ( escape_dn_chars(self.school), self._ldapBase, ) @property def administrativeDCGroup(self): # type: () -> str return "cn=OU%s-DC-Verwaltungsnetz,cn=ucsschool,cn=groups,%s" % ( escape_dn_chars(self.school), self._ldapBase, ) @property def administrativeMemberGroup(self): # type: () -> str return "cn=OU%s-Member-Verwaltungsnetz,cn=ucsschool,cn=groups,%s" % ( escape_dn_chars(self.school), self._ldapBase, ) @property def examGroupName(self): # type: () -> str # replace '%(ou)s' strings in generic exam_group_name ucr_value_keywords = {"ou": self.school} return self._examGroupNameTemplate % ucr_value_keywords @property def examGroup(self): # type: () -> str return "cn=%s,cn=ucsschool,cn=groups,%s" % (escape_dn_chars(self.examGroupName), self._ldapBase)
[docs] def isWorkgroup(self, groupDN): # type: (str) -> bool # a workgroup cannot lie in a sub directory if not groupDN.endswith(self.workgroups): return False return len(explode_dn(groupDN)) - len(explode_dn(self.workgroups)) == 1
[docs] def isGroup(self, groupDN): # type: (str) -> bool return groupDN.endswith(self.groups)
[docs] def isClass(self, groupDN): # type: (str) -> bool return groupDN.endswith(self.classes)
[docs] def isRoom(self, groupDN): # type: (str) -> bool return groupDN.endswith(self.rooms)
[docs] @classmethod def get_is_teachers_group_regex(cls): # type: () -> Pattern if "is_teachers_group" not in cls._regex_cache: if not cls._containerTeachers: cls._load_containers_and_prefixes() cls._regex_cache["is_teachers_group"] = re.compile( r"cn={}-(?P<ou>[^,]+?),cn=groups,ou=(?P=ou),{}".format( cls._containerTeachers, cls.ucr["ldap/base"] ), flags=re.IGNORECASE, ) return cls._regex_cache["is_teachers_group"]
[docs] @classmethod def get_is_admins_group_regex(cls): # type: () -> Pattern if "is_admins_group" not in cls._regex_cache: if not cls._containerAdmins: cls._load_containers_and_prefixes() cls._regex_cache["is_admins_group"] = re.compile( r"cn={}-[^,]+?,cn=ouadmins,cn=groups,{}".format( cls._containerAdmins, cls.ucr["ldap/base"] ), flags=re.IGNORECASE, ) return cls._regex_cache["is_admins_group"]
[docs] @classmethod def get_is_staff_group_regex(cls): # type: () -> Pattern if "is_staff_group" not in cls._regex_cache: if not cls._containerStaff: cls._load_containers_and_prefixes() cls._regex_cache["is_staff_group"] = re.compile( r"cn={}-(?P<ou>[^,]+?),cn=groups,ou=(?P=ou),{}".format( cls._containerStaff, cls.ucr["ldap/base"] ), flags=re.IGNORECASE, ) return cls._regex_cache["is_staff_group"]
[docs] @classmethod def get_is_student_group_regex(cls): # type: () -> Pattern if "is_student_group" not in cls._regex_cache: if not cls._containerStudents: cls._load_containers_and_prefixes() cls._regex_cache["is_student_group"] = re.compile( r"cn={}-(?P<ou>[^,]+?),cn=groups,ou=(?P=ou),{}".format( cls._containerStudents, cls.ucr["ldap/base"] ), flags=re.IGNORECASE, ) return cls._regex_cache["is_student_group"]
[docs] @classmethod def get_staff_group_regex(cls): # type: () -> Pattern if "staff" not in cls._regex_cache: if not cls._containerStaff: cls._load_containers_and_prefixes() cls._regex_cache["staff"] = re.compile( r"cn={}-(?P<ou>[^,]?),cn=groups,ou=(?P=ou),{}".format( cls._containerStaff, cls.ucr["ldap/base"] ), flags=re.IGNORECASE, ) return cls._regex_cache["staff"]
[docs] @classmethod def get_students_group_regex(cls): # type: () -> Pattern if "students" not in cls._regex_cache: if not cls._containerStudents: cls._load_containers_and_prefixes() cls._regex_cache["students"] = re.compile( r"cn={}-(?P<ou>[^,]?),cn=groups,ou=(?P=ou),{}".format( cls._containerStudents, cls.ucr["ldap/base"] ), flags=re.IGNORECASE, ) return cls._regex_cache["students"]
[docs] @classmethod def get_students_pos_regex(cls): # type: () -> Pattern if "students_pos" not in cls._regex_cache: if not cls._containerStudents: cls._load_containers_and_prefixes() cls._regex_cache["students_pos"] = re.compile( r"cn={},cn=users,ou=[^,]+,{}".format(cls._containerStudents, cls.ucr["ldap/base"]), flags=re.IGNORECASE, ) return cls._regex_cache["students_pos"]
[docs] @classmethod def get_teachers_pos_regex(cls): # type: () -> Pattern if "teachers_pos" not in cls._regex_cache: if not cls._containerTeachers: cls._load_containers_and_prefixes() cls._regex_cache["teachers_pos"] = re.compile( r"cn={},cn=users,ou=[^,]+,{}".format(cls._containerTeachers, cls.ucr["ldap/base"]), flags=re.IGNORECASE, ) return cls._regex_cache["teachers_pos"]
[docs] @classmethod def get_staff_pos_regex(cls): # type: () -> Pattern if "staff_pos" not in cls._regex_cache: if not cls._containerStaff: cls._load_containers_and_prefixes() cls._regex_cache["staff_pos"] = re.compile( r"cn={},cn=users,ou=[^,]+,{}".format(cls._containerStaff, cls.ucr["ldap/base"]), flags=re.IGNORECASE, ) return cls._regex_cache["staff_pos"]
[docs] @classmethod def get_teachers_and_staff_pos_regex(cls): # type: () -> Pattern if "teachers_and_staff_pos" not in cls._regex_cache: if not cls._containerTeachersAndStaff: cls._load_containers_and_prefixes() cls._regex_cache["teachers_and_staff_pos"] = re.compile( r"cn={},cn=users,ou=[^,]+,{}".format( cls._containerTeachersAndStaff, cls.ucr["ldap/base"] ), flags=re.IGNORECASE, ) return cls._regex_cache["teachers_and_staff_pos"]
[docs] @classmethod def get_admins_pos_regex(cls): # type: () -> Pattern if "admins_pos" not in cls._regex_cache: if not cls._containerAdmins: cls._load_containers_and_prefixes() cls._regex_cache["admins_pos"] = re.compile( r"cn={},cn=users,ou=[^,]+,{}".format(cls._containerAdmins, cls.ucr["ldap/base"]), flags=re.IGNORECASE, ) return cls._regex_cache["admins_pos"]
[docs] @classmethod def get_exam_users_pos_regex(cls): # type: () -> Pattern if "exam_user_pos" not in cls._regex_cache: if not cls._examUserContainerName: cls._load_containers_and_prefixes() cls._regex_cache["exam_user_pos"] = re.compile( r"cn={},ou=[^,]+,{}".format(cls._examUserContainerName, cls.ucr["ldap/base"]), flags=re.IGNORECASE, ) return cls._regex_cache["exam_user_pos"]
[docs] @classmethod def get_schoolclass_pos_regex(cls): # type: () -> Pattern if "schoolclass_pos" not in cls._regex_cache: if not cls._containerStudents or not cls._containerClass: cls._load_containers_and_prefixes() cls._regex_cache["schoolclass_pos"] = re.compile( r"cn={},cn={},cn=groups,ou=[^,]+?,{}".format( cls._containerClass, cls._containerStudents, cls.ucr["ldap/base"] ), flags=re.IGNORECASE, ) return cls._regex_cache["schoolclass_pos"]
[docs] @classmethod def get_workgroup_pos_regex(cls): # type: () -> Pattern if "workgroup_pos" not in cls._regex_cache: if not cls._containerStudents: cls._load_containers_and_prefixes() cls._regex_cache["workgroup_pos"] = re.compile( r"cn={},cn=groups,ou=[^,]+?,{}".format(cls._containerStudents, cls.ucr["ldap/base"]), flags=re.IGNORECASE, ) return cls._regex_cache["workgroup_pos"]
[docs] @classmethod def get_computerroom_pos_regex(cls): # type: () -> Pattern if "computerroom_pos" not in cls._regex_cache: if not cls._containerRooms: cls._load_containers_and_prefixes() cls._regex_cache["computerroom_pos"] = re.compile( r"cn={},cn=groups,ou=[^,]+?,{}".format(cls._containerRooms, cls.ucr["ldap/base"]), flags=re.IGNORECASE, ) return cls._regex_cache["computerroom_pos"]
[docs] @classmethod def get_workgroup_share_pos_regex(cls): # type: () -> Pattern if "workgroup_share_pos" not in cls._regex_cache: cls._regex_cache["workgroup_share_pos"] = re.compile( r"cn=shares,ou=[^,]+?,{}".format(cls.ucr["ldap/base"]), flags=re.IGNORECASE, ) return cls._regex_cache["workgroup_share_pos"]
[docs] @classmethod def get_school_class_share_pos_regex(cls): # type: () -> Pattern if "school_class_share_pos" not in cls._regex_cache: if not cls._containerClass: cls._load_containers_and_prefixes() cls._regex_cache["school_class_share_pos"] = re.compile( r"cn={},cn=shares,ou=[^,]+?,{}".format(cls._containerClass, cls.ucr["ldap/base"]), flags=re.IGNORECASE, ) return cls._regex_cache["school_class_share_pos"]