RBACanable
Canable (by John Nunemaker) with a Role Based Access System latched on
Simple permissions system that features easily actors, resources for actors to manipulate, and roles actors play to define their permissions.
Ables
Ables are the resource objets that an actor will want to manipulate.
class Article
include MongoMapper::Document
include Canable::Ables
end
Including Canable::Ables adds the able methods to the class including it. In this instance, any article instance now has viewable_by?(actor), creatable_by?(actor), updatable_by?(actor) and destroyable_by?(actor).
Roles
The rules that govern your actors are defined in roles. Roles are modules that extend Canable::Role
and/or other roles. Roles can be nested as deep and mixed and matched using Ruby’s mixin system, as they are just normal Ruby modules that carry permission sets with them. The order they are included in each other will define the final permission set of the role: Roles included after (on a lower line) than others will override the settings of the others. module Canable::Roles
module EmployeeRole
include Canable::Role
default_response false
def can_view_article?(article) true end
def can_update_article?(article) article.owner == @name end
def can_destroy_article?(article) self.can_update_article?(article) end end
module ManagerRole include Canable::Role include EmployeeRole
def can_update_article?(article) true end
def can_destroy_article(article) article.owner == @name end end end
Here two roles are defined, one for employees and one for managers. Employees by default can’t do anything, this is set by default_response false
on the module. Then methods governing the actor’s interaction with the Article model by defining the can_view_article?
to always return true (Employees can view any article), and defining the can_update_article?
method to only return true if the actor playing the employee role is the owner of the article.
The manager role includes the employee role, so it inherits the default_response behaviour of false and the defined rules for interactions with articles. Here, the can_update_article?
method is override to always return true, so managers can edit any article. Note that EmployeeRole#can_destroy_article?(article)
calls self.can_update_article?(article)
, so when the ManagerRole inherits the EmployeeRole, ManagerRole#can_destroy_article?(article)
returns the result of ManagerRole#can_update_article?(article)
, which returns true. This means that when ManagerRole inherits EmployeeRole, the inherited methods behave differently, which might be unexpected. To mitigate this in the example above, ManagerRole#can_destroy_article?(article)
is overridden to check if the Manager trying to destroy the role is the article owner.
Actors
Whatever class(es) you want all permissions to run through should include Canable::Actor
, instead of Canable::Cans
from the original Canable gem.
class User > ActiveRecord::Base
include Canable::Actor
default_role :employee
end
Actors represent objects in the system that want to preform an action. An actor’s abilities are defined by the role(s) it plays because the actor simply inherits any permission. Roles can be assigned to an actor (“played”) by using the default_role(role)
class method, or calling <tt>act(role)<tt> on an actor instance.
Lets say an article can be viewed and created by anyone, but only updated or destroyed by the user that created the article. To do that, you could leave viewable_by? and creatable_by? alone as they default to true and just override the other methods.
class Article
include MongoMapper::Document
include Canable::Ables
userstamps! # adds creator and updater
def updatable_by?(user)
creator == user
end
def destroyable_by?(user)
updatable_by?(user)
end
end
Lets look at some sample code now:
john = User.create(:name => 'John')
steve = User.create(:name =. 'Steve')
ruby = Article.new(:title => 'Ruby')
john.can_create?(ruby) # true
steve.can_create?(ruby) # true
ruby.creator = john
ruby.save
john.can_view?(ruby) # true
steve.can_view?(ruby) # true
john.can_update?(ruby) # true
steve.can_update?(ruby) # false
john.can_destroy?(ruby) # true
steve.can_destroy?(ruby) # false
Now we can implement our permissions for each resource and then always check whether a user can or cannot do something. This makes it all really easy to test. Next, how would you use this in the controller.
Enforcers
class ApplicationController
include Canable::Enforcers
end
Including Canable::Enforcers adds an enforce permission method for each of the actions defined (by default view/create/update/destroy). It is the same thing as doing this for each Canable action:
class ApplicationController
include Canable::Enforcers
delegate :can_view?, :to => :current_user
helper_method :can_view? # so you can use it in your views
hide_action :can_view?
private
def (resource)
raise Canable::Transgression unless can_view?(resource)
end
end
Which means you can use it like this:
class ArticlesController < ApplicationController
def show
@article = Article.find!(params[:id])
(@article)
end
end
If the user can_view? the article, all is well. If not, a Canable::Transgression is raised which you can decide how to handle (show 404, slap them on the wrist, etc.).
Adding Your Own Actions
You can add your own actions like this:
Canable.add(:publish, :publishable)
The first parameter is the can method (ie: can_publish?) and the second is the able method (ie: publishable_by?).
Review
So, lets review: cans go on user model, ables go on everything, you override ables in each model where you want to enforce permissions, and enforcers go after each time you find or initialize an object in a controller. Bing, bang, boom.
Note on Patches/Pull Requests
-
Fork the project.
-
Make your feature addition or bug fix.
-
Add tests for it. This is important so I don’t break it in a future version unintentionally.
-
Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
-
Send me a pull request. Bonus points for topic branches.
Copyright
Copyright © 2010 John Nunemaker. See LICENSE for details.