This example is based on LoginGeneratorAccessControlList (which was based on ACLController).
It uses AccessControlListExample database model and assumes you used the LoginGenerator.
It adds Roles such that
Users <-> Roles <-> Permissions
A User’s permissions are added to their session when they authenticate. Permissions are of the form {controller}/{method}. For example:
posts/add posts/edit posts/index #use this if you want to protect "url/posts"
Using before_filter :require_login will require the user to have the correct permission string before the action is performed. If the user is unauthorized, they are returned to the requesting page. You may use the :except attribute to allow anyone to access a specific method.
Permissions are kept as part of their session. Any changes made to the User’s permissions will not take effect until they login again. The filters added to this controller will be run for all controllers in the application. Likewise all the methods added be available for all controllers.
To Install:
Put this into lib/acl_system.rb:
# See <a href="http://wiki.rubyonrails.com/rails/show/LoginGeneratorACLSystem">http://wiki.rubyonrails.com/rails/show/LoginGeneratorACLSystem</a>
module ACLSystem
include LoginSystem
# This module wires itself into the LoginSystem authorize? method. You
# should use the normal:
#
# before_filter :login_required
#
# or to leave some actions unprotected:
#
# before_filter :login_required, :except => [ :list, :show ]
#
protected
# Authorizes the user for an action.
# This works in conjunction with the LoginController.
# The LoginController loads the User object.
def authorize?(user)
required_perm = "%s/%s" % [ params['controller'], params['action'] ]
if user.authorized? required_perm
return true
end
return false
end
end
Put this into app/models/permission.rb:
# See <a href="http://wiki.rubyonrails.com/rails/show/AccessControlListExample">http://wiki.rubyonrails.com/rails/show/AccessControlListExample</a> # and <a href="http://wiki.rubyonrails.com/rails/show/LoginGeneratorAccessControlList">http://wiki.rubyonrails.com/rails/show/LoginGeneratorAccessControlList</a> class Permission < ActiveRecord::Base has_and_belongs_to_many :roles end
Put this into app/models/role.rb:
# See <a href="http://wiki.rubyonrails.com/rails/show/AccessControlListExample">http://wiki.rubyonrails.com/rails/show/AccessControlListExample</a> # and <a href="http://wiki.rubyonrails.com/rails/show/LoginGeneratorAccessControlList">http://wiki.rubyonrails.com/rails/show/LoginGeneratorAccessControlList</a> class Role < ActiveRecord::Base has_and_belongs_to_many :permissions has_and_belongs_to_many :users end
Then edit your controllers/application.rb to resemble:
# The filters added to this controller will be run for all controllers in the application. # Likewise will all the methods added be available for all controllers. require_dependency "acl_system" class ApplicationController < ActionController::Base include ACLSystem model :user end
Add the following to your user model:
has_and_belongs_to_many :roles
# Return true/false if User is authorized for resource.
def authorized?(resource)
return permission_strings.include?(resource)
end
# Load permission strings
def permission_strings
a = []
self.roles.each{|r| r.permissions.each{|p| a<< p.name }}
a
end
To use it, just call the normal LoginGenerator filter in your controllers:
before_filter :login_required
before_filter :login_required, :except => [ :list, :show ]
-JamesHillyerd
QUESTION
How would you distinguish an admin from a regular user?
ANSWER
To distinguish an admin from a regular user you need to add entries to the roles, users_roles, permissions, permissions_roles tables. For two users, moe and curly, identfied with 1 and 2, respectively:
INSERT INTO roles (id, name) VALUES (1, 'admin') INSERT INTO roles (id, name) VALUES (2, 'regular') INSERT INTO roles_users (user_id, role_id) VALUES (1, 1) INSERT INTO roles_users (user_id, role_id) VALUES (2, 2) INSERT INTO permissions (id, name) VALUES (1, 'admin/index') INSERT INTO permissions_roles (permission_id, role_id) VALUES (1, 1)
Now, moe has a ‘superuser’ role, this role has permission to do the admin/index action.
-TerryLorber
QUESTIONQUESTION
This says “A User’s permissions are added to their session when they authenticate.” How does this happen? It looks like the permissions are rebuilt every time authorized? is called.
ANSWER
I think you are correct: the permissions strings are rebuild on each call. I don’t think that ruby actually has to query the database each time, as user.roles should be lazy-loaded once and then cached.
It should be trivial to modify the user object to cache the strings. I’m not actively using this code right now, so if someone else can try it, test it and then modify this page, it would be appreciated!
-JamesHillyerd
A straight forward extension would be to keep permissions as regexp:s and do regexp matching. This would enable you to do compact patterns for an admin.
insert into permissions(name,info) values('.*/.*', 'All access');
def authorized?(resource)
match=false
permission_strings.each do |p|
r = Regexp.new(p)
match = match || ((r =~ resource) != nil)
end
return match
end
-FredrikAndersson
I have just extended this with the concept of access-levels.
The short story:
if session[:user] and authorize?(session[:user],:object => @object) return true end
The authorize?-method needs to take a second argument, “arguments”, but just passes it on to user.authorized?
def authorized?(resource,arguments)
if arguments.nil?
return permission_strings.include?(resource)
end
if !arguments[:object].nil?
return permission_strings.include?(resource) &&
(permission_levels[resource].to_i.eql?(2) || arguments[:object].creator.id.to_i.eql?(self.id.to_i))
end
end
def permission_levels
b = {}
self.roles.each do |r|
r.permissions.each do |p|
if b.empty? || b[p.name].nil? || p.level.to_i > b[p.name].to_i
b[p.name] = p.level.to_i
end
end
end
b
end
Now, the last and least elegant thing in my solution is my current strategy for finding what type of object the ID refers to. I have the controller-name, but how can I use this to find the object with id @params[‘id’] ? For now, I have hard-coded it in login_required in login_system.rb:
if !params['id'].nil?
if params['controller'].eql?('news')
@object = News.find(params['id'])
end
if session[:user] and authorize?(session[:user],:object => @object)
return true
end
else
if session[:user] and authorize?(session[:user],nil)
return true
end
end
How can I find the class-name automagically so that I can use it instead of the hard-coded News.find ?
- VegardEngen
def model_class klass_name=Inflector.classify(controller_name) Inflector.constantize(klass_name) end ... model_class.find(params['id'])
QUESTION
Perhaps I’m missing it, very very newby, but is it possible to block access? as in:
Jane has access to the whole house (living room, kitchen, bathroom, bedroom)
John has access to all rooms BUT the bedroom
Jane gets house/.* John gets house/.* - and - not house/bedroom
thanks!
- Jay Wiggins
ANSWER
Using FredrikAndersson’s regex example above, you can create these permissions:
house/.*
house/[^(bedroom)]
house/(?!(bedroom))
works.
- Calle GustafssonI am getting the following error :
protected method `authorized?' called for #\< User:0x3970708\>
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/base.rb:1494:in `method_missing'
#{RAILS_ROOT}/lib/acl_system.rb:22:in `authorize?'
#{RAILS_ROOT}/lib/login_system.rb:49:in `login_required'
what could be a solution?
Vinit
Rolled back to last good information.
Put the functions authorized? and permission_strings above the “protected” in user.rb
goodi
Updated from @session/@params to session/params
~ ba
Rails newbie here… For some reason after I moved from the simple login authentication to trying ACL I get redirected from my target page to the login page again after I login. Wh at gives??