February 27, 2010
Older: MongoMapper 0.7: Identity Map
Newer: A Nunemaker Joint
Canable: The Flesh Eating Permission System
A while back I wrote about how to add simple permissions to your apps. Since then, I have worked on a few applications (Harmony among them) where I have taken that concept and expanded it. Yesterday, I decided that I had repeated myself enough times (3) and that I should abstract the shared functionality of those apps into a gem. Thus, Canable, the flesh eating permission system, was born.
Can
Canable does not actually implement any permissions for you (or actually eat flesh). Instead, it provides you with all the helpers and then (gasp) you have to do the work. The idea centers around running all permissions through current_user. Anytime you check if a user can do something you use a can method:
user.can_create?(article)
user.can_update?(article)
Able
Instead of having a big case statement in those can methods for each different type of object, I use the strategy pattern to just ask the object if the user has permission to do the action. This is done by having a matching “able” method to the “can” method, thus canable.
class Item
def updatable_by?(user)
creator == user
end
end
The above code, for example, makes it so that only the creator of an item can update it. Obviously, you can get more in depth from there. By default, I add the following can and able methods:
:view => :viewable
:create => :creatable
:update => :updatable
:destroy => :destroyable
Custom Actions
If you need permissions for actions other than the defaults, you can add your own quite easily:
Canable.add(:publish, :publishable)
The readme over on Github has far more details, but I figured I would at least cover it here a bit. It might seem a bit weird at first, but once you start rolling with it, it makes for a pretty easy to implement and understand permission system.
The really funny part is that it is only like 80 lines of code, as most of the methods are dynamically generated. I am perfectly fine if I am the only one who uses this and finds it helpful, but you never know, so feel free to install it as a gem or fork it on github.
Note: No permissions were harmed in the making of this gem.
17 Comments
Feb 27, 2010
Nice gem John. Can this be used if you need to implement roles that are assigned to each user? (i.e. employee, manager, administrator, etc.) Also what authentication solution do you use to go along with Canable?
Feb 27, 2010
@Patrick – The great thing about canable is you can do whatever you want. If you want to use roles, use roles and then just check those roles in your able methods. We do this on the current project I am working on like this:
This code basically says that an admin can create any user, an editor can create a designer or writer, but no one else can create users. Make sense?
Also, regarding auth, it doesn’t really matter as long as you have current_user method (authlogic, restful auth, clearance, devise, warden, etc.).
Feb 27, 2010
Hi John,
This looks very interesting. I love the simplicity of it.
While looking over the code at Github (it looks good) I thought of a few questions:
1) Why instance methods? The index view?
What were your reason(s) for making all the “able” methods, instance methods? And how do you get around this in the “index” views where you may not actually have a instance of that class to check against but you still only want to show that view to certain users?
Do you do something like enforce_view_permission(current_user.articles.new)? Instantiating a new object of the class doesn’t seem like the most intuitive way to check permissions for a “readable” (over)view..
Anyways, I’m sure you’ve had some thoughts on this and reasons for doing it this way.
2) enforce_view_permission in each action?
It seems a little excessive to do this in every single action if your permissions are fairly simple. Would you discourage people from having a before filter in their controller with something like the below?
before_filter :check_abilities
[… actions here …]
private
def check_abilities
enforce_view_permission(current_user.articles.new)
end
I guess this could work as long as you’re not doing attribute-specific validations.
3) Defining permissions in each model
I’m intrigued about the idea of keeping the permission settings in each separate model and not a single massive file.
Would you mind sharing some of your experiences with this approach – especially with regard to getting an overview of your application permission schemes when working with a big app like Harmony?
Feb 27, 2010
@Jamie – 1) The main reason everything is an instance method is because anytime I have dealt with permissions, it always comes down to the object. Can the user do something to this object, which automatically leads to instance methods rather than class methods.
Typically the view stuff I only check for on the show action. For testing whether someone can create a new instance of something, yeah, it feels awkward, but right now I do something like:
can_create?(Article.new)
…if you really need to check that. Most of the permissions I have dealt with are more pertaining to whether or not someone can view/update/destroy an existing object. Typically, create is true.
As far as index actions, they are almost always scoped with an association so I test the association, not individual permissions on the objects returned by that association. In other words, I never do Article.all. Instead, I do current_user.articles.all or whatever.
2) I only enforce view permission on show. Each of the stock actions refers to a stock controller action (view => show, create => create, edit/update => update, and destroy => destroy).
3) Permissions for me really come down to a mix of associations for collections of objects and canable for individual object operations. Thus far, that has been sophisticated enough for me. While Harmony is big, we avoid permissions like the plague so there really aren’t many in it. Mostly the permissions come into play for things like whether or not they can create a site based on the account’s plan or whether they are allowed to delete a theme/site, etc. Hope that helps.
Feb 28, 2010
Good naming. I love “Transgression.”
Feb 28, 2010
I presume you have already seen http://github.com/ryanb/cancan – any particular reason you decided to pretty much rewrite this gem?
Feb 28, 2010
@Sam Elliott: No reason to be snippy and sarcastic. Yes, I am familiar with Ryan and CanCan. The code in Canable was written well before CanCan even existed, I just had not packaged up the code until a few days ago. CanCan has one file that defines all your permissions, whereas Canable defines your permissions in each individual model. Whether or not one existed before the other, there is room for both.
Feb 27, 2010
Thanks for this gem. I was already doing something similar, but without the
can_*
methods. When enforcing a permission, I have an equivalent toI’ll give canable a try as I think it can clean my code (the creation of the default able methods for example).
Feb 28, 2010
@Wes Garrison: Ha, yeah me too. :)
@Bruno: The great thing about the can methods is that all permissions are checked through one point which allows you to easily add specific, application-wide things (such as blank resource, admin override, etc.).
Mar 01, 2010
I like the fact that this spreads the authorization across the models being authorized for, as opposed to the potentially unwieldy single Ability class of cancan.
One thing I do like about cancan (and the older role_requirement) is the declarative syntax for enforcing permission in controllers. It’s kind of a bummer to have to remember to add these calls to every action.
Mar 04, 2010
I’m using this now and finding it helpful. I wonder if it would make sense to have “can_be_xd_by?” methods instead of “xable” methods. For example, “can_be_destroyed_by?”.
The reason is that I have some custom methods I want to add, and “-able” just sounds goofy.
“can_mark_wp_complete?” is fine, but “mark_wp_completable_by?” is weird. However, “can_be_marked_wp_complete?” is ok. Same with “apply_legal_hold” – no “-able” variation will sound right, but “can_have_legal_hold_applied_by?” sounds ok.
So I would still need to give my methods custom names, but they’d be more consistent with the default methods.
Mar 04, 2010
@Daniel – Cool. Glad you are finding it useful. I really like the distinction be can and able methods. If we switch to can for the ables there is no distinction and it is not as obvious where to run permissions through. Also, if you define permissions on your User model (creatable_by?, etc), you instantly have conflicting methods.
I would think harder about the name of your able methods. Maybe it should be completeable_by? or something.
Mar 06, 2010
I’m lovin’ it! Just what I was looking for. Thanks!
Mar 25, 2010
We have been doing something very close to this in Zena with secure and the publication workflow. All this is based on a groups based security model:
Example of methods used:
can_publish? can_write? can_propose? can_refuse?
Example of find:
secure(Page) { Page.find(:all, …) }
Basically, we have implemented the “visitor” pattern (stored in the current Thread). Everyone yelled at me when I talked about this idea 4 years ago. I am happy to see that good solutions finally make their way out of the fogs of trendiness.
Apr 09, 2010
This is a fantasic gem and goes a long way toward cleaning up the code for one of our projects. If I could make one suggestion: the ability to default to false for can_ methods.
This could be done in one of two ways off the top of my head:
1) an optional parameter on Canable.add()
Canable.add(can, able, default = true)
2) a global setting
Canable.default_permission = false
Just a thought.
Apr 27, 2010
Thanks for sharing
Jul 07, 2010
Funny. I have looked at Cancan before and now I find this :)
I created an Auth-Assistant gem which kind of merges cancan with devise to make it really easy to set up authentication with a simple permission system in rails. I have tweaked the use of cancan so you can define permissions externally as Permission classes.
Typically you’d have one Permission class for each role.
I think this is even better than defining the permissions directly in the models. What do you think? I will try to add support for Cannable for sure, then I can evaluate which is better… Great stuff!!!
Sorry, comments are closed for this article to ease the burden of pruning spam.