197 lines
5.7 KiB
Python
197 lines
5.7 KiB
Python
from __future__ import annotations
|
|
|
|
from apps.workspaces.models import Workspace, WorkspaceMembership
|
|
|
|
|
|
WORKSPACE_VIEW = "workspace.view"
|
|
WORKSPACE_EDIT = "workspace.edit"
|
|
WORKSPACE_DELETE = "workspace.delete"
|
|
WORKSPACE_LOGS_VIEW = "workspace.logs.view"
|
|
WORKSPACE_MEMBERS_VIEW = "workspace.members.view"
|
|
WORKSPACE_MEMBERS_ADD = "workspace.members.add"
|
|
WORKSPACE_MEMBERS_REMOVE = "workspace.members.remove"
|
|
WORKSPACE_MEMBERS_CHANGE_ROLE = "workspace.members.change_role"
|
|
CLIENTS_VIEW = "clients.view"
|
|
CLIENTS_CREATE = "clients.create"
|
|
CLIENTS_EDIT = "clients.edit"
|
|
CLIENTS_DELETE = "clients.delete"
|
|
TAGS_VIEW = "tags.view"
|
|
TAGS_CREATE = "tags.create"
|
|
TAGS_EDIT = "tags.edit"
|
|
TAGS_DELETE = "tags.delete"
|
|
PROJECTS_VIEW = "projects.view"
|
|
PROJECTS_CREATE = "projects.create"
|
|
PROJECTS_EDIT = "projects.edit"
|
|
PROJECTS_DELETE = "projects.delete"
|
|
PROJECTS_ARCHIVE = "projects.archive"
|
|
TIME_ENTRIES_VIEW_OWN = "time_entries.view_own"
|
|
TIME_ENTRIES_MANAGE_OWN = "time_entries.manage_own"
|
|
|
|
WORKSPACE_ROLE_CAPABILITIES = {
|
|
WorkspaceMembership.Role.OWNER: {
|
|
WORKSPACE_VIEW,
|
|
WORKSPACE_EDIT,
|
|
WORKSPACE_DELETE,
|
|
WORKSPACE_LOGS_VIEW,
|
|
WORKSPACE_MEMBERS_VIEW,
|
|
WORKSPACE_MEMBERS_ADD,
|
|
WORKSPACE_MEMBERS_REMOVE,
|
|
WORKSPACE_MEMBERS_CHANGE_ROLE,
|
|
CLIENTS_VIEW,
|
|
CLIENTS_CREATE,
|
|
CLIENTS_EDIT,
|
|
CLIENTS_DELETE,
|
|
TAGS_VIEW,
|
|
TAGS_CREATE,
|
|
TAGS_EDIT,
|
|
TAGS_DELETE,
|
|
PROJECTS_VIEW,
|
|
PROJECTS_CREATE,
|
|
PROJECTS_EDIT,
|
|
PROJECTS_DELETE,
|
|
PROJECTS_ARCHIVE,
|
|
TIME_ENTRIES_VIEW_OWN,
|
|
TIME_ENTRIES_MANAGE_OWN,
|
|
},
|
|
WorkspaceMembership.Role.ADMIN: {
|
|
WORKSPACE_VIEW,
|
|
WORKSPACE_EDIT,
|
|
WORKSPACE_LOGS_VIEW,
|
|
WORKSPACE_MEMBERS_VIEW,
|
|
WORKSPACE_MEMBERS_ADD,
|
|
WORKSPACE_MEMBERS_REMOVE,
|
|
WORKSPACE_MEMBERS_CHANGE_ROLE,
|
|
CLIENTS_VIEW,
|
|
CLIENTS_CREATE,
|
|
CLIENTS_EDIT,
|
|
CLIENTS_DELETE,
|
|
TAGS_VIEW,
|
|
TAGS_CREATE,
|
|
TAGS_EDIT,
|
|
TAGS_DELETE,
|
|
PROJECTS_VIEW,
|
|
PROJECTS_CREATE,
|
|
PROJECTS_EDIT,
|
|
PROJECTS_DELETE,
|
|
PROJECTS_ARCHIVE,
|
|
TIME_ENTRIES_VIEW_OWN,
|
|
TIME_ENTRIES_MANAGE_OWN,
|
|
},
|
|
WorkspaceMembership.Role.MEMBER: {
|
|
WORKSPACE_VIEW,
|
|
CLIENTS_VIEW,
|
|
TAGS_VIEW,
|
|
TAGS_CREATE,
|
|
PROJECTS_VIEW,
|
|
TIME_ENTRIES_VIEW_OWN,
|
|
TIME_ENTRIES_MANAGE_OWN,
|
|
},
|
|
WorkspaceMembership.Role.GUEST: {
|
|
WORKSPACE_VIEW,
|
|
CLIENTS_VIEW,
|
|
TAGS_VIEW,
|
|
PROJECTS_VIEW,
|
|
TIME_ENTRIES_VIEW_OWN,
|
|
},
|
|
}
|
|
|
|
|
|
def get_workspace_membership(user, workspace: Workspace) -> WorkspaceMembership | None:
|
|
if not user or not user.is_authenticated:
|
|
return None
|
|
|
|
return WorkspaceMembership.objects.filter(
|
|
workspace=workspace,
|
|
user=user,
|
|
is_active=True,
|
|
is_deleted=False,
|
|
).first()
|
|
|
|
|
|
def get_workspace_role(user, workspace: Workspace) -> str | None:
|
|
if not user or not user.is_authenticated:
|
|
return None
|
|
|
|
if workspace.owner_id == user.id:
|
|
return WorkspaceMembership.Role.OWNER
|
|
|
|
membership = get_workspace_membership(user, workspace)
|
|
return getattr(membership, "role", None)
|
|
|
|
|
|
def has_workspace_capability(user, workspace: Workspace, capability: str) -> bool:
|
|
role = get_workspace_role(user, workspace)
|
|
if not role:
|
|
return False
|
|
return capability in WORKSPACE_ROLE_CAPABILITIES.get(role, set())
|
|
|
|
|
|
def has_project_capability(user, project, capability: str) -> bool:
|
|
return has_workspace_capability(user, project.workspace, capability)
|
|
|
|
|
|
def can_delete_workspace_object(user, obj, capability: str) -> bool:
|
|
workspace = getattr(obj, "workspace", None)
|
|
if workspace is None:
|
|
return False
|
|
|
|
if not has_workspace_capability(user, workspace, capability):
|
|
return False
|
|
|
|
actor_role = get_workspace_role(user, workspace)
|
|
if actor_role == WorkspaceMembership.Role.OWNER:
|
|
return True
|
|
|
|
return getattr(obj, "created_by_id", None) == getattr(user, "id", None)
|
|
|
|
|
|
def can_manage_workspace_members(user, workspace: Workspace) -> bool:
|
|
return has_workspace_capability(user, workspace, WORKSPACE_MEMBERS_CHANGE_ROLE)
|
|
|
|
|
|
def can_assign_workspace_role(user, workspace: Workspace, role: str) -> bool:
|
|
actor_role = get_workspace_role(user, workspace)
|
|
if actor_role == WorkspaceMembership.Role.OWNER:
|
|
return True
|
|
if actor_role == WorkspaceMembership.Role.ADMIN:
|
|
return role not in {
|
|
WorkspaceMembership.Role.OWNER,
|
|
WorkspaceMembership.Role.ADMIN,
|
|
}
|
|
return False
|
|
|
|
|
|
def can_change_workspace_membership(user, membership: WorkspaceMembership, *, new_role: str | None = None) -> bool:
|
|
workspace = membership.workspace
|
|
actor_role = get_workspace_role(user, workspace)
|
|
if actor_role not in {
|
|
WorkspaceMembership.Role.OWNER,
|
|
WorkspaceMembership.Role.ADMIN,
|
|
}:
|
|
return False
|
|
|
|
if membership.user_id == user.id:
|
|
return False
|
|
|
|
target_is_canonical_owner = workspace.owner_id == membership.user_id
|
|
target_is_owner_role = membership.role == WorkspaceMembership.Role.OWNER
|
|
target_is_admin_role = membership.role == WorkspaceMembership.Role.ADMIN
|
|
|
|
if actor_role == WorkspaceMembership.Role.ADMIN:
|
|
if target_is_owner_role or target_is_admin_role or target_is_canonical_owner:
|
|
return False
|
|
if new_role in {
|
|
WorkspaceMembership.Role.OWNER,
|
|
WorkspaceMembership.Role.ADMIN,
|
|
}:
|
|
return False
|
|
return True
|
|
|
|
if target_is_canonical_owner:
|
|
return False
|
|
|
|
if new_role == WorkspaceMembership.Role.OWNER and workspace.owner_id != user.id:
|
|
return False
|
|
|
|
return True
|