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 +9 -0
- data/README.markdown +200 -120
- data/TODO.markdown +5 -6
- data/authority.gemspec +2 -2
- data/lib/authority/configuration.rb +6 -3
- data/lib/authority/controller.rb +10 -1
- data/lib/authority/version.rb +1 -1
- data/lib/generators/authority/install_generator.rb +24 -1
- data/lib/generators/templates/authority_initializer.rb +9 -1
- data/spec/authority/controller_spec.rb +29 -2
- metadata +16 -27
- data/lib/generators/authority/authorizers_generator.rb +0 -47
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
|
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
|
-
|
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:
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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
|
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
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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
|
-
|
166
|
-
|
198
|
+
```ruby
|
199
|
+
# An authorizer shared by several admin-only models
|
200
|
+
describe AdminAuthorizer do
|
167
201
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
202
|
+
before :each do
|
203
|
+
@user = FactoryGirl.build(:user)
|
204
|
+
@admin = FactoryGirl.build(:admin)
|
205
|
+
end
|
172
206
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
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
|
-
|
179
|
-
|
180
|
-
|
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
|
-
|
228
|
+
end
|
184
229
|
|
185
|
-
|
186
|
-
|
187
|
-
@admin_resource_instance = mock_admin_resource
|
188
|
-
end
|
230
|
+
end
|
231
|
+
```
|
189
232
|
|
190
|
-
|
191
|
-
|
192
|
-
end
|
233
|
+
<a name="custom_authorizers">
|
234
|
+
#### Custom Authorizers
|
193
235
|
|
194
|
-
|
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
|
-
|
268
|
+
```ruby
|
269
|
+
class LlamaController < ApplicationController
|
209
270
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
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
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
307
|
+
- Renders `public/403.html`
|
244
308
|
- Logs the violation to whatever logger you configured.
|
245
309
|
|
246
|
-
|
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
|
-
|
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
|
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.
|
268
|
-
2.
|
269
|
-
3.
|
270
|
-
4. `
|
271
|
-
5.
|
272
|
-
6.
|
273
|
-
7.
|
274
|
-
8.
|
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
|
-
-
|
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.
|
8
|
-
gem.
|
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
|
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
|
|
data/lib/authority/controller.rb
CHANGED
@@ -6,10 +6,18 @@ module Authority
|
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
8
|
included do
|
9
|
-
rescue_from
|
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
|
data/lib/authority/version.rb
CHANGED
@@ -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
|
-
|
11
|
-
|
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
|
5
|
-
prerelease:
|
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-
|
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: &
|
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: *
|
25
|
+
version_requirements: *82923350
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: bundler
|
28
|
-
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: *
|
37
|
-
description:
|
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:
|
98
|
+
version: '0'
|
98
99
|
requirements: []
|
99
100
|
rubyforge_project:
|
100
|
-
rubygems_version: 1.8.
|
101
|
+
rubygems_version: 1.8.10
|
101
102
|
signing_key:
|
102
103
|
specification_version: 3
|
103
|
-
summary: Authority
|
104
|
-
|
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
|