feat(permissions): centralize workspace role capability checks
This commit is contained in:
@@ -31,17 +31,27 @@ from apps.projects.api.serializers import (
|
||||
ProjectRateSerializer, ProjectRateCreateSerializer, ProjectRateUpdateSerializer,
|
||||
ProjectUserRateSerializer, ProjectUserRateCreateSerializer, ProjectUserRateUpdateSerializer
|
||||
)
|
||||
from apps.projects.api.permissions import IsProjectMember, IsProjectManager
|
||||
from apps.projects.api.permissions import IsProjectMember, IsProjectManager
|
||||
from apps.projects.services.projects import (
|
||||
create_project,
|
||||
update_project,
|
||||
toggle_project_archive
|
||||
)
|
||||
from apps.projects.services.memberships import add_project_member, update_project_member
|
||||
from apps.projects.services.rates import (
|
||||
create_project_rate, update_project_rate,
|
||||
create_project_user_rate, update_project_user_rate
|
||||
)
|
||||
from apps.projects.services.rates import (
|
||||
create_project_rate, update_project_rate,
|
||||
create_project_user_rate, update_project_user_rate
|
||||
)
|
||||
from apps.workspaces.services import (
|
||||
PROJECTS_ARCHIVE,
|
||||
PROJECTS_CREATE,
|
||||
PROJECTS_EDIT,
|
||||
PROJECT_MEMBERS_ADD,
|
||||
PROJECT_MEMBERS_CHANGE_ROLE,
|
||||
PROJECT_MEMBERS_REMOVE,
|
||||
has_project_capability,
|
||||
has_workspace_capability,
|
||||
)
|
||||
|
||||
|
||||
class ProjectViewSet(ModelViewSet):
|
||||
@@ -104,8 +114,13 @@ class ProjectViewSet(ModelViewSet):
|
||||
|
||||
members_data = serializer.validated_data.pop("members", [])
|
||||
|
||||
workspace = get_object_or_404(Workspace, id=serializer.validated_data["workspace"], is_deleted=False)
|
||||
client_id = serializer.validated_data.get("client")
|
||||
workspace = get_object_or_404(Workspace, id=serializer.validated_data["workspace"], is_deleted=False)
|
||||
if not has_workspace_capability(request.user, workspace, PROJECTS_CREATE):
|
||||
return Response(
|
||||
{"detail": "You do not have permission to create projects in this workspace."},
|
||||
status=status.HTTP_403_FORBIDDEN,
|
||||
)
|
||||
client_id = serializer.validated_data.get("client")
|
||||
client = get_object_or_404(Client, id=client_id, is_deleted=False) if client_id else None
|
||||
|
||||
project = create_project(
|
||||
@@ -230,7 +245,7 @@ class ProjectViewSet(ModelViewSet):
|
||||
return Response(output_serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class BaseProjectNestedViewSet(ModelViewSet):
|
||||
class BaseProjectNestedViewSet(ModelViewSet):
|
||||
"""
|
||||
Base ViewSet for nested project models to share common permission and queryset logic.
|
||||
"""
|
||||
@@ -245,17 +260,11 @@ class BaseProjectNestedViewSet(ModelViewSet):
|
||||
permission_classes = [IsAuthenticated, IsProjectMember]
|
||||
return [permission() for permission in permission_classes]
|
||||
|
||||
def verify_manager_access(self, project_id):
|
||||
"""Helper to verify if the requesting user is a manager of the target project."""
|
||||
is_manager = ProjectMembership.objects.filter(
|
||||
project_id=project_id,
|
||||
user=self.request.user,
|
||||
role=ProjectMembership.Role.MANAGER,
|
||||
is_active=True,
|
||||
is_deleted=False
|
||||
).exists()
|
||||
if not is_manager:
|
||||
raise PermissionDenied("You must be a project manager to perform this action.")
|
||||
def verify_manager_access(self, project_id):
|
||||
"""Helper to verify if the requesting user is a manager of the target project."""
|
||||
project = get_object_or_404(Project, id=project_id, is_deleted=False)
|
||||
if not has_project_capability(self.request.user, project, PROJECT_MEMBERS_ADD):
|
||||
raise PermissionDenied("You must be a project manager to perform this action.")
|
||||
|
||||
|
||||
class ProjectMembershipViewSet(BaseProjectNestedViewSet):
|
||||
@@ -275,14 +284,14 @@ class ProjectMembershipViewSet(BaseProjectNestedViewSet):
|
||||
if self.action in ["update", "partial_update"]: return ProjectMembershipUpdateSerializer
|
||||
return ProjectMembershipSerializer
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
project_id = serializer.validated_data["project_id"]
|
||||
self.verify_manager_access(project_id)
|
||||
|
||||
project = get_object_or_404(Project, id=project_id, is_deleted=False)
|
||||
project = get_object_or_404(Project, id=project_id, is_deleted=False)
|
||||
membership = add_project_member(
|
||||
project=project,
|
||||
user_id=serializer.validated_data["user_id"],
|
||||
@@ -298,6 +307,12 @@ class ProjectMembershipViewSet(BaseProjectNestedViewSet):
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
membership = self.get_object()
|
||||
if not has_project_capability(
|
||||
request.user,
|
||||
membership.project,
|
||||
PROJECT_MEMBERS_CHANGE_ROLE,
|
||||
):
|
||||
raise PermissionDenied("You do not have permission to update project members.")
|
||||
serializer = self.get_serializer(data=request.data, partial=kwargs.pop("partial", False))
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
@@ -330,6 +345,12 @@ class ProjectMembershipViewSet(BaseProjectNestedViewSet):
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
membership = self.get_object()
|
||||
if not has_project_capability(
|
||||
request.user,
|
||||
membership.project,
|
||||
PROJECT_MEMBERS_REMOVE,
|
||||
):
|
||||
raise PermissionDenied("You do not have permission to remove project members.")
|
||||
recipient = membership.user
|
||||
project = membership.project
|
||||
role = membership.role
|
||||
|
||||
Reference in New Issue
Block a user