marc
marc

Reputation: 151

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:

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.

Upvotes: 4

Views: 307

Answers (2)

abject_error
abject_error

Reputation: 2938

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.

Upvotes: 0

Paulo Fidalgo
Paulo Fidalgo

Reputation: 22331

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.

Upvotes: 0

Related Questions