authority 2.0.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +1 -1
- data/README.markdown +73 -17
- data/TODO.markdown +4 -2
- data/lib/authority/abilities.rb +12 -4
- data/lib/authority/authorizer.rb +13 -5
- data/lib/authority/user_abilities.rb +6 -2
- data/lib/authority/version.rb +1 -1
- data/spec/authority/abilities_spec.rb +34 -7
- data/spec/authority/authorizer_spec.rb +48 -9
- data/spec/authority/user_abilities_spec.rb +17 -3
- metadata +17 -6
data/.travis.yml
CHANGED
data/README.markdown
CHANGED
@@ -22,6 +22,7 @@ It requires that you already have some kind of user object in your application,
|
|
22
22
|
<li><a href="#models">Models</a></li>
|
23
23
|
<li><a href="#authorizers">Authorizers</a>
|
24
24
|
<ul>
|
25
|
+
<li><a href="#passing_options">Passing Options</a></li>
|
25
26
|
<li><a href="#default_methods">Default methods</a></li>
|
26
27
|
<li><a href="#testing_authorizers">Testing Authorizers</a></li>
|
27
28
|
</ul></li>
|
@@ -36,18 +37,32 @@ It requires that you already have some kind of user object in your application,
|
|
36
37
|
<a name="overview">
|
37
38
|
## Overview
|
38
39
|
|
39
|
-
|
40
|
+
Using Authority, you have:
|
40
41
|
|
41
|
-
-
|
42
|
+
- Broad, **class-level** rules. Examples:
|
42
43
|
- "Basic users cannot delete any Widget."
|
43
44
|
- "Only admin users can create Offices."
|
44
|
-
-
|
45
|
+
- Fine-grained, **instance-level** rules. Examples:
|
45
46
|
- "Management users can only edit schedules with date ranges in the future."
|
46
47
|
- "Users can't create playlists more than 20 songs long unless they've paid."
|
47
|
-
-
|
48
|
+
- A clear syntax for permissions-based views. Example:
|
48
49
|
- `link_to 'Edit Widget', edit_widget_path(@widget) if current_user.can_update?(@widget)`
|
49
|
-
-
|
50
|
-
-
|
50
|
+
- Graceful handling of access violations: by default, it displays a "you can't do that" screen and logs the violation.
|
51
|
+
- Minimal effort and mess.
|
52
|
+
|
53
|
+
Most importantly, you have **total flexibility**: Authority does not constrain you into using a particular scheme of roles and/or permissions.
|
54
|
+
|
55
|
+
Authority lets you:
|
56
|
+
|
57
|
+
- Grant permissions based on users' points (like StackOverflow)
|
58
|
+
- Check user roles in a separate, single-sign-on app
|
59
|
+
- Disallow certain actions based on time or date
|
60
|
+
- Allow an action only for users with compatible browsers
|
61
|
+
- ...do anything else you can think of.
|
62
|
+
|
63
|
+
All you have to do is define the methods you need on your authorizers. You have all the flexibility of normal Ruby classes.
|
64
|
+
|
65
|
+
**You** make the rules; Authority enforces them.
|
51
66
|
|
52
67
|
<a name="flow_of_authority">
|
53
68
|
## The flow of Authority
|
@@ -180,6 +195,29 @@ ScheduleAuthorizer.updatable_by?(user)
|
|
180
195
|
|
181
196
|
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 method](#default_methods) that covers all your use cases.
|
182
197
|
|
198
|
+
<a name="passing_options">
|
199
|
+
#### Passing Options
|
200
|
+
|
201
|
+
Any options you pass when checking permissions will be passed right up the chain. One use case for this would be if you needed an associated instance in order to do a class-level check. For example:
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
# I don't have a comment instance to check, but I need to know
|
205
|
+
# which post the user wants to comment on
|
206
|
+
user.can_create?(Comment, :for => @post)
|
207
|
+
```
|
208
|
+
|
209
|
+
This would ultimately call `creatable_by?` on the designated authorizer with two arguments: the user and `{:for => @post}`. If you've defined that method yourself, you'd need to ensure that it accepts the options hash before doing this, or you'd get a "wrong number of arguments" error.
|
210
|
+
|
211
|
+
There's nothing special about the hash key `:for`; I just think it reads well in this case. You can pass any options that make sense in your case.
|
212
|
+
|
213
|
+
If you **don't** pass options, none will be passed to your authorizer, either.
|
214
|
+
|
215
|
+
And you could always handle the case above without options if you don't mind creating an extra model instance:
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
user.can_create?(Comment.new(:post => @post))
|
219
|
+
```
|
220
|
+
|
183
221
|
<a name="default_methods">
|
184
222
|
#### Default Methods
|
185
223
|
|
@@ -361,18 +399,36 @@ Your method will be handed the `SecurityViolation`, which has a `message` method
|
|
361
399
|
- [TMA](http://www.tma1.com) for employing me and letting me open source some of our code.
|
362
400
|
|
363
401
|
<a name="contributing">
|
402
|
+
|
403
|
+
## Responses, AKA 'Hollaback'
|
404
|
+
|
405
|
+
Do you like Authority? Has it cleaned up your code, made you more personable, and taught you the Secret to True Happiness? Awesome! I'd **love** to get email from you - see my Github profile for the address.
|
406
|
+
|
364
407
|
## Contributing
|
365
408
|
|
366
|
-
|
409
|
+
How can you contribute? Let me count the ways.
|
410
|
+
|
411
|
+
### 1. Publicity
|
412
|
+
|
413
|
+
If you like Authority, tell people! Blog, tweet, comment, or even... [shudder]... talk with people *in person*. If you feel up to it, I mean. It's OK if you don't.
|
414
|
+
|
415
|
+
### 2. Documentation
|
416
|
+
|
417
|
+
Add examples to the [wiki](http://github.com/nathanl/authority/wiki) to help others solve problems like yours.
|
418
|
+
|
419
|
+
### 3. Issues
|
420
|
+
|
421
|
+
Tell me your problems and/or ideas.
|
367
422
|
|
368
|
-
|
423
|
+
### 4. Code or documentation
|
369
424
|
|
370
|
-
1.
|
371
|
-
2.
|
372
|
-
3.
|
373
|
-
4.
|
374
|
-
5. `
|
375
|
-
6.
|
376
|
-
7.
|
377
|
-
8.
|
378
|
-
9.
|
425
|
+
1. Have an idea. If you don't have one, check the TODO file or grep the project for 'TODO' comments.
|
426
|
+
2. Open an issue so we can talk it over.
|
427
|
+
3. Fork this project
|
428
|
+
4. Create your feature branch (`git checkout -b my-new-feature`)
|
429
|
+
5. `bundle install` to get all dependencies
|
430
|
+
6. `rspec spec` to run all tests.
|
431
|
+
7. Update/add tests for your changes and code until they pass.
|
432
|
+
8. Commit your changes (`git commit -am 'Added some feature'`)
|
433
|
+
9. Push to the branch (`git push origin my-new-feature`)
|
434
|
+
10. Create a new Pull Request
|
data/TODO.markdown
CHANGED
data/lib/authority/abilities.rb
CHANGED
@@ -21,8 +21,12 @@ module Authority
|
|
21
21
|
Authority.adjectives.each do |adjective|
|
22
22
|
|
23
23
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
24
|
-
def #{adjective}_by?(user)
|
25
|
-
|
24
|
+
def #{adjective}_by?(user, options = {})
|
25
|
+
if options.empty?
|
26
|
+
authorizer.#{adjective}_by?(user)
|
27
|
+
else
|
28
|
+
authorizer.#{adjective}_by?(user, options)
|
29
|
+
end
|
26
30
|
end
|
27
31
|
RUBY
|
28
32
|
end
|
@@ -38,8 +42,12 @@ module Authority
|
|
38
42
|
Authority.adjectives.each do |adjective|
|
39
43
|
|
40
44
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
41
|
-
def #{adjective}_by?(user)
|
42
|
-
|
45
|
+
def #{adjective}_by?(user, options = {})
|
46
|
+
if options.empty?
|
47
|
+
authorizer.#{adjective}_by?(user)
|
48
|
+
else
|
49
|
+
authorizer.#{adjective}_by?(user, options)
|
50
|
+
end
|
43
51
|
end
|
44
52
|
|
45
53
|
def authorizer
|
data/lib/authority/authorizer.rb
CHANGED
@@ -17,8 +17,12 @@ module Authority
|
|
17
17
|
# Each instance method simply calls the corresponding class method
|
18
18
|
Authority.adjectives.each do |adjective|
|
19
19
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
20
|
-
def #{adjective}_by?(user)
|
21
|
-
|
20
|
+
def #{adjective}_by?(user, options = {})
|
21
|
+
if options.empty?
|
22
|
+
self.class.#{adjective}_by?(user)
|
23
|
+
else
|
24
|
+
self.class.#{adjective}_by?(user, options)
|
25
|
+
end
|
22
26
|
end
|
23
27
|
RUBY
|
24
28
|
end
|
@@ -26,14 +30,18 @@ module Authority
|
|
26
30
|
# Each class method simply calls the `default` method
|
27
31
|
Authority.adjectives.each do |adjective|
|
28
32
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
29
|
-
def self.#{adjective}_by?(user)
|
30
|
-
|
33
|
+
def self.#{adjective}_by?(user, options = {})
|
34
|
+
if options.empty?
|
35
|
+
default(:#{adjective}, user)
|
36
|
+
else
|
37
|
+
default(:#{adjective}, user, options)
|
38
|
+
end
|
31
39
|
end
|
32
40
|
RUBY
|
33
41
|
end
|
34
42
|
|
35
43
|
# Whitelisting approach: anything not specified will be forbidden
|
36
|
-
def self.default(adjective, user)
|
44
|
+
def self.default(adjective, user, options = {})
|
37
45
|
false
|
38
46
|
end
|
39
47
|
|
@@ -10,8 +10,12 @@ module Authority
|
|
10
10
|
|
11
11
|
Authority.verbs.each do |verb|
|
12
12
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
13
|
-
def can_#{verb}?(resource)
|
14
|
-
|
13
|
+
def can_#{verb}?(resource, options = {})
|
14
|
+
if options.empty?
|
15
|
+
resource.#{Authority.abilities[verb]}_by?(self)
|
16
|
+
else
|
17
|
+
resource.#{Authority.abilities[verb]}_by?(self, options)
|
18
|
+
end
|
15
19
|
end
|
16
20
|
RUBY
|
17
21
|
end
|
data/lib/authority/version.rb
CHANGED
@@ -52,9 +52,22 @@ describe Authority::Abilities do
|
|
52
52
|
AbilityModel.should respond_to(method_name)
|
53
53
|
end
|
54
54
|
|
55
|
-
|
56
|
-
|
57
|
-
|
55
|
+
describe "if given an options hash" do
|
56
|
+
|
57
|
+
it "should delegate `#{method_name}` to its authorizer class, passing the options" do
|
58
|
+
AbilityModel.authorizer.should_receive(method_name).with(@user, :lacking => 'nothing')
|
59
|
+
AbilityModel.send(method_name, @user, :lacking => 'nothing')
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "if not given an options hash" do
|
65
|
+
|
66
|
+
it "should delegate `#{method_name}` to its authorizer class, passing no options" do
|
67
|
+
AbilityModel.authorizer.should_receive(method_name).with(@user)
|
68
|
+
AbilityModel.send(method_name, @user)
|
69
|
+
end
|
70
|
+
|
58
71
|
end
|
59
72
|
|
60
73
|
end
|
@@ -75,10 +88,24 @@ describe Authority::Abilities do
|
|
75
88
|
@ability_model.should respond_to(method_name)
|
76
89
|
end
|
77
90
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
91
|
+
describe "if given an options hash" do
|
92
|
+
|
93
|
+
it "should delegate `#{method_name}` to a new authorizer instance, passing the options" do
|
94
|
+
AbilityModel.authorizer.stub(:new).and_return(@authorizer)
|
95
|
+
@authorizer.should_receive(method_name).with(@user, :with => 'mayo')
|
96
|
+
@ability_model.send(method_name, @user, :with => 'mayo')
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "if not given an options hash" do
|
102
|
+
|
103
|
+
it "should delegate `#{method_name}` to a new authorizer instance, passing no options" do
|
104
|
+
AbilityModel.authorizer.stub(:new).and_return(@authorizer)
|
105
|
+
@authorizer.should_receive(method_name).with(@user)
|
106
|
+
@ability_model.send(method_name, @user)
|
107
|
+
end
|
108
|
+
|
82
109
|
end
|
83
110
|
|
84
111
|
end
|
@@ -23,9 +23,23 @@ describe Authority::Authorizer do
|
|
23
23
|
@authorizer.should respond_to(method_name)
|
24
24
|
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
|
27
|
+
describe "if given an options hash" do
|
28
|
+
|
29
|
+
it "should delegate `#{method_name}` to the corresponding class method, passing the options" do
|
30
|
+
@authorizer.class.should_receive(method_name).with(@user, :under => 'God')
|
31
|
+
@authorizer.send(method_name, @user, :under => 'God')
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "if not given an options hash" do
|
37
|
+
|
38
|
+
it "should delegate `#{method_name}` to the corresponding class method, passing no options" do
|
39
|
+
@authorizer.class.should_receive(method_name).with(@user)
|
40
|
+
@authorizer.send(method_name, @user)
|
41
|
+
end
|
42
|
+
|
29
43
|
end
|
30
44
|
|
31
45
|
end
|
@@ -41,10 +55,24 @@ describe Authority::Authorizer do
|
|
41
55
|
Authority::Authorizer.should respond_to(method_name)
|
42
56
|
end
|
43
57
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
58
|
+
describe "if given an options hash" do
|
59
|
+
|
60
|
+
it "should delegate `#{method_name}` to the authorizer's `default` method, passing the options" do
|
61
|
+
able = method_name.sub('_by?', '').to_sym
|
62
|
+
Authority::Authorizer.should_receive(:default).with(able, @user, :with => 'gusto')
|
63
|
+
Authority::Authorizer.send(method_name, @user, :with => 'gusto')
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "if not given an options hash" do
|
69
|
+
|
70
|
+
it "should delegate `#{method_name}` to the authorizer's `default` method, passing no options" do
|
71
|
+
able = method_name.sub('_by?', '').to_sym
|
72
|
+
Authority::Authorizer.should_receive(:default).with(able, @user)
|
73
|
+
Authority::Authorizer.send(method_name, @user)
|
74
|
+
end
|
75
|
+
|
48
76
|
end
|
49
77
|
|
50
78
|
end
|
@@ -53,8 +81,19 @@ describe Authority::Authorizer do
|
|
53
81
|
|
54
82
|
describe "the default method" do
|
55
83
|
|
56
|
-
|
57
|
-
|
84
|
+
describe "if given an options hash" do
|
85
|
+
|
86
|
+
it "should return false" do
|
87
|
+
Authority::Authorizer.default(:implodable, @user, {:for => "my_object"}).should be_false
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "if not given an options hash" do
|
92
|
+
|
93
|
+
it "should return false" do
|
94
|
+
Authority::Authorizer.default(:implodable, @user).should be_false
|
95
|
+
end
|
96
|
+
|
58
97
|
end
|
59
98
|
|
60
99
|
end
|
@@ -16,10 +16,24 @@ describe Authority::UserAbilities do
|
|
16
16
|
@user.should respond_to(method_name)
|
17
17
|
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
describe "if given options" do
|
20
|
+
|
21
|
+
it "should delegate the authorization check to the resource, passing the options" do
|
22
|
+
@ability_model.should_receive("#{Authority.abilities[verb]}_by?").with(@user, :size => 'wee')
|
23
|
+
@user.send(method_name, @ability_model, :size => 'wee')
|
24
|
+
end
|
25
|
+
|
22
26
|
end
|
27
|
+
|
28
|
+
describe "if not given options" do
|
29
|
+
|
30
|
+
it "should delegate the authorization check to the resource, passing no options" do
|
31
|
+
@ability_model.should_receive("#{Authority.abilities[verb]}_by?").with(@user)
|
32
|
+
@user.send(method_name, @ability_model)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
23
37
|
end
|
24
38
|
|
25
39
|
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: 2.0
|
4
|
+
version: 2.1.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: 2012-
|
13
|
+
date: 2012-08-10 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rails
|
17
|
-
requirement: &
|
17
|
+
requirement: &2160582080 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,7 +22,7 @@ dependencies:
|
|
22
22
|
version: 3.0.0
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *2160582080
|
26
26
|
description: Authority helps you authorize actions in your Rails app. It's ORM-neutral
|
27
27
|
and has very little fancy syntax; just group your models under one or more Authorizer
|
28
28
|
classes and write plain Ruby methods on them.
|
@@ -90,9 +90,20 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
90
|
version: '0'
|
91
91
|
requirements: []
|
92
92
|
rubyforge_project:
|
93
|
-
rubygems_version: 1.8.
|
93
|
+
rubygems_version: 1.8.16
|
94
94
|
signing_key:
|
95
95
|
specification_version: 3
|
96
96
|
summary: Authority helps you authorize actions in your Rails app using plain Ruby
|
97
97
|
methods on Authorizer classes.
|
98
|
-
test_files:
|
98
|
+
test_files:
|
99
|
+
- spec/authority/abilities_spec.rb
|
100
|
+
- spec/authority/authorizer_spec.rb
|
101
|
+
- spec/authority/configuration_spec.rb
|
102
|
+
- spec/authority/controller_spec.rb
|
103
|
+
- spec/authority/user_abilities_spec.rb
|
104
|
+
- spec/authority_spec.rb
|
105
|
+
- spec/spec_helper.rb
|
106
|
+
- spec/support/ability_model.rb
|
107
|
+
- spec/support/example_controllers.rb
|
108
|
+
- spec/support/mock_rails.rb
|
109
|
+
- spec/support/user.rb
|