SimpleAccessControl

Ruby gem port of an old Ruby Plugin

Installation

Add this line to your application's Gemfile:

gem 'simple_access_control'

And then execute:

$ bundle

Or install it yourself as:

$ gem install simple_access_control

Usage

To test locally you can do

  1. git clone [email protected]:owainlewis/simple-access-control.git && cd simple-access-control
  2. rake test
  3. rake install (to install the gem locally)

Once installed locally you can put it into your Gemfile like this

source 'https://rubygems.org'

gem 'simple_access_control'

To include the code add the following to ApplicationController.rb

class ApplicationController < ActionController::Base

  include SimpleAccessControl

  def current_user
    # return the current user
  end
  helper_method :current_user

  def logged_in?
    # return boolean user logged in or not
  end
  helper_method :logged_in?

  protect_from_forgery with: :exception
end

Development

After checking out the repo, run bin/setup to install dependencies. Then, run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release to create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

  1. Fork it ( https://github.com/[my-github-username]/simple_access_control/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Full Information

SimpleAccessControl is a streamlined, intuitive authorisation system. It derives heavily from acl_system2 and has made clear some problems which plagued me when first using it. Some fixes to acl_system2's design:

  * a normal Rails syntax:
        access_rule 'admin', :only => :index
        access_rule '(moderator || admin)', :only => :new
  * error handling for helper methods (permit? bombed when current_user == nil)
  * one-line parser, easy to replace or alter
  * proper before_filter usage, meaning access rules are parsed only when needed
  * no overrideable default (which I found counter-intuitive in the end)

Also, it has two methods, access_control and permit?, for those moving from acl_system2.

But, let me stress, everyone likes a slightly different system, so this one may not be your style. I find it synchronises very well with the interface of Acts as Authenticated (even though I have modified it so much that it's now called Authenticated Cookie).

INSTALLATION

Create the following migration:

create_table "roles", :force => true do |t| t.column "title", :string end create_table "roles_users", :id => false, :force => true do |t| t.column "role_id", :integer t.column "user_id", :integer end

In your User model, you must have:

has_and_belongs_to_many :roles

In your Roles model, you must have:

has_and_belongs_to_many :users

Your controllers must have the following two methods or variants of them:

# Returns a User object def current_user @current_user end

# Returns true or false if a User object exists for this session def logged_in? @current_user.is_a? User end

SPECIAL NEEDS

If you want to permit anonymous users without demanding that they are logged in, first you must ensure that logged_in? returns true in all cases, otherwise permission will be denied. The following approach should work:

  1. Create the 'guest' and 'user' roles, e.g.:

guest = Role.create(:title => 'guest') user = Role.create(:title => 'user')

  1. In your registration/user creation area, ensure all real users have the 'user' role, e.g.:

@user = User.create(params[:user]) unless @user.roles.any? { |r| r.title == 'user' } @user.roles << Role.find_by_title('user') end @user.save

[At this point you have two options: a real or virtual anonymous account]

First Approach: Real Anonymous User

3a. Create an anonymous user, e.g.:

@anonymous = User.create(:login => 'anonymous', :password => '*', :activated => true)

4a. Add the role to the Anonymous user (in a migration or in script/console), e.g.:

anonymous.roles << Role.find_by_title('guest')
anonymous.save

5a. In your ApplicationController, set unauthenticated users as 'anonymous', e.g.:

before_filter :default_to_guest

def default_to_guest
  self.current_user = User.('anonymous', :include => :roles) unless logged_in?
end

Second Approach: Virtual Anonymous User

3a. In your ApplicationController, create a virtual anonymous account if unauthenticated:

before_filter :default_to_virtual_guest
def default_to_virtual_guest
  self.current_user = self.anonymous_user unless logged_in?
end

def anonymous_user
  anonymous = User.new(:login => 'anonymous', :name => 'Guest')
  anonymous.roles << Role.new(:title => 'guest')
  anonymous.readonly!
  anonymous
end

USAGE

The plugin is automatically hooked into ActionController::Base.

In your controllers, add access rules like so:

access_rule 'admin', :only => :destroy access_rule 'user || admin', :only => [:new, :create, :edit, :update]

Note the use of Ruby-style operators. These strings are real conditionals and should be treated as such. Every grouping of non-operator characters will be considered a role title.

In your views, you can use the following:

  <% restrict_to 'admin || moderator' do %>
    <%= link_to "Admin Area", admin_area_url %>
  <% end %>

AND

  <%= link_to("Admin Area", admin_area_url) if has_permission?('admin || moderator') %>

There are also transitional methods which help you move from acl_system2 to this plugin -- I do this not to denegrate acl_system2 but because I did this for myself and decided to include it. The two systems are rather similar.

Also, there are two callbacks, permission_granted and permission_denied, which may define in your controllers to customise their response. For example:


  def permission_granted
    logger.info("[authentication] Permission granted to %s at %s for %s" %
      [(logged_in? ? current_user. : 'guest'), Time.now, request.request_uri])
  end

  def permission_denied
    logger.info("[authentication] Permission denied to %s at %s for %s" %
      [(logged_in? ? current_user. : 'guest'), Time.now, request.request_uri])
  end

That's it!