infrasynth-backend-kit/infrasynth/security/apps.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

77 lines
3.9 KiB
Python

import logging
from django.apps import AppConfig
from django.db.models.signals import post_migrate
logger = logging.getLogger(__name__)
# Explicit (non model-derived) codenames the kit enforces or documents.
_KIT_PERMISSIONS: list[tuple[str, str, str, str]] = [
# (codename, name, group, description)
("security.manage_api_keys", "Manage API keys", "Security", "Create/rotate/delete tenant API keys."),
("security.manage_roles", "Manage roles", "Security", "Create and assign tenant roles."),
("security.manage_grants", "Manage grants", "Security", "Grant/revoke permissions directly."),
("security.view_permissions", "View permissions", "Security", "Read the permission catalog."),
("platform.roles.manage", "Manage platform roles", "Platform", "Create and assign global roles."),
("platform.tenants.view", "View any tenant", "Platform", "Read tenants across the platform."),
("platform.tenants.manage", "Manage tenants", "Platform", "Edit tenant metadata across the platform."),
("platform.tenants.suspend", "Suspend tenants", "Platform", "Suspend a tenant."),
("platform.tenants.reinstate", "Reinstate tenants", "Platform", "Reinstate a suspended tenant."),
("platform.tenants.delete", "Delete tenants", "Platform", "Archive/delete a tenant."),
("platform.tenants.impersonate", "Impersonate tenants", "Platform", "Open a support session into a tenant."),
("platform.users.view_any", "View any user", "Platform", "Read users/members across tenants."),
("platform.users.manage_email", "Change any user email", "Platform", "Update a user's email across tenants."),
("platform.users.deactivate", "Deactivate any user", "Platform", "Disable accounts across tenants."),
("platform.users.manage_roles", "Assign roles anywhere", "Platform", "Assign roles in any tenant."),
("platform.audit.view_all", "View all audit", "Platform", "Read audit records across tenants."),
("audit.view_model_changes", "View model changes", "Audit", "Read the model change log."),
("audit.view_api_logs", "View API logs", "Audit", "Read API interaction logs."),
("audit.view_security_events", "View security events", "Audit", "Read security events."),
("tenancy.manage_tenant", "Manage tenant", "Tenancy", "Edit the current tenant."),
("tenancy.manage_members", "Manage members", "Tenancy", "Invite/remove tenant members."),
("configs.manage", "Manage configuration", "Configs", "Write tenant configuration values."),
("configs.manage_global", "Manage global configuration", "Configs", "Write platform configuration defaults."),
]
def register_builtin_permissions() -> None:
from .registry import PermissionRegistry
for codename, name, group, description in _KIT_PERMISSIONS:
PermissionRegistry.register(codename, name=name, group=group, description=description)
def _sync_permissions_on_migrate(sender, **kwargs) -> None:
from .catalog import sync_permissions
try:
summary = sync_permissions()
except Exception: # noqa: BLE001 - migrate must never fail because of the catalog
logger.exception("Permission catalog sync failed during post_migrate")
return
logger.info("Permission catalog synced: %s", summary)
class SecurityConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "infrasynth.security"
label = "infrasynth_security"
def ready(self):
from infrasynth.features.registry import FeatureRegistry
register_builtin_permissions()
FeatureRegistry.register(
"security",
name="Security & Access",
description="Authentication, authorization, 2FA, API keys, ALTCHA",
default=True,
category="system",
)
post_migrate.connect(
_sync_permissions_on_migrate,
sender=self,
dispatch_uid="infrasynth_security.sync_permissions",
)