Module: Karst::Identity
- Defined in:
- lib/karst/identity.rb,
lib/karst/identity/devise_support.rb,
lib/karst/identity/warden_adapter.rb
Overview
Framework-neutral identity seam for controlled probes. It deliberately does not discover or enumerate principals; callers own that policy.
For a conventional single-model Devise application, Karst can also infer everything below automatically from Devise's own routing metadata and Warden's public runtime API -- see DeviseSupport and WardenAdapter. Explicit configuration (config.principals/config.principal_sources, config.assume_identity/config.clear_identity, config.assume_browser_identity/config.clear_browser_identity) always overrides inference; inference never partially combines with explicit configuration for the same seam. rubocop:disable Metrics/ModuleLength
Defined Under Namespace
Modules: DeviseSupport Classes: ConfigurationError, Error, Unavailable, WardenAdapter
Constant Summary collapse
- PrincipalDescriptor =
authentication_* is presentation-only evidence. Machine serializers deliberately ignore it; it exists so local human interfaces can be useful without broadening JSON/MCP disclosure.
Value.define(:model_name, :id, :display_label, :authentication_key, :authentication_identifier)
- SetupState =
Compact, inspectable report of why Karst's zero-config Devise/Warden path is or isn't active.
statusis one of::ready_automatic -- principal source, probe identity, and browser identity are all inferred; no configuration required. :ready_mixed -- at least one of principal source / probe identity / browser identity is explicitly configured and the rest are safely inferred (e.g. an explicit config.principals selecting one of several Devise models). :ready_explicit -- principal source, probe identity, and browser identity are all explicitly configured. :ambiguous -- more than one Devise model was detected and no explicit config.principals/principal_sources selects one. :unavailable -- Karst could not identify enough of an authentication integration to run the primary workflow without explicit configuration.messageis nil whenever the caller's own local hint text already says everything Karst can usefully add (the two ready states, and :unavailable with no principal source at all -- every hint call site already has its own "nothing is configured" wording for that). It is populated only when Karst has something more specific to say: which Devise models are ambiguous, or that a principal source exists but probe/browser identity still couldn't be wired up automatically. Value.define(:status, :message)
Class Method Summary collapse
-
.assume_browser(request, principal) ⇒ Object
Returns the Devise/Warden scope this browser identity was actually assumed under (nil for explicit hooks, or for a non-Devise bare Warden proxy) -- see Karst::Web::BrowserIdentity, which retains it for the lifetime of the browser session so #clear_browser below never has to guess which of several selected sources produced the principal being cleared.
- .browser_supported? ⇒ Boolean
- .clear(session) ⇒ Object
-
.clear_browser(request, scope: nil) ⇒ Object
scope, when given, is the exact scope #assume_browser returned for the identity actually being cleared (see Karst::Web::BrowserIdentity) -- used in preference to #scope_for_effective_source, which cannot always determine one on its own with no principal in hand and several selected sources. - .describe(principal) ⇒ Object
-
.principal_sources ⇒ Object
The effective, normalized principal population(s): a Hash of Symbol => Karst::Access::PrincipalSource, covering an explicit config.principal_sources, a bare config.principals (wrapped as one implicit :default source), and -- when neither is configured -- one inferred Devise model (see Karst::Configuration#principal_sources).
- .principals ⇒ Object
-
.resolve(model_name:, id:) ⇒ Object
Resolves only principals exposed by a configured source.
-
.setup_state ⇒ Object
See SetupState above.
- .with(session, principal) ⇒ Object
Class Method Details
.assume_browser(request, principal) ⇒ Object
Returns the Devise/Warden scope this browser identity was actually assumed under (nil for explicit hooks, or for a non-Devise bare Warden proxy) -- see Karst::Web::BrowserIdentity, which retains it for the lifetime of the browser session so #clear_browser below never has to guess which of several selected sources produced the principal being cleared.
167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/karst/identity.rb', line 167 def assume_browser(request, principal) raise Unavailable, "browser identity hooks are not configured" unless browser_supported? if explicit_browser_hooks? Karst.config.assume_browser_identity.call(request, principal) nil else warden_adapter = inferred_adapter(principal) warden_adapter.assume(request, principal) warden_adapter.scope end end |
.browser_supported? ⇒ Boolean
157 158 159 |
# File 'lib/karst/identity.rb', line 157 def browser_supported? explicit_browser_hooks? || automatic_browser_identity_available? end |
.clear(session) ⇒ Object
116 117 118 |
# File 'lib/karst/identity.rb', line 116 def clear(session) adapter.clear(session) end |
.clear_browser(request, scope: nil) ⇒ Object
scope, when given, is the exact scope #assume_browser returned for
the identity actually being cleared (see
Karst::Web::BrowserIdentity) -- used in preference to
#scope_for_effective_source, which cannot always determine one on
its own with no principal in hand and several selected sources. Karst
still refuses to guess when neither is available.
186 187 188 189 190 191 192 193 194 |
# File 'lib/karst/identity.rb', line 186 def clear_browser(request, scope: nil) raise Unavailable, "browser identity hooks are not configured" unless browser_supported? if explicit_browser_hooks? Karst.config.clear_browser_identity.call(request) else inferred_adapter(nil, scope: scope).clear(request) end end |
.describe(principal) ⇒ Object
120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/karst/identity.rb', line 120 def describe(principal) model_name = model_name_for(principal) id = id_for(principal) label_hook = Karst.config.principal_label if label_hook && !label_hook.respond_to?(:call) raise ConfigurationError, "config.principal_label must be callable" end label, key, identifier = label_attributes(principal, model_name, id, label_hook) PrincipalDescriptor.new(model_name: model_name, id: id, display_label: label, authentication_key: key, authentication_identifier: identifier) end |
.principal_sources ⇒ Object
The effective, normalized principal population(s): a Hash of Symbol => Karst::Access::PrincipalSource, covering an explicit config.principal_sources, a bare config.principals (wrapped as one implicit :default source), and -- when neither is configured -- one inferred Devise model (see Karst::Configuration#principal_sources). Every multi-source-aware caller (Identity.resolve, Access::PrincipalSelection, the panel) reads this instead of config.principals directly.
98 99 100 101 102 103 |
# File 'lib/karst/identity.rb', line 98 def principal_sources sources = Karst.config.principal_sources raise Unavailable, "no principal source is configured" unless sources sources end |
.principals ⇒ Object
80 81 82 83 84 85 86 87 88 |
# File 'lib/karst/identity.rb', line 80 def principals source = Karst.config.principals return called_principal_source(source) if source inferred = DeviseSupport.unambiguous_mapping return inferred.model.all if inferred raise Unavailable, "no principal source is configured" end |
.resolve(model_name:, id:) ⇒ Object
Resolves only principals exposed by a configured source. In particular, this never constantizes a submitted model name or performs an unrestricted model lookup.
Tries each configured Karst::Access::PrincipalSource in order and returns the first match; a model name that does not belong to any configured source resolves nothing, without ever touching that source's records. For an Active Record relation/class source, a matching model name resolves through a scoped primary-key query against that exact relation instead of enumerating it -- a source may cover hundreds of thousands of rows, and this must stay a single bounded query regardless of table size. The relation's own WHERE clauses (tenant scoping, soft deletes, and so on) still apply, so a principal outside a configured relation is never resolved. A generic Enumerable source (no scoped-query capability) keeps the original enumerate-and-compare behavior, bounded to that one source.
149 150 151 152 153 154 155 |
# File 'lib/karst/identity.rb', line 149 def resolve(model_name:, id:) principal_sources.each_value do |source| resolved = resolve_within_source(source, model_name: model_name, id: id) return resolved if resolved end nil end |
.setup_state ⇒ Object
See SetupState above. Cheap and side-effect free: touches only already-established configuration/metadata plus, at most, calling a configured principals/principal_sources callable the same way the panel already does on every render to type-check its result (see Access::PrincipalSampler.representative_capable?) -- never to enumerate or query it.
202 203 204 205 206 207 208 |
# File 'lib/karst/identity.rb', line 202 def setup_state return SetupState.new(status: :ambiguous, message: ) if ambiguous_principal_source? return SetupState.new(status: :unavailable, message: nil) unless principal_source_ready? return SetupState.new(status: :unavailable, message: ) unless identity_channels_ready? SetupState.new(status: ready_status, message: nil) end |
.with(session, principal) ⇒ Object
105 106 107 108 109 110 111 112 113 114 |
# File 'lib/karst/identity.rb', line 105 def with(session, principal) active_adapter = adapter(principal) # The hook may establish identity and then raise, so cleanup becomes # mandatory before invoking it rather than only after it returns. assumed = true active_adapter.assume(session, principal) yield ensure active_adapter.clear(session) if active_adapter && assumed end |