Class: RuboCop::Cop::DevDoc::Auth::RolePredicateOutsidePolicy
- Inherits:
-
Base
- Object
- Base
- RuboCop::Cop::DevDoc::Auth::RolePredicateOutsidePolicy
- Defined in:
- lib/rubocop/cop/dev_doc/auth/role_predicate_outside_policy.rb
Overview
Role predicates must not be called outside the policy layer.
Rationale
A role predicate (admin?, super_admin?,
effective_admin_of_organization?, ...) answers "what may this
person do?" — an authorization question. Scattering those calls
through controllers and views fragments the permission model: the
Pundit policy says one thing, page code quietly decides another.
This cop is the receiver-agnostic companion to
DevDoc/Auth/CurrentUserBranching: that cop tracks current_user
and its same-file aliases, but taint cannot follow an ivar from a
controller into a view, and any wrapper method breaks the chain.
Keying on the PREDICATE NAME instead means current_user.admin?,
@user.admin?, user.admin?, and viewer.admin? are all equally
visible — no alias or wrapper hides the question being asked.
Remedy
Move the decision into the Pundit policy and query it where needed:
policy(record).manage? — no role predicate at the call site.
Name the policy query after the DECISION it guards (auto_approve?,
initiation_blocked_for_admin?), never after the role: a generic
getter (user_is_admin?) re-exports the scattering through the
policy door — every call site still maps role to behavior itself.
Genuine display logic keyed on a role (badges, admin-only hints)
belongs in a helper (an excluded path), where the branching is
openly presentation-side.
The ratchet
Once page code queries policies by purpose, add any role-shaped
getters your policies DO export (user_is_super_admin?, ...) to
RolePredicates. Policies are excluded paths, so policy-internal use
stays free while new loose call sites in page code flag mechanically.
Configuration
RolePredicates is a closed, per-project list — add every role
predicate your User/Member models define:
DevDoc/Auth/RolePredicateOutsidePolicy:
RolePredicates:
- admin?
- super_admin?
- effective_admin_of_organization?
- explicit_admin_of_organization?
Allowed paths (Exclude:)
By default the cop is silent in:
app/policies/**/*.rb ← where the predicates belong
app/models/**/*.rb ← where they are defined/composed
app/helpers/**/*.rb ← sanctioned display branching
app/views/layouts/**/* ← sanctioned display branching
test/, spec/ ← tests assert role facts as fixture
preconditions; they verify the permission
model rather than fragment it
Constant Summary collapse
- MSG =
'`%<method>s` is a role predicate — an authorization question that belongs in ' \ 'the Pundit policy. Move the DECISION there and query it by purpose ' \ '(`policy(record).auto_approve?`) — name the query after the decision it ' \ 'guards, never after the role (`user_is_admin?` just re-exports the ' \ 'scattering through the policy door, and belongs in this cop\'s ' \ 'RolePredicates list). For pure display branching (badges, admin hints), ' \ 'use a helper. Renaming the receiver never exempts it: the flagged thing ' \ 'is the question, not who is asked.'.freeze
Instance Method Summary collapse
- #on_send(node) ⇒ Object (also: #on_csend)
Instance Method Details
#on_send(node) ⇒ Object Also known as: on_csend
85 86 87 88 89 90 |
# File 'lib/rubocop/cop/dev_doc/auth/role_predicate_outside_policy.rb', line 85 def on_send(node) method_name = node.method_name return unless role_predicates.include?(method_name) add_offense(node.loc.selector, message: format(MSG, method: method_name)) end |