From 22fe2e2a7ec1713e6199755e9b027e60f847c388 Mon Sep 17 00:00:00 2001 From: Tobias Ludwig Maier Date: Sat, 2 Aug 2025 10:10:10 +0200 Subject: [PATCH] Ensure permitted_attributes respects cannot rules The `permitted_attributes` method was not correctly handling cases where an action was denied by a `cannot` rule or by default. It would still return a list of attributes from any `can` rules defined for the model, instead of an empty array. This commit adds a guard clause to check `can?(action, subject)` at the beginning of the `permitted_attributes` method. If the action is not allowed, it now correctly returns an empty array, respecting the principle of default-deny. This ensures that attribute sanitization is consistent with the user's defined abilities. --- lib/cancan/ability/strong_parameter_support.rb | 2 ++ spec/cancan/ability_spec.rb | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/lib/cancan/ability/strong_parameter_support.rb b/lib/cancan/ability/strong_parameter_support.rb index 892d250a..2d3d6190 100644 --- a/lib/cancan/ability/strong_parameter_support.rb +++ b/lib/cancan/ability/strong_parameter_support.rb @@ -10,6 +10,8 @@ module StrongParameterSupport # they are added. The 'reverse' is so that attributes will be added before the # 'cannot' rules remove them. def permitted_attributes(action, subject) + return [] unless can?(action, subject) + relevant_rules(action, subject) .reverse .select { |rule| rule.matches_conditions? action, subject } diff --git a/spec/cancan/ability_spec.rb b/spec/cancan/ability_spec.rb index 3951f3b2..be7678fc 100644 --- a/spec/cancan/ability_spec.rb +++ b/spec/cancan/ability_spec.rb @@ -665,6 +665,16 @@ def active? expect(@ability.can?(:update, Range, :name)).to be(true) end + it 'returns an empty array for permitted_attributes when no ability is defined' do + expect(@ability.permitted_attributes(:update, NamedUser)).to eq([]) + end + + it 'returns an empty array for permitted_attributes if the action is explicitly forbidden' do + @ability.can :update, NamedUser + @ability.cannot :update, NamedUser # explicitly deny + expect(@ability.permitted_attributes(:update, NamedUser)).to eq([]) + end + it 'returns an array of permitted attributes for a given action and subject' do @ability.can :read, NamedUser @ability.can :read, Array, :special