aegis 1.1.8 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/README.rdoc +58 -165
- data/Rakefile +20 -12
- data/VERSION +1 -1
- data/aegis.gemspec +85 -56
- data/lib/aegis.rb +9 -6
- data/lib/aegis/access_denied.rb +4 -0
- data/lib/aegis/action.rb +99 -0
- data/lib/aegis/compiler.rb +113 -0
- data/lib/aegis/has_role.rb +89 -110
- data/lib/aegis/parser.rb +110 -0
- data/lib/aegis/permissions.rb +164 -107
- data/lib/aegis/resource.rb +158 -0
- data/lib/aegis/role.rb +25 -55
- data/lib/aegis/sieve.rb +39 -0
- data/lib/rails/action_controller.rb +38 -0
- data/lib/rails/active_record.rb +1 -5
- data/spec/action_controller_spec.rb +100 -0
- data/spec/app_root/app/controllers/application_controller.rb +7 -0
- data/spec/app_root/app/controllers/reviews_controller.rb +36 -0
- data/spec/app_root/app/models/permissions.rb +14 -0
- data/spec/app_root/app/models/property.rb +5 -0
- data/spec/app_root/app/models/review.rb +5 -0
- data/{test → spec}/app_root/app/models/user.rb +1 -2
- data/{test → spec}/app_root/config/boot.rb +0 -0
- data/{test → spec}/app_root/config/database.yml +0 -0
- data/{test → spec}/app_root/config/environment.rb +0 -0
- data/{test → spec}/app_root/config/environments/in_memory.rb +0 -0
- data/{test → spec}/app_root/config/environments/mysql.rb +0 -0
- data/{test → spec}/app_root/config/environments/postgresql.rb +0 -0
- data/{test → spec}/app_root/config/environments/sqlite.rb +0 -0
- data/{test → spec}/app_root/config/environments/sqlite3.rb +0 -0
- data/spec/app_root/config/routes.rb +7 -0
- data/{test/app_root/db/migrate/20090408115228_create_users.rb → spec/app_root/db/migrate/001_create_users.rb} +2 -1
- data/spec/app_root/db/migrate/002_create_properties.rb +13 -0
- data/spec/app_root/db/migrate/003_create_reviews.rb +14 -0
- data/{test → spec}/app_root/lib/console_with_fixtures.rb +0 -0
- data/{test → spec}/app_root/log/.gitignore +0 -0
- data/{test → spec}/app_root/script/console +0 -0
- data/spec/controllers/reviews_controller_spec.rb +19 -0
- data/spec/has_role_spec.rb +177 -0
- data/spec/permissions_spec.rb +550 -0
- data/spec/rcov.opts +2 -0
- data/spec/spec.opts +4 -0
- data/{test/test_helper.rb → spec/spec_helper.rb} +6 -9
- metadata +73 -57
- data/lib/aegis/constants.rb +0 -6
- data/lib/aegis/normalization.rb +0 -26
- data/lib/aegis/permission_error.rb +0 -5
- data/lib/aegis/permission_evaluator.rb +0 -34
- data/test/app_root/app/controllers/application_controller.rb +0 -2
- data/test/app_root/app/models/old_soldier.rb +0 -6
- data/test/app_root/app/models/permissions.rb +0 -49
- data/test/app_root/app/models/soldier.rb +0 -5
- data/test/app_root/app/models/trust_fund_kid.rb +0 -5
- data/test/app_root/app/models/user_subclass.rb +0 -2
- data/test/app_root/app/models/veteran_soldier.rb +0 -6
- data/test/app_root/config/routes.rb +0 -4
- data/test/app_root/db/migrate/20090429075648_create_soldiers.rb +0 -14
- data/test/app_root/db/migrate/20091110075648_create_veteran_soldiers.rb +0 -14
- data/test/app_root/db/migrate/20091110075649_create_trust_fund_kids.rb +0 -15
- data/test/has_role_options_test.rb +0 -64
- data/test/has_role_test.rb +0 -54
- data/test/permissions_test.rb +0 -109
- data/test/validation_test.rb +0 -55
data/lib/aegis/sieve.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Aegis
|
2
|
+
class Sieve
|
3
|
+
|
4
|
+
def initialize(role_name, effect, block = nil)
|
5
|
+
role_name = 'everyone' if role_name.blank?
|
6
|
+
@role_name = role_name.to_s
|
7
|
+
@effect = effect
|
8
|
+
@block = block
|
9
|
+
end
|
10
|
+
|
11
|
+
def may?(context, *args)
|
12
|
+
matches_role = @role_name == 'everyone' || @role_name == context.user.role_name
|
13
|
+
if matches_role
|
14
|
+
if @block
|
15
|
+
block_result = context.instance_exec(*args, &@block)
|
16
|
+
block_result ? @effect : !@effect
|
17
|
+
else
|
18
|
+
@effect
|
19
|
+
end
|
20
|
+
else
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
"Sieve(#{{:role_name => @role_name, :effect => @effect ? :allow : :deny, :block => @block.present?}.inspect})"
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.allow_to_all
|
30
|
+
new('everyone', true)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.deny_to_all
|
34
|
+
new('everyone', false)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Aegis
|
2
|
+
module ActionController
|
3
|
+
|
4
|
+
def permissions(resource, options = {})
|
5
|
+
|
6
|
+
before_filter :check_permissions, options.slice(:except, :only)
|
7
|
+
|
8
|
+
instance_eval do
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
actions_map = (options[:map] || {}).stringify_keys
|
13
|
+
object_method = options[:object] || :object
|
14
|
+
parent_object_method = options[:parent_object] || :parent_object
|
15
|
+
user_method = options[:user] || :current_user
|
16
|
+
permissions = lambda { Aegis::Permissions.app_permissions(options[:permissions]) }
|
17
|
+
|
18
|
+
define_method :check_permissions do
|
19
|
+
action = permissions.call.guess_action(
|
20
|
+
resource,
|
21
|
+
action_name.to_s,
|
22
|
+
actions_map
|
23
|
+
)
|
24
|
+
args = []
|
25
|
+
args << send(user_method)
|
26
|
+
args << send(parent_object_method) if action.takes_parent_object
|
27
|
+
args << send(object_method) if action.takes_object
|
28
|
+
action.may!(*args)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
ActionController::Base.extend(Aegis::ActionController)
|
data/lib/rails/active_record.rb
CHANGED
@@ -0,0 +1,100 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/spec_helper"
|
2
|
+
|
3
|
+
describe 'Aegis::ActionController' do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
|
7
|
+
permissions_class = @permissions_class = Class.new(Aegis::Permissions) do
|
8
|
+
role :user
|
9
|
+
resources :posts do
|
10
|
+
reading do
|
11
|
+
allow :user
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
@user_class = Class.new(ActiveRecord::Base) do
|
17
|
+
set_table_name 'users'
|
18
|
+
has_role :permissions => permissions_class
|
19
|
+
end
|
20
|
+
|
21
|
+
user = @user = @user_class.new(:role_name => 'user')
|
22
|
+
|
23
|
+
@controller_class = Class.new(ActionController::Base) do
|
24
|
+
define_method :current_user do
|
25
|
+
user
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'permissions' do
|
32
|
+
|
33
|
+
it "should fetch the context through #object, #parent_object and #current_user by default" do
|
34
|
+
permissions_class = @permissions_class
|
35
|
+
@controller_class.class_eval do
|
36
|
+
permissions :posts, :permissions => permissions_class
|
37
|
+
end
|
38
|
+
controller = @controller_class.new
|
39
|
+
permissions_class.stub(:guess_action => stub('action', :takes_object => true, :takes_parent_object => true).as_null_object)
|
40
|
+
controller.should_receive(:object)
|
41
|
+
controller.should_receive(:parent_object)
|
42
|
+
controller.should_receive(:current_user)
|
43
|
+
controller.send(:check_permissions)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should allow custom readers for object, parent object and the current user" do
|
47
|
+
permissions_class = @permissions_class
|
48
|
+
@controller_class.class_eval do
|
49
|
+
permissions :posts, :permissions => permissions_class, :user => :my_user, :object => :my_object, :parent_object => :my_parent
|
50
|
+
end
|
51
|
+
controller = @controller_class.new
|
52
|
+
permissions_class.stub(:guess_action => stub('action', :takes_object => true, :takes_parent_object => true).as_null_object)
|
53
|
+
controller.should_receive(:my_object)
|
54
|
+
controller.should_receive(:my_parent)
|
55
|
+
controller.should_receive(:my_user)
|
56
|
+
controller.send(:check_permissions)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should install a before_filter that checks permissions' do
|
60
|
+
@controller_class.should_receive(:before_filter).with(:check_permissions, :only => [:update])
|
61
|
+
permissions_class = @permissions_class
|
62
|
+
@controller_class.class_eval do
|
63
|
+
permissions :posts, :permissions => permissions_class, :only => [:update]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
describe 'check_permissions' do
|
70
|
+
|
71
|
+
before(:each) do
|
72
|
+
permissions_class = @permissions_class
|
73
|
+
map = @map = { 'controller_action' => 'permission_path' }
|
74
|
+
@controller_class.class_eval do
|
75
|
+
permissions :posts, :permissions => permissions_class, :map => map
|
76
|
+
end
|
77
|
+
@controller = @controller_class.new
|
78
|
+
@controller.stub(:object => "object", :parent_object => "parent object")
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should guess the aegis-action using the current resource, action name and actions-map" do
|
82
|
+
@controller.stub(:action_name => 'update')
|
83
|
+
action = stub("action").as_null_object
|
84
|
+
@permissions_class.should_receive(:guess_action).with(:posts, 'update', @map).and_return(action)
|
85
|
+
@controller.send(:check_permissions)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should raise an error if permission is denied" do
|
89
|
+
@controller.stub(:action_name => 'update')
|
90
|
+
lambda { @controller.send(:check_permissions) }.should raise_error(Aegis::AccessDenied)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should pass silently if permission is granted" do
|
94
|
+
@controller.stub(:action_name => 'show')
|
95
|
+
lambda { @controller.send(:check_permissions) }.should_not raise_error
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class ReviewsController < ApplicationController
|
2
|
+
|
3
|
+
permissions :property_reviews
|
4
|
+
|
5
|
+
def show
|
6
|
+
end
|
7
|
+
|
8
|
+
def edit
|
9
|
+
end
|
10
|
+
|
11
|
+
def update
|
12
|
+
end
|
13
|
+
|
14
|
+
def new
|
15
|
+
end
|
16
|
+
|
17
|
+
def create
|
18
|
+
end
|
19
|
+
|
20
|
+
def destroy
|
21
|
+
end
|
22
|
+
|
23
|
+
def index
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def object
|
29
|
+
@oject ||= parent_object.reviews.find(params[:id])
|
30
|
+
end
|
31
|
+
|
32
|
+
def parent_object
|
33
|
+
@parent_object ||= Property.find(params[:id])
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -3,6 +3,7 @@ class CreateUsers < ActiveRecord::Migration
|
|
3
3
|
def self.up
|
4
4
|
create_table :users do |t|
|
5
5
|
t.string :role_name
|
6
|
+
t.string :name
|
6
7
|
t.timestamps
|
7
8
|
end
|
8
9
|
end
|
@@ -10,5 +11,5 @@ class CreateUsers < ActiveRecord::Migration
|
|
10
11
|
def self.down
|
11
12
|
drop_table :users
|
12
13
|
end
|
13
|
-
|
14
|
+
|
14
15
|
end
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe ReviewsController do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
Property.stub(:find => stub("a property", :reviews => stub("reviews", :find => "a review")))
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should grant access in a fully integrated scenario" do
|
10
|
+
lambda { get :show, :property_id => '10', :id => '20' }.should_not raise_error
|
11
|
+
lambda { get :index, :property_id => '10' }.should_not raise_error
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should deny access in a fully integrated scenario" do
|
15
|
+
lambda { put :update, :property_id => '10', :id => '20' }.should raise_error(Aegis::AccessDenied)
|
16
|
+
lambda { get :new, :property_id => '10' }.should raise_error(Aegis::AccessDenied)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/spec_helper"
|
2
|
+
|
3
|
+
describe Aegis::HasRole do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
|
7
|
+
@permissions_class = permissions_class = Class.new(Aegis::Permissions) do
|
8
|
+
role :user
|
9
|
+
role :admin
|
10
|
+
end
|
11
|
+
|
12
|
+
@user_class = Class.new(ActiveRecord::Base) do
|
13
|
+
set_table_name 'users'
|
14
|
+
has_role :permissions => permissions_class
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'has_role' do
|
20
|
+
|
21
|
+
it "should define accessors for the role association" do
|
22
|
+
user = @user_class.new
|
23
|
+
user.should respond_to(:role)
|
24
|
+
user.should respond_to(:role=)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'role' do
|
30
|
+
|
31
|
+
it "should be nil by default" do
|
32
|
+
user = @user_class.new
|
33
|
+
user.role.should be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should return the role corresponding to the role_name" do
|
37
|
+
user = @user_class.new(:role_name => 'admin')
|
38
|
+
user.role.name.should == 'admin'
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should be nil if the role_name doesn't match a known role" do
|
42
|
+
user = @user_class.new(:role_name => 'nonexisting_role_name')
|
43
|
+
user.role.should be_nil
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should read the role name from a custom reader" do
|
47
|
+
permissions_class = @permissions_class
|
48
|
+
@user_class.class_eval { has_role :reader => "role_handle", :permissions => permissions_class }
|
49
|
+
user = @user_class.new
|
50
|
+
user.should_receive(:role_handle).and_return('admin')
|
51
|
+
user.role
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should read the role name from a custom accessor" do
|
55
|
+
permissions_class = @permissions_class
|
56
|
+
@user_class.class_eval { has_role :accessor => "role_handle", :permissions => permissions_class }
|
57
|
+
user = @user_class.new
|
58
|
+
user.should_receive(:role_handle).and_return('admin')
|
59
|
+
user.role
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should take a default role" do
|
63
|
+
permissions_class = @permissions_class
|
64
|
+
@user_class.class_eval { has_role :default => "admin", :permissions => permissions_class }
|
65
|
+
user = @user_class.new
|
66
|
+
user.role.name.should == 'admin'
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
describe 'role=' do
|
72
|
+
|
73
|
+
before :each do
|
74
|
+
@admin_role = @permissions_class.find_role_by_name('admin')
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should write the role name to the role_name attribute" do
|
78
|
+
user = @user_class.new
|
79
|
+
user.should_receive(:role_name=).with('admin')
|
80
|
+
user.role = @admin_role
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should write the role name to a custom writer" do
|
84
|
+
permissions_class = @permissions_class
|
85
|
+
@user_class.class_eval { has_role :writer => "role_handle=", :permissions => permissions_class }
|
86
|
+
user = @user_class.new
|
87
|
+
user.should_receive(:role_handle=).with('admin')
|
88
|
+
user.role = @admin_role
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should write the role name to a custom accessor" do
|
92
|
+
permissions_class = @permissions_class
|
93
|
+
@user_class.class_eval { has_role :accessor => "role_handle", :permissions => permissions_class }
|
94
|
+
user = @user_class.new
|
95
|
+
user.should_receive(:role_handle=).with('admin')
|
96
|
+
user.role = @admin_role
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'method_missing' do
|
102
|
+
|
103
|
+
it "should delegate may...? messages to the permissions class" do
|
104
|
+
user = @user_class.new
|
105
|
+
@permissions_class.should_receive(:may?).with(user, 'do_action', 'argument')
|
106
|
+
user.may_do_action?('argument')
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should delegate may...! messages to the permissions class" do
|
110
|
+
user = @user_class.new
|
111
|
+
@permissions_class.should_receive(:may!).with(user, 'do_action', 'argument')
|
112
|
+
user.may_do_action!('argument')
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should retain its usual behaviour for non-permission methods" do
|
116
|
+
user = @user_class.new
|
117
|
+
lambda { user.nonexisting_method }.should raise_error(NoMethodError)
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
describe 'respond_to?' do
|
123
|
+
|
124
|
+
it "should be true for all permission methods, even if they are not explicitely defined" do
|
125
|
+
user = @user_class.new
|
126
|
+
user.should respond_to(:may_foo?)
|
127
|
+
user.should respond_to(:may_foo!)
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should retain its usual behaviour for non-permission methods" do
|
131
|
+
user = @user_class.new
|
132
|
+
user.should respond_to(:to_s)
|
133
|
+
user.should_not respond_to(:nonexisting_method)
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
describe 'validates_role' do
|
139
|
+
|
140
|
+
before(:each) do
|
141
|
+
@user_class.class_eval { validates_role }
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should run the validation callback :validate_role" do
|
145
|
+
user = @user_class.new
|
146
|
+
user.should_receive(:validate_role)
|
147
|
+
user.run_callbacks(:validate)
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should add an inclusion error to the role name if the role name is blank" do
|
151
|
+
user = @user_class.new(:role_name => '')
|
152
|
+
user.errors.should_receive(:add).with(:role_name, I18n.translate('activerecord.errors.messages.inclusion'))
|
153
|
+
user.send(:validate_role)
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should add an inclusion error to the role name if the role name doesn't match a role" do
|
157
|
+
user = @user_class.new(:role_name => 'nonexisting_role')
|
158
|
+
user.errors.should_receive(:add).with(:role_name, I18n.translate('activerecord.errors.messages.inclusion'))
|
159
|
+
user.send(:validate_role)
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should add no error if the role name matches a role" do
|
163
|
+
user = @user_class.new(:role_name => 'admin')
|
164
|
+
user.errors.should_not_receive(:add)
|
165
|
+
user.send(:validate_role)
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should allow a custom error message with the :message options" do
|
169
|
+
@user_class.class_eval { validates_role :message => "custom message" }
|
170
|
+
user = @user_class.new(:role_name => '')
|
171
|
+
user.errors.should_receive(:add).with(:role_name, 'custom message')
|
172
|
+
user.send(:validate_role)
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|