ae_declarative_authorization 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +21 -21
  3. data/CHANGELOG +189 -189
  4. data/Gemfile +7 -7
  5. data/Gemfile.lock +45 -45
  6. data/LICENSE.txt +20 -20
  7. data/README.md +620 -620
  8. data/README.rdoc +597 -597
  9. data/Rakefile +33 -33
  10. data/authorization_rules.dist.rb +20 -20
  11. data/declarative_authorization.gemspec +24 -24
  12. data/gemfiles/rails4252.gemfile +10 -10
  13. data/gemfiles/rails4271.gemfile +10 -10
  14. data/gemfiles/rails507.gemfile +11 -11
  15. data/init.rb +5 -5
  16. data/lib/declarative_authorization.rb +18 -18
  17. data/lib/declarative_authorization/authorization.rb +821 -821
  18. data/lib/declarative_authorization/helper.rb +78 -78
  19. data/lib/declarative_authorization/in_controller.rb +713 -713
  20. data/lib/declarative_authorization/in_model.rb +156 -156
  21. data/lib/declarative_authorization/maintenance.rb +215 -215
  22. data/lib/declarative_authorization/obligation_scope.rb +345 -345
  23. data/lib/declarative_authorization/railsengine.rb +5 -5
  24. data/lib/declarative_authorization/reader.rb +549 -549
  25. data/lib/declarative_authorization/test/helpers.rb +261 -261
  26. data/lib/declarative_authorization/version.rb +3 -3
  27. data/lib/generators/authorization/install/install_generator.rb +77 -77
  28. data/lib/generators/authorization/rules/rules_generator.rb +13 -13
  29. data/lib/generators/authorization/rules/templates/authorization_rules.rb +27 -27
  30. data/lib/tasks/authorization_tasks.rake +89 -89
  31. data/test/authorization_test.rb +1121 -1121
  32. data/test/controller_filter_resource_access_test.rb +573 -573
  33. data/test/controller_test.rb +478 -478
  34. data/test/database.yml +3 -3
  35. data/test/dsl_reader_test.rb +178 -178
  36. data/test/functional/filter_access_to_with_id_in_scope_test.rb +88 -88
  37. data/test/functional/no_filter_access_to_test.rb +79 -79
  38. data/test/functional/params_block_arity_test.rb +39 -39
  39. data/test/helper_test.rb +248 -248
  40. data/test/maintenance_test.rb +46 -46
  41. data/test/model_test.rb +1840 -1840
  42. data/test/schema.sql +60 -60
  43. data/test/test_helper.rb +174 -174
  44. data/test/test_support/minitest_compatibility.rb +26 -26
  45. metadata +3 -9
  46. data/gemfiles/rails4252.gemfile.lock +0 -126
  47. data/gemfiles/rails4271.gemfile.lock +0 -126
  48. data/gemfiles/rails507.gemfile.lock +0 -136
  49. data/log/test.log +0 -34715
  50. data/test/profiles/access_checking +0 -46
data/README.rdoc CHANGED
@@ -1,597 +1,597 @@
1
- = Declarative Authorization
2
-
3
- The declarative authorization plugin offers an authorization mechanism inspired
4
- by _RBAC_. The most notable distinction to other authorization plugins is the
5
- declarative approach. That is, authorization rules are not defined
6
- programmatically in between business logic but in an authorization configuration.
7
-
8
- With programmatic authorization rules, the developer needs to specify which roles are
9
- allowed to access a specific controller action or a part of a view, which is
10
- not DRY. With a growing application code base roles' permissions often
11
- change and new roles are introduced. Then, at several places of the source code
12
- the changes have to be implemented, possibly leading to omissions and thus hard
13
- to find errors. In these cases, a declarative approach as offered by decl_auth
14
- increases the development and maintenance efficiency.
15
-
16
-
17
- Plugin features
18
- * Authorization at controller action level
19
- * Authorization helpers for Views
20
- * Authorization at model level
21
- * Authorize CRUD (Create, Read, Update, Delete) activities
22
- * Query rewriting to automatically only fetch authorized records
23
- * DSL for specifying Authorization rules in an authorization configuration
24
- * Support for Rails 4 and 5
25
-
26
-
27
- Requirements
28
- * An authentication mechanism
29
- * User object in Controller#current_user
30
- * (For model security) Setting Authorization.current_user
31
- * User objects need to respond to a method :role_symbols that returns an
32
- array of role symbols
33
- See below for installation instructions.
34
-
35
-
36
- There is a decl_auth screencast by Ryan Bates, nicely introducing the main concepts:
37
- http://railscasts.com/episodes/188-declarative-authorization
38
-
39
-
40
- = Quick Start
41
-
42
- === Installer
43
-
44
- Declarative Authorization comes with an installer to make setup easy.
45
-
46
- First, include declarative_authorization in your gemfile.
47
-
48
- #! Gemfile
49
- gem 'declarative_authorization'
50
-
51
- Next, bundle and install.
52
-
53
- $ bundle
54
- $ rails g authorization:install [UserModel=User] [field:type field:type ...] [--create-user --commit --user-belongs-to-role]
55
-
56
- This installer will create a Role model, an admin and a user role, and set a
57
- has_and_belongs_to_many relationship between the User model and the Role model.
58
- It will also add a +role_symbols+ method to the user model to meet
59
- declarative_authorization's requirements. The default User model is User. You can override this by simply typing the name of a model as above.
60
-
61
- You can create the model with the fields provided by using the +--create-user+ option.
62
-
63
- The +--commit+ option will run +rake db:migrate+ and +rake db:seed+.
64
-
65
- The +--user-belongs-to-role+ option will set up a one-to-many relationship between Users and Roles.
66
- That is, each user has a role_id column and can only have one role. Role inheritance can be used
67
- in authorization rules.
68
-
69
- Finally, the installer also copies default authorization rules, as below.
70
-
71
- === Generate Authorization Rules
72
-
73
- To copy a default set of authorization rules which includes CRUD priveleges, run:
74
-
75
- $ rails g authorization:rules
76
-
77
- This command will copy the following to +config/authorization_rules.rb+. Remember
78
- to implement the requirements of this gem as described in the Installation section
79
- at the end of this README if you do not use the above installer.
80
-
81
- authorization do
82
- role :guest do
83
- # add permissions for guests here, e.g.
84
- # has_permission_on :conferences, :to => :read
85
- end
86
-
87
- # permissions on other roles, such as
88
- # role :admin do
89
- # has_permission_on :conferences, :to => :manage
90
- # end
91
- # role :user do
92
- # has_permission_on :conferences, :to => [:read, :create]
93
- # has_permission_on :conferences, :to => [:update, :delete] do
94
- # if_attribute :user_id => is {user.id}
95
- # end
96
- # end
97
- # See the readme or GitHub for more examples
98
- end
99
-
100
- privileges do
101
- # default privilege hierarchies to facilitate RESTful Rails apps
102
- privilege :manage, :includes => [:create, :read, :update, :delete]
103
- privilege :create, :includes => :new
104
- privilege :read, :includes => [:index, :show]
105
- privilege :update, :includes => :edit
106
- privilege :delete, :includes => :destroy
107
- end
108
-
109
- === Controller Authorization
110
-
111
- For RESTful controllers, add +filter_resource_access+:
112
-
113
- class MyRestfulController < ApplicationController
114
- filter_resource_access
115
- ...
116
- end
117
-
118
- For a non-RESTful controller, you can use +filter_access_to+:
119
-
120
- class MyOtherController < ApplicationController
121
- filter_access_to :all
122
- # or a group: filter_access_to [:action1, :action2]
123
- ...
124
- end
125
-
126
- === View Authorization
127
-
128
- Declarative Authorization will use +current_user+ to check authorization.
129
-
130
- <%= link_to 'Edit Post', edit_post_path(@post) if permitted_to? :update, @post %>
131
-
132
-
133
- = Authorization Data Model
134
-
135
- ----- App domain ----|-------- Authorization conf ---------|------- App domain ------
136
-
137
- includes includes
138
- .--. .---.
139
- | v | v
140
- .------. can_play .------. has_permission .------------. requires .----------.
141
- | User |----------->| Role |----------------->| Permission |<-----------| Activity |
142
- '------' * * '------' * * '------------' 1 * '----------'
143
- |
144
- .-------+------.
145
- 1 / | 1 \ *
146
- .-----------. .---------. .-----------.
147
- | Privilege | | Context | | Attribute |
148
- '-----------' '---------' '-----------'
149
-
150
- In the application domain, each *User* may be assigned to *Roles* that should
151
- define the users' job in the application, such as _Administrator_. On the
152
- right-hand side of this diagram, application developers specify which *Permissions*
153
- are necessary for users to perform activities, such as calling a controller action,
154
- viewing parts of a View or acting on records in the database. Note that
155
- Permissions consist of an *Privilege* that is to be performed, such as _read_,
156
- and a *Context* in that the Operation takes place, such as _companies_.
157
-
158
- In the authorization configuration, Permissions are assigned to Roles and Role
159
- and Permission hierarchies are defined. *Attributes* may be employed to allow
160
- authorization according to dynamic information about the context and the
161
- current user, e.g. "only allow access on employees that belong to the
162
- current user's branch."
163
-
164
-
165
- = Examples
166
-
167
- A fully functional example application can be found at
168
- http://github.com/stffn/decl_auth_demo_app
169
-
170
- == Controller
171
-
172
- If authentication is in place, there are two ways to enable user-specific
173
- access control on controller actions. For resource controllers, which more
174
- or less follow the CRUD pattern, +filter_resource_access+ is the simplest
175
- approach. It sets up instance variables in before filters and calls
176
- filter_access_to with the appropriate parameters to protect the CRUD methods.
177
-
178
- class EmployeesController < ApplicationController
179
- filter_resource_access
180
- ...
181
- end
182
-
183
- See Authorization::AuthorizationInController::ClassMethods for options on
184
- nested resources and custom member and collection actions.
185
-
186
- By default, declarative_authorization will enable filter_resource_access compatibility with strong_parameters in Rails 4. If you want to disable this behavior, you can use the +:strong_parameters+ option.
187
-
188
- class EmployeesController < ApplicationController
189
- filter_resource_access :strong_parameters => false
190
- ...
191
- end
192
-
193
- Simalarly, you can use +:strong_parameters => true+ if you are using strong_parameters in Rails 3.
194
-
195
- If you prefer less magic or your controller has no resemblance with the resource
196
- controllers, directly calling filter_access_to may be the better option. Examples
197
- are given in the following. E.g. the privilege index users is required for
198
- action index. This works as a first default configuration for RESTful
199
- controllers, with these privileges easily handled in the authorization
200
- configuration, which will be described below.
201
-
202
- class EmployeesController < ApplicationController
203
- filter_access_to :all
204
- def index
205
- ...
206
- end
207
- ...
208
- end
209
-
210
- When custom actions are added to such a controller, it helps to define more
211
- clearly which privileges are the respective requirements. That is when the
212
- filter_access_to call may become more verbose:
213
-
214
- class EmployeesController < ApplicationController
215
- filter_access_to :all
216
- # this one would be included in :all, but :read seems to be
217
- # a more suitable privilege than :auto_complete_for_user_name
218
- filter_access_to :auto_complete_for_employee_name, :require => :read
219
- def auto_complete_for_employee_name
220
- ...
221
- end
222
- ...
223
- end
224
-
225
- For some actions it might be necessary to check certain attributes of the
226
- object the action is to be acting on. Then, the object needs to be loaded
227
- before the action's access control is evaluated. On the other hand, some actions
228
- might prefer the authorization to ignore specific attribute checks as the object is
229
- unknown at checking time, so attribute checks and thus automatic loading of
230
- objects needs to be enabled explicitly.
231
-
232
- class EmployeesController < ApplicationController
233
- filter_access_to :update, :attribute_check => true
234
- def update
235
- # @employee is already loaded from param[:id] because of :attribute_check
236
- end
237
- end
238
-
239
- You can provide the needed object through before_actions. This way, you have
240
- full control over the object that the conditions are checked against. Just make
241
- sure, your before_actions occur before any of the filter_access_to calls.
242
-
243
- class EmployeesController < ApplicationController
244
- before_action :new_employee_from_params, :only => :create
245
- before_action :new_employee, :only => [:index, :new]
246
- filter_access_to :all, :attribute_check => true
247
-
248
- def create
249
- @employee.save!
250
- end
251
-
252
- protected
253
- def new_employee_from_params
254
- @employee = Employee.new(params[:employee])
255
- end
256
- end
257
-
258
- If the access is denied, a +permission_denied+ method is called on the
259
- current_controller, if defined, and the issue is logged.
260
- For further customization of the filters and object loading, have a look at
261
- the complete API documentation of filter_access_to in
262
- Authorization::AuthorizationInController::ClassMethods.
263
-
264
-
265
- == Views
266
-
267
- In views, a simple permitted_to? helper makes showing blocks according to the
268
- current user's privileges easy:
269
-
270
- <% permitted_to? :create, :employees do %>
271
- <%= link_to 'New', new_employee_path %>
272
- <% end %>
273
-
274
- Only giving a symbol :employees as context prevents any checks of attributes
275
- as there is no object to check against. For example, in case of nested resources
276
- a new object may come in handy:
277
-
278
- <% permitted_to? :create, Branch.new(:company => @company) do
279
- # or @company.branches.new
280
- # or even @company.branches %>
281
- <%= link_to 'New', new_company_branch_path(@company) %>
282
- <% end %>
283
-
284
- Lists are straight-forward:
285
-
286
- <% for employee in @employees %>
287
- <%= link_to 'Edit', edit_employee_path(employee) if permitted_to? :update, employee %>
288
- <% end %>
289
-
290
- See also Authorization::AuthorizationHelper.
291
-
292
-
293
- == Models
294
-
295
- There are two distinct features for model security built into this plugin:
296
- authorizing CRUD operations on objects as well as query rewriting to limit
297
- results according to certain privileges.
298
-
299
- See also Authorization::AuthorizationInModel.
300
-
301
- === Model security for CRUD operations
302
- To activate model security, all it takes is an explicit enabling for each
303
- model that model security should be enforced on, i.e.
304
-
305
- class Employee < ActiveRecord::Base
306
- using_access_control
307
- ...
308
- end
309
-
310
- Thus,
311
- Employee.create(...)
312
- fails, if the current user is not allowed to :create :employees according
313
- to the authorization rules. For the application to find out about what
314
- happened if an operation is denied, the filters throw
315
- Authorization::NotAuthorized exceptions.
316
-
317
- As access control on read are costly, with possibly lots of objects being
318
- loaded at a time in one query, checks on read need to be activated explicitly by
319
- adding the :include_read option.
320
-
321
- === Query rewriting through named scopes
322
- When retrieving large sets of records from databases, any authorization needs
323
- to be integrated into the query in order to prevent inefficient filtering
324
- afterwards and to use LIMIT and OFFSET in SQL statements. To keep authorization
325
- rules out of the source code, this plugin offers query rewriting mechanisms
326
- through named scopes. Thus,
327
-
328
- Employee.with_permissions_to(:read)
329
-
330
- returns all employee records that the current user is authorized to read. In
331
- addition, just like normal named scopes, query rewriting may be chained with
332
- the usual find method:
333
-
334
- Employee.with_permissions_to(:read).find(:all, :conditions => ...)
335
-
336
- If the current user is completely missing the permissions, an
337
- Authorization::NotAuthorized exception is raised. Through
338
- Model.obligation_conditions, application developers may retrieve
339
- the conditions for manual rewrites.
340
-
341
-
342
- == Authorization Rules
343
-
344
- Authorization rules are defined in config/authorization_rules.rb
345
- (Or redefine rules files path via +Authorization::AUTH_DSL_FILES+). E.g.
346
-
347
- authorization do
348
- role :admin do
349
- has_permission_on :employees, :to => [:create, :read, :update, :delete]
350
- end
351
- end
352
-
353
- There is a default role :+guest+ that is used if a request is not associated
354
- with any user or with a user without any roles. So, if your application has
355
- public pages, :+guest+ can be used to allow access for users that are not
356
- logged in. All other roles are application defined and need to be associated
357
- with users by the application.
358
-
359
- If you need to change the default role, you can do so by adding an initializer
360
- that contains the following statement:
361
-
362
- Authorization.default_role = :anonymous
363
-
364
- Privileges, such as :create, may be put into hierarchies to simplify
365
- maintenance. So the example above has the same meaning as
366
-
367
- authorization do
368
- role :admin do
369
- has_permission_on :employees, :to => :manage
370
- end
371
- end
372
-
373
- privileges do
374
- privilege :manage do
375
- includes :create, :read, :update, :delete
376
- end
377
- end
378
-
379
- Privilege hierarchies may be context-specific, e.g. applicable to :employees.
380
-
381
- privileges do
382
- privilege :manage, :employees, :includes => :increase_salary
383
- end
384
-
385
- For more complex use cases, authorizations need to be based on attributes. Note
386
- that you then also need to set :attribute_check => true in controllers for filter_access_to.
387
- E.g. if a branch admin should manage only employees of his branch (see
388
- Authorization::Reader in the API docs for a full list of available operators):
389
-
390
- authorization do
391
- role :branch_admin do
392
- has_permission_on :employees do
393
- to :manage
394
- # user refers to the current_user when evaluating
395
- if_attribute :branch => is {user.branch}
396
- end
397
- end
398
- end
399
-
400
- To reduce redundancy in has_permission_on blocks, a rule may depend on
401
- permissions on associated objects:
402
-
403
- authorization do
404
- role :branch_admin do
405
- has_permission_on :branches, :to => :manage do
406
- if_attribute :managers => contains {user}
407
- end
408
-
409
- has_permission_on :employees, :to => :manage do
410
- if_permitted_to :manage, :branch
411
- # instead of
412
- #if_attribute :branch => {:managers => contains {user}}
413
- end
414
- end
415
- end
416
-
417
- Lastly, not only privileges may be organized in a hierarchy but roles as well.
418
- Here, project manager inherit the permissions of employees.
419
-
420
- role :project_manager do
421
- includes :employee
422
- end
423
-
424
- See also Authorization::Reader.
425
-
426
- == Testing
427
-
428
- declarative_authorization provides a few helpers to ease the testing with
429
- authorization in mind.
430
-
431
- In your test_helper.rb, to enable the helpers add
432
-
433
- require 'declarative_authorization/maintenance'
434
-
435
- class Test::Unit::TestCase
436
- include Authorization::TestHelper
437
- ...
438
- end
439
-
440
- For using the test helpers with RSpec, just add the following lines to your
441
- spec_helper.rb (somewhere after require 'spec/rails'):
442
-
443
- require 'declarative_authorization/maintenance'
444
- include Authorization::TestHelper
445
-
446
- Now, in unit tests, you may deactivate authorization if needed e.g. for test
447
- setup and assume certain identities for tests:
448
-
449
- class EmployeeTest < ActiveSupport::TestCase
450
- def test_should_read
451
- without_access_control do
452
- Employee.create(...)
453
- end
454
- assert_nothing_raised do
455
- with_user(admin) do
456
- Employee.find(:first)
457
- end
458
- end
459
- end
460
- end
461
-
462
- Or, with RSpec, it would work like this:
463
-
464
- describe Employee do
465
- it "should read" do
466
- without_access_control do
467
- Employee.create(...)
468
- end
469
- with_user(admin) do
470
- Employee.find(:first)
471
- end
472
- end
473
- end
474
-
475
- In functional tests, get, posts, etc. may be tested in the name of certain users:
476
-
477
- get_with admin, :index
478
- post_with admin, :update, :employee => {...}
479
-
480
- See Authorization::TestHelper for more information.
481
-
482
-
483
- = Installation of declarative_authorization
484
-
485
- One of three options to install the plugin:
486
- * Install by Gem: Add to your environment.rb in the initializer block:
487
- config.gem "declarative_authorization"
488
- Note: you need gemcutter support in place, i.e. call
489
- gem install gemcutter
490
- gem tumble
491
- And call from your application's root directory
492
- rake gems:install
493
- * Alternatively, in Rails 2, to install from github, execute in your application's root directory
494
- cd vendor/plugins && git clone git://github.com/stffn/declarative_authorization.git
495
-
496
- Then,
497
- * provide the requirements as noted below,
498
- * create a basic config/authorization_rules.rb--you might want to take the
499
- provided example authorization_rules.dist.rb in the plugin root as a starting
500
- point,
501
- * add +filter_access_to+, +permitted_to+? and model security as needed.
502
-
503
- == Providing the Plugin's Requirements
504
- The requirements are
505
- * Rails >= 4.2.5.2 and Ruby >= 2.3.3
506
- * An authentication mechanism
507
- * A user object returned by Controller#current_user
508
- * An array of role symbols returned by User#role_symbols
509
- * (For model security) Setting Authorization.current_user to the request's user
510
-
511
- Of the various ways to provide these requirements, here is one way employing
512
- restful_authentication.
513
-
514
- * Install restful_authentication
515
- cd vendor/plugins && git clone git://github.com/technoweenie/restful-authentication.git restful_authentication
516
- cd ../.. && ruby script/generate authenticated user sessions
517
- * Move "include AuthenticatedSystem" to ApplicationController
518
- * Add +filter_access_to+ calls as described above.
519
- * If you'd like to use model security, add a before_action that sets the user
520
- globally to your ApplicationController. This is thread-safe.
521
- before_action :set_current_user
522
- protected
523
- def set_current_user
524
- Authorization.current_user = current_user
525
- end
526
-
527
- * Add roles field to the User model through a :+has_many+ association
528
- (this is just one possible approach; you could just as easily use
529
- :+has_many+ :+through+ or a serialized roles array):
530
- * create a migration for table roles
531
- class CreateRoles < ActiveRecord::Migration
532
- def self.up
533
- create_table "roles" do |t|
534
- t.column :title, :string
535
- t.references :user
536
- end
537
- end
538
-
539
- def self.down
540
- drop_table "roles"
541
- end
542
- end
543
-
544
- * create a model Role,
545
- class Role < ActiveRecord::Base
546
- belongs_to :user
547
- end
548
-
549
- * add +has_many+ :+roles+ to the User model and a roles method that returns the roles
550
- as an Array of Symbols, e.g.
551
- class User < ActiveRecord::Base
552
- has_many :roles
553
- def role_symbols
554
- (roles || []).map {|r| r.title.to_sym}
555
- end
556
- end
557
-
558
- * add roles to your User objects using e.g.
559
- user.roles.create(:title => "admin")
560
-
561
- Note: If you choose to generate an Account model for restful_authentication
562
- instead of a User model as described above, you have to customize the
563
- examples and create a ApplicationController#current_user method.
564
-
565
-
566
- == Debugging Authorization
567
-
568
- Currently, the main means of debugging authorization decisions is logging and
569
- exceptions. Denied access to actions is logged to +warn+ or +info+, including
570
- some hints about what went wrong.
571
-
572
- All bang methods throw exceptions which may be used to retrieve more
573
- information about a denied access than a Boolean value.
574
-
575
-
576
- = Help and Contact
577
-
578
- We have an issue tracker[http://github.com/appfolio/ae_declarative_authorization/issues]
579
- for bugs and feature requests.
580
- You are very welcome to contribute. Just fork the git repository and send a pull request.
581
-
582
-
583
- = Contributors
584
-
585
- Thanks to John Joseph Bachir, Dennis Blöte, Eike Carls, Damian Caruso, Kai Chen, Erik Dahlstrand,
586
- Jeroen van Dijk, Alexander Dobriakov, Sebastian Dyck, Ari Epstein, Jeremy Friesen,
587
- Tim Harper, John Hawthorn, hollownest, Daniel Kristensen, Jeremy Kleindl, Joel Kociolek,
588
- Benjamin ter Kuile, Brad Langhorst, Brian Langenfeld,
589
- Georg Ledermann, Geoff Longman, Olly Lylo, Mark Mansour, Thomas Maurer, Kevin Moore,
590
- Tyler Pickett, Edward Rudd, Sharagoz,
591
- TJ Singleton, Mike Vincent, Joel Westerberg
592
-
593
-
594
- = License
595
-
596
- Copyright (c) 2008 Steffen Bartsch, TZI, Universität Bremen, Germany
597
- released under the MIT license
1
+ = Declarative Authorization
2
+
3
+ The declarative authorization plugin offers an authorization mechanism inspired
4
+ by _RBAC_. The most notable distinction to other authorization plugins is the
5
+ declarative approach. That is, authorization rules are not defined
6
+ programmatically in between business logic but in an authorization configuration.
7
+
8
+ With programmatic authorization rules, the developer needs to specify which roles are
9
+ allowed to access a specific controller action or a part of a view, which is
10
+ not DRY. With a growing application code base roles' permissions often
11
+ change and new roles are introduced. Then, at several places of the source code
12
+ the changes have to be implemented, possibly leading to omissions and thus hard
13
+ to find errors. In these cases, a declarative approach as offered by decl_auth
14
+ increases the development and maintenance efficiency.
15
+
16
+
17
+ Plugin features
18
+ * Authorization at controller action level
19
+ * Authorization helpers for Views
20
+ * Authorization at model level
21
+ * Authorize CRUD (Create, Read, Update, Delete) activities
22
+ * Query rewriting to automatically only fetch authorized records
23
+ * DSL for specifying Authorization rules in an authorization configuration
24
+ * Support for Rails 4 and 5
25
+
26
+
27
+ Requirements
28
+ * An authentication mechanism
29
+ * User object in Controller#current_user
30
+ * (For model security) Setting Authorization.current_user
31
+ * User objects need to respond to a method :role_symbols that returns an
32
+ array of role symbols
33
+ See below for installation instructions.
34
+
35
+
36
+ There is a decl_auth screencast by Ryan Bates, nicely introducing the main concepts:
37
+ http://railscasts.com/episodes/188-declarative-authorization
38
+
39
+
40
+ = Quick Start
41
+
42
+ === Installer
43
+
44
+ Declarative Authorization comes with an installer to make setup easy.
45
+
46
+ First, include declarative_authorization in your gemfile.
47
+
48
+ #! Gemfile
49
+ gem 'declarative_authorization'
50
+
51
+ Next, bundle and install.
52
+
53
+ $ bundle
54
+ $ rails g authorization:install [UserModel=User] [field:type field:type ...] [--create-user --commit --user-belongs-to-role]
55
+
56
+ This installer will create a Role model, an admin and a user role, and set a
57
+ has_and_belongs_to_many relationship between the User model and the Role model.
58
+ It will also add a +role_symbols+ method to the user model to meet
59
+ declarative_authorization's requirements. The default User model is User. You can override this by simply typing the name of a model as above.
60
+
61
+ You can create the model with the fields provided by using the +--create-user+ option.
62
+
63
+ The +--commit+ option will run +rake db:migrate+ and +rake db:seed+.
64
+
65
+ The +--user-belongs-to-role+ option will set up a one-to-many relationship between Users and Roles.
66
+ That is, each user has a role_id column and can only have one role. Role inheritance can be used
67
+ in authorization rules.
68
+
69
+ Finally, the installer also copies default authorization rules, as below.
70
+
71
+ === Generate Authorization Rules
72
+
73
+ To copy a default set of authorization rules which includes CRUD priveleges, run:
74
+
75
+ $ rails g authorization:rules
76
+
77
+ This command will copy the following to +config/authorization_rules.rb+. Remember
78
+ to implement the requirements of this gem as described in the Installation section
79
+ at the end of this README if you do not use the above installer.
80
+
81
+ authorization do
82
+ role :guest do
83
+ # add permissions for guests here, e.g.
84
+ # has_permission_on :conferences, :to => :read
85
+ end
86
+
87
+ # permissions on other roles, such as
88
+ # role :admin do
89
+ # has_permission_on :conferences, :to => :manage
90
+ # end
91
+ # role :user do
92
+ # has_permission_on :conferences, :to => [:read, :create]
93
+ # has_permission_on :conferences, :to => [:update, :delete] do
94
+ # if_attribute :user_id => is {user.id}
95
+ # end
96
+ # end
97
+ # See the readme or GitHub for more examples
98
+ end
99
+
100
+ privileges do
101
+ # default privilege hierarchies to facilitate RESTful Rails apps
102
+ privilege :manage, :includes => [:create, :read, :update, :delete]
103
+ privilege :create, :includes => :new
104
+ privilege :read, :includes => [:index, :show]
105
+ privilege :update, :includes => :edit
106
+ privilege :delete, :includes => :destroy
107
+ end
108
+
109
+ === Controller Authorization
110
+
111
+ For RESTful controllers, add +filter_resource_access+:
112
+
113
+ class MyRestfulController < ApplicationController
114
+ filter_resource_access
115
+ ...
116
+ end
117
+
118
+ For a non-RESTful controller, you can use +filter_access_to+:
119
+
120
+ class MyOtherController < ApplicationController
121
+ filter_access_to :all
122
+ # or a group: filter_access_to [:action1, :action2]
123
+ ...
124
+ end
125
+
126
+ === View Authorization
127
+
128
+ Declarative Authorization will use +current_user+ to check authorization.
129
+
130
+ <%= link_to 'Edit Post', edit_post_path(@post) if permitted_to? :update, @post %>
131
+
132
+
133
+ = Authorization Data Model
134
+
135
+ ----- App domain ----|-------- Authorization conf ---------|------- App domain ------
136
+
137
+ includes includes
138
+ .--. .---.
139
+ | v | v
140
+ .------. can_play .------. has_permission .------------. requires .----------.
141
+ | User |----------->| Role |----------------->| Permission |<-----------| Activity |
142
+ '------' * * '------' * * '------------' 1 * '----------'
143
+ |
144
+ .-------+------.
145
+ 1 / | 1 \ *
146
+ .-----------. .---------. .-----------.
147
+ | Privilege | | Context | | Attribute |
148
+ '-----------' '---------' '-----------'
149
+
150
+ In the application domain, each *User* may be assigned to *Roles* that should
151
+ define the users' job in the application, such as _Administrator_. On the
152
+ right-hand side of this diagram, application developers specify which *Permissions*
153
+ are necessary for users to perform activities, such as calling a controller action,
154
+ viewing parts of a View or acting on records in the database. Note that
155
+ Permissions consist of an *Privilege* that is to be performed, such as _read_,
156
+ and a *Context* in that the Operation takes place, such as _companies_.
157
+
158
+ In the authorization configuration, Permissions are assigned to Roles and Role
159
+ and Permission hierarchies are defined. *Attributes* may be employed to allow
160
+ authorization according to dynamic information about the context and the
161
+ current user, e.g. "only allow access on employees that belong to the
162
+ current user's branch."
163
+
164
+
165
+ = Examples
166
+
167
+ A fully functional example application can be found at
168
+ http://github.com/stffn/decl_auth_demo_app
169
+
170
+ == Controller
171
+
172
+ If authentication is in place, there are two ways to enable user-specific
173
+ access control on controller actions. For resource controllers, which more
174
+ or less follow the CRUD pattern, +filter_resource_access+ is the simplest
175
+ approach. It sets up instance variables in before filters and calls
176
+ filter_access_to with the appropriate parameters to protect the CRUD methods.
177
+
178
+ class EmployeesController < ApplicationController
179
+ filter_resource_access
180
+ ...
181
+ end
182
+
183
+ See Authorization::AuthorizationInController::ClassMethods for options on
184
+ nested resources and custom member and collection actions.
185
+
186
+ By default, declarative_authorization will enable filter_resource_access compatibility with strong_parameters in Rails 4. If you want to disable this behavior, you can use the +:strong_parameters+ option.
187
+
188
+ class EmployeesController < ApplicationController
189
+ filter_resource_access :strong_parameters => false
190
+ ...
191
+ end
192
+
193
+ Simalarly, you can use +:strong_parameters => true+ if you are using strong_parameters in Rails 3.
194
+
195
+ If you prefer less magic or your controller has no resemblance with the resource
196
+ controllers, directly calling filter_access_to may be the better option. Examples
197
+ are given in the following. E.g. the privilege index users is required for
198
+ action index. This works as a first default configuration for RESTful
199
+ controllers, with these privileges easily handled in the authorization
200
+ configuration, which will be described below.
201
+
202
+ class EmployeesController < ApplicationController
203
+ filter_access_to :all
204
+ def index
205
+ ...
206
+ end
207
+ ...
208
+ end
209
+
210
+ When custom actions are added to such a controller, it helps to define more
211
+ clearly which privileges are the respective requirements. That is when the
212
+ filter_access_to call may become more verbose:
213
+
214
+ class EmployeesController < ApplicationController
215
+ filter_access_to :all
216
+ # this one would be included in :all, but :read seems to be
217
+ # a more suitable privilege than :auto_complete_for_user_name
218
+ filter_access_to :auto_complete_for_employee_name, :require => :read
219
+ def auto_complete_for_employee_name
220
+ ...
221
+ end
222
+ ...
223
+ end
224
+
225
+ For some actions it might be necessary to check certain attributes of the
226
+ object the action is to be acting on. Then, the object needs to be loaded
227
+ before the action's access control is evaluated. On the other hand, some actions
228
+ might prefer the authorization to ignore specific attribute checks as the object is
229
+ unknown at checking time, so attribute checks and thus automatic loading of
230
+ objects needs to be enabled explicitly.
231
+
232
+ class EmployeesController < ApplicationController
233
+ filter_access_to :update, :attribute_check => true
234
+ def update
235
+ # @employee is already loaded from param[:id] because of :attribute_check
236
+ end
237
+ end
238
+
239
+ You can provide the needed object through before_actions. This way, you have
240
+ full control over the object that the conditions are checked against. Just make
241
+ sure, your before_actions occur before any of the filter_access_to calls.
242
+
243
+ class EmployeesController < ApplicationController
244
+ before_action :new_employee_from_params, :only => :create
245
+ before_action :new_employee, :only => [:index, :new]
246
+ filter_access_to :all, :attribute_check => true
247
+
248
+ def create
249
+ @employee.save!
250
+ end
251
+
252
+ protected
253
+ def new_employee_from_params
254
+ @employee = Employee.new(params[:employee])
255
+ end
256
+ end
257
+
258
+ If the access is denied, a +permission_denied+ method is called on the
259
+ current_controller, if defined, and the issue is logged.
260
+ For further customization of the filters and object loading, have a look at
261
+ the complete API documentation of filter_access_to in
262
+ Authorization::AuthorizationInController::ClassMethods.
263
+
264
+
265
+ == Views
266
+
267
+ In views, a simple permitted_to? helper makes showing blocks according to the
268
+ current user's privileges easy:
269
+
270
+ <% permitted_to? :create, :employees do %>
271
+ <%= link_to 'New', new_employee_path %>
272
+ <% end %>
273
+
274
+ Only giving a symbol :employees as context prevents any checks of attributes
275
+ as there is no object to check against. For example, in case of nested resources
276
+ a new object may come in handy:
277
+
278
+ <% permitted_to? :create, Branch.new(:company => @company) do
279
+ # or @company.branches.new
280
+ # or even @company.branches %>
281
+ <%= link_to 'New', new_company_branch_path(@company) %>
282
+ <% end %>
283
+
284
+ Lists are straight-forward:
285
+
286
+ <% for employee in @employees %>
287
+ <%= link_to 'Edit', edit_employee_path(employee) if permitted_to? :update, employee %>
288
+ <% end %>
289
+
290
+ See also Authorization::AuthorizationHelper.
291
+
292
+
293
+ == Models
294
+
295
+ There are two distinct features for model security built into this plugin:
296
+ authorizing CRUD operations on objects as well as query rewriting to limit
297
+ results according to certain privileges.
298
+
299
+ See also Authorization::AuthorizationInModel.
300
+
301
+ === Model security for CRUD operations
302
+ To activate model security, all it takes is an explicit enabling for each
303
+ model that model security should be enforced on, i.e.
304
+
305
+ class Employee < ActiveRecord::Base
306
+ using_access_control
307
+ ...
308
+ end
309
+
310
+ Thus,
311
+ Employee.create(...)
312
+ fails, if the current user is not allowed to :create :employees according
313
+ to the authorization rules. For the application to find out about what
314
+ happened if an operation is denied, the filters throw
315
+ Authorization::NotAuthorized exceptions.
316
+
317
+ As access control on read are costly, with possibly lots of objects being
318
+ loaded at a time in one query, checks on read need to be activated explicitly by
319
+ adding the :include_read option.
320
+
321
+ === Query rewriting through named scopes
322
+ When retrieving large sets of records from databases, any authorization needs
323
+ to be integrated into the query in order to prevent inefficient filtering
324
+ afterwards and to use LIMIT and OFFSET in SQL statements. To keep authorization
325
+ rules out of the source code, this plugin offers query rewriting mechanisms
326
+ through named scopes. Thus,
327
+
328
+ Employee.with_permissions_to(:read)
329
+
330
+ returns all employee records that the current user is authorized to read. In
331
+ addition, just like normal named scopes, query rewriting may be chained with
332
+ the usual find method:
333
+
334
+ Employee.with_permissions_to(:read).find(:all, :conditions => ...)
335
+
336
+ If the current user is completely missing the permissions, an
337
+ Authorization::NotAuthorized exception is raised. Through
338
+ Model.obligation_conditions, application developers may retrieve
339
+ the conditions for manual rewrites.
340
+
341
+
342
+ == Authorization Rules
343
+
344
+ Authorization rules are defined in config/authorization_rules.rb
345
+ (Or redefine rules files path via +Authorization::AUTH_DSL_FILES+). E.g.
346
+
347
+ authorization do
348
+ role :admin do
349
+ has_permission_on :employees, :to => [:create, :read, :update, :delete]
350
+ end
351
+ end
352
+
353
+ There is a default role :+guest+ that is used if a request is not associated
354
+ with any user or with a user without any roles. So, if your application has
355
+ public pages, :+guest+ can be used to allow access for users that are not
356
+ logged in. All other roles are application defined and need to be associated
357
+ with users by the application.
358
+
359
+ If you need to change the default role, you can do so by adding an initializer
360
+ that contains the following statement:
361
+
362
+ Authorization.default_role = :anonymous
363
+
364
+ Privileges, such as :create, may be put into hierarchies to simplify
365
+ maintenance. So the example above has the same meaning as
366
+
367
+ authorization do
368
+ role :admin do
369
+ has_permission_on :employees, :to => :manage
370
+ end
371
+ end
372
+
373
+ privileges do
374
+ privilege :manage do
375
+ includes :create, :read, :update, :delete
376
+ end
377
+ end
378
+
379
+ Privilege hierarchies may be context-specific, e.g. applicable to :employees.
380
+
381
+ privileges do
382
+ privilege :manage, :employees, :includes => :increase_salary
383
+ end
384
+
385
+ For more complex use cases, authorizations need to be based on attributes. Note
386
+ that you then also need to set :attribute_check => true in controllers for filter_access_to.
387
+ E.g. if a branch admin should manage only employees of his branch (see
388
+ Authorization::Reader in the API docs for a full list of available operators):
389
+
390
+ authorization do
391
+ role :branch_admin do
392
+ has_permission_on :employees do
393
+ to :manage
394
+ # user refers to the current_user when evaluating
395
+ if_attribute :branch => is {user.branch}
396
+ end
397
+ end
398
+ end
399
+
400
+ To reduce redundancy in has_permission_on blocks, a rule may depend on
401
+ permissions on associated objects:
402
+
403
+ authorization do
404
+ role :branch_admin do
405
+ has_permission_on :branches, :to => :manage do
406
+ if_attribute :managers => contains {user}
407
+ end
408
+
409
+ has_permission_on :employees, :to => :manage do
410
+ if_permitted_to :manage, :branch
411
+ # instead of
412
+ #if_attribute :branch => {:managers => contains {user}}
413
+ end
414
+ end
415
+ end
416
+
417
+ Lastly, not only privileges may be organized in a hierarchy but roles as well.
418
+ Here, project manager inherit the permissions of employees.
419
+
420
+ role :project_manager do
421
+ includes :employee
422
+ end
423
+
424
+ See also Authorization::Reader.
425
+
426
+ == Testing
427
+
428
+ declarative_authorization provides a few helpers to ease the testing with
429
+ authorization in mind.
430
+
431
+ In your test_helper.rb, to enable the helpers add
432
+
433
+ require 'declarative_authorization/maintenance'
434
+
435
+ class Test::Unit::TestCase
436
+ include Authorization::TestHelper
437
+ ...
438
+ end
439
+
440
+ For using the test helpers with RSpec, just add the following lines to your
441
+ spec_helper.rb (somewhere after require 'spec/rails'):
442
+
443
+ require 'declarative_authorization/maintenance'
444
+ include Authorization::TestHelper
445
+
446
+ Now, in unit tests, you may deactivate authorization if needed e.g. for test
447
+ setup and assume certain identities for tests:
448
+
449
+ class EmployeeTest < ActiveSupport::TestCase
450
+ def test_should_read
451
+ without_access_control do
452
+ Employee.create(...)
453
+ end
454
+ assert_nothing_raised do
455
+ with_user(admin) do
456
+ Employee.find(:first)
457
+ end
458
+ end
459
+ end
460
+ end
461
+
462
+ Or, with RSpec, it would work like this:
463
+
464
+ describe Employee do
465
+ it "should read" do
466
+ without_access_control do
467
+ Employee.create(...)
468
+ end
469
+ with_user(admin) do
470
+ Employee.find(:first)
471
+ end
472
+ end
473
+ end
474
+
475
+ In functional tests, get, posts, etc. may be tested in the name of certain users:
476
+
477
+ get_with admin, :index
478
+ post_with admin, :update, :employee => {...}
479
+
480
+ See Authorization::TestHelper for more information.
481
+
482
+
483
+ = Installation of declarative_authorization
484
+
485
+ One of three options to install the plugin:
486
+ * Install by Gem: Add to your environment.rb in the initializer block:
487
+ config.gem "declarative_authorization"
488
+ Note: you need gemcutter support in place, i.e. call
489
+ gem install gemcutter
490
+ gem tumble
491
+ And call from your application's root directory
492
+ rake gems:install
493
+ * Alternatively, in Rails 2, to install from github, execute in your application's root directory
494
+ cd vendor/plugins && git clone git://github.com/stffn/declarative_authorization.git
495
+
496
+ Then,
497
+ * provide the requirements as noted below,
498
+ * create a basic config/authorization_rules.rb--you might want to take the
499
+ provided example authorization_rules.dist.rb in the plugin root as a starting
500
+ point,
501
+ * add +filter_access_to+, +permitted_to+? and model security as needed.
502
+
503
+ == Providing the Plugin's Requirements
504
+ The requirements are
505
+ * Rails >= 4.2.5.2 and Ruby >= 2.3.3
506
+ * An authentication mechanism
507
+ * A user object returned by Controller#current_user
508
+ * An array of role symbols returned by User#role_symbols
509
+ * (For model security) Setting Authorization.current_user to the request's user
510
+
511
+ Of the various ways to provide these requirements, here is one way employing
512
+ restful_authentication.
513
+
514
+ * Install restful_authentication
515
+ cd vendor/plugins && git clone git://github.com/technoweenie/restful-authentication.git restful_authentication
516
+ cd ../.. && ruby script/generate authenticated user sessions
517
+ * Move "include AuthenticatedSystem" to ApplicationController
518
+ * Add +filter_access_to+ calls as described above.
519
+ * If you'd like to use model security, add a before_action that sets the user
520
+ globally to your ApplicationController. This is thread-safe.
521
+ before_action :set_current_user
522
+ protected
523
+ def set_current_user
524
+ Authorization.current_user = current_user
525
+ end
526
+
527
+ * Add roles field to the User model through a :+has_many+ association
528
+ (this is just one possible approach; you could just as easily use
529
+ :+has_many+ :+through+ or a serialized roles array):
530
+ * create a migration for table roles
531
+ class CreateRoles < ActiveRecord::Migration
532
+ def self.up
533
+ create_table "roles" do |t|
534
+ t.column :title, :string
535
+ t.references :user
536
+ end
537
+ end
538
+
539
+ def self.down
540
+ drop_table "roles"
541
+ end
542
+ end
543
+
544
+ * create a model Role,
545
+ class Role < ActiveRecord::Base
546
+ belongs_to :user
547
+ end
548
+
549
+ * add +has_many+ :+roles+ to the User model and a roles method that returns the roles
550
+ as an Array of Symbols, e.g.
551
+ class User < ActiveRecord::Base
552
+ has_many :roles
553
+ def role_symbols
554
+ (roles || []).map {|r| r.title.to_sym}
555
+ end
556
+ end
557
+
558
+ * add roles to your User objects using e.g.
559
+ user.roles.create(:title => "admin")
560
+
561
+ Note: If you choose to generate an Account model for restful_authentication
562
+ instead of a User model as described above, you have to customize the
563
+ examples and create a ApplicationController#current_user method.
564
+
565
+
566
+ == Debugging Authorization
567
+
568
+ Currently, the main means of debugging authorization decisions is logging and
569
+ exceptions. Denied access to actions is logged to +warn+ or +info+, including
570
+ some hints about what went wrong.
571
+
572
+ All bang methods throw exceptions which may be used to retrieve more
573
+ information about a denied access than a Boolean value.
574
+
575
+
576
+ = Help and Contact
577
+
578
+ We have an issue tracker[http://github.com/appfolio/ae_declarative_authorization/issues]
579
+ for bugs and feature requests.
580
+ You are very welcome to contribute. Just fork the git repository and send a pull request.
581
+
582
+
583
+ = Contributors
584
+
585
+ Thanks to John Joseph Bachir, Dennis Blöte, Eike Carls, Damian Caruso, Kai Chen, Erik Dahlstrand,
586
+ Jeroen van Dijk, Alexander Dobriakov, Sebastian Dyck, Ari Epstein, Jeremy Friesen,
587
+ Tim Harper, John Hawthorn, hollownest, Daniel Kristensen, Jeremy Kleindl, Joel Kociolek,
588
+ Benjamin ter Kuile, Brad Langhorst, Brian Langenfeld,
589
+ Georg Ledermann, Geoff Longman, Olly Lylo, Mark Mansour, Thomas Maurer, Kevin Moore,
590
+ Tyler Pickett, Edward Rudd, Sharagoz,
591
+ TJ Singleton, Mike Vincent, Joel Westerberg
592
+
593
+
594
+ = License
595
+
596
+ Copyright (c) 2008 Steffen Bartsch, TZI, Universität Bremen, Germany
597
+ released under the MIT license