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,550 @@
1
+ require File.dirname(__FILE__) + "/spec_helper"
2
+
3
+ describe Aegis::Permissions do
4
+
5
+ before(:each) do
6
+
7
+ permissions = @permissions = Class.new(Aegis::Permissions) do
8
+ role :user
9
+ role :moderator
10
+ role :admin, :default_permission => :allow
11
+ end
12
+
13
+ @user_class = Class.new(ActiveRecord::Base) do
14
+ set_table_name 'users'
15
+ has_role :permissions => permissions
16
+ end
17
+
18
+ @user = @user_class.new(:role_name => 'user')
19
+ @moderator = @user_class.new(:role_name => 'moderator')
20
+ @admin = @user_class.new(:role_name => 'admin')
21
+
22
+ end
23
+
24
+ describe 'find_role_by_name' do
25
+
26
+ it "should look up previously defined roles" do
27
+
28
+ user_role = @permissions.find_role_by_name('user')
29
+ admin_role = @permissions.find_role_by_name('admin')
30
+
31
+ user_role.name.should == 'user'
32
+ user_role.may_by_default?.should be_false
33
+
34
+ admin_role.name.should == 'admin'
35
+ admin_role.may_by_default?.should be_true
36
+
37
+ end
38
+
39
+ it "should be nil if no role with that name was defined" do
40
+ @permissions.find_role_by_name('nonexisting_role').should be_nil
41
+ end
42
+
43
+ end
44
+
45
+ describe 'permission definition' do
46
+
47
+ it "should allow the definition of simple actions" do
48
+
49
+ @permissions.class_eval do
50
+ action :action_name do
51
+ allow :user
52
+ end
53
+ end
54
+
55
+ @permissions.may?(@user, 'action_name').should be_true
56
+
57
+ end
58
+
59
+ it "should allow the definition of multiple actions at once" do
60
+
61
+ @permissions.class_eval do
62
+ action :action1, :action2 do
63
+ allow
64
+ end
65
+ end
66
+
67
+ @permissions.may?(@user, 'action1').should be_true
68
+ @permissions.may?(@user, 'action2').should be_true
69
+ @permissions.may?(@user, 'action3').should be_false
70
+
71
+ end
72
+
73
+ it "should match an allow/deny directive to everyone is no role is named" do
74
+ @permissions.class_eval do
75
+ action :allowed_to_all do
76
+ allow
77
+ end
78
+ action :denied_to_all do
79
+ deny
80
+ end
81
+ end
82
+
83
+ @permissions.may?(@user, 'allowed_to_all').should be_true
84
+ @permissions.may?(@admin, 'denied_to_all').should be_false
85
+ end
86
+
87
+ it "should allow to grant permissions to multiple roles at once" do
88
+
89
+ @permissions.class_eval do
90
+ action :action_name do
91
+ allow :user, :moderator
92
+ end
93
+ end
94
+
95
+ @permissions.may?(@user, 'action_name').should be_true
96
+ @permissions.may?(@moderator, 'action_name').should be_true
97
+
98
+ end
99
+
100
+ it "should return the default permission when queried for undefined actions" do
101
+
102
+ @permissions.may?(@user, 'undefined_action').should be_false
103
+ @permissions.may?(@admin, 'undefined_action').should be_true
104
+
105
+ end
106
+
107
+ it "should distinguish between roles" do
108
+
109
+ @permissions.class_eval do
110
+ action :update_news do
111
+ allow :moderator
112
+ end
113
+ end
114
+
115
+ @permissions.may?(@user, 'update_news').should be_false
116
+ @permissions.may?(@moderator, 'update_news').should be_true
117
+
118
+ end
119
+
120
+ it "should run sieves in a sequence, the result being the last matching sieve" do
121
+
122
+ @permissions.class_eval do
123
+ action :update_news do
124
+ allow :everyone
125
+ deny :user
126
+ end
127
+ end
128
+
129
+ @permissions.may?(@user, 'update_news').should be_false
130
+ @permissions.may?(@moderator, 'update_news').should be_true
131
+
132
+ end
133
+
134
+ it "should evaluate collection resources" do
135
+
136
+ @permissions.class_eval do
137
+ resources :posts do
138
+ allow :moderator
139
+ end
140
+ end
141
+
142
+ @permissions.may?(@moderator, 'update_post', "the post").should be_true
143
+ @permissions.may?(@moderator, 'show_post', "the post").should be_true
144
+ @permissions.may?(@moderator, 'create_post', "the post").should be_true
145
+ @permissions.may?(@moderator, 'destroy_post', "the post").should be_true
146
+ @permissions.may?(@moderator, 'index_posts').should be_true
147
+
148
+ end
149
+
150
+ it "should allow to configure generated resource actions" do
151
+
152
+ @permissions.class_eval do
153
+ resources :posts do
154
+ action :index do
155
+ allow :user
156
+ end
157
+ action :show do
158
+ allow :user
159
+ end
160
+ end
161
+ end
162
+
163
+ @permissions.may?(@user, 'update_post', "the post").should be_false
164
+ @permissions.may?(@user, 'show_post', "the post").should be_true
165
+ @permissions.may?(@user, 'create_post', "the post").should be_false
166
+ @permissions.may?(@user, 'destroy_post', "the post").should be_false
167
+ @permissions.may?(@user, 'index_posts').should be_true
168
+
169
+ @permissions.find_action_by_path('index_posts').takes_object.should be_false
170
+ @permissions.find_action_by_path('show_post').takes_object.should be_true
171
+
172
+ end
173
+
174
+ it "should raise an error if an action takes an object but does not get it in the arguments" do
175
+
176
+ @permissions.class_eval do
177
+ resources :posts do
178
+ allow :moderator
179
+ end
180
+ end
181
+
182
+ @permissions.may?(@moderator, 'update_post', "the post").should be_true
183
+
184
+ end
185
+
186
+ it "should evaluate sieves with blocks" do
187
+
188
+ @permissions.class_eval do
189
+ resources :posts do
190
+ allow :user do
191
+ user.name == 'Waldo'
192
+ end
193
+ end
194
+ end
195
+
196
+ frank = @user_class.new(:name => 'Frank', :role_name => 'user')
197
+ waldo = @user_class.new(:name => 'Waldo', :role_name => 'user')
198
+
199
+ frank.may_update_post?('the post').should be_false
200
+ waldo.may_update_post?('the post').should be_true
201
+
202
+ end
203
+
204
+ it "should evaluate singleton resources, which take no object" do
205
+
206
+ @permissions.class_eval do
207
+ resource :session do
208
+ allow :moderator
209
+ end
210
+ end
211
+
212
+ @permissions.may?(@moderator, 'update_session').should be_true
213
+ @permissions.may?(@moderator, 'show_session').should be_true
214
+ @permissions.may?(@moderator, 'create_session').should be_true
215
+ @permissions.may?(@moderator, 'destroy_session').should be_true
216
+
217
+ end
218
+
219
+ it "should allow to nest resources into collection resources" do
220
+
221
+ @permissions.class_eval do
222
+ resources :properties do
223
+ resources :comments do
224
+ allow :moderator
225
+ end
226
+ end
227
+ end
228
+
229
+ @permissions.may?(@moderator, 'update_property_comment', "the property", "the comment").should be_true
230
+ @permissions.may?(@moderator, 'show_property_comment', "the property", "the comment").should be_true
231
+ @permissions.may?(@moderator, 'create_property_comment', "the property").should be_true
232
+ @permissions.may?(@moderator, 'destroy_property_comment', "the property", "the comment").should be_true
233
+ @permissions.may?(@moderator, 'index_property_comments', "the property").should be_true
234
+
235
+ end
236
+
237
+ it "should allow to nest resources into singleton resources" do
238
+
239
+ @permissions.class_eval do
240
+ resource :account do
241
+ resources :bookings do
242
+ allow :moderator
243
+ end
244
+ end
245
+ end
246
+
247
+ @permissions.may?(@moderator, 'update_account_booking', "the booking").should be_true
248
+ @permissions.may?(@moderator, 'show_account_booking', "the booking").should be_true
249
+ @permissions.may?(@moderator, 'create_account_booking').should be_true
250
+ @permissions.may?(@moderator, 'destroy_account_booking', "the booking").should be_true
251
+ @permissions.may?(@moderator, 'index_account_bookings').should be_true
252
+
253
+ @permissions.find_action_by_path('update_account').should_not be_abstract
254
+
255
+ end
256
+
257
+ it "should support namespaces, which act like singleton resources but don't generate actions by default" do
258
+
259
+ @permissions.class_eval do
260
+ namespace :admin do
261
+ resources :bookings do
262
+ allow :moderator
263
+ end
264
+ end
265
+ end
266
+
267
+ @permissions.may?(@moderator, 'update_admin_booking', "the booking").should be_true
268
+ @permissions.may?(@moderator, 'show_admin_booking', "the booking").should be_true
269
+ @permissions.may?(@moderator, 'create_admin_booking').should be_true
270
+ @permissions.may?(@moderator, 'destroy_admin_booking', "the booking").should be_true
271
+ @permissions.may?(@moderator, 'index_admin_bookings').should be_true
272
+
273
+ @permissions.find_action_by_path('update_admin').should be_abstract
274
+
275
+ end
276
+
277
+ it "should allow multiple levels of resource-nesting" do
278
+
279
+ @permissions.class_eval do
280
+ resources :properties do
281
+ resources :reviews do
282
+ resources :comments do
283
+ allow :moderator
284
+ end
285
+ end
286
+ end
287
+ end
288
+
289
+ @permissions.may?(@moderator, 'update_property_review_comment', "the review", "the comment").should be_true
290
+ @permissions.may?(@moderator, 'show_property_review_comment', "the review", "the comment").should be_true
291
+ @permissions.may?(@moderator, 'create_property_review_comment', "the review").should be_true
292
+ @permissions.may?(@moderator, 'destroy_property_review_comment', "the review", "the comment").should be_true
293
+ @permissions.may?(@moderator, 'index_property_review_comments', "the review").should be_true
294
+
295
+ end
296
+
297
+ it "should raise an error if an action takes a parent object but does not get it in the arguments" do
298
+
299
+ @permissions.class_eval do
300
+ resources :properties do
301
+ resources :comments
302
+ end
303
+ end
304
+
305
+ lambda { @permissions.may?(@moderator, 'update_property_comment', "the comment") }.should raise_error(ArgumentError)
306
+
307
+ end
308
+
309
+ it "should allow sieves with blocks and arguments" do
310
+
311
+ @permissions.class_eval do
312
+ action :sign_in do
313
+ allow do |password|
314
+ user.password == password
315
+ end
316
+ end
317
+ end
318
+
319
+ @user.stub(:password => "secret")
320
+ @user.may_sign_in?("wrong_password").should be_false
321
+ @user.may_sign_in?("secret").should be_true
322
+
323
+ end
324
+
325
+ it "should provide the object and parent_object for a sieve block" do
326
+ spy = stub("spy")
327
+ @permissions.class_eval do
328
+ resources :properties do
329
+ resources :comments do
330
+ allow :moderator do |additional_argument|
331
+ spy.observe(parent_object, object, additional_argument)
332
+ end
333
+ end
334
+ end
335
+ end
336
+
337
+ spy.should_receive(:observe).with("the property", "the comment", "additional argument")
338
+ @moderator.may_update_property_comment?("the property", "the comment", "additional argument")
339
+ end
340
+
341
+ it "should evaluate additional resource actions" do
342
+
343
+ @permissions.class_eval do
344
+ resources :properties do
345
+ action :zoom_into do
346
+ allow :user
347
+ end
348
+ action :view_all, :collection => true do
349
+ allow :user
350
+ end
351
+ end
352
+ end
353
+
354
+ @permissions.may?(@user, "zoom_into_property", "the property").should be_true
355
+ @permissions.may?(@user, "view_all_properties", "the property").should be_true
356
+
357
+ end
358
+
359
+ it "should allow rules that only affect reading actions" do
360
+
361
+ @permissions.class_eval do
362
+ resources :posts do
363
+ reading do
364
+ allow :user
365
+ end
366
+ action :syndicate, :writing => false
367
+ action :close
368
+ end
369
+ end
370
+
371
+ @permissions.may?(@user, 'update_post', "the post").should be_false
372
+ @permissions.may?(@user, 'show_post', "the post").should be_true
373
+ @permissions.may?(@user, 'create_post', "the post").should be_false
374
+ @permissions.may?(@user, 'destroy_post', "the post").should be_false
375
+ @permissions.may?(@user, 'index_posts').should be_true
376
+ @permissions.may?(@user, 'syndicate_post', "the post").should be_true
377
+ @permissions.may?(@user, 'close_post", "the post').should be_false
378
+
379
+ end
380
+
381
+ it "should allow rules that only affect writing actions" do
382
+
383
+ @permissions.class_eval do
384
+ resources :posts do
385
+ writing do
386
+ allow :moderator
387
+ end
388
+ action :syndicate, :writing => false
389
+ action :close
390
+ end
391
+ end
392
+
393
+ # debugger
394
+
395
+ @permissions.may?(@moderator, 'update_post', "the post").should be_true
396
+ @permissions.may?(@moderator, 'show_post', "the post").should be_false
397
+ @permissions.may?(@moderator, 'create_post', "the post").should be_true
398
+ @permissions.may?(@moderator, 'destroy_post', "the post").should be_true
399
+ @permissions.may?(@moderator, 'index_posts').should be_false
400
+ @permissions.may?(@moderator, 'syndicate_post', "the post").should be_false
401
+ @permissions.may?(@moderator, "close_post", "the post").should be_true
402
+
403
+ end
404
+
405
+ it "should allow resources with only selected actions" do
406
+ @permissions.class_eval do
407
+ resources :posts, :only => [:show, :update]
408
+ end
409
+ @permissions.find_action_by_path('update_post').should_not be_abstract
410
+ @permissions.find_action_by_path('show_post').should_not be_abstract
411
+ @permissions.find_action_by_path('create_post').should be_abstract
412
+ @permissions.find_action_by_path('destroy_post').should be_abstract
413
+ @permissions.find_action_by_path('index_posts').should be_abstract
414
+ end
415
+
416
+ it "should allow resources with all actions except a selected few" do
417
+ @permissions.class_eval do
418
+ resources :posts, :except => [:show, :update]
419
+ end
420
+ @permissions.find_action_by_path('update_post').should be_abstract
421
+ @permissions.find_action_by_path('show_post').should be_abstract
422
+ @permissions.find_action_by_path('create_post').should_not be_abstract
423
+ @permissions.find_action_by_path('destroy_post').should_not be_abstract
424
+ @permissions.find_action_by_path('index_posts').should_not be_abstract
425
+ end
426
+
427
+
428
+ it "should alias action names for all actions and resources, aliasing #new and #edit by default" do
429
+
430
+ @permissions.class_eval do
431
+
432
+ alias_action :delete => :destroy
433
+
434
+ resources :properties do
435
+ resources :comments
436
+ end
437
+ end
438
+
439
+ @permissions.find_action_by_path('delete_property').should_not be_abstract
440
+ @permissions.find_action_by_path('new_property').should_not be_abstract
441
+ @permissions.find_action_by_path('edit_property').should_not be_abstract
442
+
443
+ @permissions.find_action_by_path('delete_property_comment').should_not be_abstract
444
+ @permissions.find_action_by_path('new_property_comment').should_not be_abstract
445
+ @permissions.find_action_by_path('edit_property_comment').should_not be_abstract
446
+
447
+ end
448
+ end
449
+
450
+ describe 'may!' do
451
+
452
+ it "should return if permission is granted" do
453
+ lambda { @permissions.may!(@admin, :delete_everything) }.should_not raise_error
454
+ end
455
+
456
+ it "should raise an error if permission is denied" do
457
+ lambda { @permissions.may!(@user, :delete_everything) }.should raise_error(Aegis::AccessDenied)
458
+ end
459
+
460
+ end
461
+
462
+ describe 'behavior when checking permissions without a user' do
463
+
464
+ it "should raise an error if the user is nil" do
465
+ lambda { @permissions.may?(nil, :some_action) }.should raise_error
466
+ end
467
+
468
+ it "should substitute the results from the blank user strategy" do
469
+ @permissions.class_eval do
470
+ missing_user_means { User.new(:role_name => 'user') }
471
+ action :create_post do
472
+ allow :moderator
473
+ end
474
+ action :show_post do
475
+ allow :user
476
+ end
477
+ end
478
+ @permissions.may?(nil, :create_post).should be_false
479
+ @permissions.may?(nil, :show_post).should be_true
480
+ end
481
+
482
+ end
483
+
484
+ describe 'behavior when a permission is not defined' do
485
+
486
+ it "should use the default permission if the strategy is :default_permission" do
487
+ @permissions.class_eval do
488
+ missing_action_means :default_permission
489
+ end
490
+ @user.may_missing_action?.should be_false
491
+ @admin.may_missing_action?.should be_true
492
+ end
493
+
494
+ it "should grant everyone access if the strategy is :allow" do
495
+ @permissions.class_eval do
496
+ missing_action_means :allow
497
+ end
498
+ @user.may_missing_action?.should be_true
499
+ @admin.may_missing_action?.should be_true
500
+ end
501
+
502
+ it "should deny everyone access if the strategy is :deny" do
503
+ @permissions.class_eval do
504
+ missing_action_means :deny
505
+ end
506
+ @user.may_missing_action?.should be_false
507
+ @admin.may_missing_action?.should be_false
508
+ end
509
+
510
+ it "should raise an error if the strategy is :error" do
511
+ @permissions.class_eval do
512
+ missing_action_means :error
513
+ end
514
+ lambda { @user.may_missing_action? }.should raise_error
515
+ lambda { @admin.may_missing_action? }.should raise_error
516
+ end
517
+
518
+ end
519
+
520
+ describe 'guess_action' do
521
+
522
+ it "should guess an action based on the given resource and action name, trying both singular and plural" do
523
+
524
+ @permissions.class_eval do
525
+ resources :posts
526
+ end
527
+
528
+ @permissions.guess_action(:posts, :index).should_not be_abstract
529
+ @permissions.guess_action(:posts, :update).should_not be_abstract
530
+ @permissions.guess_action(:posts, :unknown_action).should be_abstract
531
+
532
+ end
533
+
534
+ it "should consult the actions map first and use the the default behaviour for unmapped actions" do
535
+
536
+ @permissions.class_eval do
537
+ resources :posts, :only => [:update] do
538
+ action :view_all, :collection => true
539
+ end
540
+ end
541
+
542
+ @permissions.guess_action(:posts, 'index', 'index' => 'view_all_posts').should_not be_abstract
543
+ @permissions.guess_action(:posts, 'update', 'index' => 'view_all_posts').should_not be_abstract
544
+
545
+ end
546
+
547
+ end
548
+
549
+ end
550
+