Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

be9-acl9

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

be9-acl9

  • 0.11.0
  • Rubygems
  • Socket score

Version published
Maintainers
1
Created
Source

h1. Introduction

Acl9 is yet another solution for role-based authorization in Rails. It consists of two subsystems which can be used separately.

Role control subsystem allows you to set and query user roles for various objects.

Access control subsystem allows you to specify different role-based access rules inside controllers.

A bunch of access rules is translated into a complex boolean expression. Then it's turned into a lambda or a method and can be used with @before_filter@. Thus you can block unprivileged access to certain actions of your controller.

An example:


  class VerySecretController < ApplicationController
    access_control do
      allow :superadmin
      allow :owner, :of => :secret

      action :index do
        allow anonymous, logged_in
      end

      allow logged_in, :to => :show
      allow :manager, :of => :secret, :except => [:delete, :destroy]
      deny :thiefs
    end

    def index
      # ...
    end

    # ...
  end

h1. Contacts

Acl9 is hosted "on the GitHub":http://github.com/be9/acl9.

You may find tutorials and additional docs on the "wiki page":http://wiki.github.com/be9/acl9.

Rdocs are available "here":http://rdoc.info/projects/be9/acl9.

If you have questions, please post to the "acl9-discuss group":http://groups.google.com/group/acl9-discuss

h1. Installation

Acl9 can be installed as a gem from "GitHub":http://github.com.

Add the following line to your @config/environment.rb@:


  config.gem "be9-acl9", :source => "http://gems.github.com", :lib => "acl9"

Then run @rake gems:install@ (with possible @rake gems:unpack@ thereafter) and you're done!

Alternatively you can install Acl9 as a plugin:


  script/plugin install git://github.com/be9/acl9.git

h1. Basics

h2. Authorization is not authentication!

Both words start with "auth" but have different meaning!

Authentication is basically a mapping of credentials (username, password) or OpenID to specific user account in the system.

Authorization is an authenticated user's permission to perform some specific action somewhere in the system.

Acl9 is a authorization solution, so you will need to implement authentication by other means. I recommend "Authlogic":http://github.com/binarylogic/authlogic for that purpose, as it's simple, clean and at the same time very configurable.

h2. Roles

Role is an abstraction. You could directly assign permissions to user accounts in your system, but you'd not want to! Way more manageable solution is to assign permissions to roles and roles further to users.

For example, you can have role called admin which has all available permissions. Now you may assign this role to several trusted accounts on your system.

Acl9 also supports the notion of object roles, that is, roles with limited scope.

Imagine we are building a magazine site and want to develop a permission system. So, what roles and permissions are there?

Journalists should be able to create articles in their section and edit their own articles.

Section editors should be able to edit and delete all articles in their sections and change the published flag.

Editor-in-chief should be able to change everything.

We clearly see that journalists and section editors are tied to a specific section, whereas editor-in-chief is a role with global scope.

h2. Role interface

All permission checks in Acl9 are boiled down to calls of a single method:

@subject.has_role?(role, object)@

That should be read as "Does subject have role on object?".

Subject is an instance of a @User@, or @Account@, or whatever model you use for authentication. Object is an instance of any class (including subject class!) or @nil@ (in which case it's a global role).

Acl9 builtin role control subsystem provides @has_role?@ method for you, but you can also implemented it by hand (see Coming up with your own role implementation below).

h1. Acl9 role control subsystem

Role control subsystem has been lifted from "Rails authorization plugin":http://github.com/DocSavage/rails-authorization-plugin, but undergone some modifications.

It's based on two tables in the database. First, role table, which stores pairs @[role_name, object]@ where object is a polymorphic model instance or a class. Second, join table, which joins users and roles.

To use this subsystem, you should define a @Role@ model.

h2. Role model


  class Role < ActiveRecord::Base
    acts_as_authorization_role
  end

The structure of @roles@ table is as follows:


  create_table "roles", :force => true do |t|
    t.string   "name",              :limit => 40
    t.string   "authorizable_type", :limit => 40
    t.integer  "authorizable_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

Note that you will almost never use the @Role@ class directly.

h2. Subject model


  class User < ActiveRecord::Base
    acts_as_authorization_subject
  end

You won't need any specific columns in the @users@ table, but there should be a join table:


  create_table "roles_users", :id => false, :force => true do |t|
    t.integer  "user_id"
    t.integer  "role_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

h2. Object model

Place @acts_as_authorization_object@ call inside any model you want to act as such.


  class Foo < ActiveRecord::Base
    acts_as_authorization_object
  end
  
  class Bar < ActiveRecord::Base
    acts_as_authorization_object
  end

h2. Interface

h3. Subject model

A call of @acts_as_authorization_subject@ defines following methods on the model:

@subject.has_role?(role, object = nil)@. Returns @true@ of @false@ (has or has not).

@subject.has_role!(role, object = nil)@. Assigns a @role@ for the @object@ to the @subject@. Does nothing is subject already has such a role.

@subject.has_no_role!(role, object = nil)@. Unassigns a role from the @subject@.

@subject.has_roles_for?(object)@. Does the @subject@ has any roles for @object@? (@true@ of @false@)

@subject.has_role_for?(object)@. Same as @has_roles_for?@.

@subject.roles_for(object)@. Returns an array of @Role@ instances, corresponding to @subject@ 's roles on @object@. E.g. @subject.roles_for(object).map(&:name).sort@ will give you role names in alphabetical order.

@subject.has_no_roles_for!(object)@. Unassign any @subject@ 's roles for a given @object@.

@subject.has_no_roles!@. Unassign all roles from @subject@.

h3. Object model

A call of @acts_as_authorization_object@ defines following methods on the model:

@object.accepts_role?(role_name, subject)@. An alias for @subject.has_role?(role_name, object)@.

@object.accepts_role!(role_name, subject)@. An alias for @subject.has_role!(role_name, object)@.

@object.accepts_no_role!(role_name, subject)@. An alias for @subject.has_no_role!(role_name, object)@.

@object.accepts_roles_by?(subject)@. An alias for @subject.has_roles_for?(object)@.

@object.accepts_role_by?(subject)@. Same as @accepts_roles_by?@.

@object.accepts_roles_by(subject)@. An alias for @subject.roles_for(object)@.

h2. Custom class names

You may want to deviate from default @User@ and @Role@ class names. That can easily be done with arguments to @acts_as_...@.

Say, you have @Account@ and @AccountRole@:


  class Account < ActiveRecord::Base
    acts_as_authorization_subject :role_class_name => 'AccountRole'
  end

  class AccountRole < ActiveRecord::Base
    acts_as_authorization_role :subject_class_name => 'Account'
  end

  class FooBar < ActiveRecord::Base
    acts_as_authorization_object :role_class_name => 'AccountRole', :subject_class_name => 'Account'
  end

Or... since Acl9 defaults can be changed in a special hash, you can put the following snippet:


  Acl9::config.merge!({
    :default_role_class_name => 'AccountRole',
    :default_subject_class_name => 'Account',
  })

... into @config/initializers/acl9.rb@ and get rid of that clunky arguments:


  class Account < ActiveRecord::Base
    acts_as_authorization_subject
  end

  class AccountRole < ActiveRecord::Base
    acts_as_authorization_role
  end

  class FooBar < ActiveRecord::Base
    acts_as_authorization_object
  end

Note that you'll need to change your database structure appropriately:


  create_table "account_roles", :force => true do |t|
    t.string   "name",              :limit => 40
    t.string   "authorizable_type", :limit => 40
    t.integer  "authorizable_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end
  
  create_table "account_roles_accounts", :id => false, :force => true do |t|
    t.integer  "account_id"
    t.integer  "account_role_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

h2. Examples


  user = User.create!
  user.has_role? 'admin'              # => false

  user.has_role! :admin

  user.has_role? :admin               # => true

@user@ now has global role admin. Note that you can specify role name either as a string or as a symbol.


  foo = Foo.create!

  user.has_role? 'admin', foo         # => false

  user.has_role! :manager, foo
  
  user.has_role? :manager, foo        # => true
  foo.accepts_role? :manager, user    # => true
  
  user.has_roles_for? foo             # => true

You can see here that global and object roles are distinguished from each other. User with global role admin isn't automatically admin of @foo@.

However,


  user.has_role? :manager             # => true

That is, if you have an object role, it means that you have a global role with the same name too! In other words, you are manager if you manage at least one @foo@ (or a @bar@...).


  bar = Bar.create!

  user.has_role! :manager, bar
  user.has_no_role! :manager, foo

  user.has_role? :manager, foo        # => false
  user.has_role? :manager             # => true

Our @user@ is no more manager of @foo@, but has become a manager of @bar@.


  user.has_no_roles!

  user.has_role? :manager             # => false
  user.has_role? :admin               # => false
  user.roles                          # => []

At this time @user@ has no roles in the system.

h2. Coming up with your own role implementation

The described role system with its 2 tables (not counting the @users@ table!) might be an overkill for many cases. If all you want is global roles without any scope, you'd better off implementing it by hand.

The access control subsystem of Acl9 uses only @subject.has_role?@ method, so there's no need to implement anything else except for own convenience.

For example, if each your user can have only one global role, just add @role@ column to your @User@ class:


  class User < ActiveRecord::Base
    def has_role?(role_name, obj=nil)
      self.role == role_name
    end
    
    def has_role!(role_name, obj=nil)
      self.role = role_name
      save!
    end
  end

If you need to assign multiple roles to your users, you can use @serialize@ with role array or a special solution like "preference_fu":http://github.com/brennandunn/preference_fu.

h1. Access control subsystem

By means of access control subsystem you can protect actions of your controller from unauthorized access. Acl9 provides a nice DSL for writing access rules.

h2. Allow and deny

Access control is mostly about allowing and denying. So there are two
basic methods: @allow@ and @deny@. They have the same syntax:


  allow ROLE_LIST, OPTIONS
  deny  ROLE_LIST, OPTIONS

h3. Specifying roles

ROLE_LIST is a list of roles (at least 1 role should be there). So,


  allow :manager, :admin
  deny  :banned

will match holders of global role manager and holders of global role admin as allowed.
On the contrary, holders of banned role will match as denied.

Basically this snippet is equivalent to


  allow :manager
  allow :admin
  deny  :banned

which means that roles in argument list are OR'ed for a match, and not AND'ed.

Also note that:

  • You may use both strings and :symbols to specify roles (the latter get converted into strings).
  • Role names are singularized before check.

Thus the snippet above can also be written as


  allow :managers, :admins
  deny  'banned'

or even


  allow *%w(managers admins)
  deny  'banned'

h3. Object and class roles

Examples in the previous section were all about global roles. Let's see how we can use object and class roles in the ACL block.


  allow :responsible, :for => Widget
  allow :possessor, :of => :foo
  deny  :angry, :at => :me
  allow :interested, :in => Future
  deny  :short, :on => :time
  deny  :hated, :by => :us

To specify an object you use one of the 6 preposition options:

  • :of
  • :at
  • :on
  • :by
  • :for
  • :in

They all have the same meaning, use one that makes better English out of your rule.

Now, each of these prepositions may point to a Class or a :symbol. In the former case we get a class role. E.g. @allow :responsible, :for => Widget@ becomes @subject.has_role?('responsible', Widget)@.

Symbol is trickier, it means that the appropriate instance variable of the controller is taken as an object.

@allow :possessor, :of => :foo@ is translated into subject.has_role?('possessor', controller.instance_variable_get('@foo')).

Checking against an instance variable has sense when you have another before filter which is executed before the one generated by @access_control@, like this:


  class MoorblesController < ApplicationController
    before_filter :load_moorble, :only => [:edit, :update, :destroy]

    access_control do
      allow :creator, :of => :moorble

      # ...
    end

    # ...

    private

    def load_moorble
      @moorble = Moorble.find(params[:id])
    end
  end

Note that the object option is applied to all of the roles you specify in the argument list. As such,


  allow :devil, :son, :of => God

is equivalent to


  allow :devil, :of => God
  allow :son,   :of => God

but NOT


  allow :devil
  allow :son, :of => God

h3. Pseudo-roles

There are three pseudo-roles in the ACL: @all@, @anonymous@ and @logged_in@.

@allow all@ will always match (as well as @deny all@).

@allow anonymous@ and @deny anonymous@ will match when user is anonymous, i.e. subject is @nil@. You may also use a shorter notation: @allow nil@ (@deny nil@).

@logged_in@ is direct opposite of @anonymous@, so @allow logged_in@ will match if the user is logged in (subject is not @nil@).

No role checks are done in either case.

h3. Limiting action scope

By default rules apply to all actions of the controller. There are two options that narrow the scope of the @deny@ or @allow@ rule: @:to@ and @:except@.


  allow :owner, :of => :site, :to => [:delete, :destroy]
  deny anonymous, :except => [:index, :show]

For the first rule to match not only the current user should be an owner of the site, but also current action should be delete or destroy.

In the second rule anonymous user access is denied for all actions, except index and show.

You may not specify both @:to@ and @:except@.

Note that you can use actions block instead of @:to@ (see Actions block below). You can also use @:only@ and @:except@ options in the @access_control@ call which will serve as options of the @before_filter@ and thus limit the scope of the whole ACL.

h3. Rule conditions

You may create conditional rules using @:if@ and @:unless@ options.


  allow :owner, :of => :site, :to => [:delete, :destroy], :if => :chance_to_delete

Controller's @:chance_to_delete@ method will be called here. The rule will match if the action is 'delete' or 'destroy' AND if the method returned @true@.

@:unless@ has the opposite meaning and should return @false@ for a rule to match.

Both options can be specified in the same rule.


  allow :visitor, :to => [:index, :show], :if => :right_phase_of_the_moon?, :unless => :suspicious?

@right_phase_of_the_moon?@ should return @true@ AND @suspicious?@ should return @false@ for a poor visitor to see a page.

Currently only controller methods are supported (specify them as :symbols). Lambdas are not supported.

h2. Rule matching order

Rule matching system is similar to that of Apache web server. There are two modes: default allow (corresponding to @Order Deny,Allow@ in Apache) and default deny (@Order Allow,Deny@ in Apache).

h3. Setting modes

Mode is set with a @default@ call.

@default :allow@ will set default allow mode.

@default :deny@ will set default deny mode. Note that this is the default mode, i.e. it will be on if you don't do a @default@ call at all.

h3. Matching algorithm

First of all, regardless of the mode, all @allow@ matches are OR'ed together and all @deny@ matches are OR'ed as well.

We'll express this in the following manner:


  ALLOWED = (allow rule 1 matches?) OR ((allow rule 2 matches?) OR ...
  NOT_DENIED = NOT ((deny rule 1 matches?) OR (deny rule 2 matches?) OR ...)

So, ALLOWED is @true@ when either of the @allow@ rules matches, and NOT_DENIED is @true@ when none of the @deny@ rules matches.

Let's denote the final result of algorithm as ALLOWANCE. If it's @true@, access is allowed, if @false@ - denied.

In the case of default allow:


  ALLOWANCE = ALLOWED OR NOT_DENIED

In the case of default deny:


  ALLOWANCE = ALLOWED AND NOT_DENIED

Same result as a table:

|. Rule matches |. Default allow mode |_. Default deny mode | | None of the @allow@ and @deny@ rules matched. | Access is allowed. | Access is denied. | | Some of the @allow@ rules matched, none of the @deny@ rules matched. | Access is allowed. | Access is allowed. | | None of the @allow@ rules matched, some of the @deny@ rules matched. | Access is denied. | Access is denied. | | Some of the @allow@ rules matched, some of the @deny@ rules matched. | Access is allowed. | Access is denied. |

Apparently default deny mode is more strict, and that's because it's on by default.

h2. Actions block

You may group rules with the help of the @actions@ block.

An example from the imaginary @PostsController@:


  allow :admin
  
  actions :index, :show do
    allow all
  end

  actions :new, :create do
    allow :managers, :of => Post
  end

  actions :edit, :update do
    allow :owner, :of => :post
  end

  action :destroy do
    allow :owner, :of => :post
  end

This is equivalent to:


  allow :admin
  
  allow all, :to => [:index, :show]
  allow :managers, :of => Post, :to => [:new, :create]
  allow :owner, :of => :post, :to => [:edit, :update]
  allow :owner, :of => :post, :to => :destroy

Note that only @allow@ and @deny@ calls are available inside @actions@ block, and these may not have @:to@/@:except@ options.

@action@ is just a synonym for @actions@.

h2. access_control method

By calling @access_control@ in your controller you can get your ACL block translated into...

a lambda, installed with @before_filter@ and raising @Acl9::AccessDenied@ exception on occasion.

a method, installed with @before_filter@ and raising @Acl9::AccessDenied@ exception on occasion.

a method, returning @true@ or @false@, whether access is allowed or denied.

First case is by default. You can catch the exception with @rescue_from@ call and do something you like: make a redirect, or render "Access denied" template, or whatever.

Second case is obtained with specifying method name as an argument to @access_control@ (or using @:as_method@ option, see below) and may be helpful if you want to use @skip_before_filter@ somewhere in the derived controller.

Third case will take place if you supply @:filter => false@ along with method name. You'll get an ordinary method which you can call anywhere you want.

h3. :subject_method

Acl9 obtains the subject instance by calling specific method of the controller. By default it's @:current_user@, but you may change it.


  class MyController < ApplicationController 
    access_control :subject_method => :current_account do
      allow :nifty
      # ...
    end

    # ...
  end

Subject method can also be changed globally. Place the following into @config/initializers/acl9.rb@:


  Acl9::config[:default_subject_method] = :current_account

h3. :debug

@:debug => true@ will output the filtering expression into the debug log. If Acl9 does something strange, you may look at it as the last resort.

h3. :as_method

In the case


  class NiftyController < ApplicationController 
    access_control :as_method => :acl do
      allow :nifty
      # ...
    end

    # ...
  end

access control checks will be added as @acl@ method onto MyController, with @before_filter :acl@ call thereafter.

Instead of using @:as_method@ you may specify the name of the method as a positional argument to @access_control@:


  class MyController < ApplicationController 
    access_control :acl do
      # ...
    end

    # ...
  end

h3. :filter

If you set @:filter@ to @false@ (it's @true@ by default) and also use @:as_method@ (or method name as 1st argument to @access_control@, you'll get a method which won't raise @Acl9::AccessDenied@ exception, but rather return @true@ or @false@ (access allowed/denied).


  class SecretController < ApplicationController 
    access_control :secret_access?, :filter => false do
      allow :james_bond
      # ...
    end

    def index
      if secret_access?
        _secret_index
      else
        _ordinary_index
      end
    end

    # ...

    private

    def _secret_index
      # ...
    end
    
    def _ordinary_index
      # ...
    end
  end

The generated method can receive an objects hash as an argument. In this example,


  class LolController < ApplicationController 
    access_control :lolcats?, :filter => false do
      allow :cats, :by => :lol
      # ...
    end
  end

you may not only call @lolcats?@ with no arguments, which will basically return


  current_user.has_role?('cats', @lol)

but also as @lolcats?(:lol => Lol.find(params[:lol]))@. The hash will be looked into first, even if you have an instance variable @lol@.

h3. :helper

Sometimes you want to have a boolean method (like @:filter => false@) accessible in your views. Acl9 can call @helper_method@ for you:


  class LolController < ApplicationController 
    access_control :helper => :lolcats? do
      allow :cats, :by => :lol
      # ...
    end
  end

That's equivalent to


  class LolController < ApplicationController 
    access_control :lolcats?, :filter => false do
      allow :cats, :by => :lol
      # ...
    end

    helper_method :lolcats?
  end

h3. Other options

Other options will be passed to @before_filter@. As such, you may use @:only@ and @:except@ to narrow the action scope of the whole ACL block.


  class OmgController < ApplicationController 
    access_control :only => [:index, :show] do
      allow all
      deny :banned
    end

    # ...
  end

is basically equivalent to


  class OmgController < ApplicationController 
    access_control do
      actions :index, :show do
        allow all
        deny :banned
      end

      allow all, :except => [:index, :show]
    end

    # ...
  end

h2. access_control in your helpers

Apart from using @:helper@ option for @access_control@ call inside controller, there's a way to generate helper methods directly, like this:


  module SettingsHelper
    include Acl9Helpers

    access_control :show_settings? do
      allow :admin
      allow :settings_manager
    end
  end

Here we mix in @Acl9Helpers@ module which brings in @access_control@ method and call it, obtaining @show_settings?@ method.

An imaginary view:


  <% if show_settings? %>
    <%= link_to 'Settings', settings_path %>
  <% end %>

Copyright (c) 2009 Oleg Dashevskii, released under the MIT license.

FAQs

Package last updated on 11 Aug 2014

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc