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
- git clone [email protected]:owainlewis/simple-access-control.git && cd simple-access-control
- rake test
- 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
- Fork it ( https://github.com/[my-github-username]/simple_access_control/fork )
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - 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:
- Create the 'guest' and 'user' roles, e.g.:
guest = Role.create(:title => 'guest') user = Role.create(:title => 'user')
- 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.find_by_login('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
logger.info("[authentication] Permission granted to %s at %s for %s" %
[(logged_in? ? current_user.login : 'guest'), Time.now, request.request_uri])
end
def
logger.info("[authentication] Permission denied to %s at %s for %s" %
[(logged_in? ? current_user.login : 'guest'), Time.now, request.request_uri])
end
That's it!