be9-acl9 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE +20 -0
- data/Manifest +19 -0
- data/README.textile +765 -0
- data/Rakefile +37 -0
- data/acl9.gemspec +37 -0
- data/init.rb +1 -0
- data/lib/acl9.rb +14 -0
- data/lib/acl9/config.rb +9 -0
- data/lib/acl9/controller_extensions.rb +37 -0
- data/lib/acl9/controller_extensions/filter_producer.rb +244 -0
- data/lib/acl9/model_extensions.rb +58 -0
- data/lib/acl9/model_extensions/object.rb +27 -0
- data/lib/acl9/model_extensions/subject.rb +107 -0
- data/lib/acl9/version.rb +54 -0
- data/spec/access_control_spec.rb +185 -0
- data/spec/db/schema.rb +47 -0
- data/spec/filter_producer_spec.rb +707 -0
- data/spec/models.rb +27 -0
- data/spec/roles_spec.rb +259 -0
- data/spec/spec_helper.rb +34 -0
- metadata +102 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
module Acl9
|
2
|
+
module ModelExtensions
|
3
|
+
module Object
|
4
|
+
def accepts_role?(role_name, subject)
|
5
|
+
subject.has_role? role_name, self
|
6
|
+
end
|
7
|
+
|
8
|
+
def accepts_role!(role_name, subject)
|
9
|
+
subject.has_role! role_name, self
|
10
|
+
end
|
11
|
+
|
12
|
+
def accepts_no_role!(role_name, subject)
|
13
|
+
subject.has_no_role! role_name, self
|
14
|
+
end
|
15
|
+
|
16
|
+
def accepts_roles_by?(subject)
|
17
|
+
subject.has_roles_for? self
|
18
|
+
end
|
19
|
+
|
20
|
+
alias :accepts_role_by? :accepts_roles_by?
|
21
|
+
|
22
|
+
def accepted_roles_by(subject)
|
23
|
+
subject.roles_for self
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Acl9
|
2
|
+
module ModelExtensions
|
3
|
+
module Subject
|
4
|
+
def has_role?(role_name, object = nil)
|
5
|
+
!! if object.nil?
|
6
|
+
self.roles.find_by_name(role_name.to_s) ||
|
7
|
+
self.roles.member?(get_role(role_name, nil))
|
8
|
+
else
|
9
|
+
role = get_role(role_name, object)
|
10
|
+
role && self.roles.exists?(role.id)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def has_role!(role_name, object = nil)
|
15
|
+
role = get_role(role_name, object)
|
16
|
+
|
17
|
+
if role.nil?
|
18
|
+
role_attrs = case object
|
19
|
+
when Class then { :authorizable_type => object.to_s }
|
20
|
+
when nil then {}
|
21
|
+
else { :authorizable => object }
|
22
|
+
end.merge( { :name => role_name.to_s })
|
23
|
+
|
24
|
+
role = self._auth_role_class.create(role_attrs)
|
25
|
+
end
|
26
|
+
|
27
|
+
self.roles << role if role && !self.roles.exists?( role.id )
|
28
|
+
end
|
29
|
+
|
30
|
+
def has_no_role!(role_name, object = nil)
|
31
|
+
delete_role(get_role(role_name, object))
|
32
|
+
end
|
33
|
+
|
34
|
+
def has_roles_for?(object)
|
35
|
+
!!self.roles.detect(&role_selecting_lambda(object))
|
36
|
+
end
|
37
|
+
|
38
|
+
alias :has_role_for? :has_roles_for?
|
39
|
+
|
40
|
+
def roles_for(object)
|
41
|
+
self.roles.select(&role_selecting_lambda(object))
|
42
|
+
end
|
43
|
+
|
44
|
+
def has_no_roles_for!(object = nil)
|
45
|
+
roles_for(object).each { |role| delete_role(role) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def has_no_roles!
|
49
|
+
# for some reason simple
|
50
|
+
#
|
51
|
+
# self.roles.each { |role| delete_role(role) }
|
52
|
+
#
|
53
|
+
# doesn't work. seems like a bug in ActiveRecord
|
54
|
+
self.roles.map(&:id).each do |role_id|
|
55
|
+
delete_role self._auth_role_class.find(role_id)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def role_selecting_lambda(object)
|
62
|
+
case object
|
63
|
+
when Class
|
64
|
+
lambda { |role| role.authorizable_type == object.to_s }
|
65
|
+
when nil
|
66
|
+
lambda { |role| role.authorizable.nil? }
|
67
|
+
else
|
68
|
+
lambda do |role|
|
69
|
+
role.authorizable_type == object.class.base_class.to_s && role.authorizable == object
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_role(role_name, object)
|
75
|
+
role_name = role_name.to_s
|
76
|
+
|
77
|
+
cond = case object
|
78
|
+
when Class
|
79
|
+
[ 'name = ? and authorizable_type = ? and authorizable_id IS NULL', role_name, object.to_s ]
|
80
|
+
when nil
|
81
|
+
[ 'name = ? and authorizable_type IS NULL and authorizable_id IS NULL', role_name ]
|
82
|
+
else
|
83
|
+
[
|
84
|
+
'name = ? and authorizable_type = ? and authorizable_id = ?',
|
85
|
+
role_name, object.class.base_class.to_s, object.id
|
86
|
+
]
|
87
|
+
end
|
88
|
+
|
89
|
+
self._auth_role_class.first :conditions => cond
|
90
|
+
end
|
91
|
+
|
92
|
+
def delete_role(role)
|
93
|
+
if role
|
94
|
+
self.roles.delete role
|
95
|
+
|
96
|
+
role.destroy if role.users.empty?
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
|
102
|
+
def _auth_role_class
|
103
|
+
self.class._auth_role_class_name.constantize
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/lib/acl9/version.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
module Acl9 # :nodoc:
|
2
|
+
# = Version
|
3
|
+
#
|
4
|
+
# A class for describing the current version of a library. The version
|
5
|
+
# consists of three parts: the +major+ number, the +minor+ number, and the
|
6
|
+
# +tiny+ (or +patch+) number.
|
7
|
+
class Version
|
8
|
+
|
9
|
+
include Comparable
|
10
|
+
|
11
|
+
# A convenience method for instantiating a new Version instance with the
|
12
|
+
# given +major+, +minor+, and +tiny+ components.
|
13
|
+
def self.[](major, minor, tiny)
|
14
|
+
new(major, minor, tiny)
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :major, :minor, :tiny
|
18
|
+
|
19
|
+
# Create a new Version object with the given components.
|
20
|
+
def initialize(major, minor, tiny)
|
21
|
+
@major, @minor, @tiny = major, minor, tiny
|
22
|
+
end
|
23
|
+
|
24
|
+
# Compare this version to the given +version+ object.
|
25
|
+
def <=>(version)
|
26
|
+
to_i <=> version.to_i
|
27
|
+
end
|
28
|
+
|
29
|
+
# Converts this version object to a string, where each of the three
|
30
|
+
# version components are joined by the '.' character. E.g., 2.0.0.
|
31
|
+
def to_s
|
32
|
+
@to_s ||= [@major, @minor, @tiny].join(".")
|
33
|
+
end
|
34
|
+
|
35
|
+
# Converts this version to a canonical integer that may be compared
|
36
|
+
# against other version objects.
|
37
|
+
def to_i
|
38
|
+
@to_i ||= @major * 1_000_000 + @minor * 1_000 + @tiny
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_a
|
42
|
+
[@major, @minor, @tiny]
|
43
|
+
end
|
44
|
+
|
45
|
+
MAJOR = 0
|
46
|
+
MINOR = 9
|
47
|
+
TINY = 1
|
48
|
+
|
49
|
+
# The current version as a Version instance
|
50
|
+
CURRENT = new(MAJOR, MINOR, TINY)
|
51
|
+
# The current version as a String
|
52
|
+
STRING = CURRENT.to_s
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'acl9')
|
3
|
+
|
4
|
+
class EmptyController < ActionController::Base
|
5
|
+
attr_accessor :current_user
|
6
|
+
before_filter :set_current_user
|
7
|
+
|
8
|
+
[:index, :show, :new, :edit, :update, :delete, :destroy].each do |act|
|
9
|
+
define_method(act) {}
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def set_current_user
|
15
|
+
if params[:user]
|
16
|
+
self.current_user = params[:user]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Admin
|
22
|
+
def has_role?(role, obj = nil)
|
23
|
+
role == "admin"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# all these controllers behave the same way
|
28
|
+
|
29
|
+
class AccessControllingController1 < EmptyController
|
30
|
+
access_control do
|
31
|
+
allow all, :to => [:index, :show]
|
32
|
+
allow :admin
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class AccessControllingController2 < EmptyController
|
37
|
+
access_control :as_method => :acl do
|
38
|
+
allow all, :to => [:index, :show]
|
39
|
+
allow :admin, :except => [:index, :show]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class AccessControllingController3 < EmptyController
|
44
|
+
access_control :except => [:index, :show] do
|
45
|
+
allow :admin
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class AccessControllingController4 < EmptyController
|
50
|
+
access_control :as_method => :acl, :filter => false do
|
51
|
+
allow all, :to => [:index, :show]
|
52
|
+
allow :admin
|
53
|
+
end
|
54
|
+
|
55
|
+
before_filter :check_acl
|
56
|
+
|
57
|
+
def check_acl
|
58
|
+
if self.acl
|
59
|
+
true
|
60
|
+
else
|
61
|
+
raise Acl9::AccessDenied
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "permit anonymous to index and show and admin everywhere else", :shared => true do
|
67
|
+
[:index, :show].each do |act|
|
68
|
+
it "should permit anonymous to #{act}" do
|
69
|
+
get act
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
[:new, :edit, :update, :delete, :destroy].each do |act|
|
74
|
+
it "should forbid anonymous to #{act}" do
|
75
|
+
lambda do
|
76
|
+
get act
|
77
|
+
end.should raise_error(Acl9::AccessDenied)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
[:index, :show, :new, :edit, :update, :delete, :destroy].each do |act|
|
82
|
+
it "should permit admin to #{act}" do
|
83
|
+
get act, :user => Admin.new
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe AccessControllingController1, :type => :controller do
|
89
|
+
it_should_behave_like "permit anonymous to index and show and admin everywhere else"
|
90
|
+
end
|
91
|
+
|
92
|
+
describe AccessControllingController2, :type => :controller do
|
93
|
+
it "should add :acl as a method" do
|
94
|
+
controller.should respond_to(:acl)
|
95
|
+
end
|
96
|
+
|
97
|
+
it_should_behave_like "permit anonymous to index and show and admin everywhere else"
|
98
|
+
end
|
99
|
+
|
100
|
+
describe AccessControllingController3, :type => :controller do
|
101
|
+
it_should_behave_like "permit anonymous to index and show and admin everywhere else"
|
102
|
+
end
|
103
|
+
|
104
|
+
describe AccessControllingController4, :type => :controller do
|
105
|
+
it_should_behave_like "permit anonymous to index and show and admin everywhere else"
|
106
|
+
end
|
107
|
+
|
108
|
+
class MyDearFoo
|
109
|
+
include Singleton
|
110
|
+
end
|
111
|
+
|
112
|
+
class VenerableBar; end
|
113
|
+
|
114
|
+
class AccessControllingController5 < EmptyController
|
115
|
+
before_filter :set_ivars
|
116
|
+
|
117
|
+
access_control do
|
118
|
+
action :destroy do
|
119
|
+
allow :owner, :of => :foo
|
120
|
+
allow :bartender, :at => VenerableBar
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def set_ivars
|
127
|
+
@foo = MyDearFoo.instance
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe AccessControllingController5, :type => :controller do
|
132
|
+
class OwnerOfFoo
|
133
|
+
def has_role?(role, obj)
|
134
|
+
role == 'owner' && obj == MyDearFoo.instance
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
class Bartender
|
139
|
+
def has_role?(role, obj)
|
140
|
+
role == 'bartender' && obj == VenerableBar
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should allow owner of foo to destroy" do
|
145
|
+
delete :destroy, :user => OwnerOfFoo.new
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should allow bartender to destroy" do
|
149
|
+
delete :destroy, :user => Bartender.new
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class TheOnlyUser
|
154
|
+
include Singleton
|
155
|
+
|
156
|
+
def has_role?(role, subj)
|
157
|
+
role == "the_only_one"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
class AccessControllingController6 < ActionController::Base
|
162
|
+
access_control :subject_method => :the_only_user do
|
163
|
+
allow :the_only_one
|
164
|
+
end
|
165
|
+
|
166
|
+
def index; end
|
167
|
+
|
168
|
+
private
|
169
|
+
|
170
|
+
def the_only_user
|
171
|
+
params[:user]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe AccessControllingController6, :type => :controller do
|
176
|
+
it "should allow the only user to index" do
|
177
|
+
get :index, :user => TheOnlyUser.instance
|
178
|
+
end
|
179
|
+
|
180
|
+
it "should deny anonymous to index" do
|
181
|
+
lambda do
|
182
|
+
get :index
|
183
|
+
end.should raise_error(Acl9::AccessDenied)
|
184
|
+
end
|
185
|
+
end
|
data/spec/db/schema.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
ActiveRecord::Schema.define(:version => 0) do
|
2
|
+
create_table "roles", :force => true do |t|
|
3
|
+
t.string "name", :limit => 40
|
4
|
+
t.string "authorizable_type", :limit => 40
|
5
|
+
t.integer "authorizable_id"
|
6
|
+
t.datetime "created_at"
|
7
|
+
t.datetime "updated_at"
|
8
|
+
end
|
9
|
+
|
10
|
+
create_table "another_roles", :force => true do |t|
|
11
|
+
t.string "name", :limit => 40
|
12
|
+
t.string "authorizable_type", :limit => 40
|
13
|
+
t.integer "authorizable_id"
|
14
|
+
t.datetime "created_at"
|
15
|
+
t.datetime "updated_at"
|
16
|
+
end
|
17
|
+
|
18
|
+
create_table "users", :force => true do |t| end
|
19
|
+
create_table "another_subjects", :force => true do |t| end
|
20
|
+
|
21
|
+
create_table "roles_users", :id => false, :force => true do |t|
|
22
|
+
t.integer "user_id"
|
23
|
+
t.integer "role_id"
|
24
|
+
t.datetime "created_at"
|
25
|
+
t.datetime "updated_at"
|
26
|
+
end
|
27
|
+
|
28
|
+
create_table "another_roles_another_subjects", :id => false, :force => true do |t|
|
29
|
+
t.integer "another_subject_id"
|
30
|
+
t.integer "another_role_id"
|
31
|
+
t.datetime "created_at"
|
32
|
+
t.datetime "updated_at"
|
33
|
+
end
|
34
|
+
create_table "foos", :force => true do |t|
|
35
|
+
t.datetime "created_at"
|
36
|
+
t.datetime "updated_at"
|
37
|
+
end
|
38
|
+
|
39
|
+
create_table "bars", :force => true do |t|
|
40
|
+
t.datetime "created_at"
|
41
|
+
t.datetime "updated_at"
|
42
|
+
end
|
43
|
+
create_table "foo_bars", :force => true do |t|
|
44
|
+
t.datetime "created_at"
|
45
|
+
t.datetime "updated_at"
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,707 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'acl9', 'controller_extensions', 'filter_producer')
|
3
|
+
|
4
|
+
class FakeUser
|
5
|
+
def initialize
|
6
|
+
@roles = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def has_role?(role, object = nil)
|
10
|
+
@roles.include?([role.to_s, object])
|
11
|
+
end
|
12
|
+
|
13
|
+
def <<(role)
|
14
|
+
role = [role] unless role.is_a? Array
|
15
|
+
|
16
|
+
role << nil if role.size == 1
|
17
|
+
raise unless role[0]
|
18
|
+
|
19
|
+
role[0] = role[0].to_s
|
20
|
+
|
21
|
+
@roles[role] = true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class FakeFoo; end
|
26
|
+
class FakeBar; end
|
27
|
+
|
28
|
+
module PermissionChecks
|
29
|
+
class FakeController
|
30
|
+
attr_reader :current_user, :action_name
|
31
|
+
|
32
|
+
def initialize(user, *args)
|
33
|
+
@current_user = user
|
34
|
+
@action_name = (args[0] || 'index').to_s
|
35
|
+
|
36
|
+
ivars = args.last.is_a?(Hash) ? args.last : {}
|
37
|
+
|
38
|
+
for name, value in ivars
|
39
|
+
instance_variable_set "@#{name}", value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def permit(user, *args)
|
45
|
+
run(user, *args).should_not == false
|
46
|
+
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def forbid(user, *args)
|
51
|
+
begin
|
52
|
+
run(user, *args)
|
53
|
+
|
54
|
+
raise "User #{user.inspect} was permitted, but should not have been to"
|
55
|
+
rescue Acl9::AccessDenied
|
56
|
+
# ok here
|
57
|
+
end
|
58
|
+
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
def show_code
|
63
|
+
puts "\n", self.to_s
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def run(user, *args)
|
70
|
+
self.to_proc.call(FakeController.new(user, *args))
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
describe Acl9::FilterProducer do
|
76
|
+
describe "dsl" do
|
77
|
+
before do
|
78
|
+
@user = FakeUser.new
|
79
|
+
@user2 = FakeUser.new
|
80
|
+
@user3 = FakeUser.new
|
81
|
+
@foo = FakeFoo.new
|
82
|
+
@foo2 = FakeFoo.new
|
83
|
+
@foo3 = FakeFoo.new
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "default" do
|
87
|
+
it "should set default action to deny if none specified" do
|
88
|
+
acl do end.default_action.should == :deny
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should set default action to allow" do
|
92
|
+
acl do
|
93
|
+
default :allow
|
94
|
+
end.default_action.should == :allow
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should set default action to deny" do
|
98
|
+
acl do
|
99
|
+
default :deny
|
100
|
+
end.default_action.should == :deny
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should raise ArgumentError with unknown default_action" do
|
104
|
+
arg_err do
|
105
|
+
default 123
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should raise ArgumentError when default is called more than once" do
|
110
|
+
arg_err do
|
111
|
+
default :deny
|
112
|
+
default :deny
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "empty blocks" do
|
118
|
+
it "should deny everyone with default deny" do
|
119
|
+
acl do
|
120
|
+
end.forbid(nil).forbid(@user)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should allow everyone with default allow" do
|
124
|
+
acl do
|
125
|
+
default :allow
|
126
|
+
end.permit(nil).permit(@user)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "empty" do
|
131
|
+
it "allow should raise an ArgumentError" do
|
132
|
+
arg_err { allow }
|
133
|
+
end
|
134
|
+
|
135
|
+
it "deny should raise an ArgumentError" do
|
136
|
+
arg_err { deny }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe "anonymous" do
|
141
|
+
it "'allow nil' should allow anonymous, but not logged in" do
|
142
|
+
acl do
|
143
|
+
allow nil
|
144
|
+
end.permit(nil).forbid(@user)
|
145
|
+
end
|
146
|
+
|
147
|
+
it "'allow anonymous' should allow anonymous, but not logged in" do
|
148
|
+
acl do
|
149
|
+
allow anonymous
|
150
|
+
end.permit(nil).forbid(@user)
|
151
|
+
end
|
152
|
+
|
153
|
+
it "'deny nil' should deny anonymous, but not logged in" do
|
154
|
+
acl do
|
155
|
+
default :allow
|
156
|
+
deny nil
|
157
|
+
end.forbid(nil).permit(@user)
|
158
|
+
end
|
159
|
+
|
160
|
+
it "'deny anonymous' should deny anonymous, but not logged in" do
|
161
|
+
acl do
|
162
|
+
default :allow
|
163
|
+
deny anonymous
|
164
|
+
end.forbid(nil).permit(@user)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe "all" do
|
169
|
+
it "'allow all' should allow all" do
|
170
|
+
acl do
|
171
|
+
allow all
|
172
|
+
end.permit(nil).permit(@user)
|
173
|
+
end
|
174
|
+
|
175
|
+
it "'deny all' should deny all" do
|
176
|
+
acl do
|
177
|
+
default :allow
|
178
|
+
deny all
|
179
|
+
end.forbid(nil).forbid(@user)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe "default :allow" do
|
184
|
+
it "should allow when neither allow nor deny conditions are matched" do
|
185
|
+
acl do
|
186
|
+
default :allow
|
187
|
+
allow :blah
|
188
|
+
deny :bzz
|
189
|
+
end.permit(nil).permit(@user)
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should deny when deny is matched, but allow is not" do
|
193
|
+
acl do
|
194
|
+
default :allow
|
195
|
+
deny all
|
196
|
+
allow :blah
|
197
|
+
end.forbid(nil).forbid(@user)
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should allow when allow is matched, but deny is not" do
|
201
|
+
@user << :cool
|
202
|
+
acl do
|
203
|
+
default :allow
|
204
|
+
deny nil
|
205
|
+
allow :cool
|
206
|
+
end.permit(@user)
|
207
|
+
end
|
208
|
+
|
209
|
+
it "should allow both allow and deny conditions are matched" do
|
210
|
+
@user << :cool
|
211
|
+
acl do
|
212
|
+
default :allow
|
213
|
+
deny :cool
|
214
|
+
allow :cool
|
215
|
+
end.permit(@user)
|
216
|
+
|
217
|
+
acl do
|
218
|
+
default :allow
|
219
|
+
deny all
|
220
|
+
allow all
|
221
|
+
end.permit(@user).permit(nil).permit(@user2)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
describe "logged_in" do
|
226
|
+
it "'allow logged_in' should allow logged in, but not anonymous" do
|
227
|
+
acl do
|
228
|
+
allow logged_in
|
229
|
+
end.forbid(nil).permit(@user)
|
230
|
+
end
|
231
|
+
|
232
|
+
it "'allow logged_in' should deny logged in, but not anonymous" do
|
233
|
+
acl do
|
234
|
+
default :allow
|
235
|
+
deny logged_in
|
236
|
+
end.permit(nil).forbid(@user)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
describe "default :deny" do
|
241
|
+
it "should deny when neither allow nor deny conditions are matched" do
|
242
|
+
acl do
|
243
|
+
default :deny
|
244
|
+
allow :blah
|
245
|
+
deny :bzz
|
246
|
+
end.forbid(nil).forbid(@user)
|
247
|
+
end
|
248
|
+
|
249
|
+
it "should deny when deny is matched, but allow is not" do
|
250
|
+
acl do
|
251
|
+
default :deny
|
252
|
+
deny all
|
253
|
+
allow :blah
|
254
|
+
end.forbid(nil).forbid(@user)
|
255
|
+
end
|
256
|
+
|
257
|
+
it "should allow when allow is matched, but deny is not" do
|
258
|
+
@user << :cool
|
259
|
+
acl do
|
260
|
+
default :deny
|
261
|
+
deny nil
|
262
|
+
allow :cool
|
263
|
+
end.permit(@user)
|
264
|
+
end
|
265
|
+
|
266
|
+
it "should deny both allow and deny conditions are matched" do
|
267
|
+
@user << :cool
|
268
|
+
acl do
|
269
|
+
default :deny
|
270
|
+
deny :cool
|
271
|
+
allow :cool
|
272
|
+
end.forbid(@user)
|
273
|
+
|
274
|
+
acl do
|
275
|
+
default :deny
|
276
|
+
deny all
|
277
|
+
allow all
|
278
|
+
end.forbid(@user).forbid(nil).forbid(@user2)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
describe "global roles" do
|
283
|
+
it "#allow with role" do
|
284
|
+
@user << :admin
|
285
|
+
|
286
|
+
acl { allow :admin }.permit(@user).forbid(nil).forbid(@user2)
|
287
|
+
end
|
288
|
+
|
289
|
+
it "#allow with plural role name" do
|
290
|
+
@user << :mouse
|
291
|
+
|
292
|
+
acl do
|
293
|
+
allow :mice
|
294
|
+
end.permit(@user).forbid(nil).forbid(@user2)
|
295
|
+
end
|
296
|
+
|
297
|
+
it "#allow with several roles" do
|
298
|
+
@user << :admin
|
299
|
+
@user << :cool
|
300
|
+
|
301
|
+
@user2 << :cool
|
302
|
+
|
303
|
+
@user3 << :super
|
304
|
+
|
305
|
+
acl do
|
306
|
+
allow :admin
|
307
|
+
allow :cool
|
308
|
+
end.permit(@user).permit(@user2).forbid(nil).forbid(@user3)
|
309
|
+
end
|
310
|
+
|
311
|
+
it "#deny with role" do
|
312
|
+
@user << :foo
|
313
|
+
|
314
|
+
acl { default :allow; deny :foo }.forbid(@user).permit(nil).permit(@user2)
|
315
|
+
end
|
316
|
+
|
317
|
+
it "#deny with plural role name" do
|
318
|
+
@user << :mouse
|
319
|
+
|
320
|
+
acl do
|
321
|
+
default :allow
|
322
|
+
deny :mice
|
323
|
+
end.forbid(@user).permit(nil).permit(@user2)
|
324
|
+
end
|
325
|
+
|
326
|
+
it "#deny with several roles" do
|
327
|
+
@user << :admin
|
328
|
+
@user << :cool
|
329
|
+
|
330
|
+
@user2 << :cool
|
331
|
+
|
332
|
+
@user3 << :super
|
333
|
+
|
334
|
+
acl do
|
335
|
+
default :allow
|
336
|
+
deny :admin
|
337
|
+
deny :cool
|
338
|
+
end.forbid(@user).forbid(@user2).permit(nil).permit(@user3)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
describe "prepositions" do
|
343
|
+
[:of, :for, :in, :on, :at, :by].each do |prep|
|
344
|
+
it "#allow with object role (:#{prep}) should check controller's ivar" do
|
345
|
+
@user << [:manager, @foo]
|
346
|
+
|
347
|
+
acl do
|
348
|
+
allow :manager, prep => :foo
|
349
|
+
end.
|
350
|
+
permit(@user, :foo => @foo).
|
351
|
+
forbid(@user, :foo => @foo2).
|
352
|
+
forbid(@user, :foo => FakeFoo).
|
353
|
+
forbid(nil, :foo => @foo).
|
354
|
+
forbid(@user2, :foo => @foo)
|
355
|
+
end
|
356
|
+
|
357
|
+
it "#allow with invalid value for preposition should raise an ArgumentError" do
|
358
|
+
arg_err do
|
359
|
+
allow :hom, :by => 1
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
it "#allow with a class role should verify this role against a class" do
|
365
|
+
@user << [:owner, FakeFoo]
|
366
|
+
|
367
|
+
acl do
|
368
|
+
allow :owner, :of => FakeFoo
|
369
|
+
end.permit(@user).forbid(nil).forbid(@user2)
|
370
|
+
end
|
371
|
+
|
372
|
+
[:of, :for, :in, :on, :at, :by].each do |prep|
|
373
|
+
it "#deny with object role (:#{prep}) should check controller's ivar" do
|
374
|
+
@user << [:bastard, @foo]
|
375
|
+
|
376
|
+
acl do
|
377
|
+
default :allow
|
378
|
+
deny :bastard, prep => :foo
|
379
|
+
end.
|
380
|
+
forbid(@user, :foo => @foo).
|
381
|
+
permit(@user, :foo => @foo2).
|
382
|
+
permit(@user, :foo => FakeFoo).
|
383
|
+
permit(nil, :foo => @foo).
|
384
|
+
permit(@user2, :foo => @foo)
|
385
|
+
end
|
386
|
+
|
387
|
+
it "#deny with invalid value for preposition should raise an ArgumentError" do
|
388
|
+
arg_err do
|
389
|
+
deny :her, :for => "him"
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
it "#deny with a class role should verify this role against a class" do
|
395
|
+
@user << [:ignorant, FakeFoo]
|
396
|
+
|
397
|
+
acl do
|
398
|
+
default :allow
|
399
|
+
deny :ignorant, :of => FakeFoo
|
400
|
+
end.forbid(@user).permit(nil).permit(@user2)
|
401
|
+
end
|
402
|
+
|
403
|
+
it "#allow with several prepositions should raise an ArgumentError" do
|
404
|
+
arg_err do
|
405
|
+
allow :some, :by => :one, :for => :another
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
it "#deny with several prepositions should raise an ArgumentError" do
|
410
|
+
arg_err do
|
411
|
+
deny :some, :in => :here, :on => :today
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
describe ":to and :except" do
|
417
|
+
it "should raise an ArgumentError when both :to and :except are specified" do
|
418
|
+
arg_err do
|
419
|
+
allow all, :to => :index, :except => ['show', 'edit']
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
describe do
|
424
|
+
after do
|
425
|
+
%w(index show).each { |act| @list.permit(nil, act) }
|
426
|
+
%w(edit update delete destroy).each { |act| @list.forbid(nil, act) }
|
427
|
+
|
428
|
+
%w(index show edit update).each { |act| @list.permit(@user, act) }
|
429
|
+
%w(delete destroy).each { |act| @list.forbid(@user, act) }
|
430
|
+
|
431
|
+
%w(index show edit update delete destroy).each { |act| @list.permit(@user2, act) }
|
432
|
+
end
|
433
|
+
|
434
|
+
it ":to should limit rule scope to specified actions" do
|
435
|
+
@user << :manager
|
436
|
+
@user2 << :trusted
|
437
|
+
|
438
|
+
@list = acl do
|
439
|
+
allow all, :to => [:index, :show]
|
440
|
+
|
441
|
+
allow 'manager', :to => :edit
|
442
|
+
allow 'manager', :to => 'update'
|
443
|
+
allow 'trusted', :to => %w(edit update delete destroy)
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
it ":except should limit rule scope to all actions except specified" do
|
448
|
+
@user << :manager
|
449
|
+
@user2 << :trusted
|
450
|
+
|
451
|
+
@list = acl do
|
452
|
+
allow all, :except => %w(edit update delete destroy)
|
453
|
+
|
454
|
+
allow 'manager', :except => %w(delete destroy)
|
455
|
+
allow 'trusted'
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
describe "several roles as arguments" do
|
462
|
+
it "#allow should be able to receive a role list (global roles)" do
|
463
|
+
@user << :bzz
|
464
|
+
@user2 << :whoa
|
465
|
+
|
466
|
+
acl do
|
467
|
+
allow :bzz, :whoa
|
468
|
+
end.permit(@user).permit(@user2).forbid(nil).forbid(@user3)
|
469
|
+
end
|
470
|
+
|
471
|
+
it "#allow should be able to receive a role list (object roles)" do
|
472
|
+
@user << [:maker, @foo]
|
473
|
+
@user2 << [:faker, @foo2]
|
474
|
+
|
475
|
+
acl do
|
476
|
+
allow :maker, :faker, :of => :foo
|
477
|
+
end.
|
478
|
+
permit(@user, :foo => @foo).
|
479
|
+
forbid(@user, :foo => @foo2).
|
480
|
+
permit(@user2, :foo => @foo2).
|
481
|
+
forbid(@user2, :foo => @foo).
|
482
|
+
forbid(@user3, :foo => @foo).
|
483
|
+
forbid(@user3, :foo => @foo2).
|
484
|
+
forbid(nil)
|
485
|
+
end
|
486
|
+
|
487
|
+
it "#allow should be able to receive a role list (class roles)" do
|
488
|
+
@user << [:frooble, FakeFoo]
|
489
|
+
@user2 << [:oombigle, FakeFoo]
|
490
|
+
@user3 << :frooble
|
491
|
+
|
492
|
+
acl do
|
493
|
+
allow :frooble, :oombigle, :by => FakeFoo
|
494
|
+
end.
|
495
|
+
permit(@user).
|
496
|
+
permit(@user2).
|
497
|
+
forbid(@user3).
|
498
|
+
forbid(nil)
|
499
|
+
end
|
500
|
+
|
501
|
+
it "#deny should be able to receive a role list (global roles)" do
|
502
|
+
@user << :bzz
|
503
|
+
@user2 << :whoa
|
504
|
+
|
505
|
+
acl do
|
506
|
+
default :allow
|
507
|
+
deny :bzz, :whoa
|
508
|
+
end.forbid(@user).forbid(@user2).permit(nil).permit(@user3)
|
509
|
+
end
|
510
|
+
|
511
|
+
it "#deny should be able to receive a role list (object roles)" do
|
512
|
+
@user << [:maker, @foo]
|
513
|
+
@user2 << [:faker, @foo2]
|
514
|
+
@user3 = FakeUser.new
|
515
|
+
|
516
|
+
acl do
|
517
|
+
default :allow
|
518
|
+
deny :maker, :faker, :of => :foo
|
519
|
+
end.
|
520
|
+
forbid(@user, :foo => @foo).
|
521
|
+
permit(@user, :foo => @foo2).
|
522
|
+
forbid(@user2, :foo => @foo2).
|
523
|
+
permit(@user2, :foo => @foo).
|
524
|
+
permit(@user3, :foo => @foo).
|
525
|
+
permit(@user3, :foo => @foo2).
|
526
|
+
permit(nil)
|
527
|
+
end
|
528
|
+
|
529
|
+
it "#deny should be able to receive a role list (class roles)" do
|
530
|
+
@user << [:frooble, FakeFoo]
|
531
|
+
@user2 << [:oombigle, FakeFoo]
|
532
|
+
@user3 << :frooble
|
533
|
+
|
534
|
+
acl do
|
535
|
+
default :allow
|
536
|
+
deny :frooble, :oombigle, :by => FakeFoo
|
537
|
+
end.
|
538
|
+
forbid(@user).
|
539
|
+
forbid(@user2).
|
540
|
+
permit(@user3).
|
541
|
+
permit(nil)
|
542
|
+
end
|
543
|
+
|
544
|
+
it "should also respect :to and :except" do
|
545
|
+
class Moo; end
|
546
|
+
|
547
|
+
@user << :foo
|
548
|
+
@user2 << [:joo, @foo]
|
549
|
+
@user3 << [:qoo, Moo]
|
550
|
+
|
551
|
+
acl do
|
552
|
+
allow :foo, :boo, :to => [:index, :show]
|
553
|
+
allow :zoo, :joo, :by => :foo, :to => [:edit, :update]
|
554
|
+
allow :qoo, :woo, :of => Moo
|
555
|
+
deny :qoo, :woo, :of => Moo, :except => [:delete, :destroy]
|
556
|
+
end.
|
557
|
+
permit(@user, 'index').
|
558
|
+
permit(@user, 'show').
|
559
|
+
forbid(@user, 'edit').
|
560
|
+
permit(@user2, 'edit', :foo => @foo).
|
561
|
+
permit(@user2, 'update', :foo => @foo).
|
562
|
+
forbid(@user2, 'show', :foo => @foo).
|
563
|
+
forbid(@user2, 'show').
|
564
|
+
permit(@user3, 'delete').
|
565
|
+
permit(@user3, 'destroy').
|
566
|
+
forbid(@user3, 'edit').
|
567
|
+
forbid(@user3, 'show')
|
568
|
+
end
|
569
|
+
end
|
570
|
+
|
571
|
+
describe "actions block" do
|
572
|
+
it "should raise an ArgumentError when actions has no block" do
|
573
|
+
arg_err do
|
574
|
+
actions :foo, :bar
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
578
|
+
it "should raise an ArgumentError when actions has no arguments" do
|
579
|
+
arg_err do
|
580
|
+
actions do end
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
it "should raise an ArgumentError when actions is called inside actions block" do
|
585
|
+
arg_err do
|
586
|
+
actions :foo, :bar do
|
587
|
+
actions :foo, :bar do
|
588
|
+
end
|
589
|
+
end
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
it "should raise an ArgumentError when default is called inside actions block" do
|
594
|
+
arg_err do
|
595
|
+
actions :foo, :bar do
|
596
|
+
default :allow
|
597
|
+
end
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
[:to, :except].each do |opt|
|
602
|
+
it "should raise an ArgumentError when allow is called with #{opt} option" do
|
603
|
+
arg_err do
|
604
|
+
actions :foo do
|
605
|
+
allow all, opt => :bar
|
606
|
+
end
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
it "should raise an ArgumentError when deny is called with #{opt} option" do
|
611
|
+
arg_err do
|
612
|
+
actions :foo do
|
613
|
+
deny all, opt => :bar
|
614
|
+
end
|
615
|
+
end
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
it "empty actions block should do nothing" do
|
620
|
+
acl do
|
621
|
+
actions :foo do
|
622
|
+
end
|
623
|
+
|
624
|
+
allow all
|
625
|
+
end.permit(nil).permit(nil, :foo)
|
626
|
+
end
|
627
|
+
|
628
|
+
it "#allow should limit its scope to specified actions" do
|
629
|
+
@user << :bee
|
630
|
+
|
631
|
+
acl do
|
632
|
+
actions :edit do
|
633
|
+
allow :bee
|
634
|
+
end
|
635
|
+
end.
|
636
|
+
permit(@user, :edit).
|
637
|
+
forbid(@user, :update)
|
638
|
+
end
|
639
|
+
|
640
|
+
it "#deny should limit its scope to specified actions" do
|
641
|
+
@user << :bee
|
642
|
+
|
643
|
+
acl do
|
644
|
+
default :allow
|
645
|
+
actions :edit do
|
646
|
+
deny :bee
|
647
|
+
end
|
648
|
+
end.
|
649
|
+
forbid(@user, :edit).
|
650
|
+
permit(@user, :update)
|
651
|
+
end
|
652
|
+
|
653
|
+
it "#allow and #deny should work together inside actions block" do
|
654
|
+
@foo = FakeFoo.new
|
655
|
+
@user << [:owner, @foo]
|
656
|
+
@user2 << :hacker
|
657
|
+
@user2 << :the_destroyer
|
658
|
+
@user3 << [:owner, @foo]
|
659
|
+
@user3 << :hacker
|
660
|
+
|
661
|
+
list = acl do
|
662
|
+
actions :show, :index do
|
663
|
+
allow all
|
664
|
+
end
|
665
|
+
|
666
|
+
actions :edit, :update do
|
667
|
+
allow :owner, :of => :object
|
668
|
+
deny :hacker
|
669
|
+
end
|
670
|
+
|
671
|
+
actions :delete, :destroy do
|
672
|
+
allow :owner, :of => :object
|
673
|
+
allow :the_destroyer
|
674
|
+
end
|
675
|
+
end
|
676
|
+
|
677
|
+
@all_actions = %w(show index edit update delete destroy)
|
678
|
+
|
679
|
+
permit_some(list, @user, @all_actions, :object => @foo)
|
680
|
+
permit_some(list, @user2, %w(show index delete destroy))
|
681
|
+
permit_some(list, @user3, %w(show index delete destroy), :object => @foo)
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
private
|
686
|
+
|
687
|
+
def acl(meth = :current_user, &block)
|
688
|
+
producer = Acl9::FilterProducer.new(meth)
|
689
|
+
producer.acl(&block)
|
690
|
+
|
691
|
+
producer.extend(PermissionChecks)
|
692
|
+
|
693
|
+
producer
|
694
|
+
end
|
695
|
+
|
696
|
+
def arg_err(&block)
|
697
|
+
lambda do
|
698
|
+
acl(&block)
|
699
|
+
end.should raise_error(ArgumentError)
|
700
|
+
end
|
701
|
+
|
702
|
+
def permit_some(list, user, actions, vars = {})
|
703
|
+
actions.each { |act| list.permit(user, act, vars) }
|
704
|
+
(@all_actions - actions).each { |act| list.forbid(user, act, vars) }
|
705
|
+
end
|
706
|
+
end
|
707
|
+
end
|