acl9 0.12.3 → 1.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.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +21 -7
  3. data/.travis.yml +19 -0
  4. data/Appraisals +8 -0
  5. data/CONTRIBUTING.md +58 -0
  6. data/Gemfile +2 -0
  7. data/Gemfile.lock +88 -32
  8. data/LICENSE +9 -0
  9. data/README.md +156 -0
  10. data/Rakefile +6 -3
  11. data/acl9.gemspec +10 -13
  12. data/gemfiles/rails_4.0.gemfile +8 -0
  13. data/gemfiles/rails_4.1.gemfile +8 -0
  14. data/lib/acl9/model_extensions/for_subject.rb +5 -1
  15. data/lib/acl9/model_extensions.rb +3 -24
  16. data/lib/acl9/version.rb +1 -1
  17. data/lib/acl9.rb +1 -1
  18. data/test/controller_extensions/actions_test.rb +167 -0
  19. data/test/controller_extensions/anon_test.rb +39 -0
  20. data/test/controller_extensions/base.rb +96 -0
  21. data/test/controller_extensions/basics_test.rb +44 -0
  22. data/test/controller_extensions/conditions_test.rb +48 -0
  23. data/test/controller_extensions/method_test.rb +50 -0
  24. data/test/controller_extensions/multi_match_test.rb +142 -0
  25. data/test/controller_extensions/multiple_role_arguments_test.rb +135 -0
  26. data/test/controller_extensions/prepositions_test.rb +99 -0
  27. data/test/controller_extensions/pseudo_role_test.rb +26 -0
  28. data/test/controller_extensions/role_test.rb +75 -0
  29. data/test/controllers/acl_action_override_test.rb +24 -0
  30. data/test/controllers/acl_arguments_test.rb +5 -0
  31. data/test/controllers/acl_block_test.rb +5 -0
  32. data/test/controllers/acl_boolean_method_test.rb +5 -0
  33. data/test/controllers/acl_helper_method_test.rb +26 -0
  34. data/test/controllers/acl_ivars_test.rb +15 -0
  35. data/test/controllers/acl_method2_test.rb +6 -0
  36. data/test/controllers/acl_method_test.rb +6 -0
  37. data/test/controllers/acl_object_hash_test.rb +18 -0
  38. data/test/controllers/acl_query_method_named_test.rb +9 -0
  39. data/test/controllers/acl_query_method_test.rb +9 -0
  40. data/test/controllers/acl_query_method_with_lambda_test.rb +9 -0
  41. data/test/controllers/acl_query_mixin.rb +51 -0
  42. data/test/controllers/acl_subject_method_test.rb +15 -0
  43. data/test/controllers/arguments_checking_test.rb +43 -0
  44. data/test/dummy/app/controllers/acl_action_override.rb +15 -0
  45. data/test/dummy/app/controllers/acl_arguments.rb +10 -0
  46. data/test/dummy/app/controllers/acl_block.rb +6 -0
  47. data/test/dummy/app/controllers/acl_boolean_method.rb +23 -0
  48. data/test/dummy/app/controllers/acl_helper_method.rb +11 -0
  49. data/test/dummy/app/controllers/acl_ivars.rb +17 -0
  50. data/test/dummy/app/controllers/acl_method.rb +6 -0
  51. data/test/dummy/app/controllers/acl_method2.rb +6 -0
  52. data/test/dummy/app/controllers/acl_objects_hash.rb +10 -0
  53. data/test/dummy/app/controllers/acl_query_method.rb +9 -0
  54. data/test/dummy/app/controllers/acl_query_method_named.rb +13 -0
  55. data/test/dummy/app/controllers/acl_query_method_with_lambda.rb +9 -0
  56. data/test/dummy/app/controllers/acl_subject_method.rb +16 -0
  57. data/test/dummy/app/controllers/application_controller.rb +7 -0
  58. data/test/dummy/app/controllers/empty_controller.rb +5 -0
  59. data/test/dummy/app/helpers/application_helper.rb +2 -0
  60. data/test/dummy/app/helpers/some_helper.rb +8 -0
  61. data/test/dummy/app/models/.keep +0 -0
  62. data/test/dummy/app/models/access.rb +3 -0
  63. data/test/dummy/app/models/account.rb +3 -0
  64. data/test/dummy/app/models/bar.rb +3 -0
  65. data/test/dummy/app/models/concerns/.keep +0 -0
  66. data/test/dummy/app/models/foo.rb +3 -0
  67. data/test/dummy/app/models/foo_bar.rb +3 -0
  68. data/test/dummy/app/models/other/foo.rb +5 -0
  69. data/test/dummy/app/models/other/role.rb +5 -0
  70. data/test/dummy/app/models/other/user.rb +5 -0
  71. data/test/dummy/app/models/role.rb +3 -0
  72. data/test/dummy/app/models/user.rb +3 -0
  73. data/test/dummy/app/models/uuid.rb +4 -0
  74. data/test/dummy/config/application.rb +23 -0
  75. data/test/dummy/config/boot.rb +4 -0
  76. data/test/dummy/config/database.yml +25 -0
  77. data/test/dummy/config/environment.rb +5 -0
  78. data/test/dummy/config/environments/development.rb +37 -0
  79. data/test/dummy/config/environments/production.rb +78 -0
  80. data/test/dummy/config/environments/test.rb +39 -0
  81. data/test/dummy/config/initializers/assets.rb +8 -0
  82. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  83. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  84. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  85. data/test/dummy/config/initializers/inflections.rb +16 -0
  86. data/test/dummy/config/initializers/mime_types.rb +4 -0
  87. data/test/dummy/config/initializers/secrets.rb +1 -0
  88. data/test/dummy/config/initializers/session_store.rb +3 -0
  89. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  90. data/test/dummy/config/locales/en.yml +23 -0
  91. data/test/dummy/config/routes.rb +3 -0
  92. data/test/dummy/config.ru +4 -0
  93. data/test/dummy/db/migrate/20141117132218_create_tables.rb +102 -0
  94. data/test/helpers/helper_test.rb +89 -0
  95. data/test/models/roles_test.rb +251 -0
  96. data/test/models/roles_with_custom_association_names_test.rb +28 -0
  97. data/test/models/roles_with_custom_class_names_test.rb +28 -0
  98. data/test/models/system_roles_test.rb +16 -0
  99. data/test/models/users_roles_and_subjects_with_namespaced_class_names_test.rb +30 -0
  100. data/test/test_helper.rb +76 -23
  101. data/test/version_test.rb +2 -2
  102. metadata +190 -74
  103. data/README.textile +0 -921
  104. data/VERSION.yml +0 -5
  105. data/init.rb +0 -1
  106. data/test/access_control_test.rb +0 -338
  107. data/test/dsl_base_test.rb +0 -795
  108. data/test/helpers_test.rb +0 -133
  109. data/test/roles_test.rb +0 -370
  110. data/test/support/controllers.rb +0 -207
  111. data/test/support/models.rb +0 -59
  112. data/test/support/schema.rb +0 -93
data/README.textile DELETED
@@ -1,921 +0,0 @@
1
- h1. Introduction
2
-
3
- Acl9 is yet another solution for role-based authorization in Rails. It consists of two
4
- subsystems which can be used separately.
5
-
6
- *Role control subsystem* allows you to set and query user roles for various objects.
7
-
8
- *Access control subsystem* allows you to specify different role-based access rules
9
- inside controllers.
10
-
11
- A bunch of access rules is translated into a complex
12
- boolean expression. Then it's turned into a lambda or a method and can
13
- be used with @before_filter@. Thus you can block unprivileged access to certain
14
- actions of your controller.
15
-
16
- An example:
17
-
18
- <pre><code>
19
- class VerySecretController < ApplicationController
20
- access_control do
21
- allow :superadmin
22
- allow :owner, :of => :secret
23
-
24
- action :index do
25
- allow anonymous, logged_in
26
- end
27
-
28
- allow logged_in, :to => :show
29
- allow :manager, :of => :secret, :except => [:delete, :destroy]
30
- deny :thiefs
31
- end
32
-
33
- def index
34
- # ...
35
- end
36
-
37
- # ...
38
- end
39
- </code></pre>
40
-
41
- h1. Contacts
42
-
43
- Acl9 is hosted "on the GitHub":http://github.com/be9/acl9.
44
-
45
- You may find tutorials and additional docs on the "wiki page":http://wiki.github.com/be9/acl9.
46
-
47
- Rdocs are available "here":http://rdoc.info/projects/be9/acl9.
48
-
49
- If you have questions, please post to the
50
- "acl9-discuss group":http://groups.google.com/group/acl9-discuss
51
-
52
- h1. Installation
53
-
54
- Acl9 can be installed as a gem from "gemcutter":http://gemcutter.org.
55
-
56
- h2. in Rails 2.3
57
-
58
- Add the following line to your @config/environment.rb@:
59
-
60
- <pre><code>
61
- config.gem "acl9", :source => "http://gemcutter.org", :lib => "acl9"
62
- </pre></code>
63
-
64
- Then run @rake gems:install@ (with possible @rake gems:unpack@ thereafter) and you're done!
65
-
66
- Alternatively you can install Acl9 as a plugin:
67
-
68
- <pre><code>
69
- script/plugin install git://github.com/be9/acl9.git
70
- </pre></code>
71
-
72
- h2. in Rails 3.0
73
-
74
- Add the following line to your @Gemfile@:
75
-
76
- <pre><code>
77
- gem "acl9"
78
- </pre></code>
79
-
80
- Then run @bundle install@ and you're done!
81
-
82
- Alternatively you can install Acl9 as a plugin:
83
-
84
- <pre><code>
85
- rails plugin install git://github.com/be9/acl9.git
86
- </pre></code>
87
-
88
- h1. Basics
89
-
90
- h2. Authorization is not authentication!
91
-
92
- Both words start with "auth" but have different meaning!
93
-
94
- *Authentication* is basically a mapping of credentials (username, password) or
95
- OpenID to specific user account in the system.
96
-
97
- *Authorization* is an _authenticated_ user's permission to perform some
98
- specific action somewhere in the system.
99
-
100
- Acl9 is a authorization solution, so you will need to implement authentication
101
- by other means. I recommend "Authlogic":http://github.com/binarylogic/authlogic
102
- for that purpose, as it's simple, clean and at the same time very configurable.
103
-
104
- h2. Roles
105
-
106
- Role is an abstraction. You could directly assign permissions to user accounts in
107
- your system, but you'd not want to! Way more manageable solution is to assign permissions
108
- to roles and roles further to users.
109
-
110
- For example, you can have role called _admin_ which has all available permissions. Now
111
- you may assign this role to several trusted accounts on your system.
112
-
113
- Acl9 also supports the notion of _object roles_, that is, roles with limited scope.
114
-
115
- Imagine we are building a magazine site and want to develop a permission system. So, what roles
116
- and permissions are there?
117
-
118
- _Journalists_ should be able to create articles in their section and edit their own articles.
119
-
120
- _Section editors_ should be able to edit and delete all articles in their sections and
121
- change the _published_ flag.
122
-
123
- _Editor-in-chief_ should be able to change everything.
124
-
125
- We clearly see that journalists and section editors are tied to a specific section, whereas
126
- editor-in-chief is a role with global scope.
127
-
128
- h2. Role interface
129
-
130
- All permission checks in Acl9 are boiled down to calls of a single method:
131
-
132
- @subject.has_role?(role, object)@
133
-
134
- That should be read as "Does _subject_ have _role_ on _object_?".
135
-
136
- Subject is an instance of a @User@, or @Account@, or whatever model you use for
137
- authentication. Object is an instance of any class (including subject class!)
138
- or @nil@ (in which case it's a global role).
139
-
140
- Acl9 builtin role control subsystem provides @has_role?@ method for you, but you can
141
- also implemented it by hand (see _Coming up with your own role implementation_ below).
142
-
143
- h1. Acl9 role control subsystem
144
-
145
- Role control subsystem has been lifted from
146
- "Rails authorization plugin":http://github.com/DocSavage/rails-authorization-plugin,
147
- but undergone some modifications.
148
-
149
- It's based on two tables in the database. First, role table, which stores pairs @[role_name, object]@
150
- where object is a polymorphic model instance or a class. Second, join table, which joins users and roles.
151
-
152
- To use this subsystem, you should define a @Role@ model.
153
-
154
- h2. Role model
155
-
156
- <pre><code>
157
- class Role < ActiveRecord::Base
158
- acts_as_authorization_role
159
- end
160
- </code></pre>
161
-
162
- The structure of @roles@ table is as follows:
163
-
164
- <pre><code>
165
- create_table "roles", :force => true do |t|
166
- t.string :name, :limit => 40
167
- t.string :authorizable_type, :limit => 40
168
- t.integer :authorizable_id
169
- t.timestamps
170
- end
171
- </code></pre>
172
-
173
- Note that you will almost never use the @Role@ class directly.
174
-
175
- h2. Subject model
176
-
177
- <pre><code>
178
- class User < ActiveRecord::Base
179
- acts_as_authorization_subject :association_name => :roles
180
- end
181
- </code></pre>
182
-
183
- You won't need any specific columns in the @users@ table, but
184
- there should be a join table:
185
-
186
- <pre><code>
187
- create_table "roles_users", :id => false, :force => true do |t|
188
- t.references :user
189
- t.references :role
190
- t.timestamps
191
- end
192
- </code></pre>
193
-
194
- h2. Object model
195
-
196
- Place @acts_as_authorization_object@ call inside any model you want to act
197
- as such.
198
-
199
- <pre><code>
200
- class Foo < ActiveRecord::Base
201
- acts_as_authorization_object
202
- end
203
-
204
- class Bar < ActiveRecord::Base
205
- acts_as_authorization_object
206
- end
207
- </code></pre>
208
-
209
- h2. Interface
210
-
211
- h3. Subject model
212
-
213
- A call of @acts_as_authorization_subject@ defines following methods on the model:
214
-
215
- @subject.has_role?(role, object = nil)@. Returns @true@ of @false@ (has or has not).
216
-
217
- @subject.has_role!(role, object = nil)@. Assigns a @role@ for the @object@ to the @subject@.
218
- Does nothing is subject already has such a role.
219
-
220
- @subject.has_no_role!(role, object = nil)@. Unassigns a role from the @subject@.
221
-
222
- @subject.has_roles_for?(object)@. Does the @subject@ has any roles for @object@? (@true@ of @false@)
223
-
224
- @subject.has_role_for?(object)@. Same as @has_roles_for?@.
225
-
226
- @subject.roles_for(object)@. Returns an array of @Role@ instances, corresponding to @subject@ 's roles on
227
- @object@. E.g. @subject.roles_for(object).map(&:name).sort@ will give you role names in alphabetical order.
228
-
229
- @subject.has_no_roles_for!(object)@. Unassign any @subject@ 's roles for a given @object@.
230
-
231
- @subject.has_no_roles!@. Unassign all roles from @subject@.
232
-
233
- h3. Object model
234
-
235
- A call of @acts_as_authorization_object@ defines following methods on the model:
236
-
237
- @object.accepts_role?(role_name, subject)@. An alias for @subject.has_role?(role_name, object)@.
238
-
239
- @object.accepts_role!(role_name, subject)@. An alias for @subject.has_role!(role_name, object)@.
240
-
241
- @object.accepts_no_role!(role_name, subject)@. An alias for @subject.has_no_role!(role_name, object)@.
242
-
243
- @object.accepts_roles_by?(subject)@. An alias for @subject.has_roles_for?(object)@.
244
-
245
- @object.accepts_role_by?(subject)@. Same as @accepts_roles_by?@.
246
-
247
- @object.accepts_roles_by(subject)@. An alias for @subject.roles_for(object)@.
248
-
249
- h2. Custom class names
250
-
251
- You may want to deviate from default @User@ and @Role@ class names. That can easily be done with
252
- arguments to @acts_as_...@.
253
-
254
- Say, you have @Account@ and @AccountRole@:
255
-
256
- <pre><code>
257
- class Account < ActiveRecord::Base
258
- acts_as_authorization_subject :role_class_name => 'AccountRole'
259
- end
260
-
261
- class AccountRole < ActiveRecord::Base
262
- acts_as_authorization_role :subject_class_name => 'Account'
263
- end
264
-
265
- class FooBar < ActiveRecord::Base
266
- acts_as_authorization_object :role_class_name => 'AccountRole', :subject_class_name => 'Account'
267
- end
268
- </code></pre>
269
-
270
- Or... since Acl9 defaults can be changed in a special hash, you can put the following snippet:
271
-
272
- <pre><code>
273
- Acl9::config.merge!({
274
- :default_role_class_name => 'AccountRole',
275
- :default_subject_class_name => 'Account',
276
- })
277
- </code></pre>
278
-
279
- ... into @config/initializers/acl9.rb@ and get rid of that clunky arguments:
280
-
281
- <pre><code>
282
- class Account < ActiveRecord::Base
283
- acts_as_authorization_subject
284
- end
285
-
286
- class AccountRole < ActiveRecord::Base
287
- acts_as_authorization_role
288
- end
289
-
290
- class FooBar < ActiveRecord::Base
291
- acts_as_authorization_object
292
- end
293
- </code></pre>
294
-
295
- Note that you'll need to change your database structure appropriately:
296
-
297
- <pre><code>
298
- create_table "account_roles", :force => true do |t|
299
- t.string :name, :limit => 40
300
- t.string :authorizable_type, :limit => 40
301
- t.integer :authorizable_id
302
- t.timestamps
303
- end
304
-
305
- create_table "account_roles_accounts", :id => false, :force => true do |t|
306
- t.references :account
307
- t.references :account_role
308
- t.timestamps
309
- end
310
- </code></pre>
311
-
312
- h2. Examples
313
-
314
- <pre><code>
315
- user = User.create!
316
- user.has_role? 'admin' # => false
317
-
318
- user.has_role! :admin
319
-
320
- user.has_role? :admin # => true
321
- </code></pre>
322
-
323
- @user@ now has global role _admin_. Note that you can specify role name either
324
- as a string or as a symbol.
325
-
326
- <pre><code>
327
- foo = Foo.create!
328
-
329
- user.has_role? 'admin', foo # => false
330
-
331
- user.has_role! :manager, foo
332
-
333
- user.has_role? :manager, foo # => true
334
- foo.accepts_role? :manager, user # => true
335
-
336
- user.has_roles_for? foo # => true
337
- </code></pre>
338
-
339
- You can see here that global and object roles are distinguished from each other. User
340
- with global role _admin_ isn't automatically admin of @foo@.
341
-
342
- However,
343
-
344
- <pre><code>
345
- user.has_role? :manager # => true
346
- </code></pre>
347
-
348
- That is, if you have an object role, it means that you have a global role with the same name too!
349
- In other words, you are _manager_ if you manage at least one @foo@ (or a @bar@...).
350
-
351
- <pre><code>
352
- bar = Bar.create!
353
-
354
- user.has_role! :manager, bar
355
- user.has_no_role! :manager, foo
356
-
357
- user.has_role? :manager, foo # => false
358
- user.has_role? :manager # => true
359
- </code></pre>
360
-
361
- Our @user@ is no more manager of @foo@, but has become a manager of @bar@.
362
-
363
- <pre><code>
364
- user.has_no_roles!
365
-
366
- user.has_role? :manager # => false
367
- user.has_role? :admin # => false
368
- user.roles # => []
369
- </code></pre>
370
-
371
- At this time @user@ has no roles in the system.
372
-
373
- h2. Coming up with your own role implementation
374
-
375
- The described role system with its 2 tables (not counting the @users@ table!)
376
- might be an overkill for many cases. If all you want is global roles without
377
- any scope, you'd better off implementing it by hand.
378
-
379
- The access control subsystem of Acl9 uses only @subject.has_role?@ method, so
380
- there's no need to implement anything else except for own convenience.
381
-
382
- For example, if each your user can have only one global role, just add @role@
383
- column to your @User@ class:
384
-
385
- <pre><code>
386
- class User < ActiveRecord::Base
387
- def has_role?(role_name, obj=nil)
388
- self.role == role_name
389
- end
390
-
391
- def has_role!(role_name, obj=nil)
392
- self.role = role_name
393
- save!
394
- end
395
- end
396
- </code></pre>
397
-
398
- If you need to assign multiple roles to your users, you can use @serialize@
399
- with role array or a special solution like
400
- "preference_fu":http://github.com/brennandunn/preference_fu.
401
-
402
- h1. Access control subsystem
403
-
404
- By means of access control subsystem you can protect actions of your controller
405
- from unauthorized access. Acl9 provides a nice DSL for writing access rules.
406
-
407
- h2. Allow and deny
408
-
409
- Access control is mostly about allowing and denying. So there are two
410
- basic methods: @allow@ and @deny@. They have the same syntax:
411
-
412
- <pre><code>
413
- allow ROLE_LIST, OPTIONS
414
- deny ROLE_LIST, OPTIONS
415
- </code></pre>
416
-
417
- h3. Specifying roles
418
-
419
- ROLE_LIST is a list of roles (at least 1 role should be there). So,
420
-
421
- <pre><code>
422
- allow :manager, :admin
423
- deny :banned
424
- </code></pre>
425
- will match holders of global role _manager_ *and* holders of global role _admin_ as allowed.
426
- On the contrary, holders of _banned_ role will match as denied.
427
-
428
- Basically this snippet is equivalent to
429
-
430
- <pre><code>
431
- allow :manager
432
- allow :admin
433
- deny :banned
434
- </code></pre>
435
- which means that roles in argument list are OR'ed for a match, and not AND'ed.
436
-
437
- Also note that:
438
- * You may use both strings and :symbols to specify roles (the latter get converted into strings).
439
- * Role names are singularized before check.
440
-
441
- Thus the snippet above can also be written as
442
-
443
- <pre><code>
444
- allow :managers, :admins
445
- deny 'banned'
446
- </code></pre>
447
- or even
448
-
449
- <pre><code>
450
- allow *%w(managers admins)
451
- deny 'banned'
452
- </code></pre>
453
-
454
- h3. Object and class roles
455
-
456
- Examples in the previous section were all about global roles. Let's see how we can
457
- use object and class roles in the ACL block.
458
-
459
- <pre><code>
460
- allow :responsible, :for => Widget
461
- allow :possessor, :of => :foo
462
- deny :angry, :at => :me
463
- allow :interested, :in => Future
464
- deny :short, :on => :time
465
- deny :hated, :by => :us
466
- </code></pre>
467
-
468
- To specify an object you use one of the 6 preposition options:
469
- * :of
470
- * :at
471
- * :on
472
- * :by
473
- * :for
474
- * :in
475
-
476
- They all have the same meaning, use one that makes better English out of your rule.
477
-
478
- Now, each of these prepositions may point to a Class or a :symbol. In the former case we get
479
- a class role. E.g. @allow :responsible, :for => Widget@ becomes @subject.has_role?('responsible', Widget)@.
480
-
481
- Symbol is trickier, it means that the appropriate instance variable of the controller is taken
482
- as an object.
483
-
484
- @allow :possessor, :of => :foo@ is translated into
485
- <code>subject.has_role?('possessor', controller.instance_variable_get('@foo'))</code>.
486
-
487
- Checking against an instance variable has sense when you have another _before filter_ which is executed
488
- *before* the one generated by @access_control@, like this:
489
-
490
- <pre><code>
491
- class MoorblesController < ApplicationController
492
- before_filter :load_moorble, :only => [:edit, :update, :destroy]
493
-
494
- access_control do
495
- allow :creator, :of => :moorble
496
-
497
- # ...
498
- end
499
-
500
- # ...
501
-
502
- private
503
-
504
- def load_moorble
505
- @moorble = Moorble.find(params[:id])
506
- end
507
- end
508
- </code></pre>
509
-
510
- Note that the object option is applied to all of the roles you specify in the argument list.
511
- As such,
512
-
513
- <pre><code>
514
- allow :devil, :son, :of => God
515
- </code></pre>
516
- is equivalent to
517
-
518
- <pre><code>
519
- allow :devil, :of => God
520
- allow :son, :of => God
521
- </code></pre>
522
-
523
- but *NOT*
524
-
525
- <pre><code>
526
- allow :devil
527
- allow :son, :of => God
528
- </code></pre>
529
-
530
- h3. Pseudo-roles
531
-
532
- There are three pseudo-roles in the ACL: @all@, @anonymous@ and @logged_in@.
533
-
534
- @allow all@ will always match (as well as @deny all@).
535
-
536
- @allow anonymous@ and @deny anonymous@ will match when user is anonymous, i.e. subject is @nil@.
537
- You may also use a shorter notation: @allow nil@ (@deny nil@).
538
-
539
- @logged_in@ is direct opposite of @anonymous@, so @allow logged_in@ will match if the user is logged in
540
- (subject is not @nil@).
541
-
542
- No role checks are done in either case.
543
-
544
- h3. Limiting action scope
545
-
546
- By default rules apply to all actions of the controller. There are two options that
547
- narrow the scope of the @deny@ or @allow@ rule: @:to@ and @:except@.
548
-
549
- <pre><code>
550
- allow :owner, :of => :site, :to => [:delete, :destroy]
551
- deny anonymous, :except => [:index, :show]
552
- </code></pre>
553
-
554
- For the first rule to match not only the current user should be an _owner_ of the _site_, but also
555
- current action should be _delete_ or _destroy_.
556
-
557
- In the second rule anonymous user access is denied for all actions, except _index_ and _show_.
558
-
559
- You may not specify both @:to@ and @:except@.
560
-
561
- Note that you can use actions block instead of @:to@ (see _Actions block_
562
- below). You can also use @:only@ and @:except@ options in the
563
- @access_control@ call which will serve as options of the @before_filter@ and thus
564
- limit the scope of the whole ACL.
565
-
566
- h3. Rule conditions
567
-
568
- You may create conditional rules using @:if@ and @:unless@ options.
569
-
570
- <pre><code>
571
- allow :owner, :of => :site, :to => [:delete, :destroy], :if => :chance_to_delete
572
- </code></pre>
573
-
574
- Controller's @:chance_to_delete@ method will be called here. The rule will match if the action
575
- is 'delete' or 'destroy' AND if the method returned @true@.
576
-
577
- @:unless@ has the opposite meaning and should return @false@ for a rule to match.
578
-
579
- Both options can be specified in the same rule.
580
-
581
- <pre><code>
582
- allow :visitor, :to => [:index, :show], :if => :right_phase_of_the_moon?, :unless => :suspicious?
583
- </code></pre>
584
-
585
- @right_phase_of_the_moon?@ should return @true@ AND @suspicious?@ should return @false@ for a poor visitor to
586
- see a page.
587
-
588
- Currently only controller methods are supported (specify them as :symbols). Lambdas are *not* supported.
589
-
590
- h2. Rule matching order
591
-
592
- Rule matching system is similar to that of Apache web server. There are two modes: _default allow_
593
- (corresponding to @Order Deny,Allow@ in Apache) and _default deny_ (@Order Allow,Deny@ in Apache).
594
-
595
- h3. Setting modes
596
-
597
- Mode is set with a @default@ call.
598
-
599
- @default :allow@ will set _default allow_ mode.
600
-
601
- @default :deny@ will set _default deny_ mode. Note that this is the default mode, i.e. it will be on
602
- if you don't do a @default@ call at all.
603
-
604
- h3. Matching algorithm
605
-
606
- First of all, regardless of the mode, all @allow@ matches are OR'ed together and all @deny@ matches
607
- are OR'ed as well.
608
-
609
- We'll express this in the following manner:
610
-
611
- <pre><code>
612
- ALLOWED = (allow rule 1 matches?) OR ((allow rule 2 matches?) OR ...
613
- NOT_DENIED = NOT ((deny rule 1 matches?) OR (deny rule 2 matches?) OR ...)
614
- </code></pre>
615
-
616
- So, ALLOWED is @true@ when either of the @allow@ rules matches, and NOT_DENIED is @true@ when none
617
- of the @deny@ rules matches.
618
-
619
- Let's denote the final result of algorithm as ALLOWANCE. If it's @true@, access is allowed, if @false@ - denied.
620
-
621
- In the case of _default allow_:
622
- <pre><code>
623
- ALLOWANCE = ALLOWED OR NOT_DENIED
624
- </code></pre>
625
-
626
- In the case of _default deny_:
627
- <pre><code>
628
- ALLOWANCE = ALLOWED AND NOT_DENIED
629
- </code></pre>
630
-
631
- Same result as a table:
632
-
633
- |_. Rule matches |_. Default allow mode |_. Default deny mode |
634
- | None of the @allow@ and @deny@ rules matched. | Access is allowed. | Access is denied. |
635
- | Some of the @allow@ rules matched, none of the @deny@ rules matched. | Access is allowed. | Access is allowed. |
636
- | None of the @allow@ rules matched, some of the @deny@ rules matched. | Access is denied. | Access is denied. |
637
- | Some of the @allow@ rules matched, some of the @deny@ rules matched. | Access is allowed. | Access is denied. |
638
-
639
- Apparently _default deny_ mode is more strict, and that's because it's on by default.
640
-
641
- h2. Actions block
642
-
643
- You may group rules with the help of the @actions@ block.
644
-
645
- An example from the imaginary @PostsController@:
646
-
647
- <pre><code>
648
- allow :admin
649
-
650
- actions :index, :show do
651
- allow all
652
- end
653
-
654
- actions :new, :create do
655
- allow :managers, :of => Post
656
- end
657
-
658
- actions :edit, :update do
659
- allow :owner, :of => :post
660
- end
661
-
662
- action :destroy do
663
- allow :owner, :of => :post
664
- end
665
- </code></pre>
666
-
667
- This is equivalent to:
668
-
669
- <pre><code>
670
- allow :admin
671
-
672
- allow all, :to => [:index, :show]
673
- allow :managers, :of => Post, :to => [:new, :create]
674
- allow :owner, :of => :post, :to => [:edit, :update]
675
- allow :owner, :of => :post, :to => :destroy
676
- </code></pre>
677
-
678
- Note that only @allow@ and @deny@ calls are available inside @actions@ block, and these may not have
679
- @:to@/@:except@ options.
680
-
681
- @action@ is just a synonym for @actions@.
682
-
683
- h2. access_control method
684
-
685
- By calling @access_control@ in your controller you can get your ACL block translated into...
686
-
687
- # a lambda, installed with @before_filter@ and raising @Acl9::AccessDenied@ exception on occasion.
688
- # a method, installed with @before_filter@ and raising @Acl9::AccessDenied@ exception on occasion.
689
- # a method, returning @true@ or @false@, whether access is allowed or denied.
690
-
691
- First case is by default. You can catch the exception with @rescue_from@ call and do something
692
- you like: make a redirect, or render "Access denied" template, or whatever.
693
-
694
- Second case is obtained with specifying method name as an argument to
695
- @access_control@ (or using @:as_method@ option, see below) and may be helpful
696
- if you want to use @skip_before_filter@ somewhere in the derived controller.
697
-
698
- Third case will take place if you supply @:filter => false@ along with method
699
- name. You'll get an ordinary method which you can call anywhere you want.
700
-
701
- h3. :subject_method
702
-
703
- Acl9 obtains the subject instance by calling specific method of the controller. By default it's
704
- @:current_user@, but you may change it.
705
-
706
- <pre><code>
707
- class MyController < ApplicationController
708
- access_control :subject_method => :current_account do
709
- allow :nifty
710
- # ...
711
- end
712
-
713
- # ...
714
- end
715
- </code></pre>
716
-
717
- Subject method can also be changed globally. Place the following into @config/initializers/acl9.rb@:
718
-
719
- <pre><code>
720
- Acl9::config[:default_subject_method] = :current_account
721
- </code></pre>
722
-
723
- h3. :debug
724
-
725
- @:debug => true@ will output the filtering expression into the debug log. If
726
- Acl9 does something strange, you may look at it as the last resort.
727
-
728
- h3. :as_method
729
-
730
- In the case
731
-
732
- <pre><code>
733
- class NiftyController < ApplicationController
734
- access_control :as_method => :acl do
735
- allow :nifty
736
- # ...
737
- end
738
-
739
- # ...
740
- end
741
- </code></pre>
742
-
743
- access control checks will be added as @acl@ method onto MyController, with @before_filter :acl@ call thereafter.
744
-
745
- Instead of using @:as_method@ you may specify the name of the method as a positional argument
746
- to @access_control@:
747
-
748
- <pre><code>
749
- class MyController < ApplicationController
750
- access_control :acl do
751
- # ...
752
- end
753
-
754
- # ...
755
- end
756
- </code></pre>
757
-
758
-
759
- h3. :filter
760
-
761
- If you set @:filter@ to @false@ (it's @true@ by default) and also use
762
- @:as_method@ (or method name as 1st argument to @access_control@, you'll get a
763
- method which won't raise @Acl9::AccessDenied@ exception, but rather return
764
- @true@ or @false@ (access allowed/denied).
765
-
766
- <pre><code>
767
- class SecretController < ApplicationController
768
- access_control :secret_access?, :filter => false do
769
- allow :james_bond
770
- # ...
771
- end
772
-
773
- def index
774
- if secret_access?
775
- _secret_index
776
- else
777
- _ordinary_index
778
- end
779
- end
780
-
781
- # ...
782
-
783
- private
784
-
785
- def _secret_index
786
- # ...
787
- end
788
-
789
- def _ordinary_index
790
- # ...
791
- end
792
- end
793
- </code></pre>
794
-
795
- The generated method can receive an objects hash as an argument. In this example,
796
-
797
- <pre><code>
798
- class LolController < ApplicationController
799
- access_control :lolcats?, :filter => false do
800
- allow :cats, :by => :lol
801
- # ...
802
- end
803
- end
804
- </code></pre>
805
-
806
- you may not only call @lolcats?@ with no arguments, which will basically return
807
-
808
- <pre><code>
809
- current_user.has_role?('cats', @lol)
810
- </code></pre>
811
-
812
- but also as @lolcats?(:lol => Lol.find(params[:lol]))@. The hash will be looked into first,
813
- even if you have an instance variable @lol@.
814
-
815
- h3. :helper
816
-
817
- Sometimes you want to have a boolean method (like @:filter => false@) accessible
818
- in your views. Acl9 can call @helper_method@ for you:
819
-
820
- <pre><code>
821
- class LolController < ApplicationController
822
- access_control :helper => :lolcats? do
823
- allow :cats, :by => :lol
824
- # ...
825
- end
826
- end
827
- </code></pre>
828
-
829
- That's equivalent to
830
-
831
- <pre><code>
832
- class LolController < ApplicationController
833
- access_control :lolcats?, :filter => false do
834
- allow :cats, :by => :lol
835
- # ...
836
- end
837
-
838
- helper_method :lolcats?
839
- end
840
- </code></pre>
841
-
842
- h3. Other options
843
-
844
- Other options will be passed to @before_filter@. As such, you may use @:only@ and @:except@ to narrow
845
- the action scope of the whole ACL block.
846
-
847
- <pre><code>
848
- class OmgController < ApplicationController
849
- access_control :only => [:index, :show] do
850
- allow all
851
- deny :banned
852
- end
853
-
854
- # ...
855
- end
856
- </code></pre>
857
-
858
- is basically equivalent to
859
-
860
- <pre><code>
861
- class OmgController < ApplicationController
862
- access_control do
863
- actions :index, :show do
864
- allow all
865
- deny :banned
866
- end
867
-
868
- allow all, :except => [:index, :show]
869
- end
870
-
871
- # ...
872
- end
873
- </code></pre>
874
-
875
- h2. access_control in your helpers
876
-
877
- Apart from using @:helper@ option for @access_control@ call inside controller, there's a
878
- way to generate helper methods directly, like this:
879
-
880
- <pre><code>
881
- module SettingsHelper
882
- include Acl9Helpers
883
-
884
- access_control :show_settings? do
885
- allow :admin
886
- allow :settings_manager
887
- end
888
- end
889
- </code></pre>
890
-
891
- Here we mix in @Acl9Helpers@ module which brings in @access_control@ method and call it,
892
- obtaining @show_settings?@ method.
893
-
894
- An imaginary view:
895
-
896
- <pre><code>
897
- <% if show_settings? %>
898
- <%= link_to 'Settings', settings_path %>
899
- <% end %>
900
- </code></pre>
901
-
902
- h2. show_to in your views
903
-
904
- @show_to@ is predefined helper for your views:
905
-
906
- <pre><code>
907
- <% show_to :admin, :supervisor do %>
908
- <%= link_to 'destroy', destroy_path %>
909
- <% end %>
910
- </code></pre>
911
-
912
- or even
913
-
914
- <pre><code>
915
- <% show_to :prince, :of => :persia do %>
916
- <%= link_to 'Princess', princess_path %>
917
- <% end %>
918
- </code></pre>
919
-
920
-
921
- Copyright (c) 2009, 2010 Oleg Dashevskii, released under the MIT license.