Class: Scryer::AuthorizationWatcher
- Inherits:
-
Object
- Object
- Scryer::AuthorizationWatcher
- Defined in:
- lib/scryer/authorization_watcher.rb
Overview
Runtime companion to the static idor/missing_authorization/
missing_policy_scope rules — those can only ever say "no call to a
known authorization method is visible anywhere in this controller's
source," which is exactly as wrong as it sounds whenever the real check
happens somewhere the static AST walk can't see (a shared base
controller, a concern, a class-level macro whose effect isn't visible by
name). This watcher answers a narrower but much more reliable question
instead: for this actual request, did Pundit's authorize/
policy_scope or CanCanCan's authorize! genuinely get called?
How: both libraries already track this themselves, for their own
verify_authorized/check_authorization after_action helpers —
Pundit::Authorization#pundit_policy_authorized?/#pundit_policy_scoped?
(public API, @_pundit_policy_authorized/@_pundit_policy_scoped under
the hood) and CanCanCan's @_authorized ivar (set by authorize! and by
skip_authorization_check; verified by reading both gems' actual source,
pundit-2.5.2/lib/pundit/authorization.rb and
cancancan-3.6.1/lib/cancan/controller_additions.rb — not guessed). This
class registers one more after_action, alongside those, that checks the
same flags and reports when a write action completed with neither set.
Deliberately Pundit/CanCanCan-only, same scope as the static rules this
complements: with neither gem loaded, enable! still runs but every
request is silently skipped (see authorization_library_present?) — an
app with fully custom, non-object-level authorization (a single
before_action :require_admin!, say) gets no findings and no false
positives here, rather than a flood of "unauthorized" reports for a
pattern this watcher has no way to recognize as intentional.
Deliberately narrower than "check every action": only create/update/
destroy (or any POST/PUT/PATCH/DELETE), matching MissingAuthorizationRule
exactly — and only requests that actually completed (status < 400).
Read-scoping gaps (an unscoped index — see MissingPolicyScopeRule) are
NOT covered here; verifying "was the returned data correctly scoped" at
runtime, rather than "was a method called," is a materially different
and harder check this class doesn't attempt.
Defined Under Namespace
Classes: Finding
Constant Summary collapse
- WRITE_ACTIONS =
%w[create update destroy].freeze
- WRITE_METHODS =
%w[POST PUT PATCH DELETE].freeze
Class Method Summary collapse
- .clear! ⇒ Object
-
.enable!(logger: nil) ⇒ Object
Turns the watcher on for the life of the process.
- .enabled? ⇒ Boolean
-
.findings ⇒ Object
Every finding recorded so far this process — inspect, log, or feed into your own alerting.
Class Method Details
.clear! ⇒ Object
87 88 89 |
# File 'lib/scryer/authorization_watcher.rb', line 87 def clear! @findings = [] end |
.enable!(logger: nil) ⇒ Object
Turns the watcher on for the life of the process. Idempotent — safe
to call more than once (later calls are no-ops). No Rack middleware
to install, unlike QueryWatcher: a Rails controller instance is
already fresh per request, so there's no shared/leaking state to
scope — the after_action below just runs once per completed
action.
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/scryer/authorization_watcher.rb', line 55 def enable!(logger: nil) return if @enabled @logger = logger || default_logger @findings = [] @enabled = true # Covers both API-only and normal Rails apps without special-casing # either: ActionController::Base and ActionController::API are # sibling classes (neither inherits from the other — verified via # `ActionController::API.ancestors.include?(ActionController::Base) # #=> false`), but Rails' own actionpack source calls # `ActiveSupport.run_load_hooks(:action_controller, self)` from # *both* action_controller/base.rb and action_controller/api.rb, so # this block runs once per base class and `install_hook` ends up # registering the after_action on both. Confirmed with a real # ActionController::API + Pundit integration test, not assumed. ActiveSupport.on_load(:action_controller) { Scryer::AuthorizationWatcher.send(:install_hook, self) } end |
.enabled? ⇒ Boolean
75 76 77 |
# File 'lib/scryer/authorization_watcher.rb', line 75 def enabled? !!@enabled end |
.findings ⇒ Object
Every finding recorded so far this process — inspect, log, or feed
into your own alerting. Not reset automatically; call clear!
yourself (e.g. between test examples, or on a timer) if you don't
want it growing for the life of a long-running process.
83 84 85 |
# File 'lib/scryer/authorization_watcher.rb', line 83 def findings @findings ||= [] end |