Source code for univention.authorization.config

#!/usr/bin/python3
# SPDX-FileCopyrightText: 2025 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only

import yaml

from univention.authorization.management import (
    expand_condition, expand_permission, expand_role, expand_string, implode_string,
)


[docs] class AuthorizationConfig: """ A YAML based configuration format for Guardian. This intermediate layer allows to de-duplicate data while Guardian dosen't offer capability bundles, permission bundles, and multiple role to capability assignments, etc. """ def __init__(self, filename): self.filename = filename self.conditions = {} # named reusable conditions self.permission_sets = {} # named permission bundles (emulated as not actually implemented in Guardian) self.capabilities = {} # raw capabilities suitable for re-use/reference, suitable as API for customers self.capability_bundles = {} # re-useable capability bundles realizing one specific use case self.role_capability_mapping = {}
[docs] def parse(self): with open(self.filename) as fd: data = yaml.safe_load(fd) self.conditions = data['conditions'] self.permission_sets = data['permission-sets'] self.capabilities = {f'{ns}:{cap_name}': cap for ns, caps in data['capabilities'].items() for cap_name, cap in caps.items()} self.capability_bundles = {f'{ns}:{cap_name}': cap for ns, caps in data['capability-bundles'].items() for cap_name, cap in caps.items()} self.role_capability_mapping = {f'{ns}:{cap_name}': cap for ns, caps in data['role-capability-mapping'].items() for cap_name, cap in caps.items()}
[docs] def compose(self): return { 'conditions': self.conditions, 'permission-sets': self.permission_sets, 'capabilities': self.capabilities, 'capability-bundles': self.capability_bundles, 'role-capability-mapping': self.role_capability_mapping, }
[docs] def create(self, client): self.client = client for role_string, role in self.role_capability_mapping.items(): app_name, namespace_name, role_name = expand_string(role_string) self.client.create_role(app_name, namespace_name, role_name, role.get('displayname', '')) for bundle in role.get('capability-bundles', []): self.create_capability_bundle(role_string, bundle) for capability in role.get('capabilities', []): self.create_capability(role_string, capability) self.create_permission(role_string, role.get('permissions', []))
[docs] def create_permission(self, role_string, permissions): # permissions can't be granted in Guardian, so we need a capability for it if not permissions: return role_name = expand_string(role_string)[2] capability = {'grants-permissions': permissions} self._create_capability_from_obj(role_string, capability, f"{role_name}-capability-permissions")
[docs] def create_capability_bundle(self, role_string, bundle_name): # there are no capability bundles in Guardian, so we create a new capability for each capability in the bundle for cap_name in self.capability_bundles[bundle_name]: self.create_capability(role_string, cap_name)
[docs] def create_capability(self, role_string, capability_string): capability = self.capabilities[capability_string] capability_name = expand_string(capability_string)[2] role_name = expand_string(role_string)[2] self._create_capability_from_obj(role_string, capability, f"{role_name}-capability-{capability_name}")
def _create_capability_from_obj(self, role_string, capability, capability_string): conditions = capability.get('conditions', {}) relation = next(iter(conditions), 'AND') permissions = list(self.resolve_permissions(capability['grants-permissions'])) self.client.create_role_capability_mapping( *expand_string(permissions[0])[:2], capability_string, capability.get('displayname', ''), expand_role(*expand_string(role_string)), [expand_permission(*expand_string(perm)) for perm in permissions], conditions=[ expand_condition(*cd) for cond in conditions.get(relation, []) for cd in self.resolve_conditions(cond) ], relation=relation, )
[docs] def resolve_conditions(self, condition_name): for cond, params in self.conditions[condition_name].items(): yield cond, [{'name': name, 'value': value} for name, value in (params or {}).items()]
[docs] def resolve_permissions(self, permission_names): for permission_name in permission_names: for perm in self.permission_sets[permission_name]: app_name, namespace_name, permission_name = expand_string(perm) # TODO: sanitization here? yield implode_string(app_name, self._sanitize_module_name(namespace_name), self._sanitize_property_name(permission_name))
def _sanitize_module_name(self, module_name): return module_name.replace('/', '-') def _sanitize_property_name(self, property_name): return property_name.lower()
if __name__ == '__main__': import argparse import json parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--config') args = parser.parse_args() conf = AuthorizationConfig(args.config) conf.parse() print(json.dumps(conf.__dict__, indent=4))