Set Current.organization for Grape API requests
What does this MR do and why?
The Current.organization availability documentation lists Grape API endpoints as an exception: Current.organization is not set automatically and every API class has to call set_current_organization in its own before block. This MR closes that gap.
It adds a global before_validation hook on API::API that resolves Current.organization for every Grape REST API request, following the same priority chain used by the Rails controllers concern: URL params, the X-GitLab-Organization-ID header, the authenticated user, then the default organization. The user lookup is deferred behind a safe, lazy resolver so requests using non-user tokens (deploy tokens, runner tokens, cluster agent tokens) do not trigger a 401.
The whole hook is gated by the set_current_organization_for_grape_api gitlab_com_derisk feature flag, defaulting to off:
- Flag off: behaviour is identical to master. Every per-endpoint
set_current_organization(...)call (inlib/api/groups.rb,lib/api/projects.rb, the ~46 other call sites, andlib/api/internal/base.rb) runs and sets the organization itself. - Flag on: the global hook resolves
Current.organization. The per-endpoint helper short-circuits to a no-op (except for endpoints that opt out viaskip_global_organization_setup!), which makes the explicit calls dead code that a follow-up MR can delete in bulk.
Endpoints that assign the organization from non-user state inside the endpoint body opt out via skip_global_organization_setup! on API::Base. Applied to:
API::Ci::Runner—authenticate_runner!resolves the runner's own organization.API::Internal::Kubernetes— KAS (GitLab Agent Server) endpoints; a follow-up will add agent-token resolution.EE::API::Scim::InstanceScim— assignsOrganizations::Organization.firstdirectly.API::Invitations— needs cross-organization invitee lookups, which the data-isolation scoping would otherwise hide.API::Internal::Base— authenticates via gitlab-shell shared secret;actor.useris not visible to the global hook'sfind_user_from_sourceschain.
Two collateral fixes were needed to make the global hook compatible with existing log/auth machinery:
ApplicationContext#include_client?is reordered to short-circuit onset_valuesbefore evaluating the existingclient_idlambda. Without this, callingCurrent.organization=while@current_useris unset transitively memoises the user lazy-attribute reader asnil, droppingmeta.userfrom request logs.- The hook's
Current.organization = ...is wrapped in arescue ::Current::OrganizationAlreadyAssignedErrorto handle specs that stubCurrent.organization_assignedto return false while the underlying attribute is in fact set.
It works for everything that resolves to a User through find_user_from_sources works. That covers:
- Personal access token (header, query, Bearer)
- OAuth bearer token
- CI job token (resolves to the job's user)
- Group/project access tokens (they are PATs on a project_bot user)
- Any other token type whose finder returns a User
What the hook does not resolve will be taken care of by follow-ups:
- Deploy token (DeployToken, not a User)
- Runner token (Ci::Runner)
- Cluster agent token (Clusters::AgentToken)
- gitlab-shell shared secret (internal API)
- Plain unauthenticated requests
The documentation under doc/development/organization/ will be updated after the feature flag is fully enabled and the explicit per-endpoint calls are deleted, so the docs do not contradict the code while the flag is rolling out.
References
- Closes #558544
- Supersedes earlier attempt !166614 (closed)
- Documentation exception that this addresses: https://docs.gitlab.com/development/organization/#where-currentorganization-is-available
Screenshots or screen recordings
Backend-only change; no UI to show.
How to set up and validate locally
In Rails console, enable the feature flag and create a new organization and user:
Feature.enable(:set_current_organization_for_grape_api)
my_org = Organizations::Organization.find_or_create_by!(path: 'my-org') { |org| org.name = 'My Org' }
# Create PAT
user = FactoryBot.create(:user, username: 'my-user', organization: my_org)
pat = FactoryBot.create(:personal_access_token, user: user)
puts pat.tokenQuit the console and set the PATH:
export PAT="<the token>"Hit a user-authenticated endpoint with a Personal Access Token and use log/api_json.log to confirm meta.organization_id is the user's organization:
curl -H "PRIVATE-TOKEN: ${PAT}" http://gdk.test:3000/api/v4/userSpec runs:
bundle exec rspec spec/lib/gitlab/current/organization_spec.rbbundle exec rspec spec/requests/api/current_organization_for_grape_spec.rbbundle exec rspec spec/requests/api/api_spec.rb spec/requests/api/internal/base_spec.rb spec/requests/api/internal/kubernetes_spec.rb spec/requests/api/ci/runner/
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.