From be67aab7f97d64aebb97b41f3ae0fac2e3472973 Mon Sep 17 00:00:00 2001 From: Manish Gupta Date: Fri, 10 Jul 2026 15:38:56 +0530 Subject: [PATCH] [WEB-8066] refactor: return bool from asset access helper, build Response in views Address review (Saurabhkmr98): rename project_membership_denied -> has_project_asset_access, returning a boolean (True = allowed) instead of a Response. Each of get/patch/delete now builds the 403 Response based on the returned value. Behaviour is unchanged (same 403 + message; workspace-level assets with project_id=None still allowed). Co-authored-by: Plane AI --- apps/api/plane/app/views/asset/v2.py | 40 ++++++++++++++-------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/api/plane/app/views/asset/v2.py b/apps/api/plane/app/views/asset/v2.py index 4583e42000..4d108d1275 100644 --- a/apps/api/plane/app/views/asset/v2.py +++ b/apps/api/plane/app/views/asset/v2.py @@ -312,35 +312,29 @@ class WorkspaceFileAssetEndpoint(BaseAPIView): else: return - def project_membership_denied(self, request, asset): - """Enforce project-level access on a workspace-scoped asset lookup. + def has_project_asset_access(self, request, asset): + """Return whether the user may access a workspace-scoped asset. This endpoint is authorized at the WORKSPACE level, so a workspace member/guest could otherwise reach an asset that belongs to a project they are not a member of. For project-bound assets, require an active ProjectMember of the asset's project. Workspace-level entity types (WORKSPACE_LOGO, USER_AVATAR, USER_COVER) have project_id=None and are - exempt. Returns a 403 Response when access is denied, else None. + always allowed. """ if asset.project_id is None: - return None + return True # Scope the membership lookup to the asset's workspace as well as its # project, mirroring allow_permission's PROJECT branch. This prevents a # member of the same project in a different workspace from passing the # check should an asset row ever be inconsistent (asset.workspace_id != # asset.project.workspace_id). - is_project_member = ProjectMember.objects.filter( + return ProjectMember.objects.filter( member=request.user, workspace_id=asset.workspace_id, project_id=asset.project_id, is_active=True, ).exists() - if not is_project_member: - return Response( - {"error": "You don't have access to this asset."}, - status=status.HTTP_403_FORBIDDEN, - ) - return None @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def post(self, request, slug): @@ -424,9 +418,11 @@ class WorkspaceFileAssetEndpoint(BaseAPIView): # get the asset id asset = FileAsset.objects.get(id=asset_id, workspace__slug=slug) # enforce project-level access for project-bound assets - denied = self.project_membership_denied(request, asset) - if denied is not None: - return denied + if not self.has_project_asset_access(request, asset): + return Response( + {"error": "You don't have access to this asset."}, + status=status.HTTP_403_FORBIDDEN, + ) # get the storage metadata asset.is_uploaded = True # get the storage metadata @@ -449,9 +445,11 @@ class WorkspaceFileAssetEndpoint(BaseAPIView): def delete(self, request, slug, asset_id): asset = FileAsset.objects.get(id=asset_id, workspace__slug=slug) # enforce project-level access for project-bound assets - denied = self.project_membership_denied(request, asset) - if denied is not None: - return denied + if not self.has_project_asset_access(request, asset): + return Response( + {"error": "You don't have access to this asset."}, + status=status.HTTP_403_FORBIDDEN, + ) asset.is_deleted = True asset.deleted_at = timezone.now() # get the entity and save the asset id for the request field @@ -464,9 +462,11 @@ class WorkspaceFileAssetEndpoint(BaseAPIView): # get the asset id asset = FileAsset.objects.get(id=asset_id, workspace__slug=slug) # enforce project-level access for project-bound assets - denied = self.project_membership_denied(request, asset) - if denied is not None: - return denied + if not self.has_project_asset_access(request, asset): + return Response( + {"error": "You don't have access to this asset."}, + status=status.HTTP_403_FORBIDDEN, + ) # Check if the asset is uploaded if not asset.is_uploaded: