Authorization in Rails 3.1 : CanCan, CanTango, declarative_authorization?
-
17-04-2021 - |
Question
I have looked at declarative_authorization, CanCan, and CanTango. They all are good in adding authorization to the application but I was wondering how does one add authorization to specific instance of a model i.e. a person can have a manage access in one project and only limited (read less than manage: limited update, etc) in another.
Could you please a better way? Apologies if my question sounds too trivial. It could be because I am new to RoR.
thanks, John
Solution
As I know CanCan and declarative_authorization, and I implemented role-based authorizations with both, I recommend CanCan. Just my two cents.
Example (untested, unfortunately I cannot test here and I have no access to my code)
So let's say we have a structure like this:
class User < ActiveRecord::Base
belongs_to :role
end
class Role < ActiveRecord::Base
has_many :users
# attributes: project_read, project_create, project_update
end
Then, CanCan could look like this:
class Ability
include CanCan::Ability
def initialize(user)
@user = user
@role = user.role
# user can see a project if he has project_read => true in his role
can :read, Project if role.project_read?
# same, but with create
can :create, Project if role.project_create?
# can do everything with projects if he is an admin
can :manage, Project if user.admin?
end
end
You can find all information you need in the CanCan wiki on github. Personal recommendation to read:
- https://github.com/ryanb/cancan/wiki/Defining-Abilities
- https://github.com/ryanb/cancan/wiki/Defining-Abilities-with-Blocks
- https://github.com/ryanb/cancan/wiki/Authorizing-Controller-Actions
Basically you just need to extend the example above to include your roles through your relations. To keep it simple, you can also create additional helper methods in ability.rb
.
The main mean caveat you may fall for (at least I do): Make sure your user can do something with a model before you define what the user can't. Otherwise you'll sit there frustrated and think "but why? I never wrote the user can't.". Yeah. But you also never explicitly wrote that he can...
OTHER TIPS
class User < ActiveRecord::Base
belongs_to :role
delegate :permissions, :to => :role
def method_missing(method_id, *args)
if match = matches_dynamic_role_check?(method_id)
tokenize_roles(match.captures.first).each do |check|
return true if role.name.downcase == check
end
return false
elsif match = matches_dynamic_perm_check?(method_id)
return true if permissions.find_by_name(match.captures.first)
else
super
end
end
private
def matches_dynamic_perm_check?(method_id)
/^can_([a-zA-Z]\w*)\?$/.match(method_id.to_s)
end
def matches_dynamic_role_check?(method_id)
/^is_an?_([a-zA-Z]\w*)\?$/.match(method_id.to_s)
end
def tokenize_roles(string_to_split)
string_to_split.split(/_or_/)
end
end
Usage:
user.is_an? admin
user.can_delete?