authority 1.0.0.pre4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.markdown CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  This is mainly to document major new features and backwards-incompatible changes.
4
4
 
5
+ ## v1.0.0
6
+
7
+ - Added `config.security_violation_handler` so users can specify which controller method to use when rescuing `SecurityViolation`s
8
+ - Removed generator to make blank authorizers. On further consideration, one authorizer per model is counterproductive for most use cases, and I'd rather not encourage misuse.
9
+
10
+ ## v1.0.0.pre4
11
+
12
+ Added generator to make blank authorizers. See `rails g authority:authorizers --help`.
13
+
5
14
  ## v1.0.0.pre3
6
15
 
7
16
  - Rename controller methods (again):
data/README.markdown CHANGED
@@ -1,8 +1,10 @@
1
1
  # Authority
2
2
 
3
- Authority gives you a clean and easy way to say, in your Rails app, **who** is allowed to do **what** with your models. Unauthorized actions get a warning and an entry in a log file.
3
+ Authority helps you authorize actions in your Rails app. It's **ORM-neutral** and has very little fancy syntax; just group your models under one or more Authorizer classes and write plain Ruby methods on them.
4
4
 
5
- It requires that you already have some kind of user object in your application, accessible from all controllers (like `current_user`).
5
+ Authority will work fine with a standalone app or a single sign-on system. You can check roles in a database or permissions in a YAML file. It doesn't care! What it **does** do is give you an easy way to organize your logic, define a default strategy, and handle unauthorized actions.
6
+
7
+ It requires that you already have some kind of user object in your application, accessible from all controllers and views via a method like `current_user` (configurable).
6
8
 
7
9
  [![Build Status](https://secure.travis-ci.org/nathanl/authority.png)](http://travis-ci.org/nathanl/authority)
8
10
 
@@ -19,9 +21,9 @@ It requires that you already have some kind of user object in your application,
19
21
  <li><a href="#models">Models</a></li>
20
22
  <li><a href="#authorizers">Authorizers</a>
21
23
  <ul>
22
- <li><a href="#custom_authorizers">Custom Authorizers</a></li>
23
24
  <li><a href="#default_strategies">Default strategies</a></li>
24
25
  <li><a href="#testing_authorizers">Testing Authorizers</a></li>
26
+ <li><a href="#custom_authorizers">Custom Authorizers</a></li>
25
27
  </ul></li>
26
28
  <li><a href="#controllers">Controllers</a></li>
27
29
  <li><a href="#views">Views</a></li>
@@ -36,29 +38,59 @@ It requires that you already have some kind of user object in your application,
36
38
 
37
39
  The goals of Authority are:
38
40
 
39
- - To allow broad, class-level rules. Examples:
41
+ - To allow broad, **class-level** rules. Examples:
40
42
  - "Basic users cannot delete any Widget."
41
43
  - "Only admin users can create Offices."
42
- - To allow fine-grained, instance-level rules. Examples:
44
+ - To allow fine-grained, **instance-level** rules. Examples:
43
45
  - "Management users can only edit schedules with date ranges in the future."
44
46
  - "Users can't create playlists more than 20 songs long unless they've paid."
45
47
  - To provide a clear syntax for permissions-based views. Example:
46
48
  - `link_to 'Edit Widget', edit_widget_path(@widget) if current_user.can_update?(@widget)`
47
- - To gracefully handle any access violations: display a "you can't do that" screen and log the violation.
49
+ - To gracefully handle any access violations: by default, it displays a "you can't do that" screen and logs the violation.
48
50
  - To do all this with minimal effort and mess.
49
51
 
50
52
  <a name="flow_of_authority">
51
53
  ## The flow of Authority
52
54
 
53
- Authority encapsulates all authorization logic in `Authorizer` classes. Want to do something with a model? Ask its authorizer.
55
+ Authority encapsulates all authorization logic in `Authorizer` classes. Want to do something with a model? **Ask its authorizer**.
56
+
57
+ You can group models under authorizers in any way you wish. For example:
58
+
59
+
60
+ +-------------+ +--------------+ +-------------+
61
+ |Simplest case| |Logical groups| |Most granular|
62
+ +-------------+ +--------------+ +-------------+
63
+
64
+ default_strategy default_strategy default_strategy
65
+ + + +
66
+ | +--------+-------+ +-------------------+-------------------+
67
+ + + + + + +
68
+ EverythingAuthorizer BasicAuthorizer AdminAuthorizer CommentAuthorizer ArticleAuthorizer EditionAuthorizer
69
+ + + + + + +
70
+ +-------+-------+ +-+ +------+ | | |
71
+ + + + + + + + + +
72
+ Comment Article Edition Comment Article Edition Comment Article Edition
73
+
54
74
 
55
75
  The process generally flows like this:
56
76
 
57
- - In a controller or view, a user object is asked whether it can do some action to a resource class or instance, like `current_user.can_create?(Widget)` or `current_user.can_update?(@widget)`.
58
- - The user just asks the model the same question: `resource.creatable_by?(self)`.
59
- - The model passes that question to its Authorizer, which actually contains the logic to answer the question.
60
- - The Authorizer returns an answer back up the call chain to the original caller.
61
- - If the answer is "no" and the original caller was a controller, this is treated as a `SecurityViolation`. If it was a view, maybe you just don't show a link.
77
+ current_user.can_create?(Moose) # You ask this question, and the user
78
+ + # automatically asks the model...
79
+ |
80
+ v
81
+ Moose.creatable_by?(current_user) # The model automatically asks
82
+ + # its authorizer...
83
+ |
84
+ v
85
+ MooseAuthorizer.creatable_by?(current_user) # *You define this method.*
86
+ + # If it's missing, the default
87
+ | # strategy is used...
88
+ v
89
+ config.default_strategy.call(:creatable, MooseAuthorizer, user) # *You define this strategy.*
90
+
91
+ If the answer is `false` and the original caller was a controller, this is treated as a `SecurityViolation`. If it was a view, maybe you just don't show a link.
92
+
93
+ (Diagrams made with [AsciiFlow](http://asciiflow.com))
62
94
 
63
95
  <a name="installation">
64
96
  ## Installation
@@ -70,12 +102,14 @@ Starting from a clean commit status, add `authority` to your Gemfile, `bundle`,
70
102
 
71
103
  Edit `config/initializers/authority.rb`. That file documents all your options, but one of particular interest is `config.abilities`, which defines the verbs and corresponding adjectives in your app. The defaults are:
72
104
 
73
- config.abilities = {
74
- :create => 'creatable',
75
- :read => 'readable',
76
- :update => 'updatable',
77
- :delete => 'deletable'
78
- }
105
+ ```ruby
106
+ config.abilities = {
107
+ :create => 'creatable',
108
+ :read => 'readable',
109
+ :update => 'updatable',
110
+ :delete => 'deletable'
111
+ }
112
+ ```
79
113
 
80
114
  This option determines what methods are added to your users, models and authorizers. If you need to ask `user.can_deactivate?(Satellite)` and `@satellite.deactivatable_by?(user)`, add those to the hash.
81
115
 
@@ -84,170 +118,215 @@ This option determines what methods are added to your users, models and authoriz
84
118
 
85
119
  <a name="users">
86
120
  ### Users
87
- In whatever class represents a logged-in user in your application, `include Authority::UserAbilities`.
121
+
122
+ ```ruby
123
+ # Whatever class represents a logged-in user in your app
124
+ class User
125
+ # Adds `can_create?(resource)`, etc
126
+ include Authority::Abilities
127
+ ...
128
+ end
129
+ ```
88
130
 
89
131
  <a name="models">
90
132
  ### Models
91
-
92
- In every model, `include Authority::Abilities`. Give every model an [Authorizer](#authorizers). By default, the `Llama` model will look for a `LlamaAuthorizer`. To specify a different one, call `authorizer_name UngulateAuthorizer`; this way, the `UngulateAuthorizer` could also protect the `Zebra` and `Antelope` models, or the `AdminAuthorizer` could protect business-critical models.
133
+
134
+ ```ruby
135
+ class Article
136
+ # Adds `creatable_by?(user)`, etc
137
+ include Authority::Abilities
138
+
139
+ # Without this, 'ArticleAuthorizer' is assumed
140
+ self.authorizer_name = 'AdminAuthorizer'
141
+ ...
142
+ end
143
+ ```
93
144
 
94
145
  <a name="authorizers">
95
146
  ### Authorizers
96
147
 
97
- Add your authorizers under `app/authorizers`, subclassing `Authority::Authorizer` or your own authorizer class. (See `rails g authority::authorizers --help`.)
148
+ Add your authorizers under `app/authorizers`, subclassing `Authority::Authorizer`.
98
149
 
99
150
  These are where your actual authorization logic goes. Here's how it works:
100
151
 
101
152
  - Instance methods answer questions about model instances, like "can this user update this **particular** widget?" (Within an instance method, you can get the model instance with `resource`).
102
153
  - Any instance method you don't define (for example, if you didn't make a `def deletable_by?(user)`) will fall back to the corresponding class method. In other words, if you haven't said whether a user can update **this particular** widget, we'll decide by checking whether they can update **any** widget.
103
154
  - Class methods answer questions about model classes, like "is it **ever** permissible for this user to update a Widget?"
104
- - Any class method you don't define (for example, if you didn't make a `def self.updatable_by?(user)`) will fall back to your [configurable default strategy](#default_strategies).
155
+ - Any class method you don't define (for example, if you didn't make a `def self.updatable_by?(user)`) will fall back to your configurable [default strategy](#default_strategies).
105
156
 
106
157
  For example:
107
158
 
108
- # app/authorizers/schedule_authorizer.rb
109
- class ScheduleAuthorizer < Authority::Authorizer
110
-
111
- # Class method: can this user at least sometimes create a Schedule?
112
- def self.creatable_by?(user)
113
- user.manager?
114
- end
115
-
116
- # Instance method: can this user delete this particular schedule?
117
- def deletable_by?(user)
118
- resource.in_future? && user.manager? && resource.department == user.department
119
- end
120
-
121
- end
159
+ ```ruby
160
+ # app/authorizers/schedule_authorizer.rb
161
+ class ScheduleAuthorizer < Authority::Authorizer
162
+ # Class method: can this user at least sometimes create a Schedule?
163
+ def self.creatable_by?(user)
164
+ user.manager?
165
+ end
166
+
167
+ # Instance method: can this user delete this particular schedule?
168
+ def deletable_by?(user)
169
+ resource.in_future? && user.manager? && resource.department == user.department
170
+ end
171
+ end
172
+ ```
122
173
 
123
174
  As you can see, you can specify different logic for every method on every model, if necessary. On the other extreme, you could simply supply a [default strategy](#default_strategies) that covers all your use cases.
124
175
 
125
- #### Custom Authorizers
126
-
127
- If you want to customize your authorizers even further - for example, maybe you want them all to have a method like `has_permission?(user, permission_name)` - you can insert a custom class into the inheritance chain.
128
-
129
- # lib/my_app/authorizer.rb
130
- module MyApp
131
- class Authorizer < Authority::Authorizer
132
-
133
- def self.has_permission(user, permission_name)
134
- # look that up somewhere
135
- end
136
-
137
- end
138
- end
139
-
140
- #app/authorizers/badger_authorizer.rb
141
- class BadgerAuthorizer < MyApp::Authorizer
142
- # contents
143
- end
144
-
145
- If you decide to place your custom class in `lib` as shown above (as opposed to putting it in `app`), you should require it at the bottom of `config/initializers/authority.rb`.
146
-
147
176
  <a name="default_strategies">
148
177
  #### Default Strategies
149
178
 
150
- Any class method you don't define on an authorizer will use your default strategy. The **default** default strategy simply returns false, meaning that everything is forbidden. This whitelisting approach will keep you from accidentally allowing things you didn't intend.
179
+ Any class method you don't define on an authorizer will use your default strategy. The **default** default strategy simply returns `false`, meaning that everything is forbidden. This whitelisting approach will keep you from accidentally allowing things you didn't intend.
151
180
 
152
181
  You can configure a different default strategy. For example, you might want one that looks up permissions in your database:
153
182
 
154
- # In config/initializers/authority.rb
155
- config.default_strategy = Proc.new { |able, authorizer, user|
156
- # Does the user have any of the roles which give this permission?
157
- (roles_which_grant(able, authorizer) & user.roles).any?
158
- }
183
+ ```ruby
184
+ # In config/initializers/authority.rb
185
+ config.default_strategy = Proc.new do |able, authorizer, user|
186
+ # Does the user have any of the roles which give this permission?
187
+ (roles_which_grant(able, authorizer) & user.roles).any?
188
+ end
189
+ ```
190
+
191
+ If your system is uniform enough, **this strategy alone might handle all the logic you need**.
159
192
 
160
193
  <a name="testing_authorizers">
161
194
  #### Testing Authorizers
162
195
 
163
196
  One nice thing about putting your authorization logic in authorizers is the ease of testing. Here's a brief example.
164
197
 
165
- # An authorizer shared by several admin-only models
166
- describe AdminAuthorizer do
198
+ ```ruby
199
+ # An authorizer shared by several admin-only models
200
+ describe AdminAuthorizer do
167
201
 
168
- before :each do
169
- @user = Factory.build(:user)
170
- @admin = Factory.build(:admin)
171
- end
202
+ before :each do
203
+ @user = FactoryGirl.build(:user)
204
+ @admin = FactoryGirl.build(:admin)
205
+ end
172
206
 
173
- describe "class" do
174
- it "should let admins update in bulk" do
175
- AdminAuthorizer.should be_bulk_updatable_by(@admin)
176
- end
207
+ describe "class" do
208
+ it "should let admins update in bulk" do
209
+ AdminAuthorizer.should be_bulk_updatable_by(@admin)
210
+ end
211
+
212
+ it "should not let users update in bulk" do
213
+ AdminAuthorizer.should_not be_bulk_updatable_by(@user)
214
+ end
215
+ end
216
+
217
+ describe "instances" do
218
+
219
+ before :each do
220
+ # A mock model that uses AdminAuthorizer
221
+ @admin_resource_instance = mock_admin_resource
222
+ end
177
223
 
178
- it "should not let users update in bulk" do
179
- AdminAuthorizer.should_not be_bulk_updatable_by(@user)
180
- end
181
- end
224
+ it "should not allow users to delete" do
225
+ @admin_resource_instance.authorizer.should_not be_deletable_by(@user)
226
+ end
182
227
 
183
- describe "instances" do
228
+ end
184
229
 
185
- before :each do
186
- # A mock model that uses AdminAuthorizer
187
- @admin_resource_instance = mock_admin_resource
188
- end
230
+ end
231
+ ```
189
232
 
190
- it "should not allow users to delete" do
191
- @admin_resource_instance.authorizer.should_not be_deletable_by(@user)
192
- end
233
+ <a name="custom_authorizers">
234
+ #### Custom Authorizers
193
235
 
194
- end
236
+ If you want to customize your authorizers even further - for example, maybe you want them all to have a method like `has_permission?(user, permission_name)` - just use normal Ruby inheritance. For example, add your own parent class, like this:
195
237
 
238
+ ```ruby
239
+ # lib/my_app/authorizer.rb
240
+ module MyApp
241
+ class Authorizer < Authority::Authorizer
242
+
243
+ def self.has_permission(user, permission_name)
244
+ # look that up somewhere
196
245
  end
197
246
 
247
+ end
248
+ end
249
+
250
+ #app/authorizers/badger_authorizer.rb
251
+ class BadgerAuthorizer < MyApp::Authorizer
252
+ # contents
253
+ end
254
+ ```
255
+
256
+ If you decide to place your custom class in `lib` as shown above (as opposed to putting it in `app`), you should require it at the bottom of `config/initializers/authority.rb`.
257
+
198
258
  <a name="controllers">
199
259
  ### Controllers
200
260
 
201
261
  Anytime a controller finds a user attempting something they're not authorized to do, a [Security Violation](#security_violations_and_logging) will result. Controllers get two ways to check authorization:
202
262
 
203
- - `authorize_actions_for Transaction` protects multiple controller actions with a `before_filter`, which performs a class-level check. If the current user is never allowed to delete a `Transaction`, they'll never even get to the controller's `destroy` method.
204
- - `authorize_action_for @transaction` can be called inside a single controller action, and performs an instance-level check. If called inside `update`, it will check whether the current user is allowed to update this particular `@transaction` instance.
263
+ - `authorize_actions_for Transaction` protects multiple controller actions with a `before_filter`, which performs a **class-level** check. If the current user is never allowed to delete a `Transaction`, they'll never even get to the controller's `destroy` method.
264
+ - `authorize_action_for @transaction` can be called inside a single controller action, and performs an **instance-level** check. If called inside `update`, it will check whether the current user is allowed to update this particular `@transaction` instance.
205
265
 
206
266
  The relationship between controller actions and abilities - like checking `readable_by?` on the `index` action - is configurable both globally, using `config.controller_action_map`, and per controller, as below.
207
267
 
208
- class LlamaController < ApplicationController
268
+ ```ruby
269
+ class LlamaController < ApplicationController
209
270
 
210
- # Check class-level authorizations before all actions except :create
211
- # Before this controller's 'neuter' action, ask whether current_user.can_update?(Llama)
212
- authorize_actions_for Llama, :actions => {:neuter => :update}, :except => :create
213
-
214
- # Before this controller's 'breed' action, ask whether current_user.can_create?(Llama)
215
- authority_action :breed => 'new'
271
+ # Check class-level authorizations before all actions except :create
272
+ # Before this controller's 'neuter' action, ask whether current_user.can_update?(Llama)
273
+ authorize_actions_for Llama, :actions => {:neuter => :update}, :except => :create
274
+
275
+ # Before this controller's 'breed' action, ask whether current_user.can_create?(Llama)
276
+ authority_action :breed => 'new'
216
277
 
217
- ...
278
+ ...
218
279
 
219
- def edit
220
- @llama = Llama.find(params[:id])
221
- @llama.attributes = params[:llama] # Don't save the attributes before authorizing
222
- authorize_action_for(@llama) # failure == SecurityViolation
223
- if @llama.save?
224
- # etc
225
- end
280
+ def edit
281
+ @llama = Llama.find(params[:id])
282
+ @llama.attributes = params[:llama] # Don't save the attributes before authorizing
283
+ authorize_action_for(@llama) # failure == SecurityViolation
284
+ if @llama.save?
285
+ # etc
286
+ end
226
287
 
227
- end
288
+ end
289
+ ```
228
290
 
229
291
  <a name="views">
230
292
  ### Views
231
293
 
232
294
  Assuming your user object is available in your views, you can do all kinds of conditional rendering. For example:
233
295
 
234
- link_to 'Edit Widget', edit_widget_path(@widget) if current_user.can_update?(@widget)
296
+ ```ruby
297
+ link_to 'Edit Widget', edit_widget_path(@widget) if current_user.can_update?(@widget)
298
+ ```
235
299
 
236
300
  If the user isn't allowed to edit widgets, they won't see the link. If they're nosy and try to hit the URL directly, they'll get a [Security Violation](#security_violations_and_logging) from the controller.
237
301
 
238
302
  <a name="security_violations_and_logging">
239
303
  ## Security Violations & Logging
240
304
 
241
- Anytime a user attempts an unauthorized action, Authority does two things:
305
+ Anytime a user attempts an unauthorized action, Authority calls whatever controller method is specified by your `security_violation_handler` option, handing it the exception. The default handler is `authority_forbidden`, which Authority adds to your `ApplicationController`. It does the following:
242
306
 
243
- - Renders your `public/403.html`
307
+ - Renders `public/403.html`
244
308
  - Logs the violation to whatever logger you configured.
245
309
 
246
- If you want to have nice log messages for security violations, you should ensure that your user object and models have `to_s` methods; this will control how they show up in log messages saying things like
310
+ You can specify a different handler like so:
311
+
312
+ ```ruby
313
+ # config/initializers/authority.rb
314
+ ...
315
+ config.security_violation_handler = :fire_ze_missiles
316
+ ...
317
+
318
+ # app/controllers/application_controller.rb
319
+ class ApplicationController < ActionController::Base
247
320
 
248
- "Kenneth Lay is not allowed to delete this resource: 'accounting_tricks.doc'"
321
+ def fire_ze_missiles(exception)
322
+ # Log? Set a flash message? Dispatch minions to
323
+ # fill their mailbox with goose droppings? It's up to you.
324
+ end
325
+ ...
326
+ end
327
+ ```
249
328
 
250
- If you feel like setting up a `cron` job to watch the log file, look up the user's name and address, and dispatch minions to fill their mailbox with goose droppings, that's really up to you. I got nothing to do with it, man.
329
+ If you want different error handling per controller, define `fire_ze_missiles` on each of them.
251
330
 
252
331
  <a name="credits">
253
332
  ## Credits, AKA 'Shout-Outs'
@@ -264,11 +343,12 @@ What should you contribute? Try the TODO file for ideas, or grep the project for
264
343
 
265
344
  How can you contribute?
266
345
 
267
- 1. Fork this project
268
- 2. Create your feature branch (`git checkout -b my-new-feature`)
269
- 3. `bundle install` to get all dependencies
270
- 4. `rspec spec` to run all tests.
271
- 5. Update/add tests for your changes and code until they pass.
272
- 6. Commit your changes (`git commit -am 'Added some feature'`)
273
- 7. Push to the branch (`git push origin my-new-feature`)
274
- 8. Create a new Pull Request
346
+ 1. Let's talk! Before you do a bunch of work, open an issue so we can be sure we agree.
347
+ 2. Fork this project
348
+ 3. Create your feature branch (`git checkout -b my-new-feature`)
349
+ 4. `bundle install` to get all dependencies
350
+ 5. `rspec spec` to run all tests.
351
+ 6. Update/add tests for your changes and code until they pass.
352
+ 7. Commit your changes (`git commit -am 'Added some feature'`)
353
+ 8. Push to the branch (`git push origin my-new-feature`)
354
+ 9. Create a new Pull Request
data/TODO.markdown CHANGED
@@ -1,14 +1,13 @@
1
1
  # TODO
2
2
 
3
- ## Design
4
-
5
- - Carefully think through names of all public methods & see if they could be clearer or more intuitive
6
-
7
3
  ## Chores
8
4
 
9
- - Test generators
10
- - Configurable proc for logging method
5
+ - Add tests for the generators
11
6
 
12
7
  ## Documentation
13
8
 
14
9
  - Example of checking clean/dirty attributes in instance-level checks. For example, if I'm only allowed to update blue laser cannons, can I make them red? Maybe I need to check whether the old value was blue?
10
+
11
+ ## Features
12
+
13
+ - It would be nice to have an `authorized_link_to` method, which determines from the given path and the user's permissions whether to show the link. Not sure yet how hard this would be.
data/authority.gemspec CHANGED
@@ -4,8 +4,8 @@ require File.expand_path('../lib/authority/version', __FILE__)
4
4
  Gem::Specification.new do |gem|
5
5
  gem.authors = ["Nathan Long", "Adam Hunter"]
6
6
  gem.email = ["nathanmlong@gmail.com", "adamhunter@me.com"]
7
- gem.description = %q{Gem for managing authorization on model actions in Rails.}
8
- gem.summary = %q{Authority gives you a clean and easy way to say, in your Rails app, **who** is allowed to do **what** with your models, with minimal clutter.}
7
+ gem.summary = %q{Authority helps you authorize actions in your Rails app using plain Ruby methods on Authorizer classes.}
8
+ gem.description = %q{Authority helps you authorize actions in your Rails app. It's ORM-neutral and has very little fancy syntax; just group your models under one or more Authorizer classes and write plain Ruby methods on them.}
9
9
  gem.homepage = "https://github.com/nathanl/authority"
10
10
 
11
11
  gem.add_dependency "rails", ">= 3.0.0"
@@ -3,12 +3,13 @@ module Authority
3
3
 
4
4
  # Has default settings, overrideable in the initializer.
5
5
 
6
- attr_accessor :default_strategy, :abilities, :controller_action_map, :user_method, :logger
6
+ attr_accessor :default_strategy, :abilities, :controller_action_map, :user_method, :security_violation_handler, :logger
7
7
 
8
8
  def initialize
9
- @default_strategy = Proc.new { |able, authorizer, user|
9
+ @default_strategy = Proc.new do |able, authorizer, user|
10
10
  false
11
- }
11
+ end
12
+
12
13
 
13
14
  @abilities = {
14
15
  :create => 'creatable',
@@ -29,6 +30,8 @@ module Authority
29
30
 
30
31
  @user_method = :current_user
31
32
 
33
+ @security_violation_handler = :authority_forbidden
34
+
32
35
  @logger = Logger.new(STDERR)
33
36
  end
34
37
 
@@ -6,10 +6,18 @@ module Authority
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  included do
9
- rescue_from Authority::SecurityViolation, :with => :authority_forbidden
9
+ rescue_from(Authority::SecurityViolation, :with => Authority::Controller.security_violation_callback)
10
10
  class_attribute :authority_resource
11
11
  end
12
12
 
13
+ def self.security_violation_callback
14
+ Proc.new do |exception|
15
+ # Through the magic of ActiveSupport's Proc#bind, `ActionController::Base#rescue_from`
16
+ # can call this proc and make `self` the actual controller instance
17
+ self.send(Authority.configuration.security_violation_handler, exception)
18
+ end
19
+ end
20
+
13
21
  module ClassMethods
14
22
 
15
23
  # Sets up before_filter to ensure user is allowed to perform a given controller action
@@ -37,6 +45,7 @@ module Authority
37
45
  def authority_action_map
38
46
  @authority_action_map ||= Authority.configuration.controller_action_map.dup
39
47
  end
48
+
40
49
  end
41
50
 
42
51
  protected
@@ -1,3 +1,3 @@
1
1
  module Authority
2
- VERSION = "1.0.0.pre4"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -5,9 +5,32 @@ module Authority
5
5
  class InstallGenerator < Rails::Generators::Base
6
6
 
7
7
  source_root File.expand_path("../../templates", __FILE__)
8
-
9
8
  desc "Creates an Authority initializer for your application."
10
9
 
10
+ def do_all
11
+ copy_initializer
12
+ copy_forbidden
13
+ create_authorizers_directory
14
+ message = <<-RUBY
15
+
16
+ Install complete! See the README on Github for instructions on getting your
17
+ app running with Authority.
18
+
19
+ One note: each model needs to know the name of its its authorizer class.
20
+ You can specify that in the model like `authorizer_name FooAuthorizer`.
21
+ If you don't, the `Article` model (for example) will look for `ArticleAuthorizer`.
22
+
23
+ To generate one authorizer like that for each of your models, see
24
+ `rails g authority:authorizers`. If you also want to specify your own
25
+ parent class for them, use `rails g authority:authorizers MyClass`.
26
+
27
+ RUBY
28
+ puts message.strip_heredoc
29
+
30
+ end
31
+
32
+ private
33
+
11
34
  def copy_initializer
12
35
  template "authority_initializer.rb", "config/initializers/authority.rb"
13
36
  end
@@ -70,10 +70,18 @@ Authority.configure do |config|
70
70
  # :update => 'updatable',
71
71
  # :delete => 'deletable'
72
72
  # }
73
+
74
+ # SECURITY_VIOLATION_HANDLER
75
+ # If a SecurityViolation is raised, what controller method should be used to rescue it?
76
+ #
77
+ # Default is:
78
+ #
79
+ # config.security_violation_handler = :authority_forbidden # Defined in controller.rb
73
80
 
74
81
  # LOGGER
75
82
  # If a user tries to perform an unauthorized action, where should we log that fact?
76
- # Provide a logger object which responds to `.warn(message)`
83
+ # Provide a logger object which responds to `.warn(message)`, unless your
84
+ # security_violation_handler calls a different method.
77
85
  #
78
86
  # Default is:
79
87
  #
@@ -3,14 +3,41 @@ require 'support/ability_model'
3
3
  require 'support/example_controllers'
4
4
  require 'support/mock_rails'
5
5
  require 'support/user'
6
+ require 'active_support/core_ext/proc'
6
7
 
7
8
  describe Authority::Controller do
8
9
 
10
+ describe "the security violation callback" do
11
+
12
+ it "should call whatever method on the controller that the configuration specifies" do
13
+ # Here be dragons!
14
+ @fake_exception = Exception.new
15
+ @sample_controller = SampleController.new
16
+ # If a callback is passed to a controller's `rescue_from` method as the value for
17
+ # the `with` option (like `SomeController.rescue_from FooException, :with => some_callback`),
18
+ # Rails will use ActiveSupport's `Proc#bind` to ensure that when the proc refers to
19
+ # `self`, it will be the controller, not the proc itself.
20
+ # I need this callback's `self` to be the controller for the purposes of
21
+ # this test, so I'm stealing that behavior.
22
+ @callback = Authority::Controller.security_violation_callback.bind(@sample_controller)
23
+
24
+ Authority.configuration.security_violation_handler = :fire_ze_missiles
25
+ @sample_controller.should_receive(:fire_ze_missiles).with(@fake_exception)
26
+ @callback.call(@fake_exception)
27
+ end
28
+ end
29
+
9
30
  describe "when including" do
10
- it "should specify rescuing security transgressions" do
11
- SampleController.should_receive(:rescue_from).with(Authority::SecurityViolation, :with => :authority_forbidden)
31
+
32
+ before :each do
33
+ Authority::Controller.stub(:security_violation_callback).and_return(Proc.new {|exception| })
34
+ end
35
+
36
+ it "should specify rescuing security violations with a standard callback" do
37
+ SampleController.should_receive(:rescue_from).with(Authority::SecurityViolation, :with => Authority::Controller.security_violation_callback)
12
38
  SampleController.send(:include, Authority::Controller)
13
39
  end
40
+
14
41
  end
15
42
 
16
43
  describe "after including" do
metadata CHANGED
@@ -1,8 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: authority
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre4
5
- prerelease: 6
4
+ version: 1.0.0
5
+ prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Nathan Long
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-03-26 00:00:00.000000000 Z
13
+ date: 2012-04-17 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails
17
- requirement: &2152325920 !ruby/object:Gem::Requirement
17
+ requirement: &82923350 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: 3.0.0
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *2152325920
25
+ version_requirements: *82923350
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: bundler
28
- requirement: &2152325380 !ruby/object:Gem::Requirement
28
+ requirement: &82923030 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ! '>='
@@ -33,8 +33,10 @@ dependencies:
33
33
  version: 1.0.0
34
34
  type: :development
35
35
  prerelease: false
36
- version_requirements: *2152325380
37
- description: Gem for managing authorization on model actions in Rails.
36
+ version_requirements: *82923030
37
+ description: Authority helps you authorize actions in your Rails app. It's ORM-neutral
38
+ and has very little fancy syntax; just group your models under one or more Authorizer
39
+ classes and write plain Ruby methods on them.
38
40
  email:
39
41
  - nathanmlong@gmail.com
40
42
  - adamhunter@me.com
@@ -61,7 +63,6 @@ files:
61
63
  - lib/authority/railtie.rb
62
64
  - lib/authority/user_abilities.rb
63
65
  - lib/authority/version.rb
64
- - lib/generators/authority/authorizers_generator.rb
65
66
  - lib/generators/authority/install_generator.rb
66
67
  - lib/generators/templates/403.html
67
68
  - lib/generators/templates/authority_initializer.rb
@@ -92,26 +93,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
92
93
  required_rubygems_version: !ruby/object:Gem::Requirement
93
94
  none: false
94
95
  requirements:
95
- - - ! '>'
96
+ - - ! '>='
96
97
  - !ruby/object:Gem::Version
97
- version: 1.3.1
98
+ version: '0'
98
99
  requirements: []
99
100
  rubyforge_project:
100
- rubygems_version: 1.8.16
101
+ rubygems_version: 1.8.10
101
102
  signing_key:
102
103
  specification_version: 3
103
- summary: Authority gives you a clean and easy way to say, in your Rails app, **who**
104
- is allowed to do **what** with your models, with minimal clutter.
105
- test_files:
106
- - spec/authority/abilities_spec.rb
107
- - spec/authority/authorizer_spec.rb
108
- - spec/authority/configuration_spec.rb
109
- - spec/authority/controller_spec.rb
110
- - spec/authority/user_abilities_spec.rb
111
- - spec/authority_spec.rb
112
- - spec/spec_helper.rb
113
- - spec/support/ability_model.rb
114
- - spec/support/example_controllers.rb
115
- - spec/support/mock_rails.rb
116
- - spec/support/no_authorizer_model.rb
117
- - spec/support/user.rb
104
+ summary: Authority helps you authorize actions in your Rails app using plain Ruby
105
+ methods on Authorizer classes.
106
+ test_files: []
@@ -1,47 +0,0 @@
1
- require 'rails/generators/base'
2
-
3
- module Authority
4
- module Generators
5
- class AuthorizersGenerator < Rails::Generators::Base
6
-
7
- argument :parentClass, type: :string, default: 'Authority::Authorizer', banner: 'Parent class'
8
-
9
- def make_authorizer_folder
10
- # creates empty directory if none; doesn't empty the directory
11
- empty_directory authorizer_folder
12
- end
13
-
14
- desc "Generates one authorizer per model, with confirmation. Optionally, give name of parent class."
15
- def make_authorizers
16
- authorizer_names.each do |authorizer_name|
17
- filename = File.join(authorizer_folder, authorizer_name.underscore).concat('.rb')
18
- create_file filename do
19
- contents = <<-RUBY
20
- class #{authorizer_name} < #{parentClass}
21
- # Define class and instance methods
22
- end
23
- RUBY
24
- contents.strip_heredoc
25
- end
26
- end
27
- end
28
-
29
- # Non-public generator methods aren't automatically called
30
- private
31
-
32
- def authorizer_folder
33
- 'app/authorizers'
34
- end
35
-
36
- def authorizer_names
37
- # TODO: Make Dir.glob recursive(**), in case there are model subdirs,
38
- # and create same structure in authorizers
39
- models_dir = File.join(Rails.root, 'app', 'models', '*.rb')
40
- Dir.glob(models_dir).map do |filepath|
41
- filepath.split(/models./).last.gsub(/\.rb\z/, '').camelcase.concat('Authorizer')
42
- end
43
- end
44
-
45
- end
46
- end
47
- end