Block new-user INSERT during organization read-only mode
What does this MR do and why?
Keeps SSO/SAML/JIT authentication available while an organization is in read_only_initialization or read_only state, but blocks creating a brand-new users row on that path, while existing-user sign-ins (timestamp updates) remain permitted.
Problem: ApplicationController's before_action :enforce_read_only_organization blocks all write requests (including the OAuth callback POST) when the organization is read-only. Authentication must stay available during read-only, but simply exempting the whole auth controller would leave the new-user INSERT path unguarded: a new users row created during read-only would live only on the source Cell, would not be part of the cutover snapshot, and would be lost when traffic moves to the destination Cell (ADR 010).
Fix: Two parts, both in this MR:
- Add
skip_before_action :enforce_read_only_organizationtoOmniauthCallbacksControllerso authentication stays available during read-only. - Add a narrower
new_user_blocked_by_read_only_organization?guard inGitlab::Auth::OAuth::User#savethat raisesNewUserOrganizationReadOnlyErrorwhen:- the user does not yet exist in the database (new record / INSERT path);
- the owning Organization is read-only and the
organization_read_only_enforcementflag is enabled (viaOrganizations::Organization#read_only_enforced?).
OmniauthCallbacksController#sign_in_user_flow rescues the new error and surfaces a read-only flash message to the user.
References
- Issue: #604050 (closed)
- Related MR !242344 (closed) (auth-controller read-only exemption): !242344 (closed)
- ADR 010 (Organization Read-Only Mode): https://gitlab.com/gitlab-com/content-sites/handbook/-/blob/main/content/handbook/engineering/architecture/design-documents/organization/decisions/010_organization_read_only_mode.md
- Enforcement concern:
app/controllers/concerns/enforces_read_only_organization.rb
Screenshots or screen recordings
N/A — backend-only change.
How to set up and validate locally
- Enable the feature flag for an organization in read-only state:
org = Organizations::Organization.find(<id>) org.start_read_only!(read_only_reason: 'migration') Feature.enable(:organization_read_only_enforcement, org) - Attempt to sign in via SSO/SAML with a new (non-existing) user account.
- Verify the sign-in is blocked with the read-only flash message.
- Attempt to sign in with an existing user — verify it succeeds.
Database review
This MR introduces a single new query in the sign-in path:
::Organizations::Organization.find_by_id(organization_id) in
Gitlab::Auth::OAuth::User#new_user_blocked_by_read_only_organization?.
It is a primary-key lookup on organizations.id, only executed on the
new-user (INSERT) branch of OAuth/SAML sign-in, and short-circuits before the
query when organization_id is absent.
Raw SQL:
SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT 1;Query plan (PK lookup via the implicit unique index organizations_pkey):
Index Scan using organizations_pkey on organizations (cost=0.28..8.30 rows=1 width=...)
Index Cond: (id = $1)The lookup uses the primary-key index, returns at most one row, and adds a single constant-time query to the new-user sign-in path only.
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.