# SPDX-FileCopyrightText: 2004-2026 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only
"""|UDM| hook definitions for modifying |LDAP| calls when objects are created, modifier or deleted."""
from __future__ import annotations
import inspect
import os
import sys
from types import SimpleNamespace
from typing import TYPE_CHECKING, Any
from univention.admin import localization
if TYPE_CHECKING:
import univention.admin.handlers
AddList = list[tuple[str, list[str]]]
_Mod2 = tuple[str, list[str]]
_Mod3 = tuple[str, list[str], list[str]]
ModList = list[_Mod2 | _Mod3]
import univention.admin.mapping
from univention.admin.log import log
translation = localization.translation('univention/admin')
_ = translation.translate
[docs]
def import_hook_files() -> 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 open(fn, 'rb') as fd:
env = {
'simpleHook': simpleHook,
'AttributeHook': AttributeHook,
}
byte_code = compile(fd.read(), fn, "exec")
exec(byte_code, env) # noqa: S102
sys.modules[__name__].__dict__.update(
dict(
inspect.getmembers(
SimpleNamespace(**env), lambda m: inspect.isclass(m) and issubclass(m, (simpleHook, AttributeHook)) and m not in (simpleHook, AttributeHook),
),
),
)
log.debug('importing hook', hook=fn)
except Exception:
log.exception('loading hook failed', hook=fn)
[docs]
class simpleHook:
"""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 map(self, value: Any, encoding=()) -> list[bytes]:
"""
This method can be overwritten to define special method to map |UDM| property value to |LDAP| attribute value.
:param value: The UDM property value
:param encoding: Optional encoding information
:returns: The list of LDAP attributes.
.. versionadded:: 5.2-6
This mapping function is available since :uv:erratum:`5.2x416`.
"""
return univention.admin.mapping.MapToBytes(value, encoding)
[docs]
def unmap(self, value: list[bytes], encoding=()) -> Any:
"""
This method can be overwritten to define special un-map methods to map back from |LDAP| to |UDM|.
:param value: The list of LDAP attributes
:param encoding: Optional encoding information
:returns: The UDM property value.
.. versionadded:: 5.2-6
This mapping function is available since :uv:erratum:`5.2x416`.
"""
return univention.admin.mapping.UnmapToUnicode(value, encoding)
[docs]
def hook_open(self, obj: 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.
"""
log.debug('hook_open called')
[docs]
def hook_ldap_pre_create(self, obj: 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.
"""
log.debug('hook_ldap_pre_create called')
[docs]
def hook_ldap_addlist(self, obj: univention.admin.handlers.simpleLdap, al: 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.
"""
log.debug('hook_ldap_addlist called')
return al
[docs]
def hook_ldap_post_create(self, obj: univention.admin.handlers.simpleLdap) -> None:
"""
This method is called after the object was created in |LDAP|.
:param obj: The |UDM| object instance.
"""
log.debug('hook_ldap_post_create called')
[docs]
def hook_ldap_pre_modify(self, obj: 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.
"""
log.debug('hook_ldap_pre_modify called')
[docs]
def hook_ldap_modlist(self, obj: univention.admin.handlers.simpleLdap, ml: 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.
"""
log.debug('hook_ldap_modlist called')
return ml
[docs]
def hook_ldap_pre_move(self, obj: univention.admin.handlers.simpleLdap) -> None:
"""
This method is called before the object is moved in |LDAP|.
:param obj: The |UDM| object instance.
"""
log.debug('hook_ldap_post_modify called')
[docs]
def hook_ldap_post_move(self, obj: univention.admin.handlers.simpleLdap) -> None:
"""
This method is called after the object was moved in |LDAP|.
:param obj: The |UDM| object instance.
"""
log.debug('hook_ldap_post_move called')
[docs]
def hook_ldap_post_modify(self, obj: univention.admin.handlers.simpleLdap) -> None:
"""
This method is called after the object was modified in |LDAP|.
:param obj: The |UDM| object instance.
"""
log.debug('hook_ldap_post_modify called')
[docs]
def hook_ldap_pre_remove(self, obj: univention.admin.handlers.simpleLdap) -> None:
"""
This method is called before an |UDM| object is removed.
:param obj: The |UDM| object instance.
"""
log.debug('hook_ldap_pre_remove called')
[docs]
def hook_ldap_post_remove(self, obj: univention.admin.handlers.simpleLdap) -> None:
"""
This method is called after the object was removed from |LDAP|.
:param obj: The |UDM| object instance.
"""
log.debug('hook_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`
.. deprecated:: 5.2-6
Since UCS 5.2-5-errata :class:`simpleHook` provides :func:`map`
and :func:`unmap`, which should be used instead.
"""
udm_attribute_name = None # not required anymore
ldap_attribute_name = None # not required anymore
version = 3
[docs]
def map(self, value: Any, encoding=()) -> list[bytes]:
return self.map_attribute_value_to_ldap(value)
[docs]
def unmap(self, value: list[bytes], encoding=()) -> Any:
return self.map_attribute_value_to_udm(value)
[docs]
def map_attribute_value_to_ldap(self, value: Any) -> list[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: list[bytes]) -> Any:
"""
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