forked from boxuk/acl_system2
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial import of refactored acl_system
git-svn-id: http://opensvn.csie.org/ezra/rails/plugins/dev/acl_system2@75 e8081bd4-020a-0410-a7c4-8039225954a7
- Loading branch information
loob2
committed
Mar 10, 2006
0 parents
commit ecc0327
Showing
8 changed files
with
542 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
Copyright (c) 2006 Ezra Zygmuntowicz & Fabien Franzen | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining | ||
a copy of this software and associated documentation files (the | ||
"Software"), to deal in the Software without restriction, including | ||
without limitation the rights to use, copy, modify, merge, publish, | ||
distribute, sublicense, and/or sell copies of the Software, and to | ||
permit persons to whom the Software is furnished to do so, subject to | ||
the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be | ||
included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
Welcome to the acl_system plugin for rails. This plugin is designed to give you a | ||
flexible declarative way of protecting your various controller actions using roles. | ||
It's made to site on top of any authentication framework that follows a few conventions. | ||
You will need to have a current_user method that returns the currently logged in user. | ||
And you will need to make your User or Account model(or whatever you named it) have a | ||
has_and_belongs_to_many :roles. So you need a model called Role that has a title attribute. | ||
Once these two things are satisfied you can use this plugin. | ||
|
||
So lets take a look at the sugar you get from using this plugin. Keep in mind that the | ||
!blacklist part isn’t really necessary here. I was just showing it as an example of how | ||
flexible the permissions string logic parser is. | ||
|
||
class PostController < ApplicationController | ||
before_filter :login_required, :except => [:list, :index] | ||
access_control [:new, :create, :update, :edit] => '(admin | user | moderator)', | ||
:delete => 'admin & (!moderator & !blacklist)' | ||
|
||
Of course you can define them all seperately if they differ at all. | ||
|
||
class PostController < ApplicationController | ||
before_filter :login_required, :except => [:list, :index] | ||
access_control :new => '(admin | user | moderator) & !blacklist', | ||
:create => 'admin & !blacklist', | ||
:edit => '(admin | moderator) & !blacklist', | ||
:update => '(admin | moderator) & !blacklist', | ||
:delete => 'admin & (!moderator | !blacklist)' | ||
|
||
And you can also use :DEFAULT if you have a lot of actions that need the same permissions. | ||
|
||
class PostController < ApplicationController | ||
before_filter :login_required, :except => [:list, :index] | ||
access_control :DEFAULT => '!guest' | ||
[:new, :create, :update, :edit] => '(admin | user | moderator)', | ||
:delete => 'admin & (!moderator & !blacklist)' | ||
|
||
There are two callback methods you can use to define your own success and failure behaviors. | ||
If you define access_allowed and/or access_denied as protected methods in your controller you | ||
can redirect or render and error page or whatever else you might want to do if access is allowed | ||
or denied. | ||
|
||
class PostController < ApplicationController | ||
before_filter :login_required, :except => [:list, :index] | ||
access_control :DEFAULT => '!guest' | ||
[:new, :create, :update, :edit] => '(admin | user | moderator)', | ||
:delete => 'admin & (!moderator & !blacklist)' | ||
|
||
# the rest of your controller here | ||
|
||
protected | ||
|
||
def access_denied | ||
flash[:notice] = "You don't have privileges to access this action" | ||
return redirect_to :action => 'denied' | ||
end | ||
|
||
def access_allowed | ||
flash[:notice] = "Welcome to the secure area of foo.com!" | ||
end | ||
|
||
end | ||
|
||
There is also a helper method that can be used in the view or controller. In the view its | ||
handy for conditional menus or stuff like that. | ||
|
||
<% if restrict_to "(admin | moderator) & !blacklist" %> | ||
<%= link_to "Admin & Moderator only link", :action =>'foo' %> | ||
<% end %> | ||
|
||
So the gist of it is that in the access_control controller macro, you can assign | ||
permission logic strings to actions in your controller. You supply a hash of | ||
:action => ‘permissions string” pairs. Any action not in the list is left open to | ||
any user. Any action with a logic string gets evaluated on each request to see if | ||
the current user has the right role to access the action. The plugin has a small | ||
recursive descent parser that evaluates the permission logic strings against the | ||
current_user.roles. | ||
|
||
The way this works is that you have your User model and a Role model. User <= habtm => Role. | ||
So when an action that is access_control’ed gets requested the permission logic string | ||
gets evaluated against the current_user.roles . So a prerequisite of using this plugin | ||
is that you add a Role model with a title attribute that has_and_belongs_to_many User | ||
models. And you need to have a current_user method defined somewhere in your controllers | ||
or user system. Luckily the acts_as_authenticated plugin has the current_user defined already. | ||
|
||
So here is the schema of this application including the Post model and the User and Role | ||
model plus the habtm join table: | ||
|
||
ActiveRecord::Schema.define(:version => 3) do | ||
create_table "posts", :force => true do |t| | ||
t.column "title", :string, :limit => 40 | ||
t.column "body", :text | ||
end | ||
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 | ||
create_table "users", :force => true do |t| | ||
t.column "login", :string, :limit => 40 | ||
t.column "email", :string, :limit => 100 | ||
t.column "crypted_password", :string, :limit => 40 | ||
t.column "salt", :string, :limit => 40 | ||
t.column "created_at", :datetime | ||
t.column "updated_at", :datetime | ||
end | ||
end | ||
And so thats pretty much it for now. You add the roles to the Role.title attribute like admin, | ||
moderator and blacklist like above. These can be anything you want them to be, roles, groups | ||
or whatever. Then you can use as many nested parens and logic with & | ! as you want to | ||
define your complex permissions for accessing your controller. Make sure that your access_control | ||
gets called after the login_required before_filter because we assume that you are already | ||
logged in if you made it this far and then we eval the permissions logic. | ||
|
||
You will want to define these access_control in each controller that needs specific permissions. | ||
unless you want to protect the same actions in all controllers, then you can put it in | ||
application.rb but I dont recommend it. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
require 'caboose/logic_parser' | ||
require 'caboose/role_handler' | ||
require 'caboose/access_control' | ||
|
||
# | ||
ActionController::Base.send :include, Caboose | ||
ActionController::Base.send :include, Caboose::AccessControl | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
|
||
module Caboose | ||
|
||
module AccessControl | ||
|
||
def self.included(subject) | ||
subject.extend(ClassMethods) | ||
if subject.respond_to? :helper_method | ||
subject.helper_method(:permit?) | ||
subject.helper_method(:restrict_to) | ||
end | ||
end | ||
|
||
module ClassMethods | ||
# access_control [:create, :edit] => 'admin & !blacklist', | ||
# :update => '(admin | moderator) & !blacklist', | ||
# :list => '(admin | moderator | user) & !blacklist' | ||
def access_control(actions={}) | ||
# Add class-wide permission callback to before_filter | ||
defaults = {} | ||
yield defaults if block_given? | ||
before_filter do |c| | ||
c.default_access_context = defaults | ||
@access = AccessSentry.new(c, actions) | ||
if @access.allowed?(c.action_name) | ||
c.access_allowed if c.respond_to?(:access_allowed) | ||
else | ||
if c.respond_to?(:access_denied) | ||
c.access_denied | ||
else | ||
c.send(:render_text, "You have insuffient permissions") | ||
end | ||
end | ||
end | ||
end | ||
end # ClassMethods | ||
|
||
# return the active access handler, fallback to RoleHandler | ||
# implement #retrieve_access_handler to return non-default handler | ||
def access_handler | ||
if respond_to?(:retrieve_access_handler) | ||
@handler ||= retrieve_access_handler | ||
else | ||
@handler ||= RoleHandler.new(Role) | ||
end | ||
end | ||
|
||
# the current access context; will be created if not setup | ||
# will add current_user and merge any other elements of context | ||
def access_context(context = {}) | ||
default_access_context.merge(context) | ||
end | ||
|
||
def default_access_context | ||
@default_access_context ||= {} | ||
@default_access_context[:user] ||= send(:current_user) if respond_to?(:current_user) | ||
@default_access_context | ||
end | ||
|
||
def default_access_context=(defaults) | ||
@default_access_context = defaults | ||
end | ||
|
||
def permit?(logicstring, context = {}) | ||
access_handler.process(logicstring, access_context(context)) | ||
end | ||
|
||
# restrict_to "admin | moderator" do | ||
# link_to "foo" | ||
# end | ||
def restrict_to(logicstring, context = {}) | ||
return false if current_user.nil? | ||
result = '' | ||
if permit?(logicstring, context) | ||
result = yield if block_given? | ||
end | ||
result | ||
end | ||
|
||
class AccessSentry | ||
|
||
def initialize(subject, actions={}) | ||
@actions = actions.inject({}) do |auth, current| | ||
[current.first].flatten.each { |action| auth[action] = current.last } | ||
auth | ||
end | ||
@subject = subject | ||
end | ||
|
||
def allowed?(action) | ||
if @actions.has_key? action.to_sym | ||
return @subject.access_handler.process(@actions[action.to_sym].dup, @subject.access_context) | ||
elsif @actions.has_key? :DEFAULT | ||
return @subject.access_handler.process(@actions[:DEFAULT].dup, @subject.access_context) | ||
else | ||
return true | ||
end | ||
end | ||
|
||
end # AccessSentry | ||
|
||
end # AccessControl | ||
|
||
end # Caboose | ||
|
||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
module Caboose | ||
|
||
module LogicParser | ||
# This module holds our recursive descent parser that take a logic string | ||
# the logic string is tested by the enclosing Handler class' #check method | ||
# Include this module in your Handler class. | ||
|
||
# recursively processes an permission string and returns true or false | ||
def process(logicstring, context) | ||
# if logicstring contains any parenthasized patterns, call process recursively on them | ||
while logicstring =~ /\(/ | ||
logicstring.sub!(/\(([^\)]+)\)/) { | ||
process($1, context) | ||
} | ||
end | ||
|
||
# process each operator in order of precedence | ||
#! | ||
while logicstring =~ /!/ | ||
logicstring.sub!(/!([^ &|]+)/) { | ||
(!check(logicstring[$1], context)).to_s | ||
} | ||
end | ||
|
||
#& | ||
if logicstring =~ /&/ | ||
return (process(logicstring[/^[^&]+/], context) and process(logicstring[/^[^&]+&(.*)$/,1], context)) | ||
end | ||
|
||
#| | ||
if logicstring =~ /\|/ | ||
return (process(logicstring[/^[^\|]+/], context) or process(logicstring[/^[^\|]+\|(.*)$/,1], context)) | ||
end | ||
|
||
# constants | ||
if logicstring =~ /^\s*true\s*$/i | ||
return true | ||
elsif logicstring =~ /^\s*false\s*$/i | ||
return false | ||
end | ||
|
||
# single list items | ||
(check(logicstring.strip, context)) | ||
end | ||
|
||
end # LogicParser | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
module Caboose | ||
|
||
class AccessHandler | ||
include LogicParser | ||
|
||
#@acls = {} | ||
|
||
#def register_key(key) | ||
# key.downcase! | ||
# if @acls.has_key? key | ||
# raise DuplicateKeyError, "An Key named '#{key}' is already registered" | ||
# end | ||
# @acls[key] = KeyLookup.new(key) | ||
#end | ||
|
||
def check(key, context) | ||
false | ||
end | ||
|
||
end | ||
|
||
# The RoleHandler hold a collection of RoleLookup objects | ||
# one RoleLookup object gets initialized fror every key.title | ||
# returned from Role.find(:all). so your set up a RoleHandler | ||
# and give it the Role model object. | ||
# @handler = RoleHandler.new(Role) | ||
# @handler.process("(admin | moderator) & !blacklisted", @user) | ||
class RoleHandler < AccessHandler | ||
|
||
# loads an ActiveRecord Role model and registers all the keys. | ||
def initialize(model) | ||
#@acls = {} | ||
#model.find(:all).each do |role| | ||
# register_key(role.title) | ||
#end | ||
end | ||
|
||
def check(key, context) | ||
#return false unless @acls[key] | ||
context[:user].roles.map{ |role| role.title.downcase}.include? key | ||
end | ||
|
||
end # End RoleHandler | ||
|
||
=begin | ||
# Does a check using an active record lookup on a key model | ||
class KeyLookup | ||
# key gets passed in here. | ||
def initialize(key) | ||
@key = key | ||
end | ||
# Check gets called with context. context has a user model object | ||
# in context[:user]. We collect all the user.keys title attribute | ||
# and see if @key is included in those key.title's | ||
def check(user) | ||
user.roles.map{ |role| role.title.downcase}.include? @key | ||
end | ||
end # END KeyLookup | ||
# raised when two Key's of the same name are registered | ||
class DuplicateKeyError < StandardError | ||
end | ||
=end | ||
|
||
end |
Oops, something went wrong.