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 (in lib/api/groups.rb, lib/api/projects.rb, the ~46 other call sites, and lib/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 via skip_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::Runnerauthenticate_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 — assigns Organizations::Organization.first directly.
  • 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.user is not visible to the global hook's find_user_from_sources chain.

Two collateral fixes were needed to make the global hook compatible with existing log/auth machinery:

  • ApplicationContext#include_client? is reordered to short-circuit on set_values before evaluating the existing client_id lambda. Without this, calling Current.organization= while @current_user is unset transitively memoises the user lazy-attribute reader as nil, dropping meta.user from request logs.
  • The hook's Current.organization = ... is wrapped in a rescue ::Current::OrganizationAlreadyAssignedError to handle specs that stub Current.organization_assigned to 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

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.token

Quit 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/user

Spec runs:

  • bundle exec rspec spec/lib/gitlab/current/organization_spec.rb
  • bundle exec rspec spec/requests/api/current_organization_for_grape_spec.rb
  • bundle 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.

Edited by Rutger Wessels

Merge request reports

Loading