Move access permissions to access module

This commit is contained in:
2026-07-10 23:27:48 +02:00
parent a7c486788e
commit e32841077c
13 changed files with 1319 additions and 170 deletions

View File

@@ -22,13 +22,14 @@ from govoplan_access.backend.db.models import (
)
from govoplan_access.backend.semantic import ensure_identity_for_account
from govoplan_access.backend.security.passwords import hash_password
from govoplan_core.security.permissions import (
DEFAULT_SYSTEM_ROLES,
DEFAULT_TENANT_ROLES,
from govoplan_access.backend.permissions.catalog import (
delegateable_system_scopes,
delegateable_tenant_scopes,
normalize_email,
scopes_grant,
validate_system_permissions,
validate_tenant_permissions,
role_templates_for_level,
)
from govoplan_core.tenancy.service import tenant_counts # re-exported compatibility helper
@@ -48,21 +49,23 @@ def generate_temporary_password(length: int = 20) -> str:
def ensure_default_roles(session: Session, tenant: Tenant | None = None) -> dict[str, Role]:
definitions = DEFAULT_TENANT_ROLES if tenant is not None else DEFAULT_SYSTEM_ROLES
level = "tenant" if tenant is not None else "system"
roles: dict[str, Role] = {}
for slug, definition in definitions.items():
query = session.query(Role).filter(Role.slug == slug)
for template in role_templates_for_level(level):
query = session.query(Role).filter(Role.slug == template.slug)
query = query.filter(Role.tenant_id == tenant.id) if tenant is not None else query.filter(Role.tenant_id.is_(None))
role = query.one_or_none()
is_builtin = _template_is_builtin(template_managed=template.managed, protected=template.protected, tenant_role=tenant is not None)
is_assignable = True
if role is None:
role = Role(
tenant_id=tenant.id if tenant is not None else None,
slug=slug,
name=str(definition["name"]),
description=str(definition.get("description") or "") or None,
permissions=list(definition["permissions"]),
is_builtin=bool(definition.get("is_builtin", True)),
is_assignable=bool(definition.get("is_assignable", True)),
slug=template.slug,
name=template.name,
description=template.description or None,
permissions=list(template.permissions),
is_builtin=is_builtin,
is_assignable=is_assignable,
)
session.add(role)
session.flush()
@@ -70,18 +73,30 @@ def ensure_default_roles(session: Session, tenant: Tenant | None = None) -> dict
# Tenant built-ins and explicitly managed system roles remain
# code-defined. Seeded, non-protected system roles are only created
# here and can subsequently be administered in the System roles UI.
managed = tenant is not None or bool(definition.get("managed", False))
managed = _template_updates_existing(template_managed=template.managed, protected=template.protected, tenant_role=tenant is not None)
if managed:
role.name = str(definition["name"])
role.description = str(definition.get("description") or "") or None
role.permissions = list(definition["permissions"])
role.is_builtin = bool(definition.get("is_builtin", True))
role.is_assignable = bool(definition.get("is_assignable", True))
role.name = template.name
role.description = template.description or None
role.permissions = list(template.permissions)
role.is_builtin = is_builtin
role.is_assignable = is_assignable
session.add(role)
roles[slug] = role
roles[template.slug] = role
return roles
def _template_is_builtin(*, template_managed: bool, protected: bool, tenant_role: bool) -> bool:
if tenant_role:
return template_managed or protected
return protected
def _template_updates_existing(*, template_managed: bool, protected: bool, tenant_role: bool) -> bool:
if tenant_role:
return template_managed or protected
return protected
def get_or_create_account(
session: Session,
*,
@@ -381,8 +396,6 @@ def delete_system_role(session: Session, role: Role) -> None:
def assert_can_delegate_system_permissions(actor_scopes: Iterable[str], permissions: Iterable[str]) -> None:
"""Prevent a system administrator from defining stronger roles than they hold."""
from govoplan_core.security.permissions import delegateable_system_scopes
requested = set(validate_system_permissions(permissions))
if "system:*" in requested:
if not scopes_grant(actor_scopes, "system:*"):
@@ -546,7 +559,6 @@ def role_assignment_counts(session: Session, role_id: str) -> tuple[int, int]:
def assert_can_delegate_tenant_permissions(actor_scopes: Iterable[str], permissions: Iterable[str]) -> None:
"""Prevent administrators from defining or assigning roles beyond their own effective tenant powers."""
from govoplan_core.security.permissions import delegateable_tenant_scopes
requested = set(validate_tenant_permissions(permissions))
if "tenant:*" in requested:
# Only a tenant owner-equivalent can delegate the wildcard.