authority 1.0.0.pre3 → 1.0.0.pre4

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -6,23 +6,34 @@ It requires that you already have some kind of user object in your application,
6
6
 
7
7
  [![Build Status](https://secure.travis-ci.org/nathanl/authority.png)](http://travis-ci.org/nathanl/authority)
8
8
 
9
- ## TL;DR
10
-
11
- No time for reading! Reading is for chumps! Here's the skinny:
12
-
13
- - Install in your Rails project: add to Gemfile, `bundle`, then `rails g authority:install`
14
- - For each model you have, create a corresponding [authorizer](#authorizers). For example, for `app/models/lolcat.rb`, create `app/authorizers/lolcat_authorizer.rb` with an empty `class LolcatAuthorizer < Authority::Authorizer`.
15
- - Add class methods to that authorizer to set rules that can be enforced just by looking at the resource class, like "this user cannot create Lolcats, period."
16
- - Add instance methods to that authorizer to set rules that need to look at a resource instance, like "a user can only edit a Lolcat if it belongs to that user and has not been marked as 'classic'".
17
- - Wire up your user, models and controllers to work with your authorizers:
18
- - In your [user class](#users), `include Authority::UserAbilities`.
19
- - Put this in your [controllers](#controllers): `authorize_actions_for YourModelNameHere` (the model that controller works with)
20
- - Put this in your [models](#models): `include Authority::Abilities`
21
-
9
+ ## Contents
10
+
11
+ <ul>
12
+ <li><a href="#overview">Overview</a></li>
13
+ <li><a href="#flow_of_authority">The flow of Authority</a></li>
14
+ <li><a href="#installation">Installation</a></li>
15
+ <li><a href="#defining_your_abilities">Defining Your Abilities</a></li>
16
+ <li><a href="#wiring_it_together">Wiring It Together</a>
17
+ <ul>
18
+ <li><a href="#users">Users</a></li>
19
+ <li><a href="#models">Models</a></li>
20
+ <li><a href="#authorizers">Authorizers</a>
21
+ <ul>
22
+ <li><a href="#custom_authorizers">Custom Authorizers</a></li>
23
+ <li><a href="#default_strategies">Default strategies</a></li>
24
+ <li><a href="#testing_authorizers">Testing Authorizers</a></li>
25
+ </ul></li>
26
+ <li><a href="#controllers">Controllers</a></li>
27
+ <li><a href="#views">Views</a></li>
28
+ </ul></li>
29
+ <li><a href="#security_violations_and_logging">Security Violations &amp; Logging</a></li>
30
+ <li><a href="#credits">Credits</a></li>
31
+ <li><a href="#contributing">Contributing</a></li>
32
+ </ul>
33
+
34
+ <a name="overview">
22
35
  ## Overview
23
36
 
24
- Still here? Reading is fun! You always knew that. Time for a deeper look at things.
25
-
26
37
  The goals of Authority are:
27
38
 
28
39
  - To allow broad, class-level rules. Examples:
@@ -34,274 +45,211 @@ The goals of Authority are:
34
45
  - To provide a clear syntax for permissions-based views. Example:
35
46
  - `link_to 'Edit Widget', edit_widget_path(@widget) if current_user.can_update?(@widget)`
36
47
  - To gracefully handle any access violations: display a "you can't do that" screen and log the violation.
37
- - To do all of this **without cluttering** either your controllers or your models. This is done by letting Authorizer classes do most of the work. More on that below.
48
+ - To do all this with minimal effort and mess.
38
49
 
50
+ <a name="flow_of_authority">
39
51
  ## The flow of Authority
40
52
 
41
- In broad terms, the authorization process flows like this:
53
+ Authority encapsulates all authorization logic in `Authorizer` classes. Want to do something with a model? Ask its authorizer.
42
54
 
43
- - 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)`.
55
+ The process generally flows like this:
56
+
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)`.
44
58
  - The user just asks the model the same question: `resource.creatable_by?(self)`.
45
59
  - The model passes that question to its Authorizer, which actually contains the logic to answer the question.
46
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.
47
62
 
63
+ <a name="installation">
48
64
  ## Installation
49
65
 
50
- First, check in whatever changes you've made to your app already. You want to see what we're doing to your app, don't you?
51
-
52
- Now, add this line to your application's Gemfile:
53
-
54
- gem 'authority'
66
+ Starting from a clean commit status, add `authority` to your Gemfile, `bundle`, then `rails g authority:install`.
55
67
 
56
- And then execute:
68
+ <a name="defining_your_abilities">
69
+ ## Defining Your Abilities
57
70
 
58
- $ bundle
71
+ 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:
59
72
 
60
- Or install it yourself as:
61
-
62
- $ gem install authority
63
-
64
- Then run the generator:
65
-
66
- $ rails g authority:install
73
+ config.abilities = {
74
+ :create => 'creatable',
75
+ :read => 'readable',
76
+ :update => 'updatable',
77
+ :delete => 'deletable'
78
+ }
67
79
 
68
- Hooray! New files! Go look at them. Look look look.
80
+ 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.
69
81
 
70
- ## Usage
82
+ <a name="wiring_it_together">
83
+ ## Wiring It Together
71
84
 
72
85
  <a name="users">
73
86
  ### Users
74
-
75
- Your user model (whatever you call it) should `include Authority::UserAbilities`. This defines methods like `can_update?(resource)`. These methods do nothing but pass the question on to the resource itself. For example, `resource.updatable_by?(user)`.
76
-
77
- The list of methods that get defined comes from `config.abilities`.
87
+ In whatever class represents a logged-in user in your application, `include Authority::UserAbilities`.
78
88
 
79
89
  <a name="models">
80
90
  ### Models
81
-
82
- In your models, `include Authority::Abilities`. This sets up both class-level and instance-level methods like `creatable_by?(user)`, etc.
83
-
84
- The list of methods that get defined comes from `config.abilities`.
85
-
86
- You **could** define those methods yourself on the model, but to keep things organized, we want to put all our authorization logic in authorizer classes. Therefore, these methods, too, are pass-through, which delegate to corresponding methods on the model's authorizer. For example, the `Rabbit` model's `editable_by?(user)` would delegate to `RabbitAuthorizer.editable_by?(user)`.
87
-
88
- Which leads us to...
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.
89
93
 
90
94
  <a name="authorizers">
91
95
  ### Authorizers
92
96
 
93
- Authorizers should be added under `app/authorizers`, one for each of your models. So if you have a `LaserCannon` model, you should have, at minimum:
97
+ Add your authorizers under `app/authorizers`, subclassing `Authority::Authorizer` or your own authorizer class. (See `rails g authority::authorizers --help`.)
94
98
 
95
- # app/authorizers/laser_cannon_authorizer.rb
96
- class LaserCannonAuthorizer < Authority::Authorizer
97
- # Nothing defined - just use the default strategy
98
- end
99
+ These are where your actual authorization logic goes. Here's how it works:
99
100
 
100
- These are where your actual authorization logic goes. Here's how you do it:
101
-
102
- - Class methods answer questions about model classes, like "is it **ever** permissible for this user to update a widget?"
103
- - 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).
104
101
  - 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`).
105
102
  - 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
+ - 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).
106
105
 
107
- So, suppose you've got the empty `LaserCannonAuthorizer` above and haven't supplied a default strategy. Then you ask the authorizer "is `@laser_cannon_x.deletable_by?(@user_y)`?" It will ask itself:
108
-
109
- - "Do I have a `deletable_by?` method, to tell me whether this particular laser cannon can be deleted by this user? ... No."
110
- - "OK, do I have a `self.deletable_by?` method, to tell me whether **any** laser cannon can be deleted by this user? ... No."
111
- - "OK, did the user define a default strategy for deciding whether any resource can be deleted by a user? ... No."
112
- - "Well, I do have a **default** default strategy, which always returns false. So the answer is 'false' - the user can't delete this laser cannon."
113
-
114
- As you can see, **you can specify different logic for every method on every model, or [supply a single default strategy](#default_strategies) that covers them all, or anything in between**.
115
-
116
- And because the **default** default strategy returns false, we start by assuming that **everything is forbidden**. This whitelisting approach will keep you from accidentally allowing things you didn't intend.
106
+ For example:
117
107
 
118
- #### An Authorizer Tutorial
108
+ # app/authorizers/schedule_authorizer.rb
109
+ class ScheduleAuthorizer < Authority::Authorizer
119
110
 
120
- Let's work our way up from the simplest possible authorizer to see how you can customize your rules.
111
+ # Class method: can this user at least sometimes create a Schedule?
112
+ def self.creatable_by?(user)
113
+ user.manager?
114
+ end
121
115
 
122
- If your authorizer looks like this:
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
123
120
 
124
- # app/authorizers/laser_cannon_authorizer.rb
125
- class LaserCannonAuthorizer < Authority::Authorizer
126
121
  end
127
122
 
128
- ... you will find that everything is forbidden:
123
+ 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.
129
124
 
130
- current_user.can_create?(LaserCannon) # false; you haven't defined a class-level `can_create?`, so the
131
- # `default_strategy` is used. It returns false.
132
- current_user.can_create?(@laser_cannon) # false; instance-level permissions check class-level ones by default,
133
- # so this is the same as the previous example.
125
+ #### Custom Authorizers
134
126
 
135
- If you update your authorizer as follows:
136
-
137
- # app/authorizers/laser_cannon_authorizer.rb
138
- class LaserCannonAuthorizer < Authority::Authorizer
139
-
140
- # Class-level permissions
141
- #
142
- def self.creatable_by?(user)
143
- true # blanket true means that **any** user can create a laser cannon
144
- end
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.
145
128
 
146
- def self.deletable_by?(user)
147
- false # blanket false means that **no** user can delete a laser cannon
148
- end
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
149
136
 
150
- # Instance-level permissions
151
- #
152
- def updatable_by?(user)
153
- resource.color == 'blue' && user.first_name == 'Larry' && Date.today.friday?
154
137
  end
155
-
138
+ end
139
+
140
+ #app/authorizers/badger_authorizer.rb
141
+ class BadgerAuthorizer < MyApp::Authorizer
142
+ # contents
156
143
  end
157
144
 
158
- ... you can now do the following:
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`.
159
146
 
160
- current_user.can_create?(LaserCannon) # true, per class method above
161
- current_user.can_create?(@laser_cannon) # true; inherited instance method calls class method
162
- current_user.can_delete?(@laser_cannon) # false
163
- current_user.can_update?(@laser_cannon) # Only Larry, only blue laser cannons, and only on
164
- # Fridays (weapons maintenance day)
165
147
  <a name="default_strategies">
166
148
  #### Default Strategies
167
149
 
168
- To take a different approach, if you wanted to answer all these questions in a uniform way - perhaps by looking up permissions in a database table - you could just supply a default strategy that does that.
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.
151
+
152
+ You can configure a different default strategy. For example, you might want one that looks up permissions in your database:
169
153
 
170
154
  # In config/initializers/authority.rb
171
155
  config.default_strategy = Proc.new { |able, authorizer, user|
172
- # Does the user have any of the roles which give this permission?
173
- (roles_which_grant(able, authorizer) & user.roles).any?
156
+ # Does the user have any of the roles which give this permission?
157
+ (roles_which_grant(able, authorizer) & user.roles).any?
174
158
  }
175
159
 
176
- That's it! All your authorizer classes could be left empty.
177
-
178
- <a name="controllers">
179
- ### Controllers
180
-
181
- #### Basic Usage
182
-
183
- In your controllers, add this method call:
160
+ <a name="testing_authorizers">
161
+ #### Testing Authorizers
184
162
 
185
- authorize_actions_for ModelName
163
+ One nice thing about putting your authorization logic in authorizers is the ease of testing. Here's a brief example.
186
164
 
187
- That sets up a `before_filter` that **calls your class-level methods before each action**. For instance, before running the `update` action, it will check whether the current user (determined using the configurable `user_method`) `can_update?(ModelName)` at a class level. A return value of false means "this user can never update models of this class."
165
+ # An authorizer shared by several admin-only models
166
+ describe AdminAuthorizer do
188
167
 
189
- By the way, any options you pass in will be used on the `before_filter` that gets created, so you can do things like this:
190
-
191
- authorize_actions_for InvisibleSwordsman, :only => :show
192
-
193
- #### Usage within a controller action
168
+ before :each do
169
+ @user = Factory.build(:user)
170
+ @admin = Factory.build(:admin)
171
+ end
194
172
 
195
- If you need to check some attributes of a model instance to decide if an action is permissible, you can use the **singular** `authorize_action_for(@resource_instance)`. This method will determine which controller action it was called from, look at the controller action map, determine which method should be checked on the model, and check it.
173
+ describe "class" do
174
+ it "should let admins update in bulk" do
175
+ AdminAuthorizer.should be_bulk_updatable_by(@admin)
176
+ end
196
177
 
197
- The default controller action map is as follows:
178
+ it "should not let users update in bulk" do
179
+ AdminAuthorizer.should_not be_bulk_updatable_by(@user)
180
+ end
181
+ end
198
182
 
199
- {
200
- :index => 'read', # index action requires that the user can_read?(resource)
201
- :show => 'read', # etc
202
- :new => 'create',
203
- :create => 'create',
204
- :edit => 'update',
205
- :update => 'update',
206
- :destroy => 'delete'
207
- }
183
+ describe "instances" do
208
184
 
209
- So, for example, if you did this:
185
+ before :each do
186
+ # A mock model that uses AdminAuthorizer
187
+ @admin_resource_instance = mock_admin_resource
188
+ end
210
189
 
211
- class MessageController < ApplicationController
212
- ...
190
+ it "should not allow users to delete" do
191
+ @admin_resource_instance.authorizer.should_not be_deletable_by(@user)
192
+ end
213
193
 
214
- def edit
215
- @message = Message.find(params[:id])
216
- authorize_action_for(@message)
217
194
  end
218
- ...
219
195
 
220
196
  end
221
197
 
222
- ... Authority would determine that it was called from within `edit`, that the `edit` controller action requires permission to `update`, and check whether the current user `can_update?(@message)`.
198
+ <a name="controllers">
199
+ ### Controllers
223
200
 
224
- Each controller gets its own copy of the controller action map. If you want to edit a **single** controller's action map, you can either pass a hash into `authorize_actions_for`, which will get merged into the existing actions hash...
201
+ 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:
225
202
 
226
- class BadgerController < ApplicationController
227
- authorize_actions_for Badger, :actions => {:neuter => 'update'}
228
- ...
229
- end
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.
230
205
 
231
- ...or you can use a separate method call:
206
+ 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.
232
207
 
233
- class BadgerController < ApplicationController
234
- authorize_actions_for Badger
208
+ class LlamaController < ApplicationController
235
209
 
236
- authority_action :neuter => 'update'
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'
237
216
 
238
217
  ...
239
- end
240
218
 
241
- Finally, if you want to update this hash for **all** your controllers, you can do that with `config.controller_action_map` in the initializer.
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
242
226
 
243
- ## Views
227
+ end
228
+
229
+ <a name="views">
230
+ ### Views
244
231
 
245
232
  Assuming your user object is available in your views, you can do all kinds of conditional rendering. For example:
246
233
 
247
- `link_to 'Edit Widget', edit_widget_path(@widget) if current_user.can_update?(@widget)`
234
+ link_to 'Edit Widget', edit_widget_path(@widget) if current_user.can_update?(@widget)
248
235
 
249
- 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** (cue cheesy, dramatic music).
236
+ 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.
250
237
 
251
- ## Security Violations
238
+ <a name="security_violations_and_logging">
239
+ ## Security Violations & Logging
252
240
 
253
241
  Anytime a user attempts an unauthorized action, Authority does two things:
254
242
 
255
243
  - Renders your `public/403.html`
256
244
  - Logs the violation to whatever logger you configured.
257
245
 
258
- If you want to set 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.
259
-
260
- ## Configuration
261
-
262
- Configuration should be done from `config/initializers/authority.rb`, which will be generated for you by `rails g authority:install`. That file includes copious documentation. Copious, do you hear me?!
263
-
264
- Ahem. Note that the configuration block in that file **must** run in your application. Authority metaprograms its methods on boot, but waits until your configuration block has run to do so. If you want the default settings, you don't have to put anything in your configure block, but you must at least run `Authority.configure`.
265
-
266
- Some of the things you can configure which haven't already been mentioned are...
267
-
268
- ### Abilities
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
269
247
 
270
- If you want to be able to say `user.can_eat?` and have Authority ask the model and authorizer if the resource is `edible_by?` the user, edit your `config.abilities` to include `{:eat => 'edible'}`.
248
+ "Kenneth Lay is not allowed to delete this resource: 'accounting_tricks.doc'"
271
249
 
272
- ### Logging
273
-
274
- Authority will log a message any time a user tries to access a resource for which they are not authorized. By default, this is logged to standard error, but you can supply whatever logger you want, as long as it responds to `warn`. Some possible settings are:
275
-
276
- config.logger = Rails.logger
277
- config.logger = Logger.new('logs/authority.log') # From Ruby standard library
278
-
279
- ## Custom authorizer inheritence
280
-
281
- 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.
282
-
283
- # lib/my_app/authorizer.rb
284
- module MyApp
285
- class Authorizer < Authority::Authorizer
286
-
287
- def self.has_permission(user, permission_name)
288
- # look that up somewhere
289
- end
290
-
291
- end
292
- end
293
-
294
- #app/authorizers/badger_authorizer.rb
295
- class BadgerAuthorizer < MyApp::Authorizer
296
- # contents
297
- end
298
-
299
- 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`.
300
-
301
- ## Integration Notes
302
-
303
- - If you want to have nice log messages for security violations, you should ensure that your user object has a `to_s` method; this will control how it shows up in log messages saying things like "**Kenneth Lay** is not allowed to delete this resource:..."
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.
304
251
 
252
+ <a name="credits">
305
253
  ## Credits, AKA 'Shout-Outs'
306
254
 
307
255
  - [adamhunter](https://github.com/adamhunter) for pairing with me on this gem. The only thing faster than his typing is his brain.
@@ -309,12 +257,10 @@ If you decide to place your custom class in `lib` as shown above (as opposed to
309
257
  - [jnunemaker](https://github.com/jnunemaker) for later creating [Canable](http://github.com/jnunemaker/canable), another inspiration for Authority.
310
258
  - [TMA](http://www.tma1.com) for employing me and letting me open source some of our code.
311
259
 
260
+ <a name="contributing">
312
261
  ## Contributing
313
262
 
314
- What should you contribute? Some ideas:
315
-
316
- - Documentation improvements will always be welcome (though of course, whether something is an improvement will be up to me to decide).
317
- - Look in the separate TODO file or grep the project for 'TODO' for other ideas.
263
+ What should you contribute? Try the TODO file for ideas, or grep the project for 'TODO' comments.
318
264
 
319
265
  How can you contribute?
320
266
 
data/TODO.markdown CHANGED
@@ -6,12 +6,9 @@
6
6
 
7
7
  ## Chores
8
8
 
9
- - Add separate generator to make an empty authorizer for each file in `app/models` (prompt for each one)
10
9
  - Test generators
10
+ - Configurable proc for logging method
11
11
 
12
12
  ## Documentation
13
13
 
14
- - Make README more concise, or at least more navigable.
15
- - How to bypass creating an authorizer for each model - by setting authorizer name directly and having them share.
16
- - For instance-level checks, ensuring that you don't call `update` first; use `attributes=` before calling `authorize_action_on`.
17
14
  - 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?
data/lib/authority.rb CHANGED
@@ -26,13 +26,12 @@ module Authority
26
26
  # @param [Symbol] action
27
27
  # @param [Model] resource instance
28
28
  # @param [User] user instance
29
- # @raise [SecurityTransgression] if user is not allowed to perform action on resource
29
+ # @raise [SecurityViolation] if user is not allowed to perform action on resource
30
30
  # @return [Model] resource instance
31
31
  def self.enforce(action, resource, user)
32
32
  action_authorized = user.send("can_#{action}?", resource)
33
33
  unless action_authorized
34
- message = "#{user} is not authorized to #{action} this resource: #{resource.inspect}"
35
- raise SecurityTransgression.new(message)
34
+ raise SecurityViolation.new(user, action, resource)
36
35
  end
37
36
  resource
38
37
  end
@@ -57,7 +56,17 @@ module Authority
57
56
  require 'authority/user_abilities'
58
57
  end
59
58
 
60
- class SecurityTransgression < StandardError ; end
59
+ class SecurityViolation < StandardError
60
+ def initialize(user, action, resource)
61
+ @user = user
62
+ @action = action
63
+ @resource = resource
64
+ end
65
+
66
+ def message
67
+ "#{@user} is not authorized to #{@action} this resource: #{@resource}"
68
+ end
69
+ end
61
70
 
62
71
  end
63
72
 
@@ -11,6 +11,7 @@ module Authority
11
11
  extend ActiveSupport::Concern
12
12
 
13
13
  # Let the Foo model know that its authorizer is called 'FooAuthorizer'
14
+ # (but let the user change that)
14
15
  included do
15
16
  class_attribute :authorizer_name
16
17
  self.authorizer_name = "#{name}Authorizer"
@@ -6,7 +6,7 @@ module Authority
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  included do
9
- rescue_from Authority::SecurityTransgression, :with => :authority_forbidden
9
+ rescue_from Authority::SecurityViolation, :with => :authority_forbidden
10
10
  class_attribute :authority_resource
11
11
  end
12
12
 
@@ -1,3 +1,3 @@
1
1
  module Authority
2
- VERSION = "1.0.0.pre3"
2
+ VERSION = "1.0.0.pre4"
3
3
  end
@@ -0,0 +1,47 @@
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
@@ -42,6 +42,9 @@ Authority.configure do |config|
42
42
  # For a given controller method, what verb must a user be able to do?
43
43
  # For example, a user can access 'show' if they 'can_read' the resource.
44
44
  #
45
+ # These can be modified on a per-controller basis; see README. This option
46
+ # applies to all controllers.
47
+ #
45
48
  # Defaults are as follows:
46
49
  #
47
50
  # config.controller_action_map = {
@@ -77,9 +80,9 @@ Authority.configure do |config|
77
80
  # config.logger = Logger.new(STDERR)
78
81
  #
79
82
  # Some possible settings:
80
- # config.logger = Rails.logger
81
- # config.logger = Logger.new('logs/authority.log')
82
- # config.logger = Logger.new('/dev/null')
83
+ # config.logger = Rails.logger # Log with all your app's other messages
84
+ # config.logger = Logger.new('logs/authority.log') # Use this file
85
+ # config.logger = Logger.new('/dev/null') # Don't log at all (on a Unix system)
83
86
 
84
87
  end
85
88
 
@@ -8,7 +8,7 @@ describe Authority::Controller do
8
8
 
9
9
  describe "when including" do
10
10
  it "should specify rescuing security transgressions" do
11
- SampleController.should_receive(:rescue_from).with(Authority::SecurityTransgression, :with => :authority_forbidden)
11
+ SampleController.should_receive(:rescue_from).with(Authority::SecurityViolation, :with => :authority_forbidden)
12
12
  SampleController.send(:include, Authority::Controller)
13
13
  end
14
14
  end
@@ -68,8 +68,8 @@ describe Authority::Controller do
68
68
  @controller.send(:run_authorization_check)
69
69
  end
70
70
 
71
- it "should raise a SecurityTransgression if authorization fails" do
72
- expect { @controller.send(:run_authorization_check) }.to raise_error(Authority::SecurityTransgression)
71
+ it "should raise a SecurityViolation if authorization fails" do
72
+ expect { @controller.send(:run_authorization_check) }.to raise_error(Authority::SecurityViolation)
73
73
  end
74
74
 
75
75
  it "should raise a MissingAction if there is no corresponding action for the controller" do
@@ -44,12 +44,12 @@ describe Authority do
44
44
  @user = User.new
45
45
  end
46
46
 
47
- it "should raise a SecurityTransgression if the action is unauthorized" do
48
- expect { Authority.enforce(:update, AbilityModel, @user) }.to raise_error(Authority::SecurityTransgression)
47
+ it "should raise a SecurityViolation if the action is unauthorized" do
48
+ expect { Authority.enforce(:update, AbilityModel, @user) }.to raise_error(Authority::SecurityViolation)
49
49
  end
50
50
 
51
- it "should not raise a SecurityTransgression if the action is authorized" do
52
- expect { Authority.enforce(:read, AbilityModel, @user) }.not_to raise_error(Authority::SecurityTransgression)
51
+ it "should not raise a SecurityViolation if the action is authorized" do
52
+ expect { Authority.enforce(:read, AbilityModel, @user) }.not_to raise_error(Authority::SecurityViolation)
53
53
  end
54
54
 
55
55
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: authority
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre3
4
+ version: 1.0.0.pre4
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-03-20 00:00:00.000000000 Z
13
+ date: 2012-03-26 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails
17
- requirement: &2164558540 !ruby/object:Gem::Requirement
17
+ requirement: &2152325920 !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: *2164558540
25
+ version_requirements: *2152325920
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: bundler
28
- requirement: &2164557960 !ruby/object:Gem::Requirement
28
+ requirement: &2152325380 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ! '>='
@@ -33,7 +33,7 @@ dependencies:
33
33
  version: 1.0.0
34
34
  type: :development
35
35
  prerelease: false
36
- version_requirements: *2164557960
36
+ version_requirements: *2152325380
37
37
  description: Gem for managing authorization on model actions in Rails.
38
38
  email:
39
39
  - nathanmlong@gmail.com
@@ -61,6 +61,7 @@ files:
61
61
  - lib/authority/railtie.rb
62
62
  - lib/authority/user_abilities.rb
63
63
  - lib/authority/version.rb
64
+ - lib/generators/authority/authorizers_generator.rb
64
65
  - lib/generators/authority/install_generator.rb
65
66
  - lib/generators/templates/403.html
66
67
  - lib/generators/templates/authority_initializer.rb