infrasynth-backend-kit/infrasynth/security/registry.py
jcv-dev a2930426b4 feat: tenant configs, live signals, and automatic permission management
- infrasynth.configs: typed multi-tenant config store (registry, service,
  secrets, cache) + public config_changed/config_reset signals and API
- emit the declared-but-dead signals (features flags/overrides, scheduler
  task_completed/task_failed, tenancy tenant_updated, audit model_changed)
  and per-model audit field exclusions
- security: permission catalog (security_permission), Django-style
  model-derived AutoPermission, PermissionRegistry, RoleAssignment,
  global-or-tenant Grant/Revoke, catalog API
- consolidate the permission surface: PermissionRegistry only (drop the
  settings dict), IsAuthenticatedAndPermitted aliases HybridPermission,
  require_permission replaced by required_permissions + require_all
- packaging: add [build-system]; add Forgejo publish workflow (.forgejo)
2026-09-29 17:06:54 -05:00

71 lines
1.9 KiB
Python

"""Registry of custom permissions declared by apps.
Consuming apps (and the kit itself) register codenames in ``apps.py:ready()``
so they appear in the permission catalog and can be assigned to roles/users
without editing the kit. Model-derived CRUD/view permissions are generated
automatically by :mod:`infrasynth.security.catalog` and do not need to be
registered here.
"""
from __future__ import annotations
from dataclasses import dataclass
__all__ = ["PermissionDefinition", "PermissionRegistry"]
@dataclass(frozen=True)
class PermissionDefinition:
codename: str
name: str = ""
app: str = ""
model: str = ""
action: str = ""
group: str = ""
description: str = ""
is_custom: bool = True
class PermissionRegistry:
"""Global registry of explicitly declared permissions."""
_permissions: dict[str, PermissionDefinition] = {}
@classmethod
def register(
cls,
codename: str,
*,
name: str = "",
app: str = "",
model: str = "",
action: str = "",
group: str = "",
description: str = "",
is_custom: bool = True,
) -> PermissionDefinition:
definition = PermissionDefinition(
codename=codename,
name=name or codename,
app=app or codename.split(".", 1)[0],
model=model,
action=action,
group=group or (app or codename.split(".", 1)[0]).replace("_", " ").title(),
description=description,
is_custom=is_custom,
)
cls._permissions[codename] = definition
return definition
@classmethod
def get(cls, codename: str) -> PermissionDefinition | None:
return cls._permissions.get(codename)
@classmethod
def all(cls) -> dict[str, PermissionDefinition]:
return dict(cls._permissions)
@classmethod
def clear(cls) -> None:
"""Clears the registry. Tests only."""
cls._permissions.clear()