Files
MemoryBear/api/app/core/permissions/policies.py

152 lines
4.9 KiB
Python

"""
Permission policies for access control.
Defines various policy classes that implement different permission rules:
- SuperuserPolicy: Superusers can perform any action
- OwnerPolicy: Resource owners can perform any action on their resources
- TenantPolicy: Users in the same tenant can access public resources
- RoleBasedPolicy: Permission based on user roles
- WorkspaceMemberPolicy: Workspace members can access workspace resources
"""
from abc import ABC, abstractmethod
from typing import Set
from app.core.permissions.models import Subject, Resource, Action, ResourceType
class PermissionPolicy(ABC):
"""Base class for permission policies."""
@abstractmethod
def can_perform(self, subject: Subject, action: Action, resource: Resource) -> bool:
"""
Determine if a subject can perform an action on a resource.
Args:
subject: The user/actor attempting the action
action: The action being attempted
resource: The resource being acted upon
Returns:
True if the action is allowed, False otherwise
"""
pass
class SuperuserPolicy(PermissionPolicy):
"""Superusers can perform any action on any resource."""
def can_perform(self, subject: Subject, action: Action, resource: Resource) -> bool:
return subject.is_superuser
class OwnerPolicy(PermissionPolicy):
"""Resource owners can perform any action on their own resources."""
def can_perform(self, subject: Subject, action: Action, resource: Resource) -> bool:
return subject.id == resource.owner_id
class TenantPolicy(PermissionPolicy):
"""
Users in the same tenant can access public resources.
Args:
allowed_actions: Set of actions allowed on public resources (default: READ only)
"""
def __init__(self, allowed_actions: Set[Action] = None):
self.allowed_actions = allowed_actions or {Action.READ}
def can_perform(self, subject: Subject, action: Action, resource: Resource) -> bool:
return (
subject.tenant_id == resource.tenant_id and
resource.is_public and
action in self.allowed_actions
)
class RoleBasedPolicy(PermissionPolicy):
"""
Permission based on user roles.
Args:
required_roles: Set of roles that grant permission
allowed_actions: Set of actions these roles can perform
"""
def __init__(self, required_roles: Set[str], allowed_actions: Set[Action]):
self.required_roles = required_roles
self.allowed_actions = allowed_actions
def can_perform(self, subject: Subject, action: Action, resource: Resource) -> bool:
has_role = bool(subject.roles & self.required_roles)
return has_role and action in self.allowed_actions
class WorkspaceMemberPolicy(PermissionPolicy):
"""
Workspace members can access workspace resources.
Args:
allowed_actions: Set of actions workspace members can perform
"""
def __init__(self, allowed_actions: Set[Action] = None):
self.allowed_actions = allowed_actions or {Action.READ, Action.UPDATE}
def can_perform(self, subject: Subject, action: Action, resource: Resource) -> bool:
if resource.type != ResourceType.WORKSPACE:
return False
return (
resource.id in subject.workspace_memberships and
action in self.allowed_actions
)
class SameTenantSuperuserPolicy(PermissionPolicy):
"""
Superusers in the same tenant can perform specific actions.
This is useful for tenant-scoped admin operations where even superusers
should be limited to their own tenant.
Args:
allowed_actions: Set of actions allowed (default: all actions)
"""
def __init__(self, allowed_actions: Set[Action] = None):
self.allowed_actions = allowed_actions or set(Action)
def can_perform(self, subject: Subject, action: Action, resource: Resource) -> bool:
return (
subject.is_superuser and
subject.tenant_id == resource.tenant_id and
action in self.allowed_actions
)
class SelfAccessPolicy(PermissionPolicy):
"""
Users can access their own user resource.
This is specifically for user resources where users should be able
to read/update their own profile.
Args:
allowed_actions: Set of actions users can perform on themselves
"""
def __init__(self, allowed_actions: Set[Action] = None):
self.allowed_actions = allowed_actions or {Action.READ, Action.UPDATE}
def can_perform(self, subject: Subject, action: Action, resource: Resource) -> bool:
if resource.type != ResourceType.USER:
return False
return (
subject.id == resource.id and
action in self.allowed_actions
)