Source code for univention.admin.handlers.container.ou

# SPDX-FileCopyrightText: 2004-2025 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only

"""|UDM| module for Organizational Unit containers"""

from __future__ import annotations

import univention.admin.filter
import univention.admin.handlers
import univention.admin.localization
import univention.admin.syntax
from univention.admin import configRegistry
from univention.admin.handlers.container import default_container_for_objects
from univention.admin.layout import Group, Tab


translation = univention.admin.localization.translation('univention.admin.handlers.container')
_ = translation.translate

module = 'container/ou'
operations = ['add', 'edit', 'remove', 'search', 'move', 'subtree_move']
childs = True
short_description = _('Container: Organisational Unit')
object_name = _('Organisational Unit')
object_name_plural = _('Organisational Units')
long_description = ''
# fmt: off
options = {
    'default': univention.admin.option(
        short_description=short_description,
        default=True,
        objectClasses=['top', 'organizationalUnit'],
    ),
    'group-settings': univention.admin.option(
        short_description=_('Default Group Settings'),
        objectClasses=['univentionContainerDefault'],
        default=False,
        editable=True,
    ),
}
property_descriptions = {
    'name': univention.admin.property(
        short_description=_('Name'),
        long_description='',
        syntax=univention.admin.syntax.string,
        include_in_default_search=True,
        required=True,
        identifies=True,
        readonly_when_synced=True,
    ),
    'policyPath': univention.admin.property(
        short_description=_('Add to standard policy containers'),
        long_description='',
        syntax=univention.admin.syntax.boolean,
        size='One',
    ),
    'dhcpPath': univention.admin.property(
        short_description=_('Add to standard DHCP containers'),
        long_description='',
        syntax=univention.admin.syntax.boolean,
        size='One',
    ),
    'dnsPath': univention.admin.property(
        short_description=_('Add to standard DNS containers'),
        long_description='',
        syntax=univention.admin.syntax.boolean,
        size='One',
    ),
    'userPath': univention.admin.property(
        short_description=_('Add to standard user containers'),
        long_description='',
        syntax=univention.admin.syntax.boolean,
        size='One',
    ),
    'groupPath': univention.admin.property(
        short_description=_('Add to standard group containers'),
        long_description='',
        syntax=univention.admin.syntax.boolean,
        size='One',
    ),
    'computerPath': univention.admin.property(
        short_description=_('Add to standard computer containers'),
        long_description='',
        syntax=univention.admin.syntax.boolean,
        size='One',
    ),
    'domaincontrollerPath': univention.admin.property(
        short_description=_('Add to standard Directory Node computer containers'),
        long_description='',
        syntax=univention.admin.syntax.boolean,
        size='One',
    ),
    'networkPath': univention.admin.property(
        short_description=_('Add to standard network containers'),
        long_description='',
        syntax=univention.admin.syntax.boolean,
        size='One',
    ),
    'sharePath': univention.admin.property(
        short_description=_('Add to standard share containers'),
        long_description='',
        syntax=univention.admin.syntax.boolean,
        size='One',
    ),
    'printerPath': univention.admin.property(
        short_description=_('Add to standard printer containers'),
        long_description='',
        syntax=univention.admin.syntax.boolean,
        size='One',
    ),
    'mailPath': univention.admin.property(
        short_description=_('Add to standard mail containers'),
        long_description='',
        syntax=univention.admin.syntax.boolean,
        size='One',
    ),
    'licensePath': univention.admin.property(
        short_description=_('Add to standard license containers'),
        long_description='',
        syntax=univention.admin.syntax.boolean,
        size='One',
    ),
    'description': univention.admin.property(
        short_description=_('Description'),
        long_description='',
        syntax=univention.admin.syntax.string,
        include_in_default_search=True,
        readonly_when_synced=True,
    ),
    'defaultGroup': univention.admin.property(
        short_description=_('Default Primary Group'),
        long_description='',
        syntax=univention.admin.syntax.GroupDNOrEmpty,
        options=['group-settings'],
        dontsearch=True,
        required=False,
    ),
    'defaultComputerGroup': univention.admin.property(
        short_description=_('Default Group for Computers'),
        long_description='',
        syntax=univention.admin.syntax.GroupDNOrEmpty,
        options=['group-settings'],
        dontsearch=True,
        required=False,
    ),
    'defaultDomainControllerGroup': univention.admin.property(
        short_description=_('Default Group for Replica Directory Nodes'),
        long_description='',
        syntax=univention.admin.syntax.GroupDNOrEmpty,
        options=['group-settings'],
        dontsearch=True,
        required=False,
    ),
    'defaultDomainControllerMBGroup': univention.admin.property(
        short_description=_('Default Group for Primary and Backup Directory Nodes'),
        long_description='',
        syntax=univention.admin.syntax.GroupDNOrEmpty,
        options=['group-settings'],
        dontsearch=True,
        required=False,
    ),
    'defaultMemberServerGroup': univention.admin.property(
        short_description=_('Default Group for Managed Nodes'),
        long_description='',
        syntax=univention.admin.syntax.GroupDNOrEmpty,
        options=['group-settings'],
        dontsearch=True,
        required=False,
    ),
    'defaultClientGroup': univention.admin.property(
        short_description=_('Default Group for Client Computers'),
        long_description='',
        syntax=univention.admin.syntax.GroupDNOrEmpty,
        options=['group-settings'],
        dontsearch=True,
        required=False,
    ),
}

layout = [
    Tab(_('General'), _('Basic settings'), layout=[
        Group(_('Organisational unit description'), layout=[
            ["name", "description"],
        ]),
        Group(_('Container settings'), _('Default position when adding objects'), layout=[
            ["userPath", "groupPath"],
            ["computerPath", "domaincontrollerPath"],
            ["dnsPath", "dhcpPath"],
            ["networkPath", "sharePath"],
            ["printerPath", "mailPath"],
            ["policyPath", "licensePath"],
        ]),
    ]),
    Tab(_('Default Primary Groups'), _('Default Primary Groups'), layout=[
        Group(_('Default Primary Groups'), layout=[
            "defaultGroup",
            "defaultComputerGroup",
            "defaultDomainControllerMBGroup",
            "defaultDomainControllerGroup",
            "defaultMemberServerGroup",
            "defaultClientGroup",
        ]),
    ]),
]

mapping = univention.admin.mapping.mapping()
mapping.register('name', 'ou', None, univention.admin.mapping.ListToString)
mapping.register('description', 'description', None, univention.admin.mapping.ListToString)

mapping.register('defaultGroup', 'univentionDefaultGroup', None, univention.admin.mapping.ListToString)
mapping.register('defaultComputerGroup', 'univentionDefaultComputerGroup', None, univention.admin.mapping.ListToString)
mapping.register('defaultDomainControllerMBGroup', 'univentionDefaultDomainControllerMasterGroup', None, univention.admin.mapping.ListToString)
mapping.register('defaultDomainControllerGroup', 'univentionDefaultDomainControllerGroup', None, univention.admin.mapping.ListToString)
mapping.register('defaultMemberServerGroup', 'univentionDefaultMemberserverGroup', None, univention.admin.mapping.ListToString)
mapping.register('defaultClientGroup', 'univentionDefaultClientGroup', None, univention.admin.mapping.ListToString)
# fmt: on


[docs] class object(univention.admin.handlers.simpleLdap): module = module PATH_KEYS = { 'userPath': 'univentionUsersObject', 'groupPath': 'univentionGroupsObject', 'computerPath': 'univentionComputersObject', 'domaincontrollerPath': 'univentionDomainControllerComputersObject', 'policyPath': 'univentionPolicyObject', 'dnsPath': 'univentionDnsObject', 'dhcpPath': 'univentionDhcpObject', 'networkPath': 'univentionNetworksObject', 'sharePath': 'univentionSharesObject', 'printerPath': 'univentionPrintersObject', 'mailPath': 'univentionMailObject', 'licensePath': 'univentionLicenseObject', }
[docs] def open(self) -> None: univention.admin.handlers.simpleLdap.open(self) pathResult, self.default_dn = default_container_for_objects(self.lo, self.position.getDomain()) for prop in self.PATH_KEYS: self.info[prop] = '0' dn_bytes = self.dn.encode('UTF-8') for prop, attr in self.PATH_KEYS.items(): if any(x == dn_bytes for x in pathResult.get(attr, [])): self.info[prop] = '1' self.save()
def _ldap_pre_create(self) -> None: super()._ldap_pre_create() if configRegistry.is_false('directory/manager/child/cn/ou', True) and not self.lo.compare_dn(self.position.getDn(), configRegistry.get('ldap/base')): # it is possible to have a basedn with cn=foo # in this case it is allowed to create a ou # under a cn. if any(m and m.module == 'container/cn' for m in univention.admin.modules.identify(self.position.getDn(), self.lo.authz_connection.get(self.position.getDn()))): raise univention.admin.uexceptions.invalidChild(_('It is not allowed to create a container/ou as child object of a container/cn.')) def _ldap_post_create(self) -> None: super()._ldap_post_create() changes = [] dn_bytes = self.dn.encode('UTF-8') for prop, attr in self.PATH_KEYS.items(): if self.oldinfo.get(prop) != self.info.get(prop): entries = self.lo.authz_connection.getAttr(self.default_dn, attr) if self.info[prop] == '0': if dn_bytes in entries: changes.append((attr, self.dn.encode('utf-8'), b'')) else: if dn_bytes not in entries: changes.append((attr, b'', self.dn.encode('utf-8'))) if changes: self.lo.authz_connection.modify(self.default_dn, changes) def _ldap_pre_rename(self, newdn: str) -> None: super()._ldap_pre_rename(newdn) self.move(newdn) def _ldap_post_move(self, olddn: str) -> None: super()._ldap_post_move(olddn) settings_module = univention.admin.modules._get('settings/directory') settings_object = univention.admin.objects.get(settings_module, None, self.lo, position='', dn=self.default_dn, authz=False) settings_object.open() for attr in ['dns', 'license', 'computers', 'shares', 'groups', 'printers', 'policies', 'dhcp', 'networks', 'users', 'mail']: if olddn in settings_object[attr]: settings_object[attr].remove(olddn) settings_object[attr].append(self.dn) settings_object.modify() def _ldap_post_modify(self) -> None: super()._ldap_post_modify() changes = [] dn_bytes = self.dn.encode('UTF-8') for prop, attr in self.PATH_KEYS.items(): if self.oldinfo.get(prop) != self.info.get(prop): if self.info[prop] == '0': changes.append((attr, dn_bytes, b'')) else: changes.append((attr, b'', dn_bytes)) if changes: self.lo.authz_connection.modify(self.default_dn, changes) def _ldap_pre_remove(self) -> None: super()._ldap_pre_remove() changes = [] self.open() dn_bytes = self.dn.encode('UTF-8') for prop, attr in self.PATH_KEYS.items(): if self.oldinfo.get(prop) == '1': changes.append((attr, dn_bytes, b'')) self.lo.authz_connection.modify(self.default_dn, changes)
[docs] @classmethod def unmapped_lookup_filter(cls) -> univention.admin.filter.conjunction: return univention.admin.filter.conjunction('&', [ univention.admin.filter.expression('objectClass', 'organizationalUnit'), univention.admin.filter.conjunction('!', [univention.admin.filter.expression('objectClass', 'univentionBase')]), ]) # fmt: skip
lookup = object.lookup lookup_filter = object.lookup_filter
[docs] def identify(dn: str, attr: univention.admin.handlers._Attributes, canonical: bool = False) -> bool: return b'organizationalUnit' in attr.get('objectClass', []) and b'univentionBase' not in attr.get('objectClass', [])