aegis 1.1.8 → 2.0.0
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/.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
|