# SPDX-FileCopyrightText: 2004-2025 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only
"""|UDM| objects."""
from __future__ import annotations
from typing import Any
import ldap
import univention.admin.modules
from univention.admin.log import log
[docs]
def module(object: univention.admin.handlers.simpleLdap) -> str | None:
"""
Return handler name for |UDM| object.
:param object: |UDM| object instance
:returns: |UDM| handler name or `None`.
"""
return getattr(object, 'module', None)
[docs]
def get_superordinate(module: univention.admin.modules.UdmModule, co: None, lo: univention.admin.uldap.access, dn: str) -> univention.admin.handlers.simpleLdap | None:
"""
Searches for the superordinate object for the given DN.
:param module: |UDM| module name
:param co: |UDM| configuation object.
:param lo: |LDAP| connection.
:param dn: |DN|.
:returns: the superoridnate or `None` if the object does not require a superordinate object or it is not found.
"""
super_modules = set(univention.admin.modules.superordinate_names(module))
if super_modules:
while dn:
attr = lo.authz_connection.get(dn)
for module in {univention.admin.modules.name(x) for x in univention.admin.modules.identify(dn, attr)} & super_modules:
super_module = univention.admin.modules._get(module)
return get(super_module, co, lo, None, dn, authz='settings/cn' != super_module)
dn = lo.parentDn(dn)
return None
[docs]
def get(module: univention.admin.modules.UdmModule, co: None, lo: univention.admin.uldap.access, position: univention.admin.uldap.position, dn: str = '', attr: dict[str, list[Any]] | None = None, superordinate: Any | None = None, attributes: Any | None = None, authz: bool = True) -> univention.admin.handlers.simpleLdap | None:
"""
Return object of module while trying to identify objects of
superordinate modules as well.
:param module: |UDM| handler.
:param co: |UDM| configuation object.
:param lo: |LDAP| connection.
:param position: |UDM| position instance.
"""
# module was deleted
if not module:
return None
if not superordinate:
superordinate = get_superordinate(module, co, lo, dn or position.getDn())
if dn:
try:
obj = univention.admin.modules.lookup(module.module, co, lo, base=dn, superordinate=superordinate, scope='base', unique=True, required=True, authz=authz)[0]
obj.position.setDn(position.getDn() if position else dn)
return obj
except (ldap.NO_SUCH_OBJECT, univention.admin.uexceptions.noObject, IndexError):
if not lo.authz_connection.get(dn, attr=['objectClass']): # FIXME: information disclosure
raise univention.admin.uexceptions.noObject(dn)
if not univention.admin.modules.virtual(module.module):
raise univention.admin.uexceptions.wrongObjectType('The object %s is not a %s.' % (dn, module.module))
return module.object(co, lo, position, dn, superordinate=superordinate, attributes=attributes)
[docs]
def get_object(lo: univention.admin.uldap.access, dn: str) -> univention.admin.handlers.simpleLdap:
"""
Get a |UDM| object for the specified LDAP DN by automatically detecting the object type.
Returns `None` if the object doesn't exists or no |UDM| handler exists for it.
:param lo: |LDAP| connection.
:param dn: |DN| of the object.
"""
attr = lo.authz_connection.get(dn, ['*', '+'])
for modname in univention.admin.modules.objectType(None, lo, dn, attr):
module = univention.admin.modules.get(modname)
if module:
return module.object(None, lo, None, dn, None, attr)
[docs]
def open(object: univention.admin.handlers.simpleLdap) -> None:
"""
Initialization of properties not necessary for browsing etc.
:param object: |UDM| object.
"""
if not object:
return
if hasattr(object, 'open'):
object.open()
[docs]
def default(module: univention.admin.modules.UdmModule, co: None, lo: univention.admin.uldap.access, position: univention.admin.uldap.position) -> univention.admin.handlers.simpleLdap:
"""
Create |UDM| object and initialize default values.
:param module: |UDM| handler.
:param co: |UDM| configuation object.
:param lo: |LDAP| connection.
:param position: |UDM| position instance.
:returns: An initialized |UDM| object.
"""
module = univention.admin.modules._get(module)
object = module.object(co, lo, position)
for name, property in module.property_descriptions.items():
default = property.default(object)
if default:
object[name] = default
return object
[docs]
def description(object: univention.admin.handlers.simpleLdap) -> str:
"""
Return short description for object.
:param object: |UDM| object.
"""
return object.description()
[docs]
def shadow(
lo: univention.admin.uldap.access, module: univention.admin.modules.UdmModule, object: univention.admin.handlers.simpleLdap, position: univention.admin.uldap.position,
) -> tuple[univention.admin.handlers.simpleLdap, univention.admin.modules.UdmModule] | tuple[None, None]:
"""
If object is a container, return object and module the container
shadows (that is usually the one that is subordinate in the LDAP tree).
:param lo: |LDAP| connection.
:param module: |UDM| handler.
:param object: |UDM| object.
:param position: |UDM| position instance.
:returnd: 2-tuple (module, object) or `(None, None)`
"""
if not object:
return (None, None)
dn = object.dn
# this is equivalent to if ...; while 1:
while univention.admin.modules.isContainer(module):
dn = lo.parentDn(dn)
if not dn:
return (None, None)
attr = lo.authz_connection.get(dn) # TODO: information disclosure?
for m in univention.admin.modules.identify(dn, attr):
if not univention.admin.modules.isContainer(m):
o = get(m, None, lo, position=position, dn=dn)
return (m, o)
# module is not a container
return (module, object)
[docs]
def dn(object: univention.admin.handlers.simpleLdap) -> str | None:
"""
Return the |DN| of the object.
:param object: |UDM| object.
:returns: the |DN| or `None`.
"""
return getattr(object, 'dn', None)
[docs]
def ocToType(oc: str) -> str | None:
"""
Return the |UDM| module capabale of handling the given |LDAP| objectClass.
:param oc: |LDAP| object class.
:returns: name of the |UDM| module.
"""
for module in univention.admin.modules.modules.values():
if univention.admin.modules.policyOc(module) == oc:
return univention.admin.modules.name(module)
return None # FIXME:
[docs]
def fixedAttribute(object: univention.admin.handlers.simpleLdap, key: str) -> int:
"""
Check if the named property is a fixed attribute (not overwritten by more specific policies).
:param object: |UDM| object.
:param key: |UDM| property name
:returns: `True` if the property is fixed, `False` otherwise.
"""
if not hasattr(object, 'fixedAttributes'):
return False
return object.fixedAttributes().get(key, False)
[docs]
def emptyAttribute(object: univention.admin.handlers.simpleLdap, key: str) -> int:
"""
Check if the named property is an empty attribute (reset to empty by a general policy).
:param object: |UDM| object.
:param key: |UDM| property name
:returns: `True` if the property is empty, `False` otherwise.
"""
if not hasattr(object, 'emptyAttributes'):
return False
return object.emptyAttributes().get(key, False)
[docs]
def getPolicyReference(object: univention.admin.handlers.simpleLdap, policy_type: str) -> univention.admin.handlers.simplePolicy | None:
"""
Return the policy of the requested type.
:param object: |UDM| object.
:param policy_type: Name of the |UDM| policy to lookup.
:returns: The policy applying to the object or `None`.
"""
# FIXME: Move this to handlers.simpleLdap?
policyReference = None
for policy_dn in object.policies:
for m in univention.admin.modules.identify(policy_dn, object.lo.authz_connection.get(policy_dn)):
if univention.admin.modules.name(m) == policy_type:
policyReference = policy_dn
log.trace('get policy reference', policy=policyReference, dn=object.dn, type=object.module)
return policyReference
[docs]
def removePolicyReference(object: univention.admin.handlers.simpleLdap, policy_type: str) -> None:
"""
Remove the policy of the requested type.
:param object: |UDM| object.
:param policy_type: Name of the |UDM| policy to lookup.
"""
# FIXME: Move this to handlers.simpleLdap?
remove = None
for policy_dn in object.policies:
for m in univention.admin.modules.identify(policy_dn, object.lo.authz_connection.get(policy_dn)):
if univention.admin.modules.name(m) == policy_type:
remove = policy_dn
if remove:
log.debug('removing policy reference', policy=remove, dn=object.dn, type=object.module)
object.policies.remove(remove)
[docs]
def replacePolicyReference(object: univention.admin.handlers.simpleLdap, policy_type: str, new_reference: str) -> None:
"""
Replace the policy of the requested type with a new instance.
:param object: |UDM| object.
:param policy_type: Name of the |UDM| policy to lookup.
"""
# FIXME: Move this to handlers.simpleLdap?
module = univention.admin.modules._get(policy_type)
if not univention.admin.modules.recognize(module, new_reference, object.lo.authz_connection.get(new_reference)):
log.debug('error replacing policy reference', policy=new_reference, dn=object.dn, type=object.module)
return
removePolicyReference(object, policy_type)
log.debug('appending policy reference', policy=new_reference, dn=object.dn, type=object.module)
object.policies.append(new_reference)
[docs]
def restorePolicyReference(object: univention.admin.handlers.simpleLdap, policy_type: str) -> None:
"""
Restore the policy of the requested type.
:param object: |UDM| object.
:param policy_type: Name of the |UDM| policy to lookup.
"""
# FIXME: Move this to handlers.simpleLdap?
try:
module = univention.admin.modules._get(policy_type)
except LookupError:
return
removePolicyReference(object, policy_type)
restore = None
for policy_dn in object.oldpolicies:
if univention.admin.modules.recognize(module, policy_dn, object.lo.authz_connection.get(policy_dn)):
restore = policy_dn
if restore:
object.policies.append(restore)
[docs]
def wantsCleanup(object: univention.admin.handlers.simpleLdap) -> bool:
"""
Check if the given object wants to perform a cleanup (delete
other objects, etc.) before it is deleted itself.
:param object: parent object.
:returns: `True´ if a cleanup is requested, `False` otherwise.
"""
# TODO: make this a method of object
wantsCleanup = False
object_module = module(object)
object_module = univention.admin.modules._get(object_module)
if hasattr(object_module, 'docleanup'):
wantsCleanup = object_module.docleanup
return wantsCleanup