cancancan icon indicating copy to clipboard operation
cancancan copied to clipboard

accessible_by permanently modifies a rule's conditions via normalize_conditions, causing can? to fail

Open rockorequin opened this issue 11 months ago • 0 comments

The ConditionNormalizer in accessible_by permanently modifies a rule's conditions when you call accessible_by, meaning that a call to the can? method that worked before calling accessible_by may not work afterwards.

For example, say I have the models Client, Project, and Activity, where an activity belongs to a project and a project belongs to a client. In Ability, I can create a rule to say that a user can read any project or activity belonging to client with id 10:

can :read, [ Project, Activity ], client: { id: 10 }

This creates a rule with conditions: { client: { id: 10 } }.

Then if I run this code in the controller:

    @project = Project.first
    can? :read, @project
    @activities = Activity.accessible_by current_ability
    can? :read, @project

then the first call to can? runs without error. The call to Activity.accessible_by current_ability permanently changes the rule's conditions to { project: { client: { id: 10 } } } via a call to normalize_conditions, and the second identical call to can? then raises a NoMethodError saying "undefined method `project' for an instance of Project" because the new conditions make cancancan call @project.project.client instead of @project.client.

Is the rule illegal, ie should you only ever create a rule with an array of models that all have through reflections to the condition OR all have belongs_to reflections to the condition? Or should accessible_by not permanently change the conditions for the rule?

I'm using cancancan 3.6.1 and Rails 8.0.1.

rockorequin avatar Jan 20 '25 07:01 rockorequin