Exempt auth controllers from read-only organization enforcement (MR A)
What does this MR do and why?
This is MR A of the Org Read-Only Mode authentication exemption work tracked in #602813 (closed).
MR B (block new-user-row creation in Gitlab::Auth::OAuth::User during read-only mode) is a separate follow-up MR. MR C (audit/block cascading auth-time writes into org-owned state, per ADR 010's "Cascading writes" rule) will be tracked as a follow-up on the same work item.
Problem
!241161 (merged) added enforce_read_only_organization as a before_action in ApplicationController. It blocks all POST/PATCH/PUT/DELETE requests when Current.organization.read_only? and the organization_read_only_enforcement feature flag is enabled.
Because authentication controllers inherit from ApplicationController, sign-in / SSO / token POSTs are currently blocked when an organization is read-only — which wrongly locks users out of obtaining a session to READ a read-only organization's data.
ADR 010 (Organization Read-Only Mode, section 'Authentication exemption') requires authentication endpoints to remain available during read-only.
Solution
Add skip_before_action :enforce_read_only_organization to the authentication controllers, mirroring how GraphqlController already does this. Skips are action-scoped (only:) wherever a controller mixes auth-plane and non-auth actions.
Controllers exempted:
| Controller | Scope | Reason |
|---|---|---|
SessionsController |
whole controller | Sign-in / sign-out — session row, audit event, last-used timestamp |
OmniauthCallbacksController |
whole controller | SSO/SAML/OmniAuth callbacks — completes the SSO handshake |
Ldap::OmniauthCallbacksController |
inherited | Inherits the skip from OmniauthCallbacksController |
Groups::OmniauthCallbacksController |
inherited | Inherits the skip from OmniauthCallbacksController |
JwtController |
whole controller | Container registry JWT auth |
PasswordsController |
:create, :update |
Password reset is required to complete sign-in for users who lost credentials |
ConfirmationsController |
:create |
Resending the confirmation email is part of completing authentication |
Users::TermsController |
:accept, :decline |
Terms acceptance is a hard gate wedged into the sign-in flow (enforce_terms!); blocking it locks users out entirely |
Users::BaseIdentityVerificationController (EE) |
:verify_email_code, :send_phone_verification_code, :verify_phone_verification_code |
Identity verification is a sign-in gate |
Groups::SsoController (EE) |
:saml only |
Render-only SSO sign-in landing page. #unlink is a real identity write and stays enforced (has a negative spec) |
SmartcardController (EE) |
:auth |
Smartcard authentication |
Controllers NOT exempted (and why):
| Controller | Reason |
|---|---|
Oauth::TokensController |
Inherits from Doorkeeper::ApplicationMetalController via Gitlab::BaseDoorkeeperController, NOT from ApplicationController — the before_action is never registered there, so no skip is needed |
Groups::SsoController#unlink (EE) |
Unlinking an identity is an org-owned write, not part of completing authentication — remains enforced |
Every exemption ships with a regression spec asserting the write action succeeds (no read-only flash/redirect) while the organization is read-only, and negative specs assert non-exempt actions remain enforced.
References
- Issue: #602813 (closed)
- Original enforcement MR: !241161 (merged)
- ADR 010: https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/organization/decisions/010_organization_read_only_mode/
Rollout safety
The organization_read_only_enforcement feature flag must remain disabled until MR B lands: with OmniauthCallbacksController exempted and no JIT-provisioning block yet, a first-time SSO/SAML sign-in against a read-only Organization could create a new users row on the source Cell, which ADR 010 explicitly forbids (orphan-row risk at cutover).
Screenshots or screen recordings
N/A — backend-only change, no UI impact.
| Before | After |
|---|---|
| N/A | N/A |
How to set up and validate locally
- Enable the feature flag in rails console:
Feature.enable(:organization_read_only_enforcement) - Put an organization into read-only mode:
org = Organizations::Organization.find(1) org.start_read_only(read_only_reason: 'migration') org.confirm_read_only - Attempt to sign in at
/users/sign_in— should succeed (not blocked by read-only enforcement). - Without this fix, the POST to
/users/sign_inwould return a 503 or redirect with a read-only flash message.
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.