# -*- coding: utf-8 -*-
#
# Copyright 2004-2022 Univention GmbH
#
# https://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
# <https://www.gnu.org/licenses/>.
"""
|UDM| hook definitions for modifying |LDAP| calls when objects are created, modifier or deleted.
"""
import sys
import io
import os
import traceback
import six
from typing import TYPE_CHECKING, List, Tuple, Union # noqa: F401
import univention.debug as ud
from univention.admin import localization
if TYPE_CHECKING:
import univention.admin.handlers # noqa: F401
AddList = List[Tuple[str, List[str]]]
_Mod2 = Tuple[str, List[str]]
_Mod3 = Tuple[str, List[str], List[str]]
ModList = List[Union[_Mod2, _Mod3]]
translation = localization.translation('univention/admin')
_ = translation.translate
[docs]def import_hook_files():
# type: () -> None
"""
Load all additional hook files from :file:`.../univention/admin/hooks.d/*.py`
"""
for dir_ in sys.path:
hooks_d = os.path.join(dir_, 'univention/admin/hooks.d/')
if os.path.isdir(hooks_d):
hooks_files = (os.path.join(hooks_d, f) for f in os.listdir(hooks_d) if f.endswith('.py'))
for fn in hooks_files:
try:
with io.open(fn, 'rb') as fd:
exec(fd.read(), sys.modules[__name__].__dict__)
ud.debug(ud.ADMIN, ud.INFO, 'admin.hook.import_hook_files: importing %r' % (fn,))
except Exception:
ud.debug(ud.ADMIN, ud.ERROR, 'admin.hook.import_hook_files: loading %r failed' % (fn,))
ud.debug(ud.ADMIN, ud.ERROR, 'admin.hook.import_hook_files: TRACEBACK:\n%s' % traceback.format_exc())
[docs]class simpleHook(object):
"""
Base class for a |UDM| hook performing logging.
"""
type = 'simpleHook'
#
# To use the LDAP connection of the parent UDM call in any of the following
# methods, use obj.lo and obj.position.
#
[docs] def hook_open(self, obj):
# type: (univention.admin.handlers.simpleLdap) -> None
"""
This method is called by the default open handler just before the current state of all properties is saved.
:param obj: The |UDM| object instance.
"""
ud.debug(ud.ADMIN, ud.INFO, 'admin.syntax.hook.simpleHook: _open called')
[docs] def hook_ldap_pre_create(self, obj):
# type: (univention.admin.handlers.simpleLdap) -> None
"""
This method is called before an |UDM| object is created.
It is called after the module validated all properties but before the add-list is created.
:param obj: The |UDM| object instance.
"""
ud.debug(ud.ADMIN, ud.INFO, 'admin.syntax.hook.simpleHook: _ldap_pre_create called')
[docs] def hook_ldap_addlist(self, obj, al=[]):
# type: (univention.admin.handlers.simpleLdap, AddList) -> AddList
"""
This method is called before an |UDM| object is created.
Notice that :py:meth:`hook_ldap_modlist` will also be called next.
:param obj: The |UDM| object instance.
:param al: A list of two-tuples (ldap-attribute-name, list-of-values) which will be used to create the LDAP object.
:returns: The (modified) add-list.
"""
ud.debug(ud.ADMIN, ud.INFO, 'admin.syntax.hook.simpleHook: _ldap_addlist called')
return al
[docs] def hook_ldap_post_create(self, obj):
# type: (univention.admin.handlers.simpleLdap) -> None
"""
This method is called after the object was created in |LDAP|.
:param obj: The |UDM| object instance.
"""
ud.debug(ud.ADMIN, ud.INFO, 'admin.syntax.hook.simpleHook: _ldap_post_create called')
[docs] def hook_ldap_pre_modify(self, obj):
# type: (univention.admin.handlers.simpleLdap) -> None
"""
This method is called before an |UDM| object is modified.
It is called after the module validated all properties but before the modification-list is created.
:param obj: The |UDM| object instance.
"""
ud.debug(ud.ADMIN, ud.INFO, 'admin.syntax.hook.simpleHook: _ldap_pre_modify called')
[docs] def hook_ldap_modlist(self, obj, ml=[]):
# type: (univention.admin.handlers.simpleLdap, ModList) -> ModList
"""
This method is called before an |UDM| object is created or modified.
:param obj: The |UDM| object instance.
:param ml: A list of tuples, which are either two-tuples (ldap-attribute-name, list-of-new-values) or three-tuples (ldap-attribute-name, list-of-old-values, list-of-new-values). It will be used to create or modify the |LDAP| object.
:returns: The (modified) modification-list.
"""
ud.debug(ud.ADMIN, ud.INFO, 'admin.syntax.hook.simpleHook: _ldap_modlist called')
return ml
[docs] def hook_ldap_post_modify(self, obj):
# type: (univention.admin.handlers.simpleLdap) -> None
"""
This method is called after the object was modified in |LDAP|.
:param obj: The |UDM| object instance.
"""
ud.debug(ud.ADMIN, ud.INFO, 'admin.syntax.hook.simpleHook: _ldap_post_modify called')
[docs] def hook_ldap_pre_remove(self, obj):
# type: (univention.admin.handlers.simpleLdap) -> None
"""
This method is called before an |UDM| object is removed.
:param obj: The |UDM| object instance.
"""
ud.debug(ud.ADMIN, ud.INFO, 'admin.syntax.hook.simpleHook: _ldap_pre_remove called')
[docs] def hook_ldap_post_remove(self, obj):
# type: (univention.admin.handlers.simpleLdap) -> None
"""
This method is called after the object was removed from |LDAP|.
:param obj: The |UDM| object instance.
"""
ud.debug(ud.ADMIN, ud.INFO, 'admin.syntax.hook.simpleHook: _ldap_post_remove called')
[docs]class AttributeHook(simpleHook):
"""
Convenience Hook that essentially implements a mapping
between |UDM| and |LDAP| for your extended attributes.
Derive from this class, set :py:attr:`attribute_name` to the name of
the |UDM| attribute and implement :py:meth:`map_attribute_value_to_udm`
and :py:meth:`map_attribute_value_to_ldap`.
.. warning::
Only derive from this class when you are sure
every system in your domain has the update installed that
introduced this hook. (Nov 2018; UCS 4.3-2)
Otherwise you will get errors when you are distributing your new
hook via `ucs_registerLDAPExtension --udm_hook`
"""
udm_attribute_name = None
ldap_attribute_name = None
[docs] def hook_open(self, obj):
# type: (univention.admin.handlers.simpleLdap) -> None
"""
Open |UDM| object by loading value from |LDAP|.
:param obj: The |UDM| object instance.
"""
assert isinstance(self.udm_attribute_name, six.string_types), "udm_attribute_name has to be a str" # noqa: F821
ud.debug(ud.ADMIN, ud.INFO, 'admin.syntax.hook.AttributeHook: Mapping %s (LDAP) -> %s (UDM)' % (self.ldap_attribute_name, self.udm_attribute_name))
old_value = obj[self.udm_attribute_name]
new_value = self.map_attribute_value_to_udm(old_value)
ud.debug(ud.ADMIN, ud.INFO, 'admin.syntax.hook.AttributeHook: Setting UDM value from %r to %r' % (old_value, new_value))
obj[self.udm_attribute_name] = new_value
[docs] def hook_ldap_addlist(self, obj, al):
# type: (univention.admin.handlers.simpleLdap, AddList) -> AddList
"""
Extend |LDAP| add list.
:param obj: The |UDM| object instance.
:param al: The add list to extend.
:returns: The extended add list.
"""
return self.hook_ldap_modlist(obj, al)
[docs] def hook_ldap_modlist(self, obj, ml):
# type: (univention.admin.handlers.simpleLdap, ModList) -> ModList
"""
Extend |LDAP| modification list.
:param obj: The |UDM| object instance.
:param ml: The modification list to extend.
:returns: The extended modification list.
"""
assert isinstance(self.ldap_attribute_name, six.string_types), "ldap_attribute_name has to be a str" # noqa: F821
new_ml = []
for ml_value in ml:
if len(ml_value) == 2:
key, old_value, new_value = ml_value[0], [], ml_value[1]
else:
key, old_value, new_value = ml_value
if key == self.ldap_attribute_name:
ud.debug(ud.ADMIN, ud.INFO, 'admin.syntax.hook.AttributeHook: Mapping %s (UDM) -> %s (LDAP)' % (self.udm_attribute_name, self.ldap_attribute_name))
old_value = self.map_attribute_value_to_ldap(old_value)
new_new_value = self.map_attribute_value_to_ldap(new_value)
ud.debug(ud.ADMIN, ud.INFO, 'admin.syntax.hook.AttributeHook: Setting LDAP value from %r to %r' % (new_value, new_new_value))
new_value = new_new_value
new_ml.append((key, old_value, new_value))
return new_ml
[docs] def map_attribute_value_to_ldap(self, value):
# type: (bytes) -> bytes
"""
Return value as it shall be saved in |LDAP|.
:param value: The |UDM| value.
:returns: The |LDAP| value.
"""
return value
[docs] def map_attribute_value_to_udm(self, value):
# type: (str) -> str
"""
Return value as it shall be used in |UDM| objects.
The mapped value needs to be syntax compliant.
:param value: The |LDAP| value.
:returns: The |UDM| value.
"""
return value