authority 2.3.2 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
-
[](http://travis-ci.org/nathanl/authority)
|
9
|
+
<!--[](http://travis-ci.org/nathanl/authority) -->
|
10
10
|
<!-- [](https://gemnasium.com/nathanl/authority) -->
|
11
11
|
[](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
|