Module: StandardId::AuthorizationBypass
- Defined in:
- lib/standard_id/authorization_bypass.rb
Constant Summary collapse
- FRAMEWORK_CALLBACKS =
{ action_policy: :verify_authorized, pundit: :verify_authorized, cancancan: :check_authorization }.freeze
- AFTER_ACTION_FRAMEWORKS =
Frameworks where the skip falls through to skip_after_action. ActionPolicy is NOT listed here because it is handled first via CLASS_METHOD_SKIP. Only :pundit reaches this branch.
%i[pundit].freeze
- CLASS_METHOD_SKIP =
ActionPolicy provides a dedicated class method to undo verify_authorized. Using skip_before_action or skip_after_action would silently do nothing for ActionPolicy because it manages the callback through its own DSL.
{ action_policy: :skip_verify_authorized }.freeze
Class Method Summary collapse
-
.applied? ⇒ Boolean
Whether apply has been called.
-
.apply(framework: nil, callback: nil) ⇒ Object
Skips the host app’s authorization callback on all engine controllers, and also skips authenticate_account! on public controllers (login, signup, callbacks, etc.) since those must be accessible without a session.
-
.apply_skips! ⇒ Object
Must remain public because it is invoked from a to_prepare lambda registered in apply, which executes outside this module’s scope.
-
.apply_to_controller(controller, policy) ⇒ Object
Apply skips to a single controller.
-
.reset! ⇒ Object
NOTE: This clears @callback_name and @framework (so applied? returns false and apply can be called again with a different framework) but intentionally does NOT clear @prepared, so no additional to_prepare block is registered.
Class Method Details
.applied? ⇒ Boolean
Whether apply has been called. Used by ControllerPolicy.register to decide if newly loaded controllers need an immediate authorization skip.
82 83 84 |
# File 'lib/standard_id/authorization_bypass.rb', line 82 def applied? MUTEX.synchronize { !@callback_name.nil? } end |
.apply(framework: nil, callback: nil) ⇒ Object
Skips the host app’s authorization callback on all engine controllers, and also skips authenticate_account! on public controllers (login, signup, callbacks, etc.) since those must be accessible without a session.
In production (eager_load=true), controllers are already loaded when this runs so the registry is populated. In development (eager_load=false), controllers are loaded lazily on first request; newly registered controllers receive skips immediately via apply_to_controller (called from ControllerPolicy.register). The to_prepare block handles class reloading — after Zeitwerk unloads/reloads classes, the freshly loaded controllers re-register and receive skips again.
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 70 71 72 73 74 75 76 77 78 |
# File 'lib/standard_id/authorization_bypass.rb', line 37 def apply(framework: nil, callback: nil) if framework && callback raise ArgumentError, "Provide framework: or callback:, not both" end register_prepare = false MUTEX.synchronize do # Guard against duplicate to_prepare registrations if called more than # once (e.g. in tests or misconfigured initializers). The skip methods # are idempotent so duplicates are harmless, but this keeps things tidy. return if @callback_name @callback_name = resolve_callback(framework, callback) @framework = framework&.to_sym # @prepared is intentionally NOT cleared by reset!. This ensures # at most one to_prepare block is registered per process lifetime. # Trade-off: after reset! + apply (e.g. in tests switching # frameworks), the to_prepare code path is not re-registered, so # it can only be verified by the first test that calls apply. register_prepare = !@prepared @prepared = true end apply_skips! # Re-apply after class reloading in development. In dev (eager_load=false), # reset_registry! + apply_skips! is effectively a no-op because the # registry is empty at this point — lazy-loaded controllers haven't # registered yet. The real work for lazy-loaded controllers is done by # apply_to_controller (called from ControllerPolicy.register). This # block is still needed because after a Zeitwerk reload, controllers # re-register and apply_to_controller fires again for each one, but the # reset_registry! here clears stale references to the old class objects # to prevent memory leaks in long dev sessions. return unless register_prepare Rails.application.config.to_prepare do StandardId::ControllerPolicy.reset_registry! StandardId::AuthorizationBypass.apply_skips! end end |
.apply_skips! ⇒ Object
Must remain public because it is invoked from a to_prepare lambda registered in apply, which executes outside this module’s scope.
105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/standard_id/authorization_bypass.rb', line 105 def apply_skips! # ControllerPolicy lives in app/controllers/concerns/ and is autoloaded # by Zeitwerk. When apply is called early (e.g. from a Rails initializer), # the constant may not be loaded yet. This is safe to skip — controllers # that register later will receive skips via apply_to_controller (called # from ControllerPolicy.register), and the to_prepare block re-runs # apply_skips! after class loading is complete. return unless defined?(StandardId::ControllerPolicy) StandardId::ControllerPolicy.registry_snapshot.each do |policy, controllers| controllers.each { |controller| apply_to_controller(controller, policy) } end end |
.apply_to_controller(controller, policy) ⇒ Object
Apply skips to a single controller. Called by ControllerPolicy.register when a controller is lazily loaded after apply has already been called.
88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/standard_id/authorization_bypass.rb', line 88 def apply_to_controller(controller, policy) callback, framework = MUTEX.synchronize { [@callback_name, @framework] } return unless callback (controller, callback, framework) if policy == :public # authenticate_account! is defined in WebAuthentication, not on API # controllers. raise: false ensures this is a safe no-op for API # controllers that don't have the callback. controller.skip_before_action :authenticate_account!, raise: false end end |
.reset! ⇒ Object
NOTE: This clears @callback_name and @framework (so applied? returns false and apply can be called again with a different framework) but intentionally does NOT clear @prepared, so no additional to_prepare block is registered.
124 125 126 127 128 129 |
# File 'lib/standard_id/authorization_bypass.rb', line 124 def reset! MUTEX.synchronize do @callback_name = nil @framework = nil end end |