aegis 1.1.8 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.gitignore +4 -0
  2. data/README.rdoc +58 -165
  3. data/Rakefile +20 -12
  4. data/VERSION +1 -1
  5. data/aegis.gemspec +85 -56
  6. data/lib/aegis.rb +9 -6
  7. data/lib/aegis/access_denied.rb +4 -0
  8. data/lib/aegis/action.rb +99 -0
  9. data/lib/aegis/compiler.rb +113 -0
  10. data/lib/aegis/has_role.rb +89 -110
  11. data/lib/aegis/parser.rb +110 -0
  12. data/lib/aegis/permissions.rb +164 -107
  13. data/lib/aegis/resource.rb +158 -0
  14. data/lib/aegis/role.rb +25 -55
  15. data/lib/aegis/sieve.rb +39 -0
  16. data/lib/rails/action_controller.rb +38 -0
  17. data/lib/rails/active_record.rb +1 -5
  18. data/spec/action_controller_spec.rb +100 -0
  19. data/spec/app_root/app/controllers/application_controller.rb +7 -0
  20. data/spec/app_root/app/controllers/reviews_controller.rb +36 -0
  21. data/spec/app_root/app/models/permissions.rb +14 -0
  22. data/spec/app_root/app/models/property.rb +5 -0
  23. data/spec/app_root/app/models/review.rb +5 -0
  24. data/{test → spec}/app_root/app/models/user.rb +1 -2
  25. data/{test → spec}/app_root/config/boot.rb +0 -0
  26. data/{test → spec}/app_root/config/database.yml +0 -0
  27. data/{test → spec}/app_root/config/environment.rb +0 -0
  28. data/{test → spec}/app_root/config/environments/in_memory.rb +0 -0
  29. data/{test → spec}/app_root/config/environments/mysql.rb +0 -0
  30. data/{test → spec}/app_root/config/environments/postgresql.rb +0 -0
  31. data/{test → spec}/app_root/config/environments/sqlite.rb +0 -0
  32. data/{test → spec}/app_root/config/environments/sqlite3.rb +0 -0
  33. data/spec/app_root/config/routes.rb +7 -0
  34. data/{test/app_root/db/migrate/20090408115228_create_users.rb → spec/app_root/db/migrate/001_create_users.rb} +2 -1
  35. data/spec/app_root/db/migrate/002_create_properties.rb +13 -0
  36. data/spec/app_root/db/migrate/003_create_reviews.rb +14 -0
  37. data/{test → spec}/app_root/lib/console_with_fixtures.rb +0 -0
  38. data/{test → spec}/app_root/log/.gitignore +0 -0
  39. data/{test → spec}/app_root/script/console +0 -0
  40. data/spec/controllers/reviews_controller_spec.rb +19 -0
  41. data/spec/has_role_spec.rb +177 -0
  42. data/spec/permissions_spec.rb +550 -0
  43. data/spec/rcov.opts +2 -0
  44. data/spec/spec.opts +4 -0
  45. data/{test/test_helper.rb → spec/spec_helper.rb} +6 -9
  46. metadata +73 -57
  47. data/lib/aegis/constants.rb +0 -6
  48. data/lib/aegis/normalization.rb +0 -26
  49. data/lib/aegis/permission_error.rb +0 -5
  50. data/lib/aegis/permission_evaluator.rb +0 -34
  51. data/test/app_root/app/controllers/application_controller.rb +0 -2
  52. data/test/app_root/app/models/old_soldier.rb +0 -6
  53. data/test/app_root/app/models/permissions.rb +0 -49
  54. data/test/app_root/app/models/soldier.rb +0 -5
  55. data/test/app_root/app/models/trust_fund_kid.rb +0 -5
  56. data/test/app_root/app/models/user_subclass.rb +0 -2
  57. data/test/app_root/app/models/veteran_soldier.rb +0 -6
  58. data/test/app_root/config/routes.rb +0 -4
  59. data/test/app_root/db/migrate/20090429075648_create_soldiers.rb +0 -14
  60. data/test/app_root/db/migrate/20091110075648_create_veteran_soldiers.rb +0 -14
  61. data/test/app_root/db/migrate/20091110075649_create_trust_fund_kids.rb +0 -15
  62. data/test/has_role_options_test.rb +0 -64
  63. data/test/has_role_test.rb +0 -54
  64. data/test/permissions_test.rb +0 -109
  65. data/test/validation_test.rb +0 -55
@@ -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)
@@ -1,5 +1 @@
1
- ActiveRecord::Base.class_eval do
2
-
3
- extend Aegis::HasRole
4
-
5
- end
1
+ ActiveRecord::Base.extend(Aegis::HasRole)
@@ -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,7 @@
1
+ class ApplicationController < ActionController::Base
2
+
3
+ def current_user
4
+ User.new(:role_name => 'user')
5
+ end
6
+
7
+ 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
@@ -0,0 +1,14 @@
1
+ class Permissions < Aegis::Permissions
2
+
3
+ role :user
4
+
5
+ resources :properties do
6
+ resources :reviews do
7
+ reading do
8
+ allow :user
9
+ end
10
+ end
11
+ end
12
+
13
+ end
14
+
@@ -0,0 +1,5 @@
1
+ class Property < ActiveRecord::Base
2
+
3
+ has_many :reviews
4
+
5
+ end
@@ -0,0 +1,5 @@
1
+ class Review < ActiveRecord::Base
2
+
3
+ belongs_to :property
4
+
5
+ end
@@ -1,6 +1,5 @@
1
1
  class User < ActiveRecord::Base
2
2
 
3
3
  has_role
4
- validates_role_name
5
4
 
6
- end
5
+ end
File without changes
File without changes
File without changes
@@ -0,0 +1,7 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+
3
+ map.resources :properties do |properties|
4
+ properties.resources :reviews
5
+ end
6
+
7
+ end
@@ -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
@@ -0,0 +1,13 @@
1
+ class CreateProperties < ActiveRecord::Migration
2
+
3
+ def self.up
4
+ create_table :properties do |t|
5
+ t.timestamps
6
+ end
7
+ end
8
+
9
+ def self.down
10
+ drop_table :properties
11
+ end
12
+
13
+ end
@@ -0,0 +1,14 @@
1
+ class CreateReviews < ActiveRecord::Migration
2
+
3
+ def self.up
4
+ create_table :reviews do |t|
5
+ t.integer :property_id
6
+ t.timestamps
7
+ end
8
+ end
9
+
10
+ def self.down
11
+ drop_table :reviews
12
+ end
13
+
14
+ end
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