mirror of
https://github.com/makeplane/plane.git
synced 2026-09-01 19:48:42 +02:00
[WEB-7892] fix(security): scope attachment PATCH/DELETE/GET by issue_id, drop created_by overwrite (GHSA-5mxw-g5mw-3v3w) (#9315)
All three V2 issue attachment handlers (PATCH, DELETE, GET single) looked up FileAsset by (pk, workspace, project_id) only — issue_id in the URL was silently ignored. Any project member could target another user's attachment UUID using their own issue_id, and PATCH would transfer ownership via unconditional created_by = request.user. Add issue_id=issue_id to all three FileAsset.objects.get() calls so the lookup is correctly scoped to the attachment's owning issue. Remove the created_by overwrite in PATCH — created_by is set at creation time and must not be reassigned by a subsequent upload-confirm call. Co-authored-by: Plane AI <noreply@plane.so>
This commit is contained in:
@@ -148,7 +148,9 @@ class IssueAttachmentV2Endpoint(BaseAPIView):
|
|||||||
|
|
||||||
@allow_permission([ROLE.ADMIN], creator=True, model=FileAsset)
|
@allow_permission([ROLE.ADMIN], creator=True, model=FileAsset)
|
||||||
def delete(self, request, slug, project_id, issue_id, pk):
|
def delete(self, request, slug, project_id, issue_id, pk):
|
||||||
issue_attachment = FileAsset.objects.get(pk=pk, workspace__slug=slug, project_id=project_id)
|
issue_attachment = FileAsset.objects.get(
|
||||||
|
pk=pk, workspace__slug=slug, project_id=project_id, issue_id=issue_id
|
||||||
|
)
|
||||||
issue_attachment.is_deleted = True
|
issue_attachment.is_deleted = True
|
||||||
issue_attachment.deleted_at = timezone.now()
|
issue_attachment.deleted_at = timezone.now()
|
||||||
issue_attachment.save()
|
issue_attachment.save()
|
||||||
@@ -171,7 +173,7 @@ class IssueAttachmentV2Endpoint(BaseAPIView):
|
|||||||
def get(self, request, slug, project_id, issue_id, pk=None):
|
def get(self, request, slug, project_id, issue_id, pk=None):
|
||||||
if pk:
|
if pk:
|
||||||
# Get the asset
|
# Get the asset
|
||||||
asset = FileAsset.objects.get(id=pk, workspace__slug=slug, project_id=project_id)
|
asset = FileAsset.objects.get(id=pk, workspace__slug=slug, project_id=project_id, issue_id=issue_id)
|
||||||
|
|
||||||
# Check if the asset is uploaded
|
# Check if the asset is uploaded
|
||||||
if not asset.is_uploaded:
|
if not asset.is_uploaded:
|
||||||
@@ -202,7 +204,9 @@ class IssueAttachmentV2Endpoint(BaseAPIView):
|
|||||||
|
|
||||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||||
def patch(self, request, slug, project_id, issue_id, pk):
|
def patch(self, request, slug, project_id, issue_id, pk):
|
||||||
issue_attachment = FileAsset.objects.get(pk=pk, workspace__slug=slug, project_id=project_id)
|
issue_attachment = FileAsset.objects.get(
|
||||||
|
pk=pk, workspace__slug=slug, project_id=project_id, issue_id=issue_id
|
||||||
|
)
|
||||||
serializer = IssueAttachmentSerializer(issue_attachment)
|
serializer = IssueAttachmentSerializer(issue_attachment)
|
||||||
|
|
||||||
# Send this activity only if the attachment is not uploaded before
|
# Send this activity only if the attachment is not uploaded before
|
||||||
@@ -219,9 +223,9 @@ class IssueAttachmentV2Endpoint(BaseAPIView):
|
|||||||
origin=base_host(request=request, is_app=True),
|
origin=base_host(request=request, is_app=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update the attachment
|
# Update the attachment — do NOT overwrite created_by; it is set at
|
||||||
|
# creation time and must not be reassigned (GHSA-5mxw-g5mw-3v3w).
|
||||||
issue_attachment.is_uploaded = True
|
issue_attachment.is_uploaded = True
|
||||||
issue_attachment.created_by = request.user
|
|
||||||
|
|
||||||
# Get the storage metadata
|
# Get the storage metadata
|
||||||
if not issue_attachment.storage_metadata:
|
if not issue_attachment.storage_metadata:
|
||||||
|
|||||||
Reference in New Issue
Block a user