SECURITY - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: ограничение создания проектов

This commit is contained in:
DCCONSTRUCTIONS
2026-05-12 22:24:36 +03:00
parent 87e1857f53
commit 5e7c9e08a0
18 changed files with 251 additions and 70 deletions
@@ -25,6 +25,10 @@ from plane.app.serializers import (
from plane.app.views.base import BaseAPIView, BaseViewSet
from plane.bgtasks.recent_visited_task import recent_visited_task
from plane.bgtasks.webhook_task import model_activity, webhook_activity
from plane.authentication.nodedc_project_memberships import (
ensure_project_admin_membership,
ensure_workspace_admin_project_memberships,
)
from plane.db.models import (
UserFavorite,
DeployBoard,
@@ -49,6 +53,20 @@ class ProjectViewSet(BaseViewSet):
webhook_event = "project"
use_read_replica = True
def ensure_workspace_admin_project_access(self, request, slug):
workspace_member = (
WorkspaceMember.objects.filter(
member=request.user,
workspace__slug=slug,
is_active=True,
role=ROLE.ADMIN.value,
)
.select_related("workspace")
.first()
)
if workspace_member is not None:
ensure_workspace_admin_project_memberships(workspace_member.workspace)
def get_queryset(self):
sort_order = ProjectUserProperty.objects.filter(
user=self.request.user,
@@ -99,6 +117,7 @@ class ProjectViewSet(BaseViewSet):
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
def list_detail(self, request, slug):
self.ensure_workspace_admin_project_access(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(
@@ -119,11 +138,8 @@ class ProjectViewSet(BaseViewSet):
role=ROLE.MEMBER.value,
).exists():
projects = projects.filter(
Q(
project_projectmember__member=self.request.user,
project_projectmember__is_active=True,
)
| Q(network=2)
project_projectmember__member=self.request.user,
project_projectmember__is_active=True,
)
if request.GET.get("per_page", False) and request.GET.get("cursor", False):
@@ -139,6 +155,7 @@ class ProjectViewSet(BaseViewSet):
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
def list(self, request, slug):
self.ensure_workspace_admin_project_access(request, slug)
sort_order = ProjectUserProperty.objects.filter(
user=self.request.user,
project_id=OuterRef("pk"),
@@ -209,11 +226,8 @@ class ProjectViewSet(BaseViewSet):
role=ROLE.MEMBER.value,
).exists():
projects = projects.filter(
Q(
project_projectmember__member=self.request.user,
project_projectmember__is_active=True,
)
| Q(network=2)
project_projectmember__member=self.request.user,
project_projectmember__is_active=True,
)
return Response(projects, status=status.HTTP_200_OK)
@@ -227,7 +241,14 @@ class ProjectViewSet(BaseViewSet):
member_ids = [str(project_member.member_id) for project_member in project.members_list]
if str(request.user.id) not in member_ids:
if project.network == ProjectNetwork.SECRET.value:
if WorkspaceMember.objects.filter(
member=request.user,
workspace__slug=slug,
is_active=True,
role=ROLE.ADMIN.value,
).exists():
ensure_project_admin_membership(project, request.user)
elif project.network == ProjectNetwork.SECRET.value:
return Response(
{"error": "You do not have permission"},
status=status.HTTP_403_FORBIDDEN,
@@ -249,7 +270,7 @@ class ProjectViewSet(BaseViewSet):
serializer = ProjectListSerializer(project)
return Response(serializer.data, status=status.HTTP_200_OK)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
@allow_permission([ROLE.ADMIN], level="WORKSPACE")
def create(self, request, slug):
workspace = Workspace.objects.get(slug=slug)
@@ -290,6 +311,7 @@ class ProjectViewSet(BaseViewSet):
)
project = self.get_queryset().filter(pk=serializer.data["id"]).first()
ensure_workspace_admin_project_memberships(workspace, project=project)
# Create the model activity
model_activity.delay(
@@ -11,6 +11,10 @@ from django.db.models import Min
from .base import BaseViewSet, BaseAPIView
from plane.app.realtime.issue_events import publish_assignee_cleanup_issue_events_on_commit
from plane.app.realtime.nodedc_events import publish_nodedc_workspace_event_on_commit
from plane.authentication.nodedc_project_memberships import (
ensure_project_admin_membership,
ensure_workspace_admin_project_memberships,
)
from plane.authentication.nodedc_workspace_policy import (
is_nodedc_launcher_managed_workspace,
nodedc_launcher_managed_workspace_response,
@@ -431,12 +435,32 @@ class ProjectMemberViewSet(BaseViewSet):
class ProjectMemberUserEndpoint(BaseAPIView):
def get(self, request, slug, project_id):
project_member = ProjectMember.objects.get(
project_member = ProjectMember.objects.filter(
project_id=project_id,
workspace__slug=slug,
member=request.user,
is_active=True,
)
).first()
if project_member is None:
project = Project.objects.filter(pk=project_id, workspace__slug=slug).select_related("workspace").first()
is_workspace_admin = WorkspaceMember.objects.filter(
workspace__slug=slug,
member=request.user,
is_active=True,
role=ROLE.ADMIN.value,
).exists()
if project is not None and is_workspace_admin:
ensure_project_admin_membership(project, request.user)
project_member = ProjectMember.objects.filter(
project=project,
member=request.user,
is_active=True,
).first()
if project_member is None:
return Response({"error": "Project member not found"}, status=status.HTTP_404_NOT_FOUND)
serializer = ProjectMemberSerializer(project_member)
return Response(serializer.data, status=status.HTTP_200_OK)
@@ -447,6 +471,19 @@ class UserProjectRolesEndpoint(BaseAPIView):
use_read_replica = True
def get(self, request, slug):
workspace_member = (
WorkspaceMember.objects.filter(
workspace__slug=slug,
member=request.user,
is_active=True,
)
.select_related("workspace")
.first()
)
if workspace_member is not None and workspace_member.role == ROLE.ADMIN.value:
ensure_workspace_admin_project_memberships(workspace_member.workspace)
project_members = ProjectMember.objects.filter(
workspace__slug=slug,
member_id=request.user.id,