From 2c609670c83a82301f41f7a69eca8bac1c8aec37 Mon Sep 17 00:00:00 2001 From: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:42:51 +0530 Subject: [PATCH] [WEB-2043] chore: updated permissions for delete operation (#5231) * chore: added permission for delete operation * chore: added permission for external apis * chore: condition changes * chore: minor changes --- apiserver/plane/api/views/cycle.py | 21 ++++++++++++-- apiserver/plane/api/views/inbox.py | 35 +++++++++++------------- apiserver/plane/api/views/issue.py | 20 ++++++++++++-- apiserver/plane/api/views/module.py | 15 ++++++++++ apiserver/plane/app/views/cycle/base.py | 21 ++++++++++++-- apiserver/plane/app/views/inbox/base.py | 33 +++++++++++----------- apiserver/plane/app/views/issue/base.py | 28 +++++++++++++++++++ apiserver/plane/app/views/issue/draft.py | 14 ++++++++++ apiserver/plane/app/views/module/base.py | 16 +++++++++++ apiserver/plane/app/views/page/base.py | 14 ++++++++++ apiserver/plane/app/views/view/base.py | 32 ++++++++++++++++------ 11 files changed, 197 insertions(+), 52 deletions(-) diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index 106b6ee3e..5a11c30dc 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -34,6 +34,7 @@ from plane.db.models import ( Project, IssueAttachment, IssueLink, + ProjectMember, ) from plane.utils.analytics_plot import burndown_plot @@ -363,14 +364,28 @@ class CycleAPIEndpoint(BaseAPIView): 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 dc74e0f0c..b09839d4b 100644 --- a/apiserver/plane/api/views/inbox.py +++ b/apiserver/plane/api/views/inbox.py @@ -390,29 +390,26 @@ class InboxIssueAPIEndpoint(BaseAPIView): 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 e2bbd6a88..68ffb1aee 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -310,11 +310,14 @@ class IssueAPIEndpoint(BaseAPIView): 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 @@ class IssueAPIEndpoint(BaseAPIView): 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 eeb29dad2..ecbf04582 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -27,6 +27,7 @@ from plane.db.models import ( ModuleIssue, ModuleLink, Project, + ProjectMember, ) from .base import BaseAPIView @@ -265,6 +266,20 @@ class ModuleAPIEndpoint(BaseAPIView): 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 76d5a75f8..52b141346 100644 --- a/apiserver/plane/app/views/cycle/base.py +++ b/apiserver/plane/app/views/cycle/base.py @@ -47,6 +47,7 @@ from plane.db.models import ( Label, User, Project, + ProjectMember, ) from plane.utils.analytics_plot import burndown_plot @@ -1039,14 +1040,28 @@ class CycleViewSet(BaseViewSet): ) 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 7919899fa..bda64f36b 100644 --- a/apiserver/plane/app/views/inbox/base.py +++ b/apiserver/plane/app/views/inbox/base.py @@ -553,28 +553,27 @@ class InboxIssueViewSet(BaseViewSet): 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 b9155f9d4..47d02721a 100644 --- a/apiserver/plane/app/views/issue/base.py +++ b/apiserver/plane/app/views/issue/base.py @@ -44,6 +44,7 @@ from plane.db.models import ( IssueReaction, IssueSubscriber, Project, + ProjectMember, ) from plane.utils.grouper import ( issue_group_values, @@ -549,6 +550,20 @@ class IssueViewSet(BaseViewSet): 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 6944f40f7..5af4433af 100644 --- a/apiserver/plane/app/views/issue/draft.py +++ b/apiserver/plane/app/views/issue/draft.py @@ -40,6 +40,7 @@ from plane.db.models import ( IssueReaction, IssueSubscriber, Project, + ProjectMember, ) from plane.utils.grouper import ( issue_group_values, @@ -380,6 +381,19 @@ class IssueDraftViewSet(BaseViewSet): 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 084204595..7615aa7d7 100644 --- a/apiserver/plane/app/views/module/base.py +++ b/apiserver/plane/app/views/module/base.py @@ -48,6 +48,7 @@ from plane.db.models import ( 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 @@ class ModuleViewSet(BaseViewSet): 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 c11ef5e94..b0d560649 100644 --- a/apiserver/plane/app/views/page/base.py +++ b/apiserver/plane/app/views/page/base.py @@ -333,6 +333,20 @@ class PageViewSet(BaseViewSet): 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 079430129..236a051aa 100644 --- a/apiserver/plane/app/views/view/base.py +++ b/apiserver/plane/app/views/view/base.py @@ -116,6 +116,20 @@ class WorkspaceViewViewSet(BaseViewSet): 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 @@ class IssueViewViewSet(BaseViewSet): 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(