Skip to content

fix(plans): allow Read-Only users to view plans (closes #999) - #1004

Merged
cristim merged 1 commit into
feat/multicloud-web-frontendfrom
fix/999-readonly-view-plans-v2
Jun 5, 2026
Merged

cristim merged 1 commit into
feat/multicloud-web-frontendfrom
fix/999-readonly-view-plans-v2

Conversation

@cristim

@cristim cristim commented Jun 5, 2026 •

Copy link
Copy Markdown
Member

Problem

A Read-Only user opening the Plans page sees Failed to load planned purchases: permission denied: requires view on purchases and no plans. Per role design, Read-Only users SHOULD be able to view existing plans.

Root cause

The Plans page loads its "Scheduled (Planned) Purchases" list via GET /api/purchases/planned (getPlannedPurchases), which gated on view:purchases. The Read-Only role (auth.DefaultReadOnlyPermissions) holds view:plans, view:recommendations, and view:history but NOT view:purchases, so the gate returned 403 and the page rendered nothing.

Fix

This endpoint serves plan-scheduled data, not purchase-execution data, so it now gates on view:plans (mirroring listPlans, which already uses view:plans).

Scope is deliberately narrow:

  • Per-plan account scoping is still enforced via isPlanAllowedCached.
  • The pause/resume/run/delete mutations keep their stronger update/execute/delete:purchases gates, so Read-Only users can VIEW planned purchases but still cannot MANAGE them.
  • getPurchaseDetails and other genuine purchase-execution reads keep view:purchases.

Tests

  • TestHandler_getPlannedPurchases_ReadOnlyCanView: a Read-Only session (view:plans yes, view:purchases no) lists planned purchases successfully. Verified to FAIL pre-fix (403 on the view:purchases gate) and PASS post-fix.
  • TestHandler_getPlannedPurchases_ReadOnlyCannotManage: the same session is denied pausing (update:purchases), proving the relaxed view gate does not widen management access.

go build ./... succeeds; go test ./internal/api/... is green (the two pre-existing internal/auth MFA login-message failures are unrelated and also fail on the base branch).

closes #999

Summary by CodeRabbit

  • Bug Fixes

    • Corrected permission requirements for accessing planned purchases; users with view:plans permission can now view the scheduled purchases list on the Plans page.
  • Tests

    • Added regression tests for planned purchases access control.

The Plans page loads its "Scheduled (Planned) Purchases" list via the
GET /api/purchases/planned endpoint (getPlannedPurchases), which gated on
view:purchases. A Read-Only user holds view:plans, view:recommendations,
and view:history but NOT view:purchases (see auth.DefaultReadOnlyPermissions),
so the page failed with "permission denied: requires view on purchases" and
rendered no plans.

This endpoint serves plan-scheduled data, not purchase-execution data, so it
should gate on view:plans (mirroring listPlans), not view:purchases. Per-plan
account scoping is still enforced via isPlanAllowedCached, and the
pause/resume/run/delete mutations keep their stronger update/execute/delete
:purchases gates, so Read-Only users can VIEW planned purchases but still
cannot MANAGE them.

Regression tests:
- TestHandler_getPlannedPurchases_ReadOnlyCanView: a Read-Only session
  (view:plans yes, view:purchases no) lists planned purchases successfully.
  Fails pre-fix (403 on the view:purchases gate), passes post-fix.
- TestHandler_getPlannedPurchases_ReadOnlyCannotManage: the same session is
  denied pausing (update:purchases), proving the relaxed view gate does not
  widen management access.
@cristim cristim added triaged Item has been triaged priority/p2 Backlog-worthy severity/medium Moderate harm urgency/this-sprint Within the current sprint impact/many Affects most users effort/s Hours type/bug Defect labels Jun 5, 2026
@coderabbitai

coderabbitai Bot commented Jun 5, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8e363cbb-3a78-4614-83d6-52530048e4ad

📥 Commits

Reviewing files that changed from the base of the PR and between 5f037eb and 3af3fe9.

📒 Files selected for processing (2)
  • internal/api/handler_purchases.go
  • internal/api/handler_purchases_test.go

📝 Walkthrough

Walkthrough

The PR fixes a permission gate in the getPlannedPurchases handler to allow Read-Only users to view the Plans page by checking view:plans instead of view:purchases, and adds two regression tests to verify read-only access works and that mutations remain restricted.

Changes

Access Control Fix for Planned Purchases

Layer / File(s) Summary
Permission gate update in getPlannedPurchases
internal/api/handler_purchases.go
Changed the permission check from view:purchases to view:plans and updated comments to reflect that this endpoint backs the Plans page list and should be accessible to Read-Only users with view:plans but not view:purchases.
Regression tests for read-only access control
internal/api/handler_purchases_test.go
Added TestHandler_getPlannedPurchases_ReadOnlyCanView to verify a read-only session with view:plans (but not view:purchases) can load the planned purchases list. Added TestHandler_getPlannedPurchases_ReadOnlyCannotManage to verify the same session cannot pause a planned purchase without update:purchases, failing with "permission denied" and not invoking TransitionExecutionStatus.

Possibly related PRs

  • LeanerCloud/CUDly#904: Modifies the same getPlannedPurchases handler logic regarding which execution statuses are returned and result ordering, with overlapping test changes.

Poem

🐰 A Read-Only bunny hops to Plans,
No longer blocked by purchase gates!
The view:plans key opens doors,
While mutations stay locked and safe—
Permission fixed with tests in place! 🔐


🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: allowing Read-Only users to view plans and references the resolved issue.
Linked Issues check ✅ Passed The PR fully addresses all acceptance criteria from issue #999: Read-Only users can now view plans, the endpoint gates on view:plans instead of view:purchases, and regression tests verify the fix.
Out of Scope Changes check ✅ Passed All changes are directly scoped to addressing issue #999: modifying getPlannedPurchases permission gating, adding related regression tests, and updating comments.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/999-readonly-view-plans-v2

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

@cristim

cristim commented Jun 5, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 5, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim
cristim merged commit 1220fe5 into feat/multicloud-web-frontend Jun 5, 2026
4 checks passed
@cristim
cristim deleted the fix/999-readonly-view-plans-v2 branch July 27, 2026 11:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/many Affects most users priority/p2 Backlog-worthy severity/medium Moderate harm triaged Item has been triaged type/bug Defect urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant