Skip to content

fix(api): resolve analytics and inventory accounts by provider and external id - #622

Merged
cristim merged 2 commits into
mainfrom
fix/analytics-inventory-provider-qualified-accounts
Oct 6, 2026
Merged

cristim merged 2 commits into
mainfrom
fix/analytics-inventory-provider-qualified-accounts

Conversation

@cristim

@cristim cristim commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Refs #520 (checklist item 3, non-#433 part). Does not touch AccountScope.Allows or name matching (#433).

Problem

External ids are unique only per (provider, external_id). Two places still resolved accounts by external id alone (the resolveAccountNamesByID map, last listed account wins), the same class as #593:

  • Analytics (validateAnalyticsAccountScope, used by /api/history/analytics, /api/history/breakdown, /api/analytics/trends): a raw account_id shared by an aws and an azure account was allowed or refused depending on account list order, and an allowed request queried the provider-unconstrained bucket, so rows of the other provider's account came back.
  • Inventory (listActiveCommitments): the row's account name came from the same map, so a row showed another account's name (including a tenant's name the caller cannot see). Row filtering was already provider-qualified by fix(api): match history rows to scoped accounts by provider and external id #593.

Change

  • Analytics: a registered UUID is authoritative; otherwise a raw external id is attributed only when exactly one account has it. A shared (cross-provider or same-provider) id is refused with 404, in either list order.
  • Inventory: name via historyRowAccountName (CloudAccountID, UUID in AccountID, provider plus external id, unique legacy match).
  • Test fixture in handler_inventory_test.go now sets Provider on its accounts (the lookup is provider-qualified).

Tests

New HandleRequest tests, both list orders, fail on main and pass here: shared id across providers (name scope, id scope, unrestricted), UUID rows, unique raw id unchanged, same-provider ambiguity fails closed. Mutations that fail them: take the first match on ambiguity; drop provider qualification; revert to the external-id-only name map. go test -race -short ./..., go vet ./..., go vet -tags integration ./internal/... pass.

Not in this PR

A scoped user passing a raw external id shared by two accounts now gets 404 instead of an order-dependent answer; the UI sends UUIDs. The other resolveAccountNamesByID callers key by UUID only and are unaffected (see the issue checklist).

Behavior change

  • A ListCloudAccounts failure in the analytics scope check (scoped users) and in inventory now returns an error. Before, analytics silently fell back to literal-id matching and inventory left the name blank.
  • A single registered raw-id account returns 404 once a second account with the same external id is registered. Shared, out-of-scope and nonexistent ids all return the same 404, so there is no oracle. The UI sends chip UUIDs (frontend/src/inventory.ts, dashboard.ts) and is unaffected. A scope entry that is the literal shared external id (scope ["123"], id "123") went from 200 to 404: intended fail-closed.

Known remaining exposure (analytics only), tracked as #624

QueryHistory, QueryBreakdown and the trends queries take the {"": {ext}} bucket directly (account_id = ANY, no provider). A scoped user's unique registered raw id can also return rows of another provider's UNREGISTERED account with the same id (for example an AWS 12-digit id and a GCP project number). Registered accounts cannot collide. Dashboard, inventory and history are not exposed (intersectAccountFilterScope and intersectDualColumnScope re-group "" under the allowed provider). Follow-up: resolve the filter to {match.Provider: {id}} or intersect it like the other paths, handling legacy rows with an empty provider deliberately.

…ternal id

Analytics scope checks and the inventory account name read a name map keyed by external id alone, so two accounts of different providers sharing an external id made the result depend on account list order: a raw account_id was allowed or refused by whichever account was listed last and then queried every provider's rows, and inventory rows showed another account's name. Analytics now attributes a raw external id only when exactly one account has it and refuses an ambiguous one; inventory resolves the name with the provider-qualified helper History uses (UUID, then provider plus external id, then a unique legacy match). Does not change AccountScope.Allows or name matching (#433).

Refs #520
@cristim cristim added priority/p2 Backlog-worthy severity/medium Moderate harm effort/l Weeks triaged Item has been triaged urgency/this-quarter Within the quarter impact/few Limited audience type/security Security finding labels Oct 6, 2026
@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

This review includes 5 billable files and costs up to $1.25.

  • Ask an admin to make reviews automatic

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Or wait 27 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available. Your 67 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: LeanerCloud/cloud-commitments-platform/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 2e4c1b49-c915-469e-a2fc-068649563bbf
📥 Commits

Reviewing files that changed from the base of the PR and between 2b86ef1 and 387c180.

📒 Files selected for processing (5)
  • internal/api/handler_analytics.go
  • internal/api/handler_analytics_scope_provider_test.go
  • internal/api/handler_inventory.go
  • internal/api/handler_inventory_scope_provider_test.go
  • internal/api/handler_inventory_test.go
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

Analytics scope checks and the inventory handler ignored a ListCloudAccounts error. Return it instead, and cover the unregistered raw id branch.

Refs #520
@cristim
cristim merged commit 39c319b into main Oct 6, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/l Weeks impact/few Limited audience priority/p2 Backlog-worthy severity/medium Moderate harm triaged Item has been triaged type/security Security finding urgency/this-quarter Within the quarter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant