nbrew-simple_access_control 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/README +162 -0
- data/Rakefile +43 -0
- data/VERSION +1 -0
- data/install.rb +1 -0
- data/lib/simple_access_control.rb +128 -0
- data/lib/tasks/simple_access_control_tasks.rake +4 -0
- data/rails/init.rb +3 -0
- data/test/helper.rb +10 -0
- data/test/simple_access_control_test.rb +8 -0
- metadata +90 -0
data/.gitignore
ADDED
data/README
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
SimpleAccessControl
|
2
|
+
===================
|
3
|
+
|
4
|
+
Acknowledgements: I give all credit to Ezra and Technoweenie for their two plugins which
|
5
|
+
inspired the interface design and a lot of the code for this one.
|
6
|
+
|
7
|
+
SimpleAccessControl is a streamlined, intuitive authorisation system. It derives heavily from
|
8
|
+
acl_system2 and has made clear some problems which plagued me when first using it. Some
|
9
|
+
fixes to acl_system2's design:
|
10
|
+
|
11
|
+
* a normal Rails syntax:
|
12
|
+
access_rule 'admin', :only => :index
|
13
|
+
access_rule '(moderator || admin)', :only => :new
|
14
|
+
* error handling for helper methods (permit? bombed when current_user == nil)
|
15
|
+
* one-line parser, easy to replace or alter
|
16
|
+
* proper before_filter usage, meaning access rules are parsed only when needed
|
17
|
+
* no overrideable default (which I found counter-intuitive in the end)
|
18
|
+
|
19
|
+
Also, it has two methods, access_control and permit?, for those moving from acl_system2.
|
20
|
+
|
21
|
+
But, let me stress, everyone likes a slightly different system, so this one may not be
|
22
|
+
your style. I find it synchronises very well with the interface of Acts as Authenticated (even
|
23
|
+
though I have modified it so much that it's now called Authenticated Cookie).
|
24
|
+
|
25
|
+
INSTALLATION
|
26
|
+
============
|
27
|
+
|
28
|
+
Create the following migration:
|
29
|
+
|
30
|
+
create_table "roles", :force => true do |t|
|
31
|
+
t.column "title", :string
|
32
|
+
end
|
33
|
+
create_table "roles_users", :id => false, :force => true do |t|
|
34
|
+
t.column "role_id", :integer
|
35
|
+
t.column "user_id", :integer
|
36
|
+
end
|
37
|
+
|
38
|
+
In your User model, you must have:
|
39
|
+
|
40
|
+
has_and_belongs_to_many :roles
|
41
|
+
|
42
|
+
In your Roles model, you must have:
|
43
|
+
|
44
|
+
has_and_belongs_to_many :users
|
45
|
+
|
46
|
+
Your controllers must have the following two methods or variants of them:
|
47
|
+
|
48
|
+
# Returns a User object
|
49
|
+
def current_user
|
50
|
+
@current_user
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns true or false if a User object exists for this session
|
54
|
+
def logged_in?
|
55
|
+
@current_user.is_a? User
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
SPECIAL NEEDS
|
60
|
+
=============
|
61
|
+
|
62
|
+
If you want to permit anonymous users without demanding that they are logged in, first you
|
63
|
+
must ensure that logged_in? returns true in all cases, otherwise permission will be denied.
|
64
|
+
The following approach should work:
|
65
|
+
|
66
|
+
1. Create the 'guest' and 'user' roles, e.g.:
|
67
|
+
|
68
|
+
guest = Role.create(:title => 'guest')
|
69
|
+
user = Role.create(:title => 'user')
|
70
|
+
|
71
|
+
2. In your registration/user creation area, ensure all real users have the 'user' role, e.g.:
|
72
|
+
|
73
|
+
@user = User.create(params[:user])
|
74
|
+
unless @user.roles.any? { |r| r.title == 'user' }
|
75
|
+
@user.roles << Role.find_by_title('user')
|
76
|
+
end
|
77
|
+
@user.save
|
78
|
+
|
79
|
+
[At this point you have two options: a real or virtual anonymous account]
|
80
|
+
|
81
|
+
First Approach: Real Anonymous User
|
82
|
+
|
83
|
+
3a. Create an anonymous user, e.g.:
|
84
|
+
|
85
|
+
@anonymous = User.create(:login => 'anonymous', :password => '*', :activated => true)
|
86
|
+
|
87
|
+
4a. Add the role to the Anonymous user (in a migration or in script/console), e.g.:
|
88
|
+
|
89
|
+
anonymous.roles << Role.find_by_title('guest')
|
90
|
+
anonymous.save
|
91
|
+
|
92
|
+
5a. In your ApplicationController, set unauthenticated users as 'anonymous', e.g.:
|
93
|
+
|
94
|
+
before_filter :default_to_guest
|
95
|
+
|
96
|
+
def default_to_guest
|
97
|
+
self.current_user = User.find_by_login('anonymous', :include => :roles) unless logged_in?
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
Second Approach: Virtual Anonymous User
|
102
|
+
|
103
|
+
3a. In your ApplicationController, create a virtual anonymous account if unauthenticated:
|
104
|
+
|
105
|
+
before_filter :default_to_virtual_guest
|
106
|
+
def default_to_virtual_guest
|
107
|
+
self.current_user = self.anonymous_user unless logged_in?
|
108
|
+
end
|
109
|
+
|
110
|
+
def anonymous_user
|
111
|
+
anonymous = User.new(:login => 'anonymous', :name => 'Guest')
|
112
|
+
anonymous.roles << Role.new(:title => 'guest')
|
113
|
+
anonymous.readonly!
|
114
|
+
anonymous
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
USAGE
|
119
|
+
=====
|
120
|
+
|
121
|
+
The plugin is automatically hooked into ActionController::Base.
|
122
|
+
|
123
|
+
In your controllers, add access rules like so:
|
124
|
+
|
125
|
+
access_rule 'admin', :only => :destroy
|
126
|
+
access_rule 'user || admin', :only => [:new, :create, :edit, :update]
|
127
|
+
|
128
|
+
Note the use of Ruby-style operators. These strings are real conditionals and should be treated as
|
129
|
+
such. Every grouping of non-operator characters will be considered a role title.
|
130
|
+
|
131
|
+
In your views, you can use the following:
|
132
|
+
|
133
|
+
<% restrict_to 'admin || moderator' do %>
|
134
|
+
<%= link_to "Admin Area", admin_area_url %>
|
135
|
+
<% end %>
|
136
|
+
|
137
|
+
AND
|
138
|
+
|
139
|
+
<%= link_to("Admin Area", admin_area_url) if has_permission?('admin || moderator') %>
|
140
|
+
|
141
|
+
There are also transitional methods which help you move from acl_system2 to this plugin -- I do this
|
142
|
+
not to denegrate acl_system2 but because I did this for myself and decided to include it. The two
|
143
|
+
systems are rather similar.
|
144
|
+
|
145
|
+
Also, there are two callbacks, permission_granted and permission_denied, which may define in your
|
146
|
+
controllers to customise their response. For example:
|
147
|
+
|
148
|
+
def permission_granted
|
149
|
+
logger.info("[authentication] Permission granted to %s at %s for %s" %
|
150
|
+
[(logged_in? ? current_user.login : 'guest'), Time.now, request.request_uri])
|
151
|
+
end
|
152
|
+
|
153
|
+
def permission_denied
|
154
|
+
logger.info("[authentication] Permission denied to %s at %s for %s" %
|
155
|
+
[(logged_in? ? current_user.login : 'guest'), Time.now, request.request_uri])
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
That's it!
|
160
|
+
|
161
|
+
|
162
|
+
VARIATION BY MABS29
|
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
|
6
|
+
|
7
|
+
begin
|
8
|
+
require 'jeweler'
|
9
|
+
Jeweler::Tasks.new do |gem|
|
10
|
+
gem.name = "nbrew-simple_access_control"
|
11
|
+
gem.summary = %Q{Simple role-based access control plugin for Rails controllers and views.}
|
12
|
+
gem.description = %Q{Simple access controls for use with ActsAsAuthenticated, RestfulAuthentication, etc. A clone of Mathew Abonyi's (mabs29) repo: http://mabs29.googlecode.com/svn/trunk/plugins/simple_access_control}
|
13
|
+
gem.email = "nhyde@bigdrift.com"
|
14
|
+
gem.homepage = "http://github.com/nbrew/simple_access_control"
|
15
|
+
gem.authors = ["Mathew Abonyi"]
|
16
|
+
gem.add_development_dependency "shoulda", ">= 0"
|
17
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
18
|
+
end
|
19
|
+
Jeweler::GemcutterTasks.new
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
desc 'Default: run unit tests.'
|
26
|
+
task :test => :check_dependencies
|
27
|
+
task :default => :test
|
28
|
+
|
29
|
+
desc 'Test the simple_access_control plugin.'
|
30
|
+
Rake::TestTask.new(:test) do |t|
|
31
|
+
t.libs << 'lib'
|
32
|
+
t.pattern = 'test/**/*_test.rb'
|
33
|
+
t.verbose = true
|
34
|
+
end
|
35
|
+
|
36
|
+
desc 'Generate documentation for the simple_access_control plugin.'
|
37
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
38
|
+
rdoc.rdoc_dir = 'rdoc'
|
39
|
+
rdoc.title = 'SimpleAccessControl'
|
40
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
41
|
+
rdoc.rdoc_files.include('README')
|
42
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
43
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# SimpleAccessControl
|
2
|
+
#
|
3
|
+
# Acknowledgements: I give all credit to Ezra and Technoweenie for their two plugins which
|
4
|
+
# inspired the interface design and a lot of the code for this one.
|
5
|
+
#
|
6
|
+
# SimpleAccessControl is a streamlined, intuitive authorisation system. It derives heavily from
|
7
|
+
# acl_system2 and has made clear some problems which plagued the author when first using it. Some
|
8
|
+
# fixes to acl_system2's design:
|
9
|
+
#
|
10
|
+
# * a normal Rails syntax:
|
11
|
+
# access_rule 'admin', :only => :index
|
12
|
+
# access_rule '(moderator || admin)', :only => :new
|
13
|
+
# * error handling for helper methods (permit? bombs out with current_user == nil)
|
14
|
+
# * one-line parser, easy to replace or alter
|
15
|
+
# * proper before_filter usage, meaning access rules are parsed only when needed
|
16
|
+
# * no overrideable default (which I found counter-intuitive in the end)
|
17
|
+
#
|
18
|
+
# Also, it has two methods, access_control and permit?, for those moving from acl_system2.
|
19
|
+
#
|
20
|
+
# But, let me stress, everyone likes a slightly different system, so this one may not be
|
21
|
+
# your style. I find it synchronises very well with the interface of Acts as Authenticated (even
|
22
|
+
# though I have modified it so much that it's now called Authenticated Cookie).
|
23
|
+
#
|
24
|
+
module SimpleAccessControl
|
25
|
+
|
26
|
+
def self.included(base)
|
27
|
+
base.extend(ClassMethods)
|
28
|
+
if base.respond_to?(:helper_method)
|
29
|
+
base.send :helper_method, :restrict_to
|
30
|
+
base.send :helper_method, :has_permission?
|
31
|
+
base.send :helper_method, :permit?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module ClassMethods
|
36
|
+
|
37
|
+
# Support for acl_system2 migration
|
38
|
+
def access_control(ruleset = {})
|
39
|
+
ruleset.each do |actions, rule|
|
40
|
+
case actions
|
41
|
+
when :DEFAULT
|
42
|
+
access_rule rule
|
43
|
+
when Array, Symbol, String
|
44
|
+
access_rule rule, :only => actions
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# This is the core of the filtering system and it couldn't be simpler:
|
50
|
+
# access_rule '(admin || moderator)', :only => [:edit, :update]
|
51
|
+
def access_rule(rule, filter_options = {})
|
52
|
+
before_filter (filter_options||{}) { |c| c.send :permission_required, rule }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
# As in AAA you have login_required, here you have permission_required. Pass it a
|
59
|
+
# rule and it will use SimpleAccessControl#has_permission? to evaluate against the
|
60
|
+
# current user. Use SimpleAccessControl#has_permission? if you are not guarding an
|
61
|
+
# action or whole controller. An empty or nil rule will always return true.
|
62
|
+
# permission_required('admin')
|
63
|
+
def permission_required(rule = nil)
|
64
|
+
if respond_to?(:logged_in?) && logged_in? && has_permission?(rule)
|
65
|
+
send(:permission_granted) if respond_to?(:permission_granted)
|
66
|
+
true
|
67
|
+
else
|
68
|
+
send(:permission_denied) if respond_to?(:permission_denied)
|
69
|
+
false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# For use in both controllers and views.
|
74
|
+
# has_permission?('role')
|
75
|
+
# has_permission?('admin', other_user)
|
76
|
+
def has_permission?(rule, user = nil)
|
77
|
+
user ||= (send(:current_user) if respond_to?(:current_user)) || nil
|
78
|
+
access_controller.process(rule, user)
|
79
|
+
end
|
80
|
+
|
81
|
+
# For those of you converting from acl_system2
|
82
|
+
def permit?(rule, context = {})
|
83
|
+
has_permission?(rule, (context && context[:user] ? context[:user] : nil))
|
84
|
+
end
|
85
|
+
|
86
|
+
# A much shortened version of Ezra's acl_system2 version.
|
87
|
+
# restrict_to "admin | moderator" do
|
88
|
+
# link_to "foo"
|
89
|
+
# end
|
90
|
+
def restrict_to(rule, user = nil)
|
91
|
+
yield if block_given? && has_permission?(rule, user)
|
92
|
+
end
|
93
|
+
|
94
|
+
def access_controller #:nodoc:
|
95
|
+
@access_controller ||= AccessControlHandler.new
|
96
|
+
end
|
97
|
+
|
98
|
+
# A dramatically simpler version than that found in acl_system2
|
99
|
+
# It is SLOWER because it uses instance_eval to analyse the conditional, but it's DRY.
|
100
|
+
class AccessControlHandler
|
101
|
+
|
102
|
+
# Takes a string (which may be a complex conditional string or a single word as a string
|
103
|
+
# or symbol) and checks if the user has those roles
|
104
|
+
def process(string, user)
|
105
|
+
return(check('', user)) if string.blank?
|
106
|
+
if string =~ /^([^()\|&!]+)$/ then check($1, user) # it is simple enough to just pump through
|
107
|
+
else instance_eval("!! (#{parse(string)})") # give it the going-over
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Super-simple parsing, turning single or multiple & and | into && and ||. Wraps all the roles
|
112
|
+
# in a check call to be evaluated.
|
113
|
+
def parse(string)
|
114
|
+
string.gsub(/(\|+|\&+)/) { $1[0,1]*2 }.gsub(/([^()|&! ]+)/) { "check('#{$1}', user)" }
|
115
|
+
end
|
116
|
+
|
117
|
+
# The heart of the system, all credit to Ezra for the original algorithm
|
118
|
+
# Defaults to false if there is no user or that user does not have a roles association
|
119
|
+
# Defaults to true if the role is blank
|
120
|
+
def check(role, user)
|
121
|
+
return(false) if user.blank? || !user.respond_to?(:roles)
|
122
|
+
return(true) if role.blank?
|
123
|
+
user.roles.map{ |r| r.title.downcase }.include? role.downcase
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
data/rails/init.rb
ADDED
data/test/helper.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'test/helper'
|
2
|
+
|
3
|
+
class SimpleAccessControlTest < Test::Unit::TestCase
|
4
|
+
# Replace this with your real tests.
|
5
|
+
should "probably rename this file and start testing for real" do
|
6
|
+
flunk "hey buddy, you should probably rename this file and start testing for real"
|
7
|
+
end
|
8
|
+
end
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nbrew-simple_access_control
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Mathew Abonyi
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-07-12 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: shoulda
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
description: "Simple access controls for use with ActsAsAuthenticated, RestfulAuthentication, etc. A clone of Mathew Abonyi's (mabs29) repo: http://mabs29.googlecode.com/svn/trunk/plugins/simple_access_control"
|
36
|
+
email: nhyde@bigdrift.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- README
|
43
|
+
files:
|
44
|
+
- .gitignore
|
45
|
+
- README
|
46
|
+
- Rakefile
|
47
|
+
- VERSION
|
48
|
+
- install.rb
|
49
|
+
- lib/simple_access_control.rb
|
50
|
+
- lib/tasks/simple_access_control_tasks.rake
|
51
|
+
- rails/init.rb
|
52
|
+
- test/helper.rb
|
53
|
+
- test/simple_access_control_test.rb
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: http://github.com/nbrew/simple_access_control
|
56
|
+
licenses: []
|
57
|
+
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options:
|
60
|
+
- --charset=UTF-8
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
hash: 3
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 3
|
78
|
+
segments:
|
79
|
+
- 0
|
80
|
+
version: "0"
|
81
|
+
requirements: []
|
82
|
+
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 1.3.7
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: Simple role-based access control plugin for Rails controllers and views.
|
88
|
+
test_files:
|
89
|
+
- test/helper.rb
|
90
|
+
- test/simple_access_control_test.rb
|