-
Notifications
You must be signed in to change notification settings - Fork 399
Description
If you have an active record callback set up that will add roles to an instance of a resource that has not yet been persisted, Rolify will by default add the role on the class level, instead of just one instance, which could be disastrous. This one snuck up on me and I was scratching my head for a while so I thought I'd share. Here's a simple (non-comprehensive) example:
class User < ActiveRecord::Base
rolify
end
class Organization < ActiveRecord::Base
resourcify
belongs_to :parent_organization, class_name: 'Organization'
has_many :child_organizations, class_name: 'Organization', after_add: :cascade_roles_to_child
after_create :add_dummy_child
def add_dummy_child
return unless is_parent? && child_organizations.empty
child_organizations.build(name: 'Dummy') # Will trigger the after_add hook
end
def cascade_roles_to_child(child_org)
User.with_role(:admin, self).each do |user|
# child_org is not yet persisted!
# The next line will give users admin role on all organizations.
user.add_role(:admin, child_org)
end
endI'm aware this is not a common use case, but it's high risk and Rolify does not offer protection against accidental misuse. The cause of this is in Rolify::Role#add_role line 15 because it doesn't check if the resource is persisted or not before passing the instance id as nil. The only difference between class level and instance level role creation is an optional parameter in the RolesAdapter. Very easy to miss.
To protect against this in your application, prefer to call #create on relationships instead of #build, or assign an id before persistence (have not checked if this second part is supported).
NOTE: I have not tried to recreate this functionality using the Mongoid adapter.
The more you know.