cancancan icon indicating copy to clipboard operation
cancancan copied to clipboard

Support Rails's `attribute` in Cancancan's `permitted_attributes`

Open kalsan opened this issue 2 years ago • 1 comments

TLDR: Cancancan does not "see" non-database backed attributes, so they are missing in permitted_attributes.

Steps to reproduce

Rails 7.1.2 application with a basic user model with the database fields email and password_digest, with the important parts from the model being:

class User < ApplicationRecord

has_secure_password

With the god-ability:

class Ability
  include CanCan::Ability

  attr_reader :user

  def initialize(user)
    return if user.blank?

    can :manage, :all
  end
end

Now run: Ability.new(User.first).permitted_attributes(:edit, User.first)

Expected behavior

[:email, :password_digest, :password, :password_confirmation]

Actual behavior

[:email, :password_digest]

-> Cancancan misses out on the password attributes. The same problem can be reproduced by using Rail's attribute method, i.e.

class MyModel < ApplicationRecord
  attribute :foo # Cancancan will miss this
end

System configuration

Rails version: 7.1.2

Ruby version: 3.2.2

CanCanCan version 3.4.0

I guess this also interferes with other Gems such as active_type or any custom accessor logic using virtual fields using attribute.

I hope the examples above are useful to you. Let me know if I can help you enlighten this further.

Best, Kalsan

kalsan avatar Nov 30 '23 14:11 kalsan

I have created pull request #839 to address this. This pull request fixes the core issue where cancancan misses attribute :foo in general.

Rail's method has_secure_password is still missed, because for some reason, Rails does not declare an explicit attribute for password and password_confirmation. I don't think there is a simple way to detect those (and hardcoding a search for those two methods is likely a terrible idea), so I'd suggest mentionning in the documentation that for this particular case, cancancan can be brought to "see" the extra attributes by typing:

class User < ApplicationRecord
  has_secure_password
  attribute :password # perhaps, add additional arguments such as the type (string)
  attribute :password_confirmation # as above
end

Looking forward to hear what you think :-)

kalsan avatar Dec 01 '23 15:12 kalsan