"""DRF permission classes built on :class:`AuthorizationService`. Enforcement model ----------------- * A superuser is always allowed. * A **tenant owner** (``TenantMembership.is_owner`` for the resolved tenant) is allowed — ownership is a capability, not a permission row. * Otherwise the request user must hold at least one of the view's ``required_permissions`` (``HybridPermission``) or all of them (``require_permission``). * A view with no ``required_permissions`` only needs authentication. The underlying :class:`AuthorizationService` is deliberately strict (owners are not implicitly granted every codename) so it stays a pure permission resolver; ownership is handled at the HTTP boundary here. """ from __future__ import annotations from typing import Any from rest_framework.permissions import BasePermission from infrasynth.gates import evaluate_gates from .services import AuthorizationService def is_tenant_owner(user: Any) -> bool: """True when ``user`` owns the currently bound tenant.""" if not user or not getattr(user, "is_authenticated", False): return False # System users (API keys) are not database-backed memberships. if not hasattr(user, "_meta") or getattr(user, "pk", None) is None: return False from infrasynth.tenancy.context import get_current_tenant from infrasynth.tenancy.models import TenantMembership tenant = get_current_tenant() if tenant is None: return False return TenantMembership.objects.filter( tenant=tenant, user=user, is_active=True, is_owner=True, ).exists() class HybridPermission(BasePermission): """Allows when the user is an owner or holds any ``required_permissions``.""" def has_permission(self, request, view): user = getattr(request, "user", None) if not user or not getattr(user, "is_authenticated", False): return False # Declared gates apply to everyone, including owners and superusers. evaluate_gates(request, view) if getattr(user, "is_superuser", False): return True if is_tenant_owner(user): return True required = getattr(view, "required_permissions", []) or [] if not required: return True return AuthorizationService().has_any_permission(user, required) class IsAuthenticatedAndPermitted(HybridPermission): """The idiom for kit views: authenticated, then permission-checked.""" def has_permission(self, request, view): if not getattr(getattr(request, "user", None), "is_authenticated", False): return False return super().has_permission(request, view) def require_permission(*codenames: str): """View (or view-decorator) requiring *all* listed permissions.""" class PermissionRequired(IsAuthenticatedAndPermitted): def has_permission(self, request, view): if not super().has_permission(request, view): return False user = request.user if getattr(user, "is_superuser", False) or is_tenant_owner(user): return True return AuthorizationService().has_all_permissions(user, list(codenames)) return PermissionRequired