roadblock 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +70 -21
- data/lib/roadblock.rb +1 -0
- data/lib/roadblock/authorizer.rb +9 -5
- data/lib/roadblock/stack.rb +51 -0
- data/lib/roadblock/version.rb +1 -1
- data/roadblock.gemspec +1 -1
- data/spec/roadblock_spec.rb +104 -3
- metadata +15 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: af1f328bbe876f53f82f57c5b51f6b432fa0bb5e
|
4
|
+
data.tar.gz: d649ced8b0f46d0ca810fe558c8ce508252f4474
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bda6816c0d10e740b186e6358b7fd080ec520aa17e6b82a15a2d46f524a98ca79659c1da98ac9a624945e978dd6cd89be5cfb510736c5b4ed13f9b56d85b8319
|
7
|
+
data.tar.gz: 1e614fe8b7d11bdf763a36919f95edfaca225d61fab8cb4e4698b6ac9b06a337aae26b4b2cde04fd5f0b045732a88f6c9a9b3f08bd2ddda3853916327e46364c
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# Roadblock
|
2
2
|
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/roadblock.png)](http://badge.fury.io/rb/roadblock)
|
3
4
|
[![Semaphore](https://semaphoreapp.com/api/v1/projects/f1ccf0c3ff7565f975caef0fdfcf649f24f033fb/118939/shields_badge.png)](https://semaphoreapp.com/minter/roadblock)
|
4
5
|
[![Code Climate](https://codeclimate.com/github/teamsnap/roadblock.png)](https://codeclimate.com/github/teamsnap/roadblock)
|
5
6
|
[![Coverage Status](https://coveralls.io/repos/teamsnap/roadblock/badge.png?branch=master)](https://coveralls.io/r/teamsnap/roadblock?branch=master)
|
@@ -12,7 +13,7 @@ A simple authorization library.
|
|
12
13
|
|
13
14
|
Roadblock provides a simple interface for checking if a ruby object has the authority to interact with another object. The most obvious example being if the current user in your rails controller can read/write the object they're attempting to access.
|
14
15
|
|
15
|
-
Nearly all authorization libraries require heavy weight configuration and tight integration with Rails. This library was created to provide the simplest solution to the problem without requiring any external dependencies. It doesn't require Rails or any of it's subcomponents and weighs in at less than
|
16
|
+
Nearly all authorization libraries require heavy weight configuration and tight integration with Rails. This library was created to provide the simplest solution to the problem without requiring any external dependencies. It doesn't require Rails or any of it's subcomponents and weighs in at less than 100 LOC for the actual implementation (less than 25 LOC if you don't care about authorizer stacks).
|
16
17
|
|
17
18
|
## Installation
|
18
19
|
|
@@ -30,38 +31,86 @@ Or install it yourself as:
|
|
30
31
|
|
31
32
|
## Usage
|
32
33
|
|
33
|
-
|
34
|
+
```ruby
|
35
|
+
require "roadblock"
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
+
class TeamAuthorizer
|
38
|
+
include Roadblock.authorizer
|
37
39
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
40
|
+
def can_read?(team)
|
41
|
+
scopes.include?("read") &&
|
42
|
+
user.teams.include?(team)
|
43
|
+
end
|
42
44
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
45
|
+
def can_write?(team)
|
46
|
+
scopes.include?("write_teams") && (
|
47
|
+
user.managed_teams.include?(team) ||
|
48
|
+
user.owned_teams.include?(team)
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
scopes = ["read", "write_teams"] # Optional oauth scopes
|
54
|
+
auth = TeamAuthorizer.new(current_user, :scopes => scopes)
|
55
|
+
team = Team.find(params[:id])
|
56
|
+
|
57
|
+
# Scopes are optional, just don't pass any in if you don't want them.
|
58
|
+
|
59
|
+
auth.can?(:read, team) # or auth.can_read?(team)
|
60
|
+
auth.can?(:write, team) # or auth.can_write?(team)
|
61
|
+
|
62
|
+
# When using the #can? syntax, you can pass in an enumerable
|
63
|
+
# #can? will then tell you if the user is able to perform the
|
64
|
+
# action on all of the objects. `true` they can, `false` they
|
65
|
+
# cannot.
|
66
|
+
|
67
|
+
teams = Team.where(:sport => :hockey)
|
68
|
+
|
69
|
+
auth.can?(:read, teams)
|
70
|
+
auth.can?(:write, teams)
|
71
|
+
```
|
72
|
+
|
73
|
+
### Middleware
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
require "roadblock"
|
77
|
+
|
78
|
+
class TeamAuthorizer
|
79
|
+
include Roadblock.authorizer
|
80
|
+
|
81
|
+
def can_read?(team)
|
82
|
+
user.teams.include?(team)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class AdminAuthorizer
|
87
|
+
include Roadblock.authorizer
|
88
|
+
|
89
|
+
def can?(action, object)
|
90
|
+
if user.is_admin?
|
91
|
+
true
|
92
|
+
else
|
93
|
+
yield(object)
|
49
94
|
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
stack = Roadblock::Stack.new(current_user, :scopes => scopes)
|
99
|
+
stack.add(AdminAuthorizer, TeamAuthorizer)
|
100
|
+
|
101
|
+
# Then use stack just as you would a standalone authorizer
|
50
102
|
|
51
|
-
|
52
|
-
|
53
|
-
|
103
|
+
stack.can?(:read, team) # or stack.can_read?(team)
|
104
|
+
stack.can?(:read, teams) # or stack.can_read?(teams)
|
105
|
+
```
|
54
106
|
|
55
|
-
auth.can?(:read, team)
|
56
|
-
auth.can?(:write, team)
|
57
|
-
|
58
107
|
## Roadmap
|
59
108
|
|
60
109
|
- Add optional faliure messages
|
61
110
|
|
62
111
|
## Contributing
|
63
112
|
|
64
|
-
1. Fork it
|
113
|
+
1. Fork it ( http://github.com/teamsnap/roadblock/fork )
|
65
114
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
66
115
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
67
116
|
4. Push to the branch (`git push origin my-new-feature`)
|
data/lib/roadblock.rb
CHANGED
data/lib/roadblock/authorizer.rb
CHANGED
@@ -5,11 +5,15 @@ module Roadblock
|
|
5
5
|
self.scopes = scopes
|
6
6
|
end
|
7
7
|
|
8
|
-
def can?(action,
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
def can?(action, object)
|
9
|
+
if block_given?
|
10
|
+
yield(object)
|
11
|
+
else
|
12
|
+
objects = [*object]
|
13
|
+
objects
|
14
|
+
.map { |obj| send("can_#{action}?", obj) }
|
15
|
+
.all?
|
16
|
+
end
|
13
17
|
end
|
14
18
|
|
15
19
|
private
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Roadblock
|
2
|
+
class Stack
|
3
|
+
NULL_AUTHORIZER_PROC = lambda { |object| false }
|
4
|
+
|
5
|
+
def initialize(user, scopes: [])
|
6
|
+
self.user = user
|
7
|
+
self.scopes = scopes
|
8
|
+
self.authorizers = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(*auths)
|
12
|
+
self.authorizers = authorizers + auths
|
13
|
+
end
|
14
|
+
|
15
|
+
def can?(action, objects)
|
16
|
+
objects = [*objects]
|
17
|
+
|
18
|
+
stack = authorizers.reverse.inject(NULL_AUTHORIZER_PROC) do |authorizer_proc, authorizer_klass|
|
19
|
+
lambda { |obj|
|
20
|
+
authorizer = authorizer_klass.new(user, scopes: scopes)
|
21
|
+
|
22
|
+
authorizer.can?(action, obj) do |inner_obj|
|
23
|
+
authorizer.send("can_#{action}?", inner_obj, &authorizer_proc)
|
24
|
+
end
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
objects
|
29
|
+
.map { |object| stack.call(object) }
|
30
|
+
.all?
|
31
|
+
end
|
32
|
+
|
33
|
+
def respond_to?(method)
|
34
|
+
/can_(.*?)\?/.match(method) ? true : false
|
35
|
+
end
|
36
|
+
|
37
|
+
def method_missing(method, *args)
|
38
|
+
match = /can_(.*?)\?/.match(method)
|
39
|
+
|
40
|
+
if match
|
41
|
+
can?(match[1], *args)
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_accessor :user, :scopes, :authorizers
|
50
|
+
end
|
51
|
+
end
|
data/lib/roadblock/version.rb
CHANGED
data/roadblock.gemspec
CHANGED
@@ -6,7 +6,7 @@ require 'roadblock/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "roadblock"
|
8
8
|
spec.version = Roadblock::VERSION
|
9
|
-
spec.authors = ["Shane Emmons"]
|
9
|
+
spec.authors = ["Shane Emmons", "Emily Dobervich"]
|
10
10
|
spec.email = ["oss@teamsnap.com"]
|
11
11
|
spec.description = <<DESC
|
12
12
|
Roadblock provides a simple interface for checking if a ruby object has the
|
data/spec/roadblock_spec.rb
CHANGED
@@ -14,7 +14,23 @@ class TestAuthorizer
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
|
17
|
+
class ForwardingAuthorizer
|
18
|
+
include Roadblock.authorizer
|
19
|
+
|
20
|
+
def can_peek?(object)
|
21
|
+
yield(object)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class ReturningMiddleware
|
26
|
+
include Roadblock.authorizer
|
27
|
+
|
28
|
+
def can_peek?(object)
|
29
|
+
false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe Roadblock::Authorizer do
|
18
34
|
subject { TestAuthorizer }
|
19
35
|
|
20
36
|
describe "#can?" do
|
@@ -23,7 +39,7 @@ describe Roadblock do
|
|
23
39
|
scopes = ["peekable"]
|
24
40
|
auth = subject.new(user, :scopes => scopes)
|
25
41
|
|
26
|
-
expect(auth.can?(:peek, user)).to
|
42
|
+
expect(auth.can?(:peek, user)).to be true
|
27
43
|
end
|
28
44
|
|
29
45
|
it "accepts multiple objects" do
|
@@ -31,7 +47,7 @@ describe Roadblock do
|
|
31
47
|
scopes = ["peekable"]
|
32
48
|
auth = subject.new(user, :scopes => scopes)
|
33
49
|
|
34
|
-
expect(auth.can?(:peek, [user, user])).to
|
50
|
+
expect(auth.can?(:peek, [user, user])).to be true
|
35
51
|
end
|
36
52
|
|
37
53
|
it "requires all objects to pass authorization" do
|
@@ -50,3 +66,88 @@ describe Roadblock do
|
|
50
66
|
end
|
51
67
|
end
|
52
68
|
end
|
69
|
+
|
70
|
+
describe Roadblock::Stack do
|
71
|
+
describe "#can?" do
|
72
|
+
it "correctly forwards call" do
|
73
|
+
user = double
|
74
|
+
scopes = ["peekable"]
|
75
|
+
|
76
|
+
stack = Roadblock::Stack.new(user, :scopes => scopes)
|
77
|
+
stack.add(TestAuthorizer)
|
78
|
+
|
79
|
+
expect(stack.can?(:peek, user)).to be(true)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "correctly forwards call with alternate call style" do
|
83
|
+
user = double
|
84
|
+
scopes = ["peekable"]
|
85
|
+
|
86
|
+
stack = Roadblock::Stack.new(user, :scopes => scopes)
|
87
|
+
stack.add(TestAuthorizer)
|
88
|
+
|
89
|
+
expect(stack.can_peek?(user)).to be(true)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "correctly forwards call through middleware" do
|
93
|
+
user = double
|
94
|
+
scopes = ["peekable"]
|
95
|
+
|
96
|
+
stack = Roadblock::Stack.new(user, :scopes => scopes)
|
97
|
+
stack.add(ForwardingAuthorizer)
|
98
|
+
stack.add(TestAuthorizer)
|
99
|
+
|
100
|
+
expect(stack.can?(:peek, user)).to be(true)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "correctly handles middleware returning early" do
|
104
|
+
user = double
|
105
|
+
scopes = ["peekable"]
|
106
|
+
|
107
|
+
stack = Roadblock::Stack.new(user, :scopes => scopes)
|
108
|
+
stack.add(ReturningMiddleware)
|
109
|
+
stack.add(TestAuthorizer)
|
110
|
+
|
111
|
+
expect(stack.can?(:peek, user)).to be(false)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "accepts multiple objects" do
|
115
|
+
user = double
|
116
|
+
scopes = ["peekable"]
|
117
|
+
|
118
|
+
stack = Roadblock::Stack.new(user, :scopes => scopes)
|
119
|
+
stack.add(TestAuthorizer)
|
120
|
+
|
121
|
+
expect(stack.can?(:peek, [user, user])).to be true
|
122
|
+
end
|
123
|
+
|
124
|
+
it "accepts multiple objects with alternate call style" do
|
125
|
+
user = double
|
126
|
+
scopes = ["peekable"]
|
127
|
+
|
128
|
+
stack = Roadblock::Stack.new(user, :scopes => scopes)
|
129
|
+
stack.add(TestAuthorizer)
|
130
|
+
|
131
|
+
expect(stack.can_peek?([user, user])).to be true
|
132
|
+
end
|
133
|
+
|
134
|
+
it "requires all objects to pass authorization" do
|
135
|
+
user = double
|
136
|
+
scopes = ["peekable"]
|
137
|
+
|
138
|
+
stack = Roadblock::Stack.new(user, :scopes => scopes)
|
139
|
+
stack.add(TestAuthorizer)
|
140
|
+
|
141
|
+
expect(stack.can?(:peek, [user, nil])).to eq(false)
|
142
|
+
end
|
143
|
+
|
144
|
+
it "doesn't require scopes to be used" do
|
145
|
+
user = double
|
146
|
+
|
147
|
+
stack = Roadblock::Stack.new(user)
|
148
|
+
stack.add(TestAuthorizer)
|
149
|
+
|
150
|
+
expect(stack.can?(:wink, user)).to be(true)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
metadata
CHANGED
@@ -1,55 +1,56 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: roadblock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shane Emmons
|
8
|
+
- Emily Dobervich
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2014-02-14 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: bundler
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
16
17
|
requirements:
|
17
|
-
- - ~>
|
18
|
+
- - "~>"
|
18
19
|
- !ruby/object:Gem::Version
|
19
20
|
version: '1.3'
|
20
21
|
type: :development
|
21
22
|
prerelease: false
|
22
23
|
version_requirements: !ruby/object:Gem::Requirement
|
23
24
|
requirements:
|
24
|
-
- - ~>
|
25
|
+
- - "~>"
|
25
26
|
- !ruby/object:Gem::Version
|
26
27
|
version: '1.3'
|
27
28
|
- !ruby/object:Gem::Dependency
|
28
29
|
name: rake
|
29
30
|
requirement: !ruby/object:Gem::Requirement
|
30
31
|
requirements:
|
31
|
-
- -
|
32
|
+
- - ">="
|
32
33
|
- !ruby/object:Gem::Version
|
33
34
|
version: '0'
|
34
35
|
type: :development
|
35
36
|
prerelease: false
|
36
37
|
version_requirements: !ruby/object:Gem::Requirement
|
37
38
|
requirements:
|
38
|
-
- -
|
39
|
+
- - ">="
|
39
40
|
- !ruby/object:Gem::Version
|
40
41
|
version: '0'
|
41
42
|
- !ruby/object:Gem::Dependency
|
42
43
|
name: rspec
|
43
44
|
requirement: !ruby/object:Gem::Requirement
|
44
45
|
requirements:
|
45
|
-
- - ~>
|
46
|
+
- - "~>"
|
46
47
|
- !ruby/object:Gem::Version
|
47
48
|
version: 3.0.0.beta1
|
48
49
|
type: :development
|
49
50
|
prerelease: false
|
50
51
|
version_requirements: !ruby/object:Gem::Requirement
|
51
52
|
requirements:
|
52
|
-
- - ~>
|
53
|
+
- - "~>"
|
53
54
|
- !ruby/object:Gem::Version
|
54
55
|
version: 3.0.0.beta1
|
55
56
|
description: |
|
@@ -63,13 +64,14 @@ executables: []
|
|
63
64
|
extensions: []
|
64
65
|
extra_rdoc_files: []
|
65
66
|
files:
|
66
|
-
- .gitignore
|
67
|
+
- ".gitignore"
|
67
68
|
- Gemfile
|
68
69
|
- LICENSE.txt
|
69
70
|
- README.md
|
70
71
|
- Rakefile
|
71
72
|
- lib/roadblock.rb
|
72
73
|
- lib/roadblock/authorizer.rb
|
74
|
+
- lib/roadblock/stack.rb
|
73
75
|
- lib/roadblock/version.rb
|
74
76
|
- roadblock.gemspec
|
75
77
|
- spec/roadblock_spec.rb
|
@@ -84,20 +86,21 @@ require_paths:
|
|
84
86
|
- lib
|
85
87
|
required_ruby_version: !ruby/object:Gem::Requirement
|
86
88
|
requirements:
|
87
|
-
- -
|
89
|
+
- - ">="
|
88
90
|
- !ruby/object:Gem::Version
|
89
91
|
version: '0'
|
90
92
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
93
|
requirements:
|
92
|
-
- -
|
94
|
+
- - ">="
|
93
95
|
- !ruby/object:Gem::Version
|
94
96
|
version: '0'
|
95
97
|
requirements: []
|
96
98
|
rubyforge_project:
|
97
|
-
rubygems_version: 2.0
|
99
|
+
rubygems_version: 2.2.0
|
98
100
|
signing_key:
|
99
101
|
specification_version: 4
|
100
102
|
summary: A simple authorization library.
|
101
103
|
test_files:
|
102
104
|
- spec/roadblock_spec.rb
|
103
105
|
- spec/spec_helper.rb
|
106
|
+
has_rdoc:
|