Skip to content

Commit 12f8b3c

Browse files
sangeethailangoyarikoptic
authored andcommitted
[WEB-4854] chore: project admin accesss to workspace admins (makeplane#7749)
* chore: project admin accesss to workspace admins * chore: frontend changes * chore: remove console.log * chore: refactor permission decorator * chore: role enum * chore: rearrange role_choices
1 parent b1add7d commit 12f8b3c

File tree

5 files changed

+92
-29
lines changed

5 files changed

+92
-29
lines changed

apps/api/plane/app/permissions/base.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,31 @@ def _wrapped_view(instance, request, *args, **kwargs):
3939
).exists():
4040
return view_func(instance, request, *args, **kwargs)
4141
else:
42-
if ProjectMember.objects.filter(
42+
is_user_has_allowed_role = ProjectMember.objects.filter(
4343
member=request.user,
4444
workspace__slug=kwargs["slug"],
4545
project_id=kwargs["project_id"],
4646
role__in=allowed_role_values,
4747
is_active=True,
48-
).exists():
48+
).exists()
49+
50+
# Return if the user has the allowed role else if they are workspace admin and part of the project regardless of the role
51+
if is_user_has_allowed_role:
52+
return view_func(instance, request, *args, **kwargs)
53+
elif (
54+
ProjectMember.objects.filter(
55+
member=request.user,
56+
workspace__slug=kwargs["slug"],
57+
project_id=kwargs["project_id"],
58+
is_active=True,
59+
).exists()
60+
and WorkspaceMember.objects.filter(
61+
member=request.user,
62+
workspace__slug=kwargs["slug"],
63+
role=ROLE.ADMIN.value,
64+
is_active=True,
65+
).exists()
66+
):
4967
return view_func(instance, request, *args, **kwargs)
5068

5169
# Return permission denied if no conditions are met

apps/api/plane/app/permissions/project.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,7 @@
33

44
# Module import
55
from plane.db.models import ProjectMember, WorkspaceMember
6-
7-
# Permission Mappings
8-
Admin = 20
9-
Member = 15
10-
Guest = 5
6+
from plane.db.models.project import ROLE
117

128

139
class ProjectBasePermission(BasePermission):
@@ -26,18 +22,31 @@ def has_permission(self, request, view):
2622
return WorkspaceMember.objects.filter(
2723
workspace__slug=view.workspace_slug,
2824
member=request.user,
29-
role__in=[Admin, Member],
25+
role__in=[ROLE.ADMIN.value, ROLE.MEMBER.value],
3026
is_active=True,
3127
).exists()
3228

33-
## Only Project Admins can update project attributes
34-
return ProjectMember.objects.filter(
29+
project_member_qs = ProjectMember.objects.filter(
3530
workspace__slug=view.workspace_slug,
3631
member=request.user,
37-
role=Admin,
3832
project_id=view.project_id,
3933
is_active=True,
40-
).exists()
34+
)
35+
36+
## Only project admins or workspace admin who is part of the project can access
37+
38+
if project_member_qs.filter(role=ROLE.ADMIN.value).exists():
39+
return True
40+
else:
41+
return (
42+
project_member_qs.exists()
43+
and WorkspaceMember.objects.filter(
44+
member=request.user,
45+
workspace__slug=view.workspace_slug,
46+
role=ROLE.ADMIN.value,
47+
is_active=True,
48+
).exists()
49+
)
4150

4251

4352
class ProjectMemberPermission(BasePermission):
@@ -55,15 +64,15 @@ def has_permission(self, request, view):
5564
return WorkspaceMember.objects.filter(
5665
workspace__slug=view.workspace_slug,
5766
member=request.user,
58-
role__in=[Admin, Member],
67+
role__in=[ROLE.ADMIN.value, ROLE.MEMBER.value],
5968
is_active=True,
6069
).exists()
6170

6271
## Only Project Admins can update project attributes
6372
return ProjectMember.objects.filter(
6473
workspace__slug=view.workspace_slug,
6574
member=request.user,
66-
role__in=[Admin, Member],
75+
role__in=[ROLE.ADMIN.value, ROLE.MEMBER.value],
6776
project_id=view.project_id,
6877
is_active=True,
6978
).exists()
@@ -97,7 +106,7 @@ def has_permission(self, request, view):
97106
return ProjectMember.objects.filter(
98107
workspace__slug=view.workspace_slug,
99108
member=request.user,
100-
role__in=[Admin, Member],
109+
role__in=[ROLE.ADMIN.value, ROLE.MEMBER.value],
101110
project_id=view.project_id,
102111
is_active=True,
103112
).exists()

apps/api/plane/app/views/project/base.py

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
import json
66

77
# Django imports
8-
from django.db import IntegrityError
98
from django.db.models import Exists, F, OuterRef, Prefetch, Q, Subquery
109
from django.core.serializers.json import DjangoJSONEncoder
1110

1211
# Third Party imports
1312
from rest_framework.response import Response
14-
from rest_framework import serializers, status
13+
from rest_framework import status
1514
from rest_framework.permissions import AllowAny
1615

1716
# Module imports
@@ -106,15 +105,21 @@ def list_detail(self, request, slug):
106105
fields = [field for field in request.GET.get("fields", "").split(",") if field]
107106
projects = self.get_queryset().order_by("sort_order", "name")
108107
if WorkspaceMember.objects.filter(
109-
member=request.user, workspace__slug=slug, is_active=True, role=5
108+
member=request.user,
109+
workspace__slug=slug,
110+
is_active=True,
111+
role=ROLE.GUEST.value,
110112
).exists():
111113
projects = projects.filter(
112114
project_projectmember__member=self.request.user,
113115
project_projectmember__is_active=True,
114116
)
115117

116118
if WorkspaceMember.objects.filter(
117-
member=request.user, workspace__slug=slug, is_active=True, role=15
119+
member=request.user,
120+
workspace__slug=slug,
121+
is_active=True,
122+
role=ROLE.MEMBER.value,
118123
).exists():
119124
projects = projects.filter(
120125
Q(
@@ -189,15 +194,21 @@ def list(self, request, slug):
189194
)
190195

191196
if WorkspaceMember.objects.filter(
192-
member=request.user, workspace__slug=slug, is_active=True, role=5
197+
member=request.user,
198+
workspace__slug=slug,
199+
is_active=True,
200+
role=ROLE.GUEST.value,
193201
).exists():
194202
projects = projects.filter(
195203
project_projectmember__member=self.request.user,
196204
project_projectmember__is_active=True,
197205
)
198206

199207
if WorkspaceMember.objects.filter(
200-
member=request.user, workspace__slug=slug, is_active=True, role=15
208+
member=request.user,
209+
workspace__slug=slug,
210+
is_active=True,
211+
role=ROLE.MEMBER.value,
201212
).exists():
202213
projects = projects.filter(
203214
Q(
@@ -250,7 +261,9 @@ def create(self, request, slug):
250261

251262
# Add the user as Administrator to the project
252263
_ = ProjectMember.objects.create(
253-
project_id=serializer.data["id"], member=request.user, role=20
264+
project_id=serializer.data["id"],
265+
member=request.user,
266+
role=ROLE.ADMIN.value,
254267
)
255268
# Also create the issue property for the user
256269
_ = IssueUserProperty.objects.create(
@@ -263,7 +276,7 @@ def create(self, request, slug):
263276
ProjectMember.objects.create(
264277
project_id=serializer.data["id"],
265278
member_id=serializer.data["project_lead"],
266-
role=20,
279+
role=ROLE.ADMIN.value,
267280
)
268281
# Also create the issue property for the user
269282
IssueUserProperty.objects.create(
@@ -341,13 +354,23 @@ def create(self, request, slug):
341354

342355
def partial_update(self, request, slug, pk=None):
343356
# try:
344-
if not ProjectMember.objects.filter(
357+
is_workspace_admin = WorkspaceMember.objects.filter(
358+
member=request.user,
359+
workspace__slug=slug,
360+
is_active=True,
361+
role=ROLE.ADMIN.value,
362+
).exists()
363+
364+
is_project_admin = ProjectMember.objects.filter(
345365
member=request.user,
346366
workspace__slug=slug,
347367
project_id=pk,
348-
role=20,
368+
role=ROLE.ADMIN.value,
349369
is_active=True,
350-
).exists():
370+
).exists()
371+
372+
# Return error for if the user is neither workspace admin nor project admin
373+
if not is_project_admin and not is_workspace_admin:
351374
return Response(
352375
{"error": "You don't have the required permissions."},
353376
status=status.HTTP_403_FORBIDDEN,
@@ -402,13 +425,16 @@ def partial_update(self, request, slug, pk=None):
402425
def destroy(self, request, slug, pk):
403426
if (
404427
WorkspaceMember.objects.filter(
405-
member=request.user, workspace__slug=slug, is_active=True, role=20
428+
member=request.user,
429+
workspace__slug=slug,
430+
is_active=True,
431+
role=ROLE.ADMIN.value,
406432
).exists()
407433
or ProjectMember.objects.filter(
408434
member=request.user,
409435
workspace__slug=slug,
410436
project_id=pk,
411-
role=20,
437+
role=ROLE.ADMIN.value,
412438
is_active=True,
413439
).exists()
414440
):

apps/api/plane/db/models/project.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
ROLE_CHOICES = ((20, "Admin"), (15, "Member"), (5, "Guest"))
1919

2020

21+
class ROLE(Enum):
22+
ADMIN = 20
23+
MEMBER = 15
24+
GUEST = 5
25+
26+
2127
class ProjectNetwork(Enum):
2228
SECRET = 0
2329
PUBLIC = 2

apps/web/core/store/user/base-permissions.store.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,11 @@ export abstract class BaseUserPermissionStore implements IBaseUserPermissionStor
118118
*/
119119
protected getProjectRole = computedFn((workspaceSlug: string, projectId: string): EUserPermissions | undefined => {
120120
if (!workspaceSlug || !projectId) return undefined;
121-
return this.workspaceProjectsPermissions?.[workspaceSlug]?.[projectId] || undefined;
121+
const projectRole = this.workspaceProjectsPermissions?.[workspaceSlug]?.[projectId];
122+
if (!projectRole) return undefined;
123+
const workspaceRole = this.workspaceUserInfo?.[workspaceSlug]?.role;
124+
if (workspaceRole === EUserWorkspaceRoles.ADMIN) return EUserPermissions.ADMIN;
125+
else return projectRole;
122126
});
123127

124128
/**

0 commit comments

Comments
 (0)