Project

permissive

0.01
No commit activity in last 3 years
No release in over 3 years
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.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies
 Project Readme

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.

Installation

  1. 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

  2. Generate a migration so you can get some sweet table action:

    script/generate permissive_migration

    rake db:migrate

Usage

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 DO_PERMISSION_NAME".

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!

Scoping

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]

Replacing Permissions

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)

Next Steps

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 has_many relationship. @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:

@james.can_control_rides_or_manage_games_in? @adventureland

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