acl9 0.11.0

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