Add stub login for development - #16
Open
senid231 wants to merge 4 commits into
Open
Conversation
OIDC-only admin panels are painful to develop against: the IdP has to know your local redirect URI, which breaks the moment you run on a different port or two apps share one client. Projects work around it with per-repo bypass controllers that skip the gem's whole provisioning path, so authorization is never exercised locally. Adds `stub_login_enabled` / `stub_login_claims`, which put a second button on the normal login page. Nothing is automatic: the page still renders and the user still clicks. The button POSTs to the gem's own callbacks controller, which runs the fabricated claims through the same UserProvisioner a real callback uses -- identity lookup, takeover guard, the host's on_login hook, oidc_raw_info and active_for_authentication? all still apply. The provider stays "oidc" rather than a separate stub value, so a stub row is still matched by a later real SSO login instead of tripping the takeover guard. Guarded three ways: boot raises when enabled under the production env, the route is not drawn when disabled, and the action re-checks both. Enabling it logs a warning at boot and renders a banner naming the identity the button signs in as. Also fixes the published login view templates, which used generator escaped `<%%=` while being copied verbatim -- a host that published the view got literal ERB tags rendered as page text.
There was a problem hiding this comment.
Pull request overview
Adds a guarded development-only stub login flow through the existing OIDC provisioning pipeline.
Changes:
- Adds configurable stub claims, UI, routes, callbacks, and production safeguards.
- Updates bundled and generated login templates, documentation, and tests.
- Bumps the version to 2.2.0.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated no comments.
Show a summary per file
| File | Summary |
|---|---|
spec/unit/configuration_spec.rb |
Tests stub configuration and validation. |
spec/requests/stub_login_spec.rb |
Tests stub-login behavior and safeguards. |
spec/isolated/features/isolated_stub_login_spec.rb |
Tests isolated-engine routing. |
spec/generators/install_generator_spec.rb |
Tests generated login templates. |
README.md |
Documents stub-login configuration and usage. |
lib/generators/active_admin/oidc/install/templates/sessions_new.html.erb |
Updates the generated legacy login view. |
lib/generators/active_admin/oidc/install/templates/sessions_new_v4.html.erb |
Updates the generated ActiveAdmin 4 login view. |
lib/generators/active_admin/oidc/install/templates/initializer.rb.tt |
Documents generated stub configuration. |
lib/activeadmin/oidc/version.rb |
Bumps the version to 2.2.0. |
lib/activeadmin/oidc/engine.rb |
Registers helpers, guards, and conditional routes. |
lib/activeadmin/oidc/configuration.rb |
Adds stub settings and claim validation. |
app/views/active_admin/devise/sessions/new.html.erb |
Adds stub-login UI to the bundled view. |
app/helpers/active_admin/oidc/view_helpers.rb |
Provides login and route helpers. |
app/controllers/active_admin/oidc/devise/omniauth_callbacks_controller.rb |
Handles stub provisioning and sign-in. |
Suppressed comments (5)
app/controllers/active_admin/oidc/devise/omniauth_callbacks_controller.rb:65
- This new stub endpoint calls the shared sign-in tail, whose
after_sign_in_path_forfalls back to the hardcoded/admin. In the documented isolated-engine setup, the added feature spec already has to avoid following this redirect because/adminis not routed there, so a user who clicks the stub button can be sent to a dead page instead of completing the login flow. Make the post-login destination route-aware for the mounted Devise/ActiveAdmin route set and assert the final location in the isolated integration spec.
provision_and_sign_in(claims)
app/helpers/active_admin/oidc/view_helpers.rb:54
- Once invalid claims are represented as
ConfigurationError, this view helper still lets that exception escape while rendering the login page, because it only handles the normal missing-identity case withpresence. A malformedstub_login_claimswould therefore make GET/admin/loginfail before the user can see the endpoint's configuration flash; rescue the configuration error here and returnnilso the existing(unconfigured identity)banner is rendered.
def activeadmin_oidc_stub_login_identity
claims = activeadmin_oidc_config.resolved_stub_login_claims
claims[activeadmin_oidc_config.identity_claim.to_s].presence
lib/activeadmin/oidc/configuration.rb:87
stub_login_claimsis documented as a Hash (or a callable returning one), but a non-Hash value such as a String reaches(raw || {}).to_hand raisesNoMethodError. The stub action only translatesConfigurationError, so a malformed configuration produces a 500 instead of the intended readable misconfiguration response. Validate the resolved value's type before callingto_hand raiseConfigurationErrorfor invalid values.
(raw || {}).to_h.transform_keys(&:to_s)
lib/activeadmin/oidc/configuration.rb:19
- The default stub subject (
"stub-uid") will normally differ from the real IdP subject for the same email.UserProvisionerfirst looks up(provider, uid)and then rejects an identity row that already has any provider/uid, so using the same"oidc"provider does not make the later real login match; it causes the takeover guard to reject it. Either require/configure a stable real subject for the stub, or add an explicit and security-reviewed transition for stub-created rows instead of relying on the provider value alone.
DEFAULT_STUB_LOGIN_CLAIMS = {
'sub' => 'stub-uid',
'email' => 'stub@example.com'
lib/activeadmin/oidc/engine.rb:159
enforce_stub_login_policy!only validates the claims here, so enabling stub login withon_login = nil(or another non-callable value) still boots even thoughConfiguration#validate!explicitly requires that hook. The first stub POST then reaches@config.on_login.calland raisesNoMethodError/500 instead of failing at boot as the configuration contract implies; runcfg.validate!in this guard before the stub-claims validation.
cfg.validate_stub_login! unless cfg.stub_login_claims.respond_to?(:call)
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Replace the three stub_login_* config accessors with one method:
c.stub_dev_env_login! do |claims|
claims.merge("groups" => [ADMIN_GROUP])
end
The method is a no-op outside the development environment, so the boot
guard, the production checks and the validators it needed all go away.
The login page keeps its single button; config.login_submit_path points
it at the stub route while stub login is on.
Removed:
* app/helpers/active_admin/oidc/view_helpers.rb -- the views read the
config directly, as they did before stub login existed. This also
fixes a 500 on the generator-published view in hosts whose AdminUser
lacks :omniauthable, where the helpers were never registered.
* Engine.enforce_stub_login_policy! and its after_initialize.
* Configuration#validate_stub_login! and #sso_configured?.
* The validate! patch, restoring main's behaviour.
* Isolated-engine support for the stub URL, now a documented limit.
Fixed:
* Claims are stringified all the way down, on a copy, so a nested
symbol key no longer breaks stub/real parity and a mutating block
cannot write back into the defaults.
* The claims block may return a Hash or mutate the one it is given.
* Stub sign-in gets its own flash instead of claiming OIDC succeeded.
* The route reads login_path at draw time, and draws from
login_submit_path so the button and the route cannot drift apart.
Co-Authored-By: Clanker
`login_path` and `logout_path` were read inside the route append block. On ActiveAdmin 4 routes are drawn lazily on the first request, so the read happened long after the host initializer that set them -- and any later `ActiveAdmin::Oidc.reset!` (the spec suite does this per example) replaced the Configuration, so the block saw the defaults. The isolated engine, which sets an engine-relative `login_path = "/login"`, drew `/admin/login` instead and 404ed. Capture both paths at after_initialize time, as before stub login. The stub flag and `login_submit_path` stay a draw-time read, but through `ActiveAdmin::Oidc.config` rather than a captured instance, because `reset!` swaps the whole object and a redraw must see the current one. Co-Authored-By: Clanker
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Developing against an OIDC-only admin panel is painful: the IdP must know your local redirect URI, which breaks on a different port or when two apps share one client. Projects work around it with per-repo bypass controllers that skip the gem's provisioning path, so authorization is never exercised locally.
Adds one config method:
The login page renders as always — same button, same label — but the button signs in with locally fabricated claims instead of redirecting to the IdP. A red warning sits above it while it is on. The block is optional and runs per sign-in, so it can return a different identity each time.
It is not a separate code path. The button POSTs to the gem's own callbacks controller, which hands the claims to the same
UserProvisionera real callback uses — identity lookup, the takeover guard, youron_loginhook,oidc_raw_infoandactive_for_authentication?all still run.providerstays"oidc", so a stub row is an ordinary OIDC row that a later real SSO login still matches.Safety is the method itself:
stub_dev_env_login!is a no-op outside the development environment and the route is not drawn there. Nothing to flip off before a deploy, no boot guard to trip.Also fixes the published login view templates, which used generator-escaped
<%%=while being copied verbatim — a host that published the view got literal ERB tags rendered as page text.