Skip to content

[WEB-4899] fix: workspace admin cannot delete intake and cycle#7807

Merged
pushya22 merged 8 commits intopreviewfrom
fix-intake-cycle-update
Sep 18, 2025
Merged

[WEB-4899] fix: workspace admin cannot delete intake and cycle#7807
pushya22 merged 8 commits intopreviewfrom
fix-intake-cycle-update

Conversation

@sangeethailango
Copy link
Member

@sangeethailango sangeethailango commented Sep 16, 2025

Description

This PR modifies the permission checks in the intake and cycle viewsets, since these are already handled in the decorator.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

Summary by CodeRabbit

  • Bug Fixes
    • Cycle deletion is now restricted to admins only, preventing non-admins from removing cycles.
    • Intake issue updates now consider workspace-level admin status when project membership is missing, allowing workspace admins to edit appropriately.
    • Membership handling for intake updates is more robust, avoiding errors and clarifying who can modify intake issues.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 16, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Removed an inline owner/project-member check from CycleViewSet.destroy so deletion relies on the admin-only decorator. IntakeIssueViewSet.partial_update now falls back from project-level membership to workspace-level membership (WorkspaceMember) for admin checks, updates access gating, and changes how issue data and background tasks are constructed and scheduled.

Changes

Cohort / File(s) Summary
Cycle deletion permission adjustment
apps/api/plane/app/views/cycle/base.py
Removed inline access check in CycleViewSet.destroy; deletion authorization enforced solely by the admin-only decorator. Retrieval, cleanup, and response flow unchanged.
Intake partial_update membership fallback & auth
apps/api/plane/app/views/intake/base.py
Added WorkspaceMember import. Replaced ProjectMember.objects.get(...) with ProjectMember.objects.filter(...).first() and, if absent, query WorkspaceMember to determine workspace admin rights. Updated access gating to require project admin/creator or workspace admin; removed prior guest-only restriction; construct issue_data from request or existing fields; compute current_instance via serializer and pass updated_issue=current_instance and issue_id=str(pk) to issue_description_version_task.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Router as DRF Router
  participant Auth as AdminOnlyPermission
  participant View as CycleViewSet.destroy

  User->>Router: DELETE /cycles/{id}
  Router->>Auth: Check admin-only
  alt Authorized
    Router->>View: Invoke destroy()
    View->>View: Retrieve cycle, cleanup
    View-->>User: 204 No Content
  else Forbidden
    Router-->>User: 403 Forbidden
  end
Loading
sequenceDiagram
  autonumber
  actor User
  participant View as IntakeIssueViewSet.partial_update
  participant PM as ProjectMember
  participant WM as WorkspaceMember
  participant DB as Database
  participant Tasks as BackgroundTasks

  User->>View: PATCH /intake/issues/{id}
  View->>PM: filter(project=..., member=user).first()
  alt Project member found
    View->>View: Evaluate project-member role and creator checks
  else Not found
    View->>WM: filter(workspace=..., member=user).first()
    View->>View: Evaluate workspace-admin flag and creator checks
  end
  alt Allowed
    View->>DB: Apply partial update (assemble issue_data from request or existing fields)
    View->>Tasks: issue_description_version_task(updated_issue=current_instance, issue_id=str(pk))
    View-->>User: 200 OK
  else Forbidden
    View-->>User: 403 Forbidden
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

🐛bug

Suggested reviewers

  • dheeru0198
  • prateekshourya29

Poem

I hop through branches, tidy and spry,
I let workspace friends help guard the sky.
I move a check, and stitch a task,
Now cycles close with one small ask.
Carrots for tests — merge and relax! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description Check ❓ Inconclusive The PR description includes a brief summary and correctly marks this as a bug fix, but it is too terse and omits several template sections and reviewer-relevant details: the template's Test Scenarios, explicit list of affected files/code paths, and References are missing and the Description does not explain how the changes were verified. Please expand the Description to list the specific files and permission checks changed, add Test Scenarios (steps to reproduce the original bug and how you verified the fix, including any tests run), and include References or a link to WEB-4899 so reviewers can validate and reproduce the change.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title "[WEB-4899] fix: workspace admin cannot delete intake and cycle" clearly and concisely identifies the primary bug being addressed—permission changes so workspace admins can delete intake and cycle resources—and includes the issue ID for traceability; it matches the PR's stated objective to remove redundant permission checks.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-intake-cycle-update

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5bb84de and d3dda2b.

📒 Files selected for processing (1)
  • apps/api/plane/app/views/intake/base.py (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/api/plane/app/views/intake/base.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build and lint web apps
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Analyze (javascript)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@makeplane
Copy link

makeplane bot commented Sep 16, 2025

Linked to Plane Work Item(s)

This comment was auto-generated by Plane

cursor[bot]

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/api/plane/app/views/intake/base.py (2)

343-351: Bug: passing a model instance to intake_id filter; also missing 404 when intake not found.

intake_id is a scalar FK column; passing an Intake instance (intake_id=intake_id) can raise a type error. Also guard for a missing intake before the .get().

Apply this diff:

-        intake_id = Intake.objects.filter(
+        intake = Intake.objects.filter(
             workspace__slug=slug, project_id=project_id
         ).first()
-        intake_issue = IntakeIssue.objects.get(
+        if not intake:
+            return Response({"error": "Intake not found"}, status=status.HTTP_404_NOT_FOUND)
+        intake_issue = IntakeIssue.objects.get(
             issue_id=pk,
             workspace__slug=slug,
             project_id=project_id,
-            intake_id=intake_id,
+            intake_id=intake.id,
         )

447-451: Fix role-check: include Members (use >= or ROLE.MEMBER.value)

ROLE mapping: Admin=20, Member=15, Guest=5 — current if project_member.role > 15 excludes MEMBER (15). Change to project_member.role >= 15 or compare against the enum constant (e.g. ROLE.MEMBER.value) and remove the magic number.

Location: apps/api/plane/app/views/intake/base.py:447-451. Also update the identical check in apps/api/plane/api/views/intake.py:408-411.

🧹 Nitpick comments (3)
apps/api/plane/app/views/intake/base.py (3)

366-373: Use 403 Forbidden for authz failures, not 400.

This is an authorization failure, not a bad request. Return 403 for consistency with DRF and other endpoints in this file.

-            return Response(
-                {"error": "You cannot edit intake issues"},
-                status=status.HTTP_400_BAD_REQUEST,
-            )
+            return Response(
+                {"error": "You are not allowed to edit intake issues"},
+                status=status.HTTP_403_FORBIDDEN,
+            )

608-612: Guard against None before deleting related Issue.

Issue.objects.filter(...).first() may return None; calling .delete() would raise. Add a null check.

-            issue = Issue.objects.filter(
+            issue = Issue.objects.filter(
                 workspace__slug=slug, project_id=project_id, pk=pk
             ).first()
-            issue.delete()
+            if issue:
+                issue.delete()

71-75: Possible None dereference in IntakeViewSet.list.

intake = self.get_queryset().first() can be None; serializing None can break. Mirror the 404 handling used elsewhere.

-        intake = self.get_queryset().first()
-        return Response(IntakeSerializer(intake).data, status=status.HTTP_200_OK)
+        intake = self.get_queryset().first()
+        if not intake:
+            return Response({"error": "Intake not found"}, status=status.HTTP_404_NOT_FOUND)
+        return Response(IntakeSerializer(intake).data, status=status.HTTP_200_OK)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf45635 and 13d3030.

📒 Files selected for processing (2)
  • apps/api/plane/app/views/cycle/base.py (0 hunks)
  • apps/api/plane/app/views/intake/base.py (2 hunks)
💤 Files with no reviewable changes (1)
  • apps/api/plane/app/views/cycle/base.py
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: NarayanBavisetti
PR: makeplane/plane#7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.
📚 Learning: 2025-07-23T18:18:06.875Z
Learnt from: NarayanBavisetti
PR: makeplane/plane#7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.

Applied to files:

  • apps/api/plane/app/views/intake/base.py
🧬 Code graph analysis (1)
apps/api/plane/app/views/intake/base.py (2)
apps/api/plane/db/models/workspace.py (1)
  • WorkspaceMember (202-234)
apps/api/plane/db/models/project.py (1)
  • ProjectMember (212-256)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
apps/api/plane/app/views/intake/base.py (1)

31-32: Import of WorkspaceMember looks correct.

Needed for the workspace-level fallback. No concerns.

cursor[bot]

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/plane/app/views/intake/base.py (1)

343-351: Guard against missing Intake/issue and fix misuse of intake_id

  • .get() on IntakeIssue will 500 when not found; return 404 instead.
  • You pass an Intake object into intake_id=...; use intake_id.id.

Apply this diff:

-        intake_id = Intake.objects.filter(
-            workspace__slug=slug, project_id=project_id
-        ).first()
-        intake_issue = IntakeIssue.objects.get(
-            issue_id=pk,
-            workspace__slug=slug,
-            project_id=project_id,
-            intake_id=intake_id,
-        )
+        intake_id = Intake.objects.filter(
+            workspace__slug=slug, project_id=project_id
+        ).first()
+        if not intake_id:
+            return Response(
+                {"error": "Intake not found"},
+                status=status.HTTP_404_NOT_FOUND,
+            )
+        intake_issue = (
+            IntakeIssue.objects.filter(
+                issue_id=pk,
+                workspace__slug=slug,
+                project_id=project_id,
+                intake_id=intake_id.id,
+            ).first()
+        )
+        if not intake_issue:
+            return Response(
+                {"error": "Intake issue not found"},
+                status=status.HTTP_404_NOT_FOUND,
+            )
♻️ Duplicate comments (2)
apps/api/plane/app/views/intake/base.py (2)

375-381: Remove incorrect role check and wrong status code

  • Refers to project_member.role which may not exist (workspace-admin-only path).
  • Logic is inverted and returns 400 for an authorization failure; should be 403.

Apply this diff:

-        if (project_member.role <= 5 or not is_workspace_admin) and str(
-            intake_issue.created_by_id
-        ) != str(request.user.id):
-            return Response(
-                {"error": "You cannot edit intake issues"},
-                status=status.HTTP_400_BAD_REQUEST,
-            )
+        # Authorization is already enforced above; no extra check needed here.

411-411: Guest-only edit rule is inverted; use is_admin

Current condition lets admins and everyone with role<=5 into the restricted path. Instead, restrict the limited edit path to non-admins (i.e., creators who aren’t admins).

Apply this diff:

-            if project_member.role <= 5 or is_workspace_admin:
+            if not is_admin:
🧹 Nitpick comments (1)
apps/api/plane/app/views/intake/base.py (1)

341-373: Consistency: rely on decorator for coarse auth; keep fine-grained rules here

Given @allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=Issue), avoid re-implementing coarse checks inline. The simplified is_admin/is_creator above is fine if you need it for field-level control, but don’t diverge from decorator semantics.

Would you like me to push a follow-up commit that removes the redundant coarse checks and adds tests for:

  • workspace admin (no project membership) can PATCH attributes
  • creator (non-admin) can only change name/description
  • non-creator member is forbidden (403)?
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 13d3030 and ea21cf0.

📒 Files selected for processing (1)
  • apps/api/plane/app/views/intake/base.py (4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: NarayanBavisetti
PR: makeplane/plane#7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.
📚 Learning: 2025-07-23T18:18:06.875Z
Learnt from: NarayanBavisetti
PR: makeplane/plane#7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.

Applied to files:

  • apps/api/plane/app/views/intake/base.py
🧬 Code graph analysis (1)
apps/api/plane/app/views/intake/base.py (2)
apps/api/plane/db/models/workspace.py (1)
  • WorkspaceMember (202-234)
apps/api/plane/db/models/project.py (1)
  • ProjectMember (212-256)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
apps/api/plane/app/views/intake/base.py (1)

31-31: Import looks good

WorkspaceMember is used below; import is correct.

cursor[bot]

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/api/plane/app/views/intake/base.py (2)

343-351: Bug: filtering by intake_id passes a model instance, not an ID.

This will raise a TypeError; use intake.id or filter by intake.

Apply this diff:

-        intake_id = Intake.objects.filter(
+        intake = Intake.objects.filter(
             workspace__slug=slug, project_id=project_id
         ).first()
-        intake_issue = IntakeIssue.objects.get(
+        if not intake:
+            return Response({"error": "Intake not found"}, status=status.HTTP_404_NOT_FOUND)
+        intake_issue = IntakeIssue.objects.get(
             issue_id=pk,
             workspace__slug=slug,
             project_id=project_id,
-            intake_id=intake_id,
+            intake_id=intake.id,
         )

413-420: Workspace admins are accidentally restricted to name/description only. Invert the condition.

Gate the “limited fields” path on not is_admin.

Apply this diff:

-            if project_member.role <= 5 or is_workspace_admin:
+            if not is_admin:
                 issue_data = {
                     "name": issue_data.get("name", issue.name),
                     "description_html": issue_data.get(
                         "description_html", issue.description_html
                     ),
                     "description": issue_data.get("description", issue.description),
                 }
♻️ Duplicate comments (3)
apps/api/plane/app/views/intake/base.py (3)

376-383: Remove redundant and brittle check; it can 400 for valid creators and NPE when no project_member.

Decorator already enforces admin-or-creator. This block mixes magic numbers and will AttributeError when project_member is None.

Apply this diff:

-        # Only project members admins and created_by users can access this endpoint
-        if (project_member.role <= 5 or not is_workspace_admin) and str(
-            intake_issue.created_by_id
-        ) != str(request.user.id):
-            return Response(
-                {"error": "You cannot edit intake issues"},
-                status=status.HTTP_400_BAD_REQUEST,
-            )
+        # Authorization handled by decorator; field-level rules below.

353-374: Authorization gate is inverted and duplicates the decorator. Normalize to is_admin/is_creator.

Condition if not project_member or not is_workspace_admin denies valid users; also redundant with @allow_permission. Compute is_admin once; drop the extra 403.

Apply this diff:

-        project_member = ProjectMember.objects.filter(
+        project_member = ProjectMember.objects.filter(
             workspace__slug=slug,
             project_id=project_id,
             member=request.user,
             is_active=True,
-        ).first()
-
-        is_workspace_admin = False
-
-        if not project_member:
-            is_workspace_admin = WorkspaceMember.objects.filter(
-                workspace__slug=slug,
-                is_active=True,
-                member=request.user,
-                role=ROLE.ADMIN.value,
-            ).exists()
-
-        if not project_member or not is_workspace_admin:
-            return Response(
-                {"error": "Only admin or creator can update the intake work items"},
-                status=status.HTTP_403_FORBIDDEN,
-            )
+        ).first()
+        is_project_admin = ProjectMember.objects.filter(
+            workspace__slug=slug,
+            project_id=project_id,
+            member=request.user,
+            role=ROLE.ADMIN.value,
+            is_active=True,
+        ).exists()
+        is_workspace_admin = WorkspaceMember.objects.filter(
+            workspace__slug=slug,
+            member=request.user,
+            role=ROLE.ADMIN.value,
+            is_active=True,
+        ).exists()
+        is_admin = is_project_admin or is_workspace_admin
+        is_creator = str(intake_issue.created_by_id) == str(request.user.id)

457-459: Replace magic role thresholds with is_admin.

role > 15 is opaque; rely on the boolean computed above.

Apply this diff:

-        # Only project admins can edit intake issue attributes
-        if project_member.role > 15 or is_workspace_admin:
+        # Only admins can edit intake issue attributes
+        if is_admin:
🧹 Nitpick comments (1)
apps/api/plane/app/views/intake/base.py (1)

343-459: Remove numeric role comparisons; rely on decorator-provided is_admin/is_creator for branching

Found numeric role comparisons in apps/api/plane/app/views/intake/base.py at lines 377 (project_member.role <= 5), 413 (project_member.role <= 5) and 458 (project_member.role > 15). Replace these with the boolean flags populated by @allow_permission (is_admin / is_creator), keep those booleans only for field-level branching, and remove the duplicate 403 checks inside partial_update.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea21cf0 and 47e4b93.

📒 Files selected for processing (1)
  • apps/api/plane/app/views/intake/base.py (4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: NarayanBavisetti
PR: makeplane/plane#7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.
📚 Learning: 2025-07-23T18:18:06.875Z
Learnt from: NarayanBavisetti
PR: makeplane/plane#7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.

Applied to files:

  • apps/api/plane/app/views/intake/base.py
🧬 Code graph analysis (1)
apps/api/plane/app/views/intake/base.py (2)
apps/api/plane/db/models/workspace.py (1)
  • WorkspaceMember (202-234)
apps/api/plane/db/models/project.py (1)
  • ProjectMember (212-256)
🔇 Additional comments (1)
apps/api/plane/app/views/intake/base.py (1)

31-31: Import looks good.

Adding WorkspaceMember here is appropriate for workspace‑level checks.

cursor[bot]

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/plane/app/views/intake/base.py (1)

343-351: Bug: Wrong FK lookup and missing 404 guard for Intake

  • Passing a model instance to intake_id= is invalid; use the UUID (.id) or the intake= FK field.
  • If no Intake exists, .get() on IntakeIssue will 500.

Fix with:

-        intake_id = Intake.objects.filter(
+        intake_id = Intake.objects.filter(
             workspace__slug=slug, project_id=project_id
         ).first()
+        if not intake_id:
+            return Response(
+                {"error": "Intake not found"},
+                status=status.HTTP_404_NOT_FOUND,
+            )
         intake_issue = IntakeIssue.objects.get(
             issue_id=pk,
             workspace__slug=slug,
             project_id=project_id,
-            intake_id=intake_id,
+            intake_id=intake_id.id,
         )
♻️ Duplicate comments (1)
apps/api/plane/app/views/intake/base.py (1)

457-459: Fix inverted/magic-number role check; gate attribute edits by is_admin

Replace brittle numeric role logic with the derived boolean and ensure workspace admins can edit attributes.

-        # Only project admins can edit intake issue attributes
-        if (project_member and project_member.role > 15) or is_workspace_admin:
+        # Only admins can edit intake issue attributes
+        if is_admin:
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47e4b93 and d2f72d8.

📒 Files selected for processing (1)
  • apps/api/plane/app/views/intake/base.py (4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: NarayanBavisetti
PR: makeplane/plane#7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.
📚 Learning: 2025-07-23T18:18:06.875Z
Learnt from: NarayanBavisetti
PR: makeplane/plane#7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.

Applied to files:

  • apps/api/plane/app/views/intake/base.py
🧬 Code graph analysis (1)
apps/api/plane/app/views/intake/base.py (2)
apps/api/plane/db/models/workspace.py (1)
  • WorkspaceMember (202-234)
apps/api/plane/db/models/project.py (1)
  • ProjectMember (212-256)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
apps/api/plane/app/views/intake/base.py (2)

31-31: Add WorkspaceMember import — OK

Import looks correct and scoped to the new checks.


413-419: Issue data fallback — OK

Merging with existing fields is correct and avoids nulling unspecified attributes.

cursor[bot]

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/plane/app/views/intake/base.py (1)

346-351: Bug: Wrong ORM filter uses model instance for *_id field

intake_id here is an Intake instance (from .first()), but you pass it to intake_id=…. Django expects the PK value; passing the model instance can raise a ValueError at runtime.

Apply:

-        intake_issue = IntakeIssue.objects.get(
+        intake_issue = IntakeIssue.objects.get(
             issue_id=pk,
             workspace__slug=slug,
             project_id=project_id,
-            intake_id=intake_id,
+            intake_id=intake_id.id,
         )
♻️ Duplicate comments (1)
apps/api/plane/app/views/intake/base.py (1)

457-458: Admin‑only attribute edits: replace magic role threshold with clear is_admin

Numeric role comparisons are brittle and currently invert intent. Use the derived is_admin.

-        # Only project admins can edit intake issue attributes
-        if (project_member and project_member.role > 15) or is_workspace_admin:
+        # Only admins can edit intake issue attributes
+        if is_admin:

Optional: if you want to clearly signal denial, return 403 instead of silently echoing the current state in the else branch.

🧹 Nitpick comments (1)
apps/api/plane/app/views/intake/base.py (1)

31-31: Import only if inline workspace‑admin logic remains

If we drop the inline permission logic (recommended below), remove this import to avoid dead code.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d2f72d8 and f0138ff.

📒 Files selected for processing (1)
  • apps/api/plane/app/views/intake/base.py (4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: NarayanBavisetti
PR: makeplane/plane#7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.
📚 Learning: 2025-07-23T18:18:06.875Z
Learnt from: NarayanBavisetti
PR: makeplane/plane#7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.

Applied to files:

  • apps/api/plane/app/views/intake/base.py
🧬 Code graph analysis (1)
apps/api/plane/app/views/intake/base.py (2)
apps/api/plane/db/models/workspace.py (1)
  • WorkspaceMember (202-234)
apps/api/plane/db/models/project.py (1)
  • ProjectMember (212-256)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
apps/api/plane/app/views/intake/base.py (1)

413-420: LGTM: Safe field fallback for partial updates

Merges incoming issue fields with existing values; concise and correct.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (4)
apps/api/plane/app/views/intake/base.py (4)

376-383: Incorrect auth logic and 400 status; delete this block.
The boolean is wrong and 400 is not for auth. Removing it is covered by relying on the decorator + the refactor above.

Apply:

-        if (
-            (project_member and project_member.role <= 5) and not is_workspace_admin
-        ) and str(intake_issue.created_by_id) != str(request.user.id):
-            return Response(
-                {"error": "You cannot edit intake issues"},
-                status=status.HTTP_400_BAD_REQUEST,
-            )

413-421: Field restriction uses magic numbers; tie to is_admin instead.
This currently keys off project_member.role <= 5. Use the derived flag to keep intent clear and future‑proof.

Apply:

-            if project_member and project_member.role <= 5:
+            if not is_admin:
                 issue_data = {
                     "name": issue_data.get("name", issue.name),
                     "description_html": issue_data.get(
                         "description_html", issue.description_html
                     ),
                     "description": issue_data.get("description", issue.description),
                 }

353-374: Remove redundant gate; derive is_admin/is_creator once (decorator already enforces access).
This block re-gates “admin or creator” and can wrongly block creators who aren’t project members. Replace with a single privilege derivation used later.

Apply:

-        project_member = ProjectMember.objects.filter(
-            workspace__slug=slug,
-            project_id=project_id,
-            member=request.user,
-            is_active=True,
-        ).first()
-
-        is_workspace_admin = False
-
-        if not project_member:
-            is_workspace_admin = WorkspaceMember.objects.filter(
-                workspace__slug=slug,
-                is_active=True,
-                member=request.user,
-                role=ROLE.ADMIN.value,
-            ).exists()
-
-        if not project_member and not is_workspace_admin:
-            return Response(
-                {"error": "Only admin or creator can update the intake work items"},
-                status=status.HTTP_403_FORBIDDEN,
-            )
+        # Base permission (admin OR creator) is enforced by the decorator above.
+        # Derive flags only for downstream admin-only behavior.
+        is_project_admin = ProjectMember.objects.filter(
+            workspace__slug=slug,
+            project_id=project_id,
+            member=request.user,
+            role=ROLE.ADMIN.value,
+            is_active=True,
+        ).exists()
+        is_workspace_admin = WorkspaceMember.objects.filter(
+            workspace__slug=slug,
+            member=request.user,
+            role=ROLE.ADMIN.value,
+            is_active=True,
+        ).exists()
+        is_admin = is_project_admin or is_workspace_admin
+        is_creator = str(intake_issue.created_by_id) == str(request.user.id)

458-459: Admin check inverted: non-admins can edit attributes.
The condition allows high-role-number users; flip to is_admin.

Apply:

-        # Only project admins can edit intake issue attributes
-        if (project_member and project_member.role > 15) or is_workspace_admin:
+        # Only admins (project or workspace) can edit intake issue attributes
+        if is_admin:
🧹 Nitpick comments (1)
apps/api/plane/app/views/intake/base.py (1)

346-351: ForeignKey lookup uses intake_id with a model instance.
Pass the FK object to intake= or its .id to intake_id=.

Apply:

-        intake_issue = IntakeIssue.objects.get(
-            issue_id=pk,
-            workspace__slug=slug,
-            project_id=project_id,
-            intake_id=intake_id,
-        )
+        intake_issue = IntakeIssue.objects.get(
+            issue_id=pk,
+            workspace__slug=slug,
+            project_id=project_id,
+            intake=intake_id,
+        )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f0138ff and 5cd05da.

📒 Files selected for processing (1)
  • apps/api/plane/app/views/intake/base.py (4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: NarayanBavisetti
PR: makeplane/plane#7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.
📚 Learning: 2025-07-23T18:18:06.875Z
Learnt from: NarayanBavisetti
PR: makeplane/plane#7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.

Applied to files:

  • apps/api/plane/app/views/intake/base.py
🧬 Code graph analysis (1)
apps/api/plane/app/views/intake/base.py (2)
apps/api/plane/db/models/workspace.py (1)
  • WorkspaceMember (202-234)
apps/api/plane/db/models/project.py (1)
  • ProjectMember (212-256)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
apps/api/plane/app/views/intake/base.py (1)

31-31: Import of WorkspaceMember looks good.
Used later for admin checks; no concerns.

@dheeru0198 dheeru0198 requested a review from Copilot September 17, 2025 10:33
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes workspace admin permissions for intake and cycle operations by modifying permission checks in the respective viewsets. The changes ensure workspace admins can properly delete cycles and update intake issues, even when they're not direct project members.

  • Removed redundant permission checks for cycle deletion (now handled by decorator)
  • Added workspace admin fallback permission for intake issue updates
  • Improved error handling for non-project members accessing intake functionality

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
apps/api/plane/app/views/intake/base.py Enhanced intake issue update permissions to include workspace admin checks
apps/api/plane/app/views/cycle/base.py Removed duplicate permission validation for cycle deletion

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +377 to +380
if (
(project_member and project_member.role <= ROLE.GUEST.value)
and not is_workspace_admin
) and str(intake_issue.created_by_id) != str(request.user.id):
Copy link

Copilot AI Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The complex conditional logic with nested parentheses is hard to read and maintain. Consider extracting this into a helper method or breaking it into separate conditions with descriptive variable names.

Suggested change
if (
(project_member and project_member.role <= ROLE.GUEST.value)
and not is_workspace_admin
) and str(intake_issue.created_by_id) != str(request.user.id):
if not self.can_edit_intake_issue(project_member, is_workspace_admin, intake_issue, request):

Copilot uses AI. Check for mistakes.
@pushya22 pushya22 merged commit e26c506 into preview Sep 18, 2025
7 of 9 checks passed
@pushya22 pushya22 deleted the fix-intake-cycle-update branch September 18, 2025 14:41
yarikoptic pushed a commit to yarikoptic/plane that referenced this pull request Oct 1, 2025
…lane#7807)

* fix: permission check on viewset

* chore: check workspace admin

* chore: initiative is_workspace_admin before if condition

* chore: project member check

* fix: if conditions

* chore: add condition for guests to only edit description and name

* fix: use ROLE enum instead of magic numbers

* chore: remove if condition
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants