from django.db.models import Q from django.utils import timezone from .models import Grant, Revoke class AuthorizationService: """Singleton service for permission resolution.""" def _get_system_user_scopes(self, user) -> set[str] | None: from .auth.api_keys import SystemUser if isinstance(user, SystemUser): return set(user.scopes) return None def has_permission(self, user, codename: str) -> bool: if not user or not user.is_authenticated: return False if user.is_superuser: return True system_scopes = self._get_system_user_scopes(user) if system_scopes is not None: return codename in system_scopes if Revoke.objects.filter(user=user, codename=codename).exists(): return False if ( Grant.objects.filter(user=user, codename=codename) .filter(Q(expires_at__isnull=True) | Q(expires_at__gt=timezone.now())) .exists() ): return True user_roles = self._get_role_permission_lists(user) for perm_list in user_roles: if codename in (perm_list or []): return True return False def get_effective_permissions(self, user) -> set[str]: if not user or not user.is_authenticated: return set() if user.is_superuser: return {"*"} system_scopes = self._get_system_user_scopes(user) if system_scopes is not None: return system_scopes revoked = set(Revoke.objects.filter(user=user).values_list("codename", flat=True)) granted = set( Grant.objects.filter(user=user) .filter(Q(expires_at__isnull=True) | Q(expires_at__gt=timezone.now())) .values_list("codename", flat=True) ) role_perms = set() for perm_list in self._get_role_permission_lists(user): role_perms.update(perm_list or []) return (granted | role_perms) - revoked @staticmethod def _get_role_permission_lists(user) -> list[list[str]]: roles = getattr(user, "roles", None) if roles is None: return [] return list(roles.values_list("permissions", flat=True)) def has_all_permissions(self, user, codenames: list[str]) -> bool: return all(self.has_permission(user, c) for c in codenames) def has_any_permission(self, user, codenames: list[str]) -> bool: return any(self.has_permission(user, c) for c in codenames)