diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index 106b6ee3ec4..5a11c30dca2 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -34,6 +34,7 @@ Project, IssueAttachment, IssueLink, + ProjectMember, ) from plane.utils.analytics_plot import burndown_plot @@ -363,14 +364,28 @@ def patch(self, request, slug, project_id, pk): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def delete(self, request, slug, project_id, pk): + cycle = Cycle.objects.get( + workspace__slug=slug, project_id=project_id, pk=pk + ) + if cycle.owned_by_id != request.user.id and ( + not ProjectMember.objects.filter( + workspace__slug=slug, + member=request.user, + role=20, + project_id=project_id, + is_active=True, + ).exists() + ): + return Response( + {"error": "Only admin or creator can delete the cycle"}, + status=status.HTTP_403_FORBIDDEN, + ) + cycle_issues = list( CycleIssue.objects.filter( cycle_id=self.kwargs.get("pk") ).values_list("issue", flat=True) ) - cycle = Cycle.objects.get( - workspace__slug=slug, project_id=project_id, pk=pk - ) issue_activity.delay( type="cycle.activity.deleted", diff --git a/apiserver/plane/api/views/inbox.py b/apiserver/plane/api/views/inbox.py index dc74e0f0cbd..b09839d4bb5 100644 --- a/apiserver/plane/api/views/inbox.py +++ b/apiserver/plane/api/views/inbox.py @@ -390,29 +390,26 @@ def delete(self, request, slug, project_id, issue_id): inbox_id=inbox.id, ) - # Get the project member - project_member = ProjectMember.objects.get( - workspace__slug=slug, - project_id=project_id, - member=request.user, - is_active=True, - ) - - # Check the inbox issue created - if project_member.role <= 10 and str(inbox_issue.created_by_id) != str( - request.user.id - ): - return Response( - {"error": "You cannot delete inbox issue"}, - status=status.HTTP_400_BAD_REQUEST, - ) - # Check the issue status if inbox_issue.status in [-2, -1, 0, 2]: # Delete the issue also - Issue.objects.filter( + issue = Issue.objects.filter( workspace__slug=slug, project_id=project_id, pk=issue_id - ).delete() + ).first() + if issue.created_by_id != request.user.id and ( + not ProjectMember.objects.filter( + workspace__slug=slug, + member=request.user, + role=20, + project_id=project_id, + is_active=True, + ).exists() + ): + return Response( + {"error": "Only admin or creator can delete the issue"}, + status=status.HTTP_403_FORBIDDEN, + ) + issue.delete() inbox_issue.delete() return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index e2bbd6a884b..68ffb1aeece 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -310,11 +310,14 @@ def post(self, request, slug, project_id): serializer.save() # Refetch the issue - issue = Issue.objects.filter(workspace__slug=slug, project_id=project_id, pk=serializer.data["id"]).first() + issue = Issue.objects.filter( + workspace__slug=slug, + project_id=project_id, + pk=serializer.data["id"], + ).first() issue.created_at = request.data.get("created_at") issue.save(update_fields=["created_at"]) - # Track the issue issue_activity.delay( type="issue.activity.created", @@ -386,6 +389,19 @@ def delete(self, request, slug, project_id, pk=None): issue = Issue.objects.get( workspace__slug=slug, project_id=project_id, pk=pk ) + if issue.created_by_id != request.user.id and ( + not ProjectMember.objects.filter( + workspace__slug=slug, + member=request.user, + role=20, + project_id=project_id, + is_active=True, + ).exists() + ): + return Response( + {"error": "Only admin or creator can delete the issue"}, + status=status.HTTP_403_FORBIDDEN, + ) current_instance = json.dumps( IssueSerializer(issue).data, cls=DjangoJSONEncoder ) diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py index eeb29dad244..ecbf045823f 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -27,6 +27,7 @@ ModuleIssue, ModuleLink, Project, + ProjectMember, ) from .base import BaseAPIView @@ -265,6 +266,20 @@ def delete(self, request, slug, project_id, pk): module = Module.objects.get( workspace__slug=slug, project_id=project_id, pk=pk ) + if module.created_by_id != request.user.id and ( + not ProjectMember.objects.filter( + workspace__slug=slug, + member=request.user, + role=20, + project_id=project_id, + is_active=True, + ).exists() + ): + return Response( + {"error": "Only admin or creator can delete the module"}, + status=status.HTTP_403_FORBIDDEN, + ) + module_issues = list( ModuleIssue.objects.filter(module_id=pk).values_list( "issue", flat=True diff --git a/apiserver/plane/app/views/cycle/base.py b/apiserver/plane/app/views/cycle/base.py index 76d5a75f88e..52b14134642 100644 --- a/apiserver/plane/app/views/cycle/base.py +++ b/apiserver/plane/app/views/cycle/base.py @@ -47,6 +47,7 @@ Label, User, Project, + ProjectMember, ) from plane.utils.analytics_plot import burndown_plot @@ -1039,14 +1040,28 @@ def retrieve(self, request, slug, project_id, pk): ) def destroy(self, request, slug, project_id, pk): + cycle = Cycle.objects.get( + workspace__slug=slug, project_id=project_id, pk=pk + ) + if cycle.owned_by_id != request.user.id and not ( + ProjectMember.objects.filter( + workspace__slug=slug, + member=request.user, + role=20, + project_id=project_id, + is_active=True, + ).exists() + ): + return Response( + {"error": "Only admin or owner can delete the cycle"}, + status=status.HTTP_403_FORBIDDEN, + ) + cycle_issues = list( CycleIssue.objects.filter( cycle_id=self.kwargs.get("pk") ).values_list("issue", flat=True) ) - cycle = Cycle.objects.get( - workspace__slug=slug, project_id=project_id, pk=pk - ) issue_activity.delay( type="cycle.activity.deleted", diff --git a/apiserver/plane/app/views/inbox/base.py b/apiserver/plane/app/views/inbox/base.py index 7919899fa6f..bda64f36bae 100644 --- a/apiserver/plane/app/views/inbox/base.py +++ b/apiserver/plane/app/views/inbox/base.py @@ -553,28 +553,27 @@ def destroy(self, request, slug, project_id, issue_id): project_id=project_id, inbox_id=inbox_id, ) - # Get the project member - project_member = ProjectMember.objects.get( - workspace__slug=slug, - project_id=project_id, - member=request.user, - is_active=True, - ) - - if project_member.role <= 10 and str(inbox_issue.created_by_id) != str( - request.user.id - ): - return Response( - {"error": "You cannot delete inbox issue"}, - status=status.HTTP_400_BAD_REQUEST, - ) # Check the issue status if inbox_issue.status in [-2, -1, 0, 2]: # Delete the issue also - Issue.objects.filter( + issue = Issue.objects.filter( workspace__slug=slug, project_id=project_id, pk=issue_id - ).delete() + ).first() + if issue.created_by_id != request.user.id and ( + not ProjectMember.objects.filter( + workspace__slug=slug, + member=request.user, + role=20, + project_id=project_id, + is_active=True, + ).exists() + ): + return Response( + {"error": "Only admin or creator can delete the issue"}, + status=status.HTTP_403_FORBIDDEN, + ) + issue.delete() inbox_issue.delete() return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apiserver/plane/app/views/issue/base.py b/apiserver/plane/app/views/issue/base.py index b9155f9d4a9..47d02721a54 100644 --- a/apiserver/plane/app/views/issue/base.py +++ b/apiserver/plane/app/views/issue/base.py @@ -44,6 +44,7 @@ IssueReaction, IssueSubscriber, Project, + ProjectMember, ) from plane.utils.grouper import ( issue_group_values, @@ -549,6 +550,20 @@ def destroy(self, request, slug, project_id, pk=None): issue = Issue.objects.get( workspace__slug=slug, project_id=project_id, pk=pk ) + if issue.created_by_id != request.user.id and ( + not ProjectMember.objects.filter( + workspace__slug=slug, + member=request.user, + role=20, + project_id=project_id, + is_active=True, + ).exists() + ): + return Response( + {"error": "Only admin or creator can delete the issue"}, + status=status.HTTP_403_FORBIDDEN, + ) + issue.delete() issue_activity.delay( type="issue.activity.deleted", @@ -602,6 +617,19 @@ class BulkDeleteIssuesEndpoint(BaseAPIView): ] def delete(self, request, slug, project_id): + if ProjectMember.objects.filter( + workspace__slug=slug, + member=request.user, + role=20, + project_id=project_id, + is_active=True, + ).exists(): + + return Response( + {"error": "Only admin can perform this action"}, + status=status.HTTP_403_FORBIDDEN, + ) + issue_ids = request.data.get("issue_ids", []) if not len(issue_ids): diff --git a/apiserver/plane/app/views/issue/draft.py b/apiserver/plane/app/views/issue/draft.py index 6944f40f79f..5af4433afb1 100644 --- a/apiserver/plane/app/views/issue/draft.py +++ b/apiserver/plane/app/views/issue/draft.py @@ -40,6 +40,7 @@ IssueReaction, IssueSubscriber, Project, + ProjectMember, ) from plane.utils.grouper import ( issue_group_values, @@ -380,6 +381,19 @@ def destroy(self, request, slug, project_id, pk=None): issue = Issue.objects.get( workspace__slug=slug, project_id=project_id, pk=pk ) + if issue.created_by_id != request.user.id and ( + not ProjectMember.objects.filter( + workspace__slug=slug, + member=request.user, + role=20, + project_id=project_id, + is_active=True, + ).exists() + ): + return Response( + {"error": "Only admin or creator can delete the issue"}, + status=status.HTTP_403_FORBIDDEN, + ) issue.delete() issue_activity.delay( type="issue_draft.activity.deleted", diff --git a/apiserver/plane/app/views/module/base.py b/apiserver/plane/app/views/module/base.py index 08420459559..7615aa7d7f1 100644 --- a/apiserver/plane/app/views/module/base.py +++ b/apiserver/plane/app/views/module/base.py @@ -48,6 +48,7 @@ ModuleLink, ModuleUserProperties, Project, + ProjectMember, ) from plane.utils.analytics_plot import burndown_plot from plane.utils.user_timezone_converter import user_timezone_converter @@ -737,6 +738,21 @@ def destroy(self, request, slug, project_id, pk): module = Module.objects.get( workspace__slug=slug, project_id=project_id, pk=pk ) + + if module.created_by_id != request.user.id and ( + not ProjectMember.objects.filter( + workspace__slug=slug, + member=request.user, + role=20, + project_id=project_id, + is_active=True, + ).exists() + ): + return Response( + {"error": "Only admin or creator can delete the module"}, + status=status.HTTP_403_FORBIDDEN, + ) + module_issues = list( ModuleIssue.objects.filter(module_id=pk).values_list( "issue", flat=True diff --git a/apiserver/plane/app/views/page/base.py b/apiserver/plane/app/views/page/base.py index c11ef5e94ea..b0d56064972 100644 --- a/apiserver/plane/app/views/page/base.py +++ b/apiserver/plane/app/views/page/base.py @@ -333,6 +333,20 @@ def destroy(self, request, slug, project_id, pk): pk=pk, workspace__slug=slug, projects__id=project_id ) + if not page.owned_by_id != request.user.id and not ( + ProjectMember.objects.filter( + workspace__slug=slug, + member=request.user, + role=20, + project_id=project_id, + is_active=True, + ).exists() + ): + return Response( + {"error": "Only admin or owner can delete the page"}, + status=status.HTTP_403_FORBIDDEN, + ) + # only the owner and admin can delete the page if ( ProjectMember.objects.filter( diff --git a/apiserver/plane/app/views/view/base.py b/apiserver/plane/app/views/view/base.py index 079430129a2..236a051aaa9 100644 --- a/apiserver/plane/app/views/view/base.py +++ b/apiserver/plane/app/views/view/base.py @@ -116,6 +116,20 @@ def destroy(self, request, slug, pk): pk=pk, workspace__slug=slug, ) + if not ( + WorkspaceMember.objects.filter( + workspace__slug=slug, + member=request.user, + role=20, + is_active=True, + ).exists() + and workspace_view.owned_by_id != request.user.id + ): + return Response( + {"error": "You do not have permission to delete this view"}, + status=status.HTTP_403_FORBIDDEN, + ) + workspace_member = WorkspaceMember.objects.filter( workspace__slug=slug, member=request.user, @@ -412,14 +426,16 @@ def destroy(self, request, slug, project_id, pk): project_id=project_id, workspace__slug=slug, ) - project_member = ProjectMember.objects.filter( - workspace__slug=slug, - project_id=project_id, - member=request.user, - role=20, - is_active=True, - ) - if project_member.exists() or project_view.owned_by == request.user: + if ( + ProjectMember.objects.filter( + workspace__slug=slug, + project_id=project_id, + member=request.user, + role=20, + is_active=True, + ).exists() + or project_view.owned_by_id == request.user.id + ): project_view.delete() else: return Response(