# SPDX-FileCopyrightText: 2024-2025 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only
"""|UDM| guardian roles handling"""
import itertools
import univention.admin
import univention.admin.localization
import univention.admin.syntax
from univention.admin.layout import Tab
try:
from univention.admin.syntax import GuardianRole
except ImportError: # just during errata updates. Can be removed in UCS 5.2-3
GuardianRole = univention.admin.syntax.simple
translation = univention.admin.localization.translation('univention.admin')
_ = translation.translate
[docs]
def member_role_properties():
return {
'guardianMemberRoles': univention.admin.property(
short_description=_('Roles used by Guardian for access permissions, these roles are passed to the members of this group'),
long_description=_("Lowercase ASCII alphanumeric string with underscores or dashes, in the format 'app:namespace:role' or 'app:namespace:role&app:namespace:context'"),
syntax=GuardianRole,
size='Two',
multivalue=True,
),
}
[docs]
def role_properties():
return {
'guardianRoles': univention.admin.property(
short_description=_('Roles used by Guardian for access permissions'),
long_description=_("Lowercase ASCII alphanumeric string with underscores or dashes, in the format 'app:namespace:role' or 'app:namespace:role&app:namespace:context'"),
syntax=GuardianRole,
size='Two',
multivalue=True,
),
'guardianInheritedRoles': univention.admin.property(
short_description=_('Roles used by Guardian for access permissions. Inherited by group membership'),
long_description=_('Roles used by Guardian for access permissions. Inherited by group membership'),
prevent_umc_default_popup=True,
syntax=GuardianRole,
size='Two',
may_change=False,
multivalue=True,
dontsearch=True,
show_in_lists=True,
cli_enabled=False,
lazy_loading_fn='open_guardian',
),
}
[docs]
def register_member_role_mapping(mapping):
mapping.register('guardianMemberRoles', 'univentionGuardianMemberRoles', None, None)
[docs]
def register_role_mapping(mapping):
mapping.register('guardianRoles', 'univentionGuardianRoles', None, None)
[docs]
def member_role_layout():
return Tab(
_('Guardian'),
_('Manage roles that are used for authorization'),
advanced=True,
layout=[
'guardianMemberRoles',
],
)
[docs]
def role_layout():
return Tab(
_('Guardian'),
_('Manage roles that are used for authorization'),
advanced=True,
layout=[
'guardianRoles',
'guardianInheritedRoles',
],
)
@univention.admin._ldap_cache(ttl=60)
def get_group_role(lo: univention.admin.uldap.access, dn: str) -> list[str]:
res = lo.authz_connection.get(dn, attr=['univentionGuardianMemberRoles'])
return [x.decode('UTF-8') for x in res.get('univentionGuardianMemberRoles', [])]
# TODO:
# naive approach to get role strings for groups by searching the LDAP
[docs]
def load_roles(lo: univention.admin.uldap.access, groups: list[str]) -> list[str]:
return list(set(itertools.chain.from_iterable(get_group_role(lo, group) for group in groups)))
[docs]
def get_roles_from_ldap(lo: univention.admin.uldap.access, dn: str) -> list[str]:
res = lo.authz_connection.get(dn, attr=['univentionGuardianRoles', 'memberOf'])
roles = {x.decode('UTF-8') for x in res.get('univentionGuardianRoles', [])}
if 'memberOf' in res:
roles.update(load_roles(lo, [x.decode('UTF-8') for x in res['memberOf']]))
return list(roles)
[docs]
class GuardianBase:
[docs]
def open_guardian(self) -> None:
if self.exists() and self.has_property('guardianInheritedRoles'):
groups = self.get('groups', [])
if self.get('primaryGroup'):
groups = [*groups, self['primaryGroup']]
self.info['guardianInheritedRoles'] = load_roles(self.lo, groups)
self.save()