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
@@ -0,0 +1,113 @@
ADMIN_ROLE = 20
AUTO_ADMIN_COMMENT = "nodedc:workspace-admin"
AUTO_ADMIN_CREATED_COMMENT = f"{AUTO_ADMIN_COMMENT}:created"
AUTO_ADMIN_PREVIOUS_PREFIX = f"{AUTO_ADMIN_COMMENT}:previous:"
def get_auto_admin_previous_role(comment):
if not isinstance(comment, str) or not comment.startswith(AUTO_ADMIN_PREVIOUS_PREFIX):
return None
try:
previous_role = int(comment.replace(AUTO_ADMIN_PREVIOUS_PREFIX, "", 1))
except ValueError:
return None
return previous_role if previous_role in {5, 15, ADMIN_ROLE} else None
def ensure_project_admin_membership(project, user):
from plane.db.models import ProjectMember
project_member = ProjectMember.objects.filter(
project=project,
member=user,
deleted_at__isnull=True,
).first()
if project_member is None:
ProjectMember.objects.create(
workspace=project.workspace,
project=project,
member=user,
role=ADMIN_ROLE,
is_active=True,
comment=AUTO_ADMIN_CREATED_COMMENT,
)
return 1
update_fields = []
if project_member.role != ADMIN_ROLE:
project_member.comment = f"{AUTO_ADMIN_PREVIOUS_PREFIX}{project_member.role}"
project_member.role = ADMIN_ROLE
update_fields.extend(["comment", "role"])
if not project_member.is_active:
project_member.is_active = True
update_fields.append("is_active")
if update_fields:
update_fields.append("updated_at")
project_member.save(update_fields=update_fields)
return 1
return 0
def revoke_auto_project_admin_memberships(workspace, user):
from plane.db.models import ProjectMember
revoked = 0
project_memberships = ProjectMember.objects.filter(
project__workspace=workspace,
member=user,
role=ADMIN_ROLE,
deleted_at__isnull=True,
comment__startswith=AUTO_ADMIN_COMMENT,
)
for project_member in project_memberships:
previous_role = get_auto_admin_previous_role(project_member.comment)
if project_member.comment == AUTO_ADMIN_CREATED_COMMENT or previous_role is None:
project_member.is_active = False
project_member.save(update_fields=["is_active", "updated_at"])
else:
project_member.role = previous_role
project_member.comment = None
project_member.is_active = True
project_member.save(update_fields=["role", "comment", "is_active", "updated_at"])
revoked += 1
return revoked
def ensure_user_admin_project_memberships(workspace, user):
from plane.db.models import Project
restored = 0
for project in Project.objects.filter(workspace=workspace, deleted_at__isnull=True).select_related("workspace"):
restored += ensure_project_admin_membership(project, user)
return restored
def ensure_workspace_admin_project_memberships(workspace, project=None):
from plane.db.models import WorkspaceMember
admin_memberships = (
WorkspaceMember.objects.filter(
workspace=workspace,
role=ADMIN_ROLE,
is_active=True,
deleted_at__isnull=True,
member__is_bot=False,
)
.select_related("member")
.order_by("created_at")
)
restored = 0
for workspace_member in admin_memberships:
if project is not None:
restored += ensure_project_admin_membership(project, workspace_member.member)
else:
restored += ensure_user_admin_project_memberships(workspace, workspace_member.member)
return restored
@@ -9,6 +9,10 @@ from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from plane.authentication.nodedc_project_memberships import (
ensure_user_admin_project_memberships,
revoke_auto_project_admin_memberships,
)
from plane.authentication.views.nodedc_logout import is_internal_logout_request_authorized
from plane.app.realtime.issue_events import publish_assignee_cleanup_issue_events_on_commit
from plane.app.realtime.nodedc_events import (
@@ -332,24 +336,7 @@ def serialize_project_membership(project_member, created):
def restore_admin_project_memberships(workspace, user):
restored = 0
for project_member in ProjectMember.objects.filter(
project__workspace=workspace,
member=user,
deleted_at__isnull=True,
):
update_fields = []
if project_member.role != ADMIN_ROLE:
project_member.role = ADMIN_ROLE
update_fields.append("role")
if not project_member.is_active:
project_member.is_active = True
update_fields.append("is_active")
if update_fields:
update_fields.append("updated_at")
project_member.save(update_fields=update_fields)
restored += 1
return restored
return ensure_user_admin_project_memberships(workspace, user)
@method_decorator(csrf_exempt, name="dispatch")
@@ -441,6 +428,7 @@ class NodeDCInternalWorkspaceMembershipEnsureEndpoint(View):
deleted_at__isnull=True,
).first()
created = membership is None
previous_role = membership.role if membership is not None else None
if membership is None:
membership = WorkspaceMember.objects.create(
@@ -463,6 +451,8 @@ class NodeDCInternalWorkspaceMembershipEnsureEndpoint(View):
if role == ADMIN_ROLE:
restore_admin_project_memberships(workspace, user)
elif previous_role == ADMIN_ROLE:
revoke_auto_project_admin_memberships(workspace, user)
if set_last_workspace:
profile, _ = Profile.objects.get_or_create(user=user)