简体   繁体   中英

Declarative Authorization with a permissions join table

Users have permission to manage articles for particular combinations of location and category.

For example Dave may be allowed to manage HR articles for Paris. Paul may be allowed to manage Business articles for London.

The models and their associations are as follows:

  • user has many permissions
  • permission belongs to user, location and category
  • category has many articles and permissions
  • location has many articles and permissions
  • article belongs to location and category

I can say:

has_permission_on :articles, :to => :manage do
  if_attribute :location => { :permissions => { :user_id => is {user.id} }, :category => { :permissions => { :user_id => is {user.id} } }
end

When I call permitted_to?(:delete) on an article record (whose category id is 1893 and location id is 2939), the following queries are run:

SELECT `categories`.*  FROM `categories`  WHERE `categories`.`id` = 1893 LIMIT 1
SELECT `permissions`.* FROM `permissions` WHERE (`permissions`.category_id = 1893)
SELECT `locations`.*   FROM `locations`   WHERE `locations`.`id` = 2939 LIMIT 1
SELECT `permissions`.* FROM `permissions` WHERE (`permissions`.location_id = 2939)

What I need to be run is really:

SELECT `permissions`.* FROM `permissions` WHERE (`permissions`.category_id = 1893 AND `permissions`.location_id = 2939)

Is there any way to achieve this?


Update

Ok, so I now have an instance method in the article model:

def permitted_user_ids
  Permission.select('user_id').where(:location_id => location_id, :category_id => category_id).map(&:user_id)
end

and my rule is now:

has_permission_on :articles, :to => :manage do
  if_attribute :permitted_user_ids => contains { user.id }
end

Now when I call permitted_to?(:delete) (or read/update/create/manage) on an article record whose category id is 1893 and location id is 2939, the following query is run:

SELECT user_id FROM `permissions` WHERE `permissions`.`category_id` = 1893 AND `permissions`.`location_id` = 2939

...which is exactly what I want.

Except, that the with_permissions_to scope is behaving very oddly.

Article.with_permissions_to(:read)

Now generates:

SELECT `articles`.* FROM `articles` WHERE (`articles`.`id` = 9473)

...where 9473 is the ID of the current user!

I am very confused.

You can use subclasses and manage the permissions with cancan . You can have a top class Article and then all the kind of articles can be subclasses, and in the subclasses you use cancan to manage their permissions.

This should be a comment, but I'm still at 45 :(

You say permission belongs to user , location and category . Thus, the permissions table would have user_id, location_id and category_id columns.

If that is true, then why are you declaring that category belongs_to permission and location belongs_to permission? Surely the relation should be has_many ?

You should use has_many in the model which DOES NOT have the foreign key(category and location), and belongs_to on the model WHICH DOES (permission).

Forgive me if all this is just a typo you made, and what you really meant was permission belongs_to user and has_many location and category.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM