Check organization membership, not the home organization

What does this MR do and why?

The grant, revoke and lookup services rejected every request scoped to a real organization, because they compared the organization in the request against the organization that owns the caller's account.

A user is owned by exactly one organization, but can be a member of several. At launch everyone stays owned by the default organization and gains an organization_users row for the organizations their groups move into (confirmation from the Organizations team). So the two values differ for every promoted organization, which is every closed beta customer. Reported in artifact-registry#977.

Stacked on !252511 (merged), which makes the organization an explicit input to token issuance. This MR targets that branch, so the diff shows only the check changes. Once !252511 (merged) merges this can be retargeted to master.

The caller check

caller_in_organization? now reads membership. All three services use it.

Ownership is deliberately not required. IAM's AuthorizeWrite accepts either an organization owner or a caller holding the right grant, so demanding ownership in Rails would reject grant holders before IAM ever decides. The same check also guards lookup, so requiring ownership would make reads owner-only too.

Because !252511 (merged) already passes the request organization to the token, the token follows automatically once this check stops forcing the two to be equal. A spec pins that.

The assignee check, and why grant and revoke now differ

Grant requires the assignee to be a member of the target organization. Resolved as one query for the whole request through a new Organizations::OrganizationUser.member_ids_among, rather than a lookup per assignment, since a bulk grant carries up to MAX_ARRAY_SIZE of them.

Revoke keeps the home-organization rule unchanged. Requiring membership there would make a role unrevokable the moment someone is removed from the organization, which is exactly when it has to go. Their tuples keep granting access, because AR validates tokens locally and never calls back to Rails, so deleting the tuple is the only thing that cuts access. That is the case described in #610042, and revoke's real rule belongs with that offboarding work. So the two checks separate rather than share one method.

How to set up and validate locally

bundle exec rspec \
  ee/spec/services/authz/artifact_registry/ \
  spec/models/organizations/organization_user_spec.rb

New coverage for a caller who is a member of an organization without being owned by it, on grant and lookup, for an assignee who is not a member, and for revoke still working when the assignee is not a member of the target organization. I checked the three new grant cases fail against the old home-organization check.

What this does not cover

Package clients. POST /api/v4/token_exchange still uses the caller's own organization, because Maven and npm cannot express one and the request header that could carry it has no membership check. That is being worked out in artifact-registry#977.

It also unblocks !250806 (merged), which currently skips the owner role sync for any owner whose account is homed elsewhere.

Merge request reports

Loading
Loading