Benjamin Tan Wei Hao
Benjamin Tan Wei Hao

Reputation: 9701

Cancan authorisation for a specific model

Here is what I want to do:

A User assigned an admin role cannot update the role of a User with the god role. So here's the code I came up with:

  if user.has_role? 'admin'
    can :manage, :all
    cannot :update, User do |resource|
      resource.has_role?('god')
    end
  end

However, resource seems to always return nil.

Any ideas?

Upvotes: 0

Views: 91

Answers (1)

kik
kik

Reputation: 7937

Expected behavior

In order to be able to use the block syntax for #can and #cannot, you have to first load the resource in your controller. As doc says :

The block is only evaluated when an actual instance object is present. It is not evaluated when checking permissions on the class (such as in the index action). This means any conditions which are not dependent on the object attributes should be moved outside of the block.

For this instance object to be present, you need to let cancan load it:

class UsersController < ApplicationController
  load_and_authorize_resource
end

This will make @user available both in your controller and in your block passed to #cannot. Now there's a catch.

... which breaks on rails-4 (as of 10/2/2013)

CanCan has a problem with rails-4 : it does not handle StrongParameters. This causes automatic resource loading to crash on #create and #update actions. This is a running issue and Ryan promised a solution for cancan-2 (seems to be abandonned for cancan-1).

Anyway, you can use the code in this PR to make cancan compatible with strong parameters. Here it is extracted in a monkey patch. I prefer to use monkey patches rather than pointing gem to specific commit or fork so that I don't lose any master gem update.

Alternate resource loading

Reading again the doc, I've found an other way to avoid strong parameter problem. You can overload loader if you use a before_filter before #load_resource.

There are several implications :

  • you have to implement logic yourself to make a difference between #new, #show, #create, etc (may use several conditional filters)
  • you have to assigns params yourself (and thus, you can make a proper use of strong parameters)

This defeats the simplicity of cancan resource loading, and serves the sole purpose of letting cancan know what the resource is when you want to use a block with #can and #cannot.

I would still rather go with the monkey patch, so that you don't end up with a lot of useless code in your controllers when issue is fixed upstream.

Upvotes: 1

Related Questions