#!/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"]