fix(api): scope valuations create/show/update by account-level access#1973
fix(api): scope valuations create/show/update by account-level access#1973galuis116 wants to merge 1 commit into
Conversation
`Api::V1::ValuationsController#create` resolved the target account through `current_resource_owner.family.accounts.find(...)`, and `#set_valuation` (used by `#show` + `#update`) resolved the entry through the bare family scope. Both ignored `Account.accessible_by` / `Account.writable_by`, so a family member with a `read_write` API key could write reconciliation valuations against unshared accounts and could read/mutate valuations on accounts shared with them as `read_only`. The `index` action already scopes via `accessible_by`, and the sister `Api::V1::TransactionsController` already applies the same pattern in `create` and `set_transaction`. - `create` now resolves the account through `writable_by(current_resource_owner)`; unauthorized accounts surface as the existing 404 via `RecordNotFound`. - `set_valuation` now constrains entries to `account_id IN accessible_by(user)`; unauthorized valuations surface as 404. - `update` gets a new `before_action :ensure_writable_entry` returning 403 when the loaded entry's account is not in `writable_by(user)` — so read-only shares remain readable via `show` but not mutable via `update`. Adds 7 Minitest cases covering all three bypass paths plus the read-only-share read-success / full-control-share write-success happy paths, leaning on the existing `family_admin` / `family_member` and `account_shares` fixtures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe PR fixes a security bug in the Valuations API controller where create, show, and update actions bypassed per-account share permissions. Account lookups are now scoped to writable accounts in create, entry lookups filter by accessible accounts in set_valuation, and update is guarded by a writable-access check returning 403 when access is denied. ChangesValuations API Account Access Enforcement
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
test/controllers/api/v1/valuations_controller_test.rb (1)
471-485: 💤 Low valueConsider adding explicit test for show on read-only shared account.
The update test for read-only accounts (line 451) implicitly proves that
showworks on read-only shares (sinceset_valuationsucceeds beforeensure_writable_entryreturns 403). However, an explicit positive test forshowon a read-only account would make the test suite more self-documenting and ensureaccessible_bybehavior is directly verified for the read case.🧪 Optional test to add
test "should allow show of valuation on a read-only-shared family account" do member_key = member_api_key(scopes: [ "read_write" ]) read_only_account = accounts(:credit_card) # shared :read_only with member entry = read_only_account.entries.valuations.first || read_only_account.entries.valuations.create!( date: 5.days.ago.to_date, amount: 500, currency: "USD", name: "Read-only valuation", entryable: Valuation.new(kind: "reconciliation") ) get api_v1_valuation_url(entry), headers: api_headers(member_key) assert_response :success end🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/controllers/api/v1/valuations_controller_test.rb` around lines 471 - 485, Add a new test that mirrors the existing "should allow show of valuation on a shared family account" but targets the read-only shared account fixture (use accounts(:credit_card) or the read-only fixture), create or reuse a valuations entry via read_only_account.entries.valuations.create! (date, amount, currency, name, entryable: Valuation.new(kind: "reconciliation")), call get api_v1_valuation_url(entry) with headers: api_headers(member_api_key(scopes: ["read_write"])), and assert_response :success to explicitly verify that Valuation#show is accessible for read-only shared accounts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@test/controllers/api/v1/valuations_controller_test.rb`:
- Around line 471-485: Add a new test that mirrors the existing "should allow
show of valuation on a shared family account" but targets the read-only shared
account fixture (use accounts(:credit_card) or the read-only fixture), create or
reuse a valuations entry via read_only_account.entries.valuations.create! (date,
amount, currency, name, entryable: Valuation.new(kind: "reconciliation")), call
get api_v1_valuation_url(entry) with headers: api_headers(member_api_key(scopes:
["read_write"])), and assert_response :success to explicitly verify that
Valuation#show is accessible for read-only shared accounts.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8a235643-0a4e-4cd3-a0c2-7da57151e184
📒 Files selected for processing (2)
app/controllers/api/v1/valuations_controller.rbtest/controllers/api/v1/valuations_controller_test.rb
Summary
Api::V1::ValuationsController#createresolved the target account throughcurrent_resource_owner.family.accounts.find(...), and#set_valuation(used by#show+#update) resolved the entry through the bare family scope. Both ignoredAccount.accessible_by/Account.writable_by, so a family member with aread_writeAPI key could write reconciliation valuations against unshared accounts and could read/mutate valuations on accounts shared with them asread_only. The#indexaction already scopes viaaccessible_by(line 15), and the sisterApi::V1::TransactionsControlleralready applies the same pattern increate(line 93) andset_transaction(line 209) — this is the missing alignment on the valuations resource.Fixes #1972.
Fix
createresolves the account throughfamily.accounts.writable_by(current_resource_owner).find(...). Unshared or read-only accounts surface asActiveRecord::RecordNotFoundand use the existing 404 rescue, avoiding existence-leak.set_valuationconstrains entries toaccount_id IN family.accounts.accessible_by(user).select(:id). Valuations on unshared accounts return 404.updategets a new privateensure_writable_entryregistered asbefore_action ... only: :update. It returns 403 when the loaded entry's account is not inwritable_by(user)— so read-only-shared accounts remain readable viashowbut not mutable viaupdate.Tests
Adds 7 Minitest cases at the end of
test/controllers/api/v1/valuations_controller_test.rb:createon an unshared account → 404, no row writtencreateon a read-only-shared account → 404, no row writtencreateon a full-control-shared account → 201, row written (happy path)showof a valuation on an unshared account → 404updateof a valuation on a read-only-shared account → 403, amount unchangedshowof a valuation on a full-control-shared account → 200 (happy path)A small
member_api_key(scopes:)helper builds a per-testread_writekey forfamily_member(Jakob) and clears its Redis rate-limit slot. The existingaccount_sharesfixtures (depository_shared_with_memberfull_control,credit_card_shared_with_memberread_only) provide the share matrix without needing new fixtures.Files changed
app/controllers/api/v1/valuations_controller.rb— addwritable_by/accessible_byscopes tocreate+set_valuation; addensure_writable_entrybefore_actionforupdate.test/controllers/api/v1/valuations_controller_test.rb— 7 new family-sharing scope tests + privatemember_api_keyhelper.CI / pre-PR checks
The standard CI gate from
CLAUDE.md(bin/rails test,bin/rubocop -f github -a,bin/brakeman --no-pager) is required to pass before merge; GitHub Actions will run these on push. The fix does not change any*.erbsoerb_linthas no impact, and no OpenAPI schema changes are introduced (only adds a 403/404 error response shape that mirrorsTransactionsController).[Allow edits from maintainers] is enabled on this PR.
Summary by CodeRabbit
Release Notes