Permissive gives your ActiveRecord models granular permission support
Permissive combines a model-based permissions system with bitmasking to create a flexible approach to maintaining permissions on your ActiveRecord models. It supports an easy-to-use set of methods for accessing and determining permissions, including some fun metaprogramming.
Get yourself some code. You can install as a gem:
gem install permissive
or as a plugin:
script/plugin install git://github.com/flipsasser/permissive.git
Generate a migration so you can get some sweet table action:
First, define a few permissions constants. We'll define them in
Rails.root/config/initializers/permissive.rb. The best practice is to name them in a verb format that follows this pattern: "Object can
Permission constants need to be int values counting up from zero. We use ints because Permissive uses bit masking to keep permissions data compact and performant.
module Permissive::Permissions MANAGE_GAMES = 0 CONTROL_RIDES = 1 PUNCH = 2 end
And that's all it takes to configure permissions! Now that we have them, let's grant them to a model or two:
class Employee < ActiveRecord::Base acts_as_permissive validates_presence_of :first_name, :last_name end class Company < ActiveRecord::Base validates_presence_of :name end
Easy-peasy, right? Let's try granting a few permissions:
@james = Employee.create(:first_name => 'James', :last_name => 'Brennan') @frigo = Employee.create(:first_name => 'Tommy', :last_name => 'Frigo') @adventureland = Company.create(:name => 'Adventureland') # Okay, let's do some granting. We'll start by scoping to a specific company. @james.can!(:manage_games, :on => @adventureland) # Now let's do some permission checking. @james.can?(:manage_games, :on => @adventureland) #=> true # We can also use the metaprogramming syntax: @james.can_manage_games_on?(@adventureland) #=> true @james.can_control_rides_on?(@adventureland) #=> false # We can check for multiple permissions, too: @james.can?(:manage_games, :control_rides) #=> false # OR: @james.can_manage_games_and_control_rides? # Scoping can be done through any object @frigo.can!(:punch, :on => @james) @frigo.can_punch_on?(@james) #=> true # And the permissions aren't reciprocal @james.can_punch_on?(@frigo) #=> false # Of course, we can grant global (non-scoped) permissions, too: @frigo.can!(:control_rides) @frigo.can_control_rides? #=> true # BUT! Global permissions don't override scoped permissions. @frigo.can_control_rides_on?(@adventureland) #=> false # Likewise, scoped permissions don't bubble up globally: @james.can_manage_games? #=> false # And, last but not least, let's take all of those great permissions away: @james.revoke(:manage_games, :on => @adventureland) # We can revoke all permissions, in any scope, too: @frigo.revoke(:all)
And that's it!
Permissive supports scoping at the class-configuration level, which adds relationships to permitted objects:
class Employee < ActiveRecord::Base acts_as_permissive :scope => :company end @frigo.permissive_companies #=> [Company 1, Company 2]
Sometimes you want to overwrite all previous permissions in a can! method. That's pretty easy: just add :reset => true to the options.
@frigo.can!(:control_rides, :on => @adventureland, :reset => true)
There's a number of things I want to add to the permissive settings. At the moment, Permissive currently support scoping at the class level, BUT all it really does is add a
@employee.can!(:do_anything) will still work, as will
@employee.can!(:do_something, :on => @something_that_isnt_a_company). That's pretty confusing to me. Adding more granular permissions might be cooler:
class Employee < ActiveRecord::Base has_permissions do on :companies on :employees end end
which might yield something like
@employee.permissive_companies # and @employee.can_control_rides_in_company @adventureland
I'd also like to support a more intelligent grammar:
@james.can_punch? @frigo @frigo.can!(:control_rides, :in => @adventureland)
Meta-programmed methods for granting and revoking would be cool, too:
@james.can_punch! @frigo @frigo.cannot_control_rides_in! @adventureland
And while we're on the subject of metaprogramming, let's add some OR-ing to the whole thing:
I'd also like to enable Permissive::Templates (pre-set permission groups, like roles):
administrator = Permissive::Template.named('Administrator') @james.acts_like administrator
Next up! I currently use a manual reset to grant permissions through a controller. It would by great to DRY this stuff up and provide some decent path for moving permissions into HTML forms. Right now, it looks something like this:
<%= check_box_tag("employee[permissions]", Permissive::Permissions::CONTROL_RIDES, @employee.can_control_rides?) %> Control rides
.. and in the controller:
def update @employee.can!(params[:employees].delete(:permissions), :revert => true) respond_to do |format| ... end end
Finally, I'd like to use the
grant_mask support that exists on the Permissive::Permission model to control what people can or cannot allow others to do. This would necessitate one of two things - first, a quick way of iterating over a person's granting permissions, e.g.:
<% current_user.grant_permissions.each do |permission| %> <!-- Do something! --> <% end %>
and second, write-time checking of grantor permissions. Something like this, maybe:
def update current_user.grant(params[:employees][:permissions], :to => @employee) end
which would allow the Permissive::Permission model to make sure whatever
current_user is granting to @employee, they're allowed to grant to @employee.
And that's it! Like all of my projects, I extracted it from some live development - which means it, too, is still in development. So please feel free to contribute!
Copyright (c) 2009 Flip Sasser & Simon Parsons, released under the MIT license