The Permit system for CanTango.
Permits are a way to partition Ability rules into logical units.
CanTango Permits includes:
- a basic set of useful permits
- a permit engine to execute the permits
The Permits systems comes with a few useful Permit types out-of-the-box, but you are free to develop your own to suit your needs.
Built-in permit types
The set of buit-in Permit types include:
The CanTango roles system adds Permit types for the following:
Custom permit types
You can easily customize or create your own types of Permits to suit your needs! The Permit system will introspect the ability candidate and see which Permits apply and then build and execute these permits automatically, merging all the resulting rule sets into one. The simplest way to create a custom permit types is to subclass the Attribute permit as shown below. To create more complex permit types that go beyond mapping to an attribute, please look at the code of any of the built-in permits (such as Attribute Permit) to gain an insight for how to do this.
Example of a custom permit type:
class MembershipPermit < :::: class Builder < :::::: attribute :membership end def self.inherited(base_clazz) register base_clazz, :name => attribute_name(base_clazz) end # optional override of inference of attribute via class name attribute :membership end
The permit system iterates over all the registered types of permits and executes all registered permits of each type. The result is a set of Ability rules that work with the CanCan Ability mechanism.
Defining Permits and Licenses in your app
app/permits folder will be added to Rails autoloading.
The directory layout should be the following:
- /app - /permits - /permit - /role - editor.rb - /role_group - editors.rb - /user_type - admin.rb -/admin (account) - /role - editor.rb
Default :editor Role permit (scope less):
module Permit::Role class Editor < ::::Role def calc_rules can :edit, Post end module Cache def calc_rules can :edit, Post end end end end
The :editor Role permit applicable for the Admin scope. Typically the scope is linked to the account (but doesn't have to be).
module Permit::Admin::Role class Editor < ::::Role def calc_rules can :edit, Post if session[:edit_mode] end modes :no_cache end end
#modes macro is used to indicate that this Permit should only be executed for the
Another option is fx to wrap the Permit class inside the Account class namespace like this:
- /app - /models - /admin_account - editor_role_permit.rb - admin_account.rb
class AdminAccount < ActiveRecord::Base class EditorRolePermit < ::::Role tango_permit :name => :editor, :type => :role, :ns => :admin def calc_rules can :edit, Post if session[:edit_mode] end modes :no_cache end end
By default this is not supported out-of-the-box, hence you have to use the
#tango_permit macro to tell CanTango exactly how to understand this custom class namespace layout.
Note: This is NOT a recommended approach.
Licenses have their own namespace folder
license inside the
app/permits folder. The default (scope less) licenses are placed directly in the
- /app - /permits - /license - blogging.rb - /admin - blogging.rb
Default :blogging License (scope less):
module License class Blogging < :: module Cache def calc_rules can [:create, :edit], Post end end module NoCache def calc_rules can :publish, Post if session[:publishing] == :on end end end end
The :editor License applicable for the Admin scope. Typically the scope is linked to the account (but doesn't have to be).
module License::Admin class Blogging < :: def calc_rules can :edit, Post end modes :all end end
Install 'cantango-permits' gem
gem install cantango-permits
Or insert into Gemfile
Run bundler in a terminal/console from the folder of your Gemfile (root folder of app)
require 'cantango/permits' require 'cantango/permit_engine'
Turn on/off: Enable and disable types of permits and specific permits
.config.permits do |permits| # which types of permits to enable permits.types.enable :user_type, :account_type permits.enable_all_for :account_type permits.types.disable :user_type, :account_type permits.disable_for :user_type, :admin, :editor end
Registration: Which permits have been registered (and for which types)
.config.permits do |permits| permits.registry_for :account_type # Registry for :account_type permits permits.registered_for :account_type # names of AccountType permits permits.all permits.show_all end
Debug: Which permits allowed/denied specific actions for specific candidates to be taken
.config.permits do |permits| permits.allowed candidate, actions, subjects, *extra_args permits.denied candidate, actions, subjects, *extra_args end
The Permits engine can be configured as any other CanTango Engine:
off! methods to enable to disable use of the engine.
mode= to set the execution mode.
.config.engine(:permits) do |engine| # toggle engine on/off engine.on! engine.on? engine.off! engine.modes.valid # => [:cache, :no_cache, :both] # set execution modes engine.modes.register :cache, :no_cache engine.modes.registered # => [:cache, :no_cache] end
Will look up a particular registered Permit in the Permit registry (see Configuration). Permits are registered automatically by an inheritance hook. If you want to override this, you need to register your class directly (manually) with the the permits registry.
module :::: class Base def initialize name, type @name, @type = [name, type] end end end
Builds and returns a list of all enabled permits of a specific type
module :: class Permits def initialize ability @ability = ability end # @return Array<Permit> def create permits.build end end end
Categories are loaded from a Yaml file (by default
categories.yaml). The loader
CanTango::Loader::Categories is based on the
CanTango::Loader::Yaml from cantango-core.
CanTango::Parser::Categories parses the yaml into a hash referencing constants (models).
#tango_permit can be used to attempt to register a Permit class with the Permits registry based on naming conventions and options passed in.
class MySuperPermit < :::: tango_permit :name => :super, :type => :user_type, :account => :admin # the internals end
module :: class Permits def initialize ability super end def calc_rules # push result of each permit type execution into main ability rules array permits.each_pair do |type, permits| perm_rules = executor(type, permits).execute! rules << perm_rules if !perm_rules.blank? end end def executor type, permits ::::Executor::PermitType.new self, type, permits(type) end def permits type @permits ||= permits_factory(type).build! end def permits_factory type @permits_factory ||= ::::. self, type end end end
This CanTango extension should have the least amount of dependencies on other extensions.
Contributing to Cantango Permits
- Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
- Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
- Fork the project
- Start a feature/bugfix branch
- Commit and push until you are happy with your contribution
- Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
- Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
Copyright (c) 2011 Kristian Mandrup. See LICENSE.txt for further details.