CanCan Permits

Role specific Permits for use with CanCan permission system.

Changes

See Changelog.txt (Major updates as per Nov 24. 2010)

Install

gem install cancan-permits

Usage Rails 3 app

Gemfile gem 'cancan-permits'

Update gem bundle in terminal: $ bundle install

See Generator section below. Also see CanCan permits demo app

Usage

  • Define Roles that Users can have
  • Define which Roles are available
  • Define a Permit for each Role.
  • For each Permit, define what that Role can do

To add Roles to your app, you might consider using a roles gem such as Roles Generic or any of the ORM specific variants.

Define which Roles are available

You can override the default configuration here:


  module Permits::Roles
    def self.available
      # return symbols array of Roles available to users 
    end
  end

By default it returns User.roles if such exists, otherwise it returns [:guest, :admin] by default.

Define a Permit for each Role.

Note: You might consider using the Permits generator in order to generate your permits for you (see below)


  class AdminPermit < Permit::Base
    def initialize(ability, options = {})
      super
    end

    def permit?(user, options = {})    
      super
      return if !role_match? user

      can :manage, :all    
    end  
  end

Special Permits

The Permits generator always generates the special permits Any and System.

Any permit

The Any permit, can be used to set permissions that should hold true for a user in any role. F.ex, maybe in your app, any user should be able to read comments, articles and posts:

For this to hold true, put the following permit logic in your Any permit.


  can :read, [Comment, Article, Post]

System permit

The System permit is run before any of the other permits. This gives you a chance to control the permission flow. By returning a value of :break you force a break-out from the permission flow, ensuring none of the other permits are run.

Example: The system permit can be used to allow management of all resources given the request is from localhost (which usually means "in development mode"). By default this logic is setup and ready to go.

You can be enable this simply by setting the following class instance variable:

Permits::Configuration.localhost_manager = true

Default roles

By default the permits for the roles System and Guest are also generated.

Licenses

Permits also supports creation more fine-grained permits through the use of Licenses.
Licenses are a way to group logical fragments of permission statements to be reused across multiple permits. The generator will create a licenses.rb file in the permits folder where you can put your licenses. For more complex scenarios, you might want to have a separate licenses subfolder where you put your license files.

License example:


  class BloggingLicense < License::Base
    def initialize name
      super
    end

    def enforce!
      can(:read, Blog)
      can(:create, Post)
      owns(user, Post)
    end
  end  

Usage example:


  class GuestPermit < Permit::Base
    def initialize(ability, options = {})
      super
    end

    def permit?(user, options = {}) 
      super    
      return if !role_match? user

      licenses :user_admin, :blogging
    end
  end
end

By convention the permits system will try to find a license named UserAdminLicense and BloggingLicense in this example and call enforce! on each license.

ORMs

The easiest option is to directly set the orm as a class variable. An appropriate ownership strategy will be selected accordingly for the ORM.


  Permits::Ability.orm = :data_mapper

Alternatively set it for the Ability instance for more fine grained control


  ability = Permits::Ability.new(@editor, :strategy => :string)  

The ORMs currently supported (and tested) are :active_record, :data_mapper, :mongoid, :mongo_mapper

Advanced Permit options

Note that the options hash (second argument of the initializer) can also be used to pass custom data for the permission system to use to determine whether an action should be permitted. An example use of this is to pass in the HTTP request object. This approach is used in the default SystemPermit generated.

The ability would most likely be configured with the current request in a view helper or directly from within the controller.

editor_ability = Permits::Ability.new(@editor, :request => request)

A Permit can then use this information

def permit?(user, options = {}) request = options[:request] if request && request.host.localhost? && localhost_manager? can(:manage, :all) return :break end
end

Now, if a request object is present and the host is 'localhost' and Permits has been configured to allow localhost to manage objects, then: The user is allowed to manage all objects and no other Permits are evaluated (to avoid them overriding this full right permission).

In the code above, the built in #localhost_manager? method is used.

To configure permits to allow localhost to manage objects: Permits::Configuration.localhost_manager = true

Please provide suggestions and feedback on how to improve this :)

Permits Generator

Options

  • --orm : The ORM to use (active_record, data_mapper, mongoid, mongo_mapper) - creates a Rails initializer
  • --initializer : A Rails 3 initializer file for Permits is generated by default. Use --no-initializer option to disable this
  • --roles : The roles for which to generate permits ; default Guest (read all) and Admin (manage all)
  • --licenses : The licenses to generate; default UserAdmin and Blogging licenses are generated

  • --default-licenses : By default exemplar licenses are generated. Use --no-default-licenses option to disable this

  • --default-permits : By default :guest and :admin permits are generated. Use --no-default-permits option to disable this

$ rails g permits --orm active_record --roles guest author admin

What does the generator generate?

To get an understanding of what the generator generates for a Rails 3 application, try to run the spec permit_generator_spec.rb with rspec 2 as follows:

$ rspec spec/generators/permit_generator_spec.rb

In the file permit_generator_spec.rb make the following change config.remove_temp_dir = false This will prevent the rails /tmp dir from being deleted after the test run, so you can inspect what is generated in the Rails app.

TODO ?

The Permits generator should attempt to discover which roles are currently defined as available to the system (Generic Roles API, User#roles etc.) and generate permits for those roles. Any roles specified in the --roles option should be merged with the roles available in the app.

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 (c) 2010 Kristian Mandrup. See LICENSE for details.