authority 2.3.2 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.markdown +14 -0
- data/README.markdown +35 -27
- data/TODO.markdown +2 -0
- data/lib/authority.rb +5 -8
- data/lib/authority/authorizer.rb +2 -5
- data/lib/authority/controller.rb +23 -7
- data/lib/authority/user_abilities.rb +3 -6
- data/lib/authority/version.rb +1 -1
- data/lib/generators/templates/403.html +2 -2
- data/lib/generators/templates/authority_initializer.rb +2 -9
- data/spec/authority/controller_spec.rb +48 -4
- data/spec/authority_spec.rb +2 -2
- metadata +16 -5
data/CHANGELOG.markdown
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
Authority does its best to use [semantic versioning](http://semver.org).
|
4
|
+
|
5
|
+
## v2.4.0
|
6
|
+
|
7
|
+
Controller method `authorize_actions_for` can now be given a method name to dynamically determine the class to authorize. For example, `authorize_actions_for :model_class` will call the `model_class` method on the controller instance at request time.
|
8
|
+
|
9
|
+
## v2.3.2
|
10
|
+
|
11
|
+
- Updated `can?` to only pass options if it was given options.
|
12
|
+
|
13
|
+
## v2.3.1
|
14
|
+
|
15
|
+
- Had second thought and reworked `can?(:action)` to call `Application_authorizer.authorizes_to_#{action}?`. Ensured it's backwards compatible for the few people who started using this in the last day or so.
|
16
|
+
|
3
17
|
## v2.3.0
|
4
18
|
|
5
19
|
- Added generic `current_user.can?(:mimic_lemurs)` for cases where there is no resource to work with. This calls a corresponding class method on `ApplicationAuthorizer`, like `ApplicationAuthorizer.can_mimic_lemurs?`.
|
data/README.markdown
CHANGED
@@ -6,7 +6,7 @@ Authority will work fine with a standalone app or a single sign-on system. You c
|
|
6
6
|
|
7
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).
|
8
8
|
|
9
|
-
[![Build Status](https://secure.travis-ci.org/nathanl/authority.png?branch=master)](http://travis-ci.org/nathanl/authority)
|
9
|
+
<!--[![Build Status](https://secure.travis-ci.org/nathanl/authority.png?branch=master)](http://travis-ci.org/nathanl/authority) -->
|
10
10
|
<!-- [![Dependency Status](https://gemnasium.com/nathanl/authority.png)](https://gemnasium.com/nathanl/authority) -->
|
11
11
|
[![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/nathanl/authority)
|
12
12
|
|
@@ -353,6 +353,35 @@ end
|
|
353
353
|
|
354
354
|
As with other authorization checks, you can also pass options here, and they'll be sent along to your authorization method: `authorize_action_for(@llama, :sporting => @hat_style)`. Generally, though, your authorization will depend on some attribute or association of the model instance, so the authorizer can check `@llama.neck_strength` and `@llama.owner.nationality`, etc, without needing any additional information.
|
355
355
|
|
356
|
+
Note that you can also call `authority_actions` as many times as you like, so you can specify one mapping at a time if you prefer:
|
357
|
+
|
358
|
+
```ruby
|
359
|
+
class LlamasController < ApplicationController
|
360
|
+
def breed
|
361
|
+
# some code
|
362
|
+
end
|
363
|
+
authority_actions :breed => 'create'
|
364
|
+
|
365
|
+
def vaporize
|
366
|
+
# some code
|
367
|
+
end
|
368
|
+
authority_actions :vaporize => 'delete'
|
369
|
+
end
|
370
|
+
```
|
371
|
+
|
372
|
+
Finally, note that if you have a controller that dynamically determines the class it's working with, you can pass the name of a controller instance method to `authorize_actions_for` instead of a class, and the class will be looked up when a request is made.
|
373
|
+
|
374
|
+
```ruby
|
375
|
+
class LlamasController < ApplicationController
|
376
|
+
|
377
|
+
authorize_actions_for :llama_class
|
378
|
+
|
379
|
+
def llama_class
|
380
|
+
[StandardLlama, LludicriousLlama].sample
|
381
|
+
end
|
382
|
+
end
|
383
|
+
```
|
384
|
+
|
356
385
|
<a name="views">
|
357
386
|
### Views
|
358
387
|
|
@@ -387,44 +416,23 @@ Use this very sparingly, and consider it a [code smell](http://en.wikipedia.org/
|
|
387
416
|
<a name="security_violations_and_logging">
|
388
417
|
## Security Violations & Logging
|
389
418
|
|
390
|
-
|
419
|
+
If you're using Authority's view helpers, users should only see links for actions they're authorized to take. If a user deliberately tries to access a restricted resource (for instance, by typing the URL directly), Authority raises and rescues an `Authority::SecurityViolation`.
|
420
|
+
|
421
|
+
When it rescues the exception, 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 mixes in to your `ApplicationController`. It does the following:
|
391
422
|
|
392
423
|
- Renders `public/403.html`
|
393
424
|
- Logs the violation to whatever logger you configured.
|
394
425
|
|
395
|
-
You can define your own `authority_forbidden` method:
|
396
|
-
|
426
|
+
You can define your own `authority_forbidden` method on `ApplicationController` and/or any other controller. For example:
|
397
427
|
|
398
428
|
```ruby
|
399
429
|
# Send 'em back where they came from with a slap on the wrist
|
400
|
-
def authority_forbidden(
|
430
|
+
def authority_forbidden(error)
|
401
431
|
Authority.logger.warn(error.message)
|
402
432
|
redirect_to request.referrer.presence || root_path, :alert => 'You are not authorized to complete that action.'
|
403
433
|
end
|
404
434
|
```
|
405
435
|
|
406
|
-
... or specify a different handler like this:
|
407
|
-
|
408
|
-
```ruby
|
409
|
-
# config/initializers/authority.rb
|
410
|
-
config.security_violation_handler = :fire_ze_missiles
|
411
|
-
```
|
412
|
-
Then define the method on your controller:
|
413
|
-
|
414
|
-
```ruby
|
415
|
-
# app/controllers/application_controller.rb
|
416
|
-
class ApplicationController < ActionController::Base
|
417
|
-
|
418
|
-
def fire_ze_missiles(exception)
|
419
|
-
# Log? Set a flash message? Dispatch minions to
|
420
|
-
# fill their mailbox with goose droppings? It's up to you.
|
421
|
-
end
|
422
|
-
...
|
423
|
-
end
|
424
|
-
```
|
425
|
-
|
426
|
-
If you want different error handling per controller, define `fire_ze_missiles` on each of them.
|
427
|
-
|
428
436
|
Your method will be handed the `SecurityViolation`, which has a `message` method. In case you want to build your own message, it also exposes `user`, `action` and `resource`.
|
429
437
|
|
430
438
|
<a name="credits">
|
data/TODO.markdown
CHANGED
data/lib/authority.rb
CHANGED
@@ -31,19 +31,16 @@ module Authority
|
|
31
31
|
# @param [Hash] options, arbitrary options hash to delegate to the authorizer
|
32
32
|
# @raise [SecurityViolation] if user is not allowed to perform action on resource
|
33
33
|
# @return [Model] resource instance
|
34
|
-
def self.enforce(action, resource, user,
|
35
|
-
unless action_authorized?(action, resource, user,
|
34
|
+
def self.enforce(action, resource, user, options = {})
|
35
|
+
unless action_authorized?(action, resource, user, options)
|
36
36
|
raise SecurityViolation.new(user, action, resource)
|
37
37
|
end
|
38
38
|
resource
|
39
39
|
end
|
40
40
|
|
41
|
-
def self.action_authorized?(action, resource, user,
|
42
|
-
|
43
|
-
|
44
|
-
else
|
45
|
-
user.send("can_#{action}?", resource, Hash[*options])
|
46
|
-
end
|
41
|
+
def self.action_authorized?(action, resource, user, options = {})
|
42
|
+
resource_and_maybe_options = [resource, options].tap {|args| args.pop if args.last == {}}
|
43
|
+
user.send("can_#{action}?", *resource_and_maybe_options)
|
47
44
|
end
|
48
45
|
|
49
46
|
class << self
|
data/lib/authority/authorizer.rb
CHANGED
@@ -29,11 +29,8 @@ module Authority
|
|
29
29
|
Authority.adjectives.each do |adjective|
|
30
30
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
31
31
|
def self.#{adjective}_by?(user, options = {})
|
32
|
-
|
33
|
-
|
34
|
-
else
|
35
|
-
default(:#{adjective}, user, options)
|
36
|
-
end
|
32
|
+
user_and_maybe_options = [user, options].tap {|args| args.pop if args.last == {}}
|
33
|
+
default(:#{adjective}, *user_and_maybe_options)
|
37
34
|
end
|
38
35
|
RUBY
|
39
36
|
end
|
data/lib/authority/controller.rb
CHANGED
@@ -14,18 +14,23 @@ module Authority
|
|
14
14
|
|
15
15
|
included do
|
16
16
|
rescue_from(Authority::SecurityViolation, :with => Authority::Controller.security_violation_callback)
|
17
|
-
|
17
|
+
class << self
|
18
|
+
attr_accessor :authority_resource
|
19
|
+
end
|
18
20
|
end
|
19
21
|
|
20
22
|
module ClassMethods
|
21
23
|
|
22
24
|
# Sets up before_filter to ensure user is allowed to perform a given controller action
|
23
25
|
#
|
24
|
-
# @param [Class]
|
25
|
-
#
|
26
|
+
# @param [Class OR Symbol] resource_or_finder - class whose authorizer
|
27
|
+
# should be consulted, or instance method on the controller which will
|
28
|
+
# determine that class when the request is made
|
29
|
+
# @param [Hash] options - can contain :actions to
|
30
|
+
# be merged with existing
|
26
31
|
# ones and any other options applicable to a before_filter
|
27
|
-
def authorize_actions_for(
|
28
|
-
self.authority_resource =
|
32
|
+
def authorize_actions_for(resource_or_finder, options = {})
|
33
|
+
self.authority_resource = resource_or_finder
|
29
34
|
authority_actions(options[:actions] || {})
|
30
35
|
before_filter :run_authorization_check, options
|
31
36
|
end
|
@@ -86,7 +91,17 @@ module Authority
|
|
86
91
|
# The `before_filter` that will be setup to run when the class method
|
87
92
|
# `authorize_actions_for` is called
|
88
93
|
def run_authorization_check
|
89
|
-
authorize_action_for
|
94
|
+
authorize_action_for authority_resource
|
95
|
+
end
|
96
|
+
|
97
|
+
def authority_resource
|
98
|
+
return self.class.authority_resource if self.class.authority_resource.is_a?(Class)
|
99
|
+
return send(self.class.authority_resource) if respond_to?(self.class.authority_resource)
|
100
|
+
raise MissingResource.new(
|
101
|
+
"Trying to authorize actions for '#{self.class.authority_resource}', but can't. \
|
102
|
+
Must be either a resource class OR the name of a controller instance method that \
|
103
|
+
returns one.".squeeze(' ')
|
104
|
+
)
|
90
105
|
end
|
91
106
|
|
92
107
|
# Convenience wrapper for sending configured `user_method` to extract the
|
@@ -97,6 +112,7 @@ module Authority
|
|
97
112
|
send(Authority.configuration.user_method)
|
98
113
|
end
|
99
114
|
|
100
|
-
class MissingAction
|
115
|
+
class MissingAction < StandardError ; end
|
116
|
+
class MissingResource < StandardError ; end
|
101
117
|
end
|
102
118
|
end
|
@@ -11,17 +11,14 @@ module Authority
|
|
11
11
|
Authority.verbs.each do |verb|
|
12
12
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
13
13
|
def can_#{verb}?(resource, options = {})
|
14
|
-
|
15
|
-
|
16
|
-
else
|
17
|
-
resource.#{Authority.abilities[verb]}_by?(self, options)
|
18
|
-
end
|
14
|
+
self_and_maybe_options = [self, options].tap {|args| args.pop if args.last == {}}
|
15
|
+
resource.#{Authority.abilities[verb]}_by?(*self_and_maybe_options)
|
19
16
|
end
|
20
17
|
RUBY
|
21
18
|
end
|
22
19
|
|
23
20
|
def can?(action, options = {})
|
24
|
-
self_and_maybe_options = [self,
|
21
|
+
self_and_maybe_options = [self, options].tap {|args| args.pop if args.last == {}}
|
25
22
|
begin
|
26
23
|
ApplicationAuthorizer.send("authorizes_to_#{action}?", *self_and_maybe_options)
|
27
24
|
rescue NoMethodError => original_exception
|
data/lib/authority/version.rb
CHANGED
@@ -3,7 +3,8 @@ Authority.configure do |config|
|
|
3
3
|
# USER_METHOD
|
4
4
|
# ===========
|
5
5
|
# Authority needs the name of a method, available in any controller, which
|
6
|
-
# will return the currently logged-in user.
|
6
|
+
# will return the currently logged-in user. (If this varies by controller,
|
7
|
+
# just create a common alias.)
|
7
8
|
#
|
8
9
|
# Default is:
|
9
10
|
#
|
@@ -44,14 +45,6 @@ Authority.configure do |config|
|
|
44
45
|
# :delete => 'deletable'
|
45
46
|
# }
|
46
47
|
|
47
|
-
# SECURITY_VIOLATION_HANDLER
|
48
|
-
# ==========================
|
49
|
-
# If a SecurityViolation is raised, what controller method should be used to rescue it?
|
50
|
-
#
|
51
|
-
# Default is:
|
52
|
-
#
|
53
|
-
# config.security_violation_handler = :authority_forbidden # Defined in controller.rb
|
54
|
-
|
55
48
|
# LOGGER
|
56
49
|
# ======
|
57
50
|
# If a user tries to perform an unauthorized action, where should we log that fact?
|
@@ -87,11 +87,16 @@ describe Authority::Controller do
|
|
87
87
|
|
88
88
|
describe "authorize_actions_for" do
|
89
89
|
|
90
|
-
it "allows specifying the model to protect" do
|
90
|
+
it "allows specifying the class of the model to protect" do
|
91
91
|
controller_class.authorize_actions_for(resource_class)
|
92
92
|
expect(controller_class.authority_resource).to eq(resource_class)
|
93
93
|
end
|
94
94
|
|
95
|
+
it "allows specifying an instance method to find the class of the model to protect" do
|
96
|
+
controller_class.authorize_actions_for(:finder_method)
|
97
|
+
expect(controller_class.authority_resource).to eq(:finder_method)
|
98
|
+
end
|
99
|
+
|
95
100
|
it "sets up a before_filter, passing the options it was given" do
|
96
101
|
filter_options = {:only => [:show, :edit, :update]}
|
97
102
|
controller_class.should_receive(:before_filter).with(:run_authorization_check, filter_options)
|
@@ -146,9 +151,48 @@ describe Authority::Controller do
|
|
146
151
|
|
147
152
|
describe "run_authorization_check (used as a before_filter)" do
|
148
153
|
|
149
|
-
|
150
|
-
|
151
|
-
|
154
|
+
context "if a resource class was specified" do
|
155
|
+
|
156
|
+
it "checks authorization on the model specified" do
|
157
|
+
controller_instance.should_receive(:authorize_action_for).with(resource_class)
|
158
|
+
controller_instance.send(:run_authorization_check)
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
context "if a method for determining the class was specified" do
|
164
|
+
|
165
|
+
let(:resource_class) { Hash }
|
166
|
+
let(:controller_class) do
|
167
|
+
Class.new(ExampleController).tap do |c|
|
168
|
+
c.send(:include, Authority::Controller)
|
169
|
+
c.authorize_actions_for(:method_to_find_class)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context "if the controller has such an instance method" do
|
174
|
+
|
175
|
+
before :each do
|
176
|
+
controller_instance.stub(:method_to_find_class).and_return(resource_class)
|
177
|
+
end
|
178
|
+
|
179
|
+
it "checks authorization on class returned by that method" do
|
180
|
+
controller_instance.should_receive(:authorize_action_for).with(resource_class)
|
181
|
+
controller_instance.send(:run_authorization_check)
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
context "if the controller has no such instance method" do
|
187
|
+
|
188
|
+
it "raises an exception" do
|
189
|
+
expect{controller_instance.send(:run_authorization_check)}.to raise_error(
|
190
|
+
Authority::Controller::MissingResource
|
191
|
+
)
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
|
152
196
|
end
|
153
197
|
|
154
198
|
it "raises a MissingAction if there is no corresponding action for the controller" do
|
data/spec/authority_spec.rb
CHANGED
@@ -42,7 +42,7 @@ describe Authority do
|
|
42
42
|
let(:user) { ExampleUser.new }
|
43
43
|
let(:resource_class) { ExampleResource }
|
44
44
|
|
45
|
-
describe "
|
45
|
+
describe "when given options" do
|
46
46
|
|
47
47
|
it "checks the user's authorization, passing along the options" do
|
48
48
|
options = { :for => 'context' }
|
@@ -52,7 +52,7 @@ describe Authority do
|
|
52
52
|
|
53
53
|
end
|
54
54
|
|
55
|
-
describe "
|
55
|
+
describe "when not given options" do
|
56
56
|
|
57
57
|
it "checks the user's authorization, passing no options" do
|
58
58
|
user.should_receive(:can_delete?).with(resource_class).and_return(true)
|
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: 2.
|
4
|
+
version: 2.4.0
|
5
5
|
prerelease:
|
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:
|
13
|
+
date: 2013-02-13 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rails
|
17
|
-
requirement:
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,7 +22,12 @@ dependencies:
|
|
22
22
|
version: 3.0.0
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements:
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: 3.0.0
|
26
31
|
description: Authority helps you authorize actions in your Rails app. It's ORM-neutral
|
27
32
|
and has very little fancy syntax; just group your models under one or more Authorizer
|
28
33
|
classes and write plain Ruby methods on them.
|
@@ -82,15 +87,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
82
87
|
- - ! '>='
|
83
88
|
- !ruby/object:Gem::Version
|
84
89
|
version: '0'
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
hash: -37039868613240694
|
85
93
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
94
|
none: false
|
87
95
|
requirements:
|
88
96
|
- - ! '>='
|
89
97
|
- !ruby/object:Gem::Version
|
90
98
|
version: '0'
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
hash: -37039868613240694
|
91
102
|
requirements: []
|
92
103
|
rubyforge_project:
|
93
|
-
rubygems_version: 1.8.
|
104
|
+
rubygems_version: 1.8.24
|
94
105
|
signing_key:
|
95
106
|
specification_version: 3
|
96
107
|
summary: Authority helps you authorize actions in your Rails app using plain Ruby
|