Class: ActiveAdmin::Oidc::UserProvisioner
- Inherits:
-
Object
- Object
- ActiveAdmin::Oidc::UserProvisioner
- Defined in:
- lib/activeadmin/oidc/user_provisioner.rb
Overview
Finds-or-creates an AdminUser for an OIDC callback. Runs the host’s ‘on_login` hook (which owns all authorization decisions), then saves.
provisioner = UserProvisioner.new(config, claims: merged_claims, provider: "oidc")
admin_user = provisioner.call # raises ProvisioningError on denial
Strategy:
-
Look up by (provider, uid). If found → update.
-
Otherwise look up by the configured identity_attribute. If that row is already locked to a different (provider, uid) → refuse (account-takeover guard). Otherwise adopt it.
-
Otherwise build a new record.
-
Assign the identity attribute and oidc_raw_info.
-
Call config.on_login(admin_user, claims). Falsy → deny. Truthy →save and return.
The claims hash is passed through untouched except that ‘access_token` and `refresh_token` (if present) are never persisted.
Constant Summary collapse
- BLOCKED_RAW_INFO_KEYS =
Claim keys that must never land in oidc_raw_info.
%w[access_token refresh_token id_token].freeze
Instance Method Summary collapse
- #call ⇒ Object
-
#initialize(config, claims:, provider:) ⇒ UserProvisioner
constructor
A new instance of UserProvisioner.
Constructor Details
#initialize(config, claims:, provider:) ⇒ UserProvisioner
Returns a new instance of UserProvisioner.
28 29 30 31 32 |
# File 'lib/activeadmin/oidc/user_provisioner.rb', line 28 def initialize(config, claims:, provider:) @config = config @claims = claims.transform_keys(&:to_s) @provider = provider end |
Instance Method Details
#call ⇒ Object
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/activeadmin/oidc/user_provisioner.rb', line 34 def call validate_claims! admin_user = find_or_adopt_or_build # Retry path: a concurrent first sign-in inserted between our # initial miss-and-build and our failed save. Return the # winner's row verbatim — on_login already ran on our (now # discarded) in-memory build, and re-firing it would double # any host-side side effects (audit log, webhook, email). return admin_user if @retried assign_base_attributes(admin_user) allowed = invoke_on_login(admin_user) raise ProvisioningError, unless allowed # Devise's `active_for_authentication?` guard runs in the # controller post-sign-in, but by then we've already saved # the record. Hostile attempts where on_login flips an # inactivity flag (e.g. enabled=false) would otherwise leave # provisional rows in the DB on every try. Refuse before # persisting. Raise the dedicated InactiveError so the # controller can surface the model's I18n inactive_message # instead of the generic denial flash. unless admin_user.active_for_authentication? raise InactiveError, admin_user. end save!(admin_user) admin_user rescue RetryProvisioning # Concurrent JIT provisioning: another thread inserted first. # Re-run once — find_or_adopt_or_build will now find the record. retry end |