From c40e45528ed9e77251a1e50d4dfca74d2061d8e0 Mon Sep 17 00:00:00 2001 From: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Date: Wed, 6 Dec 2023 19:30:40 +0530 Subject: [PATCH] chore: Page auth and other improvements (#3011) * chore: project query optimised * chore: page permissions changed --- apiserver/plane/api/views/inbox.py | 6 +- apiserver/plane/app/serializers/project.py | 23 ++++--- apiserver/plane/app/views/__init__.py | 1 - apiserver/plane/app/views/page.py | 78 +++++++++------------- apiserver/plane/app/views/project.py | 1 + 5 files changed, 47 insertions(+), 62 deletions(-) diff --git a/apiserver/plane/api/views/inbox.py b/apiserver/plane/api/views/inbox.py index 94ddc4f10..4f4cdc4ef 100644 --- a/apiserver/plane/api/views/inbox.py +++ b/apiserver/plane/api/views/inbox.py @@ -103,7 +103,7 @@ class InboxIssueAPIEndpoint(BaseAPIView): if inbox is None and not project.inbox_view: return Response( { - "error": "Inbox is not enabled for this project enable it through the project settings" + "error": "Inbox is not enabled for this project enable it through the project's api" }, status=status.HTTP_400_BAD_REQUEST, ) @@ -177,7 +177,7 @@ class InboxIssueAPIEndpoint(BaseAPIView): if inbox is None and not project.inbox_view: return Response( { - "error": "Inbox is not enabled for this project enable it through the project settings" + "error": "Inbox is not enabled for this project enable it through the project's api" }, status=status.HTTP_400_BAD_REQUEST, ) @@ -311,7 +311,7 @@ class InboxIssueAPIEndpoint(BaseAPIView): if inbox is None and not project.inbox_view: return Response( { - "error": "Inbox is not enabled for this project enable it through the project settings" + "error": "Inbox is not enabled for this project enable it through the project's api" }, status=status.HTTP_400_BAD_REQUEST, ) diff --git a/apiserver/plane/app/serializers/project.py b/apiserver/plane/app/serializers/project.py index 58a38f154..aef715e33 100644 --- a/apiserver/plane/app/serializers/project.py +++ b/apiserver/plane/app/serializers/project.py @@ -103,16 +103,19 @@ class ProjectListSerializer(DynamicBaseSerializer): members = serializers.SerializerMethodField() def get_members(self, obj): - project_members = ProjectMember.objects.filter( - project_id=obj.id, - is_active=True, - ).values( - "id", - "member_id", - "member__display_name", - "member__avatar", - ) - return list(project_members) + project_members = getattr(obj, "members_list", None) + if project_members is not None: + # Filter members by the project ID + return [ + { + "id": member.id, + "member_id": member.member_id, + "member__display_name": member.member.display_name, + "member__avatar": member.member.avatar, + } + for member in project_members + ] + return [] class Meta: model = Project diff --git a/apiserver/plane/app/views/__init__.py b/apiserver/plane/app/views/__init__.py index 2bfe27715..0b5da33ea 100644 --- a/apiserver/plane/app/views/__init__.py +++ b/apiserver/plane/app/views/__init__.py @@ -130,7 +130,6 @@ from .page import ( PageFavoriteViewSet, PageLogEndpoint, SubPagesEndpoint, - CreateIssueFromBlockEndpoint, ) from .search import GlobalSearchEndpoint, IssueSearchEndpoint diff --git a/apiserver/plane/app/views/page.py b/apiserver/plane/app/views/page.py index 01a0ffe98..9bd1f1dd4 100644 --- a/apiserver/plane/app/views/page.py +++ b/apiserver/plane/app/views/page.py @@ -22,6 +22,7 @@ from plane.db.models import ( IssueAssignee, IssueActivity, PageLog, + ProjectMember, ) from plane.app.serializers import ( PageSerializer, @@ -140,12 +141,6 @@ class PageViewSet(BaseViewSet): pk=page_id, workspace__slug=slug, project_id=project_id ).first() - # only the owner can lock the page - if request.user.id != page.owned_by_id: - return Response( - {"error": "Only the page owner can lock the page"}, - ) - page.is_locked = True page.save() return Response(status=status.HTTP_204_NO_CONTENT) @@ -155,12 +150,6 @@ class PageViewSet(BaseViewSet): pk=page_id, workspace__slug=slug, project_id=project_id ).first() - # only the owner can unlock the page - if request.user.id != page.owned_by_id: - return Response( - {"error": "Only the page owner can unlock the page"}, - status=status.HTTP_400_BAD_REQUEST, - ) page.is_locked = False page.save() @@ -175,10 +164,16 @@ class PageViewSet(BaseViewSet): def archive(self, request, slug, project_id, page_id): page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id) - if page.owned_by_id != request.user.id: + # only the owner and admin can archive the page + if ( + ProjectMember.objects.filter( + project_id=project_id, member=request.user, is_active=True, role__gt=20 + ).exists() + or request.user.id != page.owned_by_id + ): return Response( - {"error": "Only the owner of the page can archive a page"}, - status=status.HTTP_204_NO_CONTENT, + {"error": "Only the owner and admin can archive the page"}, + status=status.HTTP_400_BAD_REQUEST, ) unarchive_archive_page_and_descendants(page_id, datetime.now()) @@ -188,9 +183,15 @@ class PageViewSet(BaseViewSet): def unarchive(self, request, slug, project_id, page_id): page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id) - if page.owned_by_id != request.user.id: + # only the owner and admin can un archive the page + if ( + ProjectMember.objects.filter( + project_id=project_id, member=request.user, is_active=True, role__gt=20 + ).exists() + or request.user.id != page.owned_by_id + ): return Response( - {"error": "Only the owner of the page can unarchive a page"}, + {"error": "Only the owner and admin can un archive the page"}, status=status.HTTP_400_BAD_REQUEST, ) @@ -216,6 +217,18 @@ class PageViewSet(BaseViewSet): def destroy(self, request, slug, project_id, pk): page = Page.objects.get(pk=pk, workspace__slug=slug, project_id=project_id) + # only the owner and admin can delete the page + if ( + ProjectMember.objects.filter( + project_id=project_id, member=request.user, is_active=True, role__gt=20 + ).exists() + or request.user.id != page.owned_by_id + ): + return Response( + {"error": "Only the owner and admin can delete the page"}, + status=status.HTTP_400_BAD_REQUEST, + ) + if page.archived_at is None: return Response( {"error": "The page should be archived before deleting"}, @@ -227,7 +240,6 @@ class PageViewSet(BaseViewSet): parent_id=pk, project_id=project_id, workspace__slug=slug ).update(parent=None) - page.delete() return Response(status=status.HTTP_204_NO_CONTENT) @@ -310,36 +322,6 @@ class PageLogEndpoint(BaseAPIView): return Response(status=status.HTTP_204_NO_CONTENT) -class CreateIssueFromBlockEndpoint(BaseAPIView): - permission_classes = [ - ProjectEntityPermission, - ] - - def post(self, request, slug, project_id, page_id): - page = Page.objects.get( - workspace__slug=slug, - project_id=project_id, - pk=page_id, - ) - issue = Issue.objects.create( - name=request.data.get("name"), - project_id=project_id, - ) - _ = IssueAssignee.objects.create( - issue=issue, assignee=request.user, project_id=project_id - ) - - _ = IssueActivity.objects.create( - issue=issue, - actor=request.user, - project_id=project_id, - comment=f"created the issue from {page.name} block", - verb="created", - ) - - return Response(IssueLiteSerializer(issue).data, status=status.HTTP_200_OK) - - class SubPagesEndpoint(BaseAPIView): permission_classes = [ ProjectEntityPermission, diff --git a/apiserver/plane/app/views/project.py b/apiserver/plane/app/views/project.py index f2f48e7c3..5a6a9ddd3 100644 --- a/apiserver/plane/app/views/project.py +++ b/apiserver/plane/app/views/project.py @@ -165,6 +165,7 @@ class ProjectViewSet(WebhookMixin, BaseViewSet): workspace__slug=slug, is_active=True, ).select_related("member"), + to_attr='members_list' ) ) .order_by("sort_order", "name")