diff --git a/apps/api/plane/app/permissions/base.py b/apps/api/plane/app/permissions/base.py index 7ba12a2e26c..881088a3fb4 100644 --- a/apps/api/plane/app/permissions/base.py +++ b/apps/api/plane/app/permissions/base.py @@ -39,13 +39,31 @@ def _wrapped_view(instance, request, *args, **kwargs): ).exists(): return view_func(instance, request, *args, **kwargs) else: - if ProjectMember.objects.filter( + is_user_has_allowed_role = ProjectMember.objects.filter( member=request.user, workspace__slug=kwargs["slug"], project_id=kwargs["project_id"], role__in=allowed_role_values, is_active=True, - ).exists(): + ).exists() + + # Return if the user has the allowed role else if they are workspace admin and part of the project regardless of the role + if is_user_has_allowed_role: + return view_func(instance, request, *args, **kwargs) + elif ( + ProjectMember.objects.filter( + member=request.user, + workspace__slug=kwargs["slug"], + project_id=kwargs["project_id"], + is_active=True, + ).exists() + and WorkspaceMember.objects.filter( + member=request.user, + workspace__slug=kwargs["slug"], + role=ROLE.ADMIN.value, + is_active=True, + ).exists() + ): return view_func(instance, request, *args, **kwargs) # Return permission denied if no conditions are met diff --git a/apps/api/plane/app/permissions/project.py b/apps/api/plane/app/permissions/project.py index 1596d90b37b..e095ffed483 100644 --- a/apps/api/plane/app/permissions/project.py +++ b/apps/api/plane/app/permissions/project.py @@ -3,11 +3,7 @@ # Module import from plane.db.models import ProjectMember, WorkspaceMember - -# Permission Mappings -Admin = 20 -Member = 15 -Guest = 5 +from plane.db.models.project import ROLE class ProjectBasePermission(BasePermission): @@ -26,18 +22,31 @@ def has_permission(self, request, view): return WorkspaceMember.objects.filter( workspace__slug=view.workspace_slug, member=request.user, - role__in=[Admin, Member], + role__in=[ROLE.ADMIN.value, ROLE.MEMBER.value], is_active=True, ).exists() - ## Only Project Admins can update project attributes - return ProjectMember.objects.filter( + project_member_qs = ProjectMember.objects.filter( workspace__slug=view.workspace_slug, member=request.user, - role=Admin, project_id=view.project_id, is_active=True, - ).exists() + ) + + ## Only project admins or workspace admin who is part of the project can access + + if project_member_qs.filter(role=ROLE.ADMIN.value).exists(): + return True + else: + return ( + project_member_qs.exists() + and WorkspaceMember.objects.filter( + member=request.user, + workspace__slug=view.workspace_slug, + role=ROLE.ADMIN.value, + is_active=True, + ).exists() + ) class ProjectMemberPermission(BasePermission): @@ -55,7 +64,7 @@ def has_permission(self, request, view): return WorkspaceMember.objects.filter( workspace__slug=view.workspace_slug, member=request.user, - role__in=[Admin, Member], + role__in=[ROLE.ADMIN.value, ROLE.MEMBER.value], is_active=True, ).exists() @@ -63,7 +72,7 @@ def has_permission(self, request, view): return ProjectMember.objects.filter( workspace__slug=view.workspace_slug, member=request.user, - role__in=[Admin, Member], + role__in=[ROLE.ADMIN.value, ROLE.MEMBER.value], project_id=view.project_id, is_active=True, ).exists() @@ -97,7 +106,7 @@ def has_permission(self, request, view): return ProjectMember.objects.filter( workspace__slug=view.workspace_slug, member=request.user, - role__in=[Admin, Member], + role__in=[ROLE.ADMIN.value, ROLE.MEMBER.value], project_id=view.project_id, is_active=True, ).exists() diff --git a/apps/api/plane/app/views/project/base.py b/apps/api/plane/app/views/project/base.py index b4ee113c46b..d4eeca2f7b9 100644 --- a/apps/api/plane/app/views/project/base.py +++ b/apps/api/plane/app/views/project/base.py @@ -5,13 +5,12 @@ import json # Django imports -from django.db import IntegrityError from django.db.models import Exists, F, OuterRef, Prefetch, Q, Subquery from django.core.serializers.json import DjangoJSONEncoder # Third Party imports from rest_framework.response import Response -from rest_framework import serializers, status +from rest_framework import status from rest_framework.permissions import AllowAny # Module imports @@ -106,7 +105,10 @@ def list_detail(self, request, slug): fields = [field for field in request.GET.get("fields", "").split(",") if field] projects = self.get_queryset().order_by("sort_order", "name") if WorkspaceMember.objects.filter( - member=request.user, workspace__slug=slug, is_active=True, role=5 + member=request.user, + workspace__slug=slug, + is_active=True, + role=ROLE.GUEST.value, ).exists(): projects = projects.filter( project_projectmember__member=self.request.user, @@ -114,7 +116,10 @@ def list_detail(self, request, slug): ) if WorkspaceMember.objects.filter( - member=request.user, workspace__slug=slug, is_active=True, role=15 + member=request.user, + workspace__slug=slug, + is_active=True, + role=ROLE.MEMBER.value, ).exists(): projects = projects.filter( Q( @@ -189,7 +194,10 @@ def list(self, request, slug): ) if WorkspaceMember.objects.filter( - member=request.user, workspace__slug=slug, is_active=True, role=5 + member=request.user, + workspace__slug=slug, + is_active=True, + role=ROLE.GUEST.value, ).exists(): projects = projects.filter( project_projectmember__member=self.request.user, @@ -197,7 +205,10 @@ def list(self, request, slug): ) if WorkspaceMember.objects.filter( - member=request.user, workspace__slug=slug, is_active=True, role=15 + member=request.user, + workspace__slug=slug, + is_active=True, + role=ROLE.MEMBER.value, ).exists(): projects = projects.filter( Q( @@ -250,7 +261,9 @@ def create(self, request, slug): # Add the user as Administrator to the project _ = ProjectMember.objects.create( - project_id=serializer.data["id"], member=request.user, role=20 + project_id=serializer.data["id"], + member=request.user, + role=ROLE.ADMIN.value, ) # Also create the issue property for the user _ = IssueUserProperty.objects.create( @@ -263,7 +276,7 @@ def create(self, request, slug): ProjectMember.objects.create( project_id=serializer.data["id"], member_id=serializer.data["project_lead"], - role=20, + role=ROLE.ADMIN.value, ) # Also create the issue property for the user IssueUserProperty.objects.create( @@ -341,13 +354,23 @@ def create(self, request, slug): def partial_update(self, request, slug, pk=None): # try: - if not ProjectMember.objects.filter( + is_workspace_admin = WorkspaceMember.objects.filter( + member=request.user, + workspace__slug=slug, + is_active=True, + role=ROLE.ADMIN.value, + ).exists() + + is_project_admin = ProjectMember.objects.filter( member=request.user, workspace__slug=slug, project_id=pk, - role=20, + role=ROLE.ADMIN.value, is_active=True, - ).exists(): + ).exists() + + # Return error for if the user is neither workspace admin nor project admin + if not is_project_admin and not is_workspace_admin: return Response( {"error": "You don't have the required permissions."}, status=status.HTTP_403_FORBIDDEN, @@ -402,13 +425,16 @@ def partial_update(self, request, slug, pk=None): def destroy(self, request, slug, pk): if ( WorkspaceMember.objects.filter( - member=request.user, workspace__slug=slug, is_active=True, role=20 + member=request.user, + workspace__slug=slug, + is_active=True, + role=ROLE.ADMIN.value, ).exists() or ProjectMember.objects.filter( member=request.user, workspace__slug=slug, project_id=pk, - role=20, + role=ROLE.ADMIN.value, is_active=True, ).exists() ): diff --git a/apps/api/plane/db/models/project.py b/apps/api/plane/db/models/project.py index e58f60e804b..af576be6e3e 100644 --- a/apps/api/plane/db/models/project.py +++ b/apps/api/plane/db/models/project.py @@ -18,6 +18,12 @@ ROLE_CHOICES = ((20, "Admin"), (15, "Member"), (5, "Guest")) +class ROLE(Enum): + ADMIN = 20 + MEMBER = 15 + GUEST = 5 + + class ProjectNetwork(Enum): SECRET = 0 PUBLIC = 2 diff --git a/apps/web/core/store/user/base-permissions.store.ts b/apps/web/core/store/user/base-permissions.store.ts index b88d7493083..4d6f13d2685 100644 --- a/apps/web/core/store/user/base-permissions.store.ts +++ b/apps/web/core/store/user/base-permissions.store.ts @@ -118,7 +118,11 @@ export abstract class BaseUserPermissionStore implements IBaseUserPermissionStor */ protected getProjectRole = computedFn((workspaceSlug: string, projectId: string): EUserPermissions | undefined => { if (!workspaceSlug || !projectId) return undefined; - return this.workspaceProjectsPermissions?.[workspaceSlug]?.[projectId] || undefined; + const projectRole = this.workspaceProjectsPermissions?.[workspaceSlug]?.[projectId]; + if (!projectRole) return undefined; + const workspaceRole = this.workspaceUserInfo?.[workspaceSlug]?.role; + if (workspaceRole === EUserWorkspaceRoles.ADMIN) return EUserPermissions.ADMIN; + else return projectRole; }); /**