sanction 0.0.1 → 2.1.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.
- checksums.yaml +4 -4
- data/.gitignore +9 -17
- data/.travis.yml +6 -0
- data/LICENSE.txt +1 -1
- data/README.md +105 -5
- data/Rakefile +5 -0
- data/lib/sanction/attached_list.rb +57 -0
- data/lib/sanction/blacklist/list.rb +34 -0
- data/lib/sanction/blacklist/node.rb +42 -0
- data/lib/sanction/blacklist/null_list.rb +21 -0
- data/lib/sanction/blacklist/null_node.rb +37 -0
- data/lib/sanction/node.rb +135 -0
- data/lib/sanction/permission.rb +38 -0
- data/lib/sanction/tree.rb +124 -0
- data/lib/sanction/version.rb +1 -1
- data/lib/sanction/whitelist/list.rb +34 -0
- data/lib/sanction/whitelist/node.rb +43 -0
- data/lib/sanction/whitelist/null_list.rb +21 -0
- data/lib/sanction/whitelist/null_node.rb +37 -0
- data/lib/sanction.rb +32 -1
- data/sanction.gemspec +11 -6
- data/spec/application_spec.rb +91 -0
- data/spec/node_spec.rb +49 -0
- data/spec/permission_spec.rb +218 -0
- data/spec/resources_spec.rb +158 -0
- data/spec/spec_helper.rb +91 -0
- data/spec/wildcard_spec.rb +95 -0
- metadata +91 -8
@@ -0,0 +1,34 @@
|
|
1
|
+
module Sanction
|
2
|
+
module Whitelist
|
3
|
+
class List < Sanction::AttachedList
|
4
|
+
|
5
|
+
def allowed_ids
|
6
|
+
(wildcard_resource? || resources.include?(@key)) ? entries.map {|x| x.id} : []
|
7
|
+
end
|
8
|
+
|
9
|
+
def permitted?
|
10
|
+
return true if wildcard_resource?
|
11
|
+
return false if ids_blank?
|
12
|
+
return true if resources.include?(@key)
|
13
|
+
false
|
14
|
+
end
|
15
|
+
|
16
|
+
def blacklist?
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
def whitelist?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def denied_ids
|
25
|
+
[]
|
26
|
+
end
|
27
|
+
|
28
|
+
def null_node_class
|
29
|
+
Sanction::Whitelist::NullNode
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Sanction
|
2
|
+
module Whitelist
|
3
|
+
class Node < Sanction::Node
|
4
|
+
|
5
|
+
def permitted?
|
6
|
+
super
|
7
|
+
root? ? true : (@parent[type].permitted? && @parent[type].allowed_ids.include?(id))
|
8
|
+
end
|
9
|
+
|
10
|
+
def allow!
|
11
|
+
false
|
12
|
+
end
|
13
|
+
|
14
|
+
def deny!
|
15
|
+
@parent.resources << type
|
16
|
+
@parent.resources.uniq!
|
17
|
+
unlink
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def whitelist?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def blacklist?
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
def mode
|
30
|
+
'whitelist'
|
31
|
+
end
|
32
|
+
|
33
|
+
def array_class
|
34
|
+
Sanction::Whitelist::List
|
35
|
+
end
|
36
|
+
|
37
|
+
def null_array_class
|
38
|
+
Sanction::Whitelist::NullList
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Sanction
|
2
|
+
module Whitelist
|
3
|
+
class NullList < Sanction::Whitelist::List
|
4
|
+
|
5
|
+
def permitted?
|
6
|
+
return true if wildcard_resource?
|
7
|
+
return true if resources.include?(@key) && !ids_blank?
|
8
|
+
return false if ids_blank?
|
9
|
+
end
|
10
|
+
|
11
|
+
def persisted?
|
12
|
+
false
|
13
|
+
end
|
14
|
+
|
15
|
+
def null_node_class
|
16
|
+
Sanction::Whitelist::NullNode
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Sanction
|
2
|
+
module Whitelist
|
3
|
+
class NullNode < Sanction::Whitelist::Node
|
4
|
+
|
5
|
+
def permitted?
|
6
|
+
a = ancestors.reject(&:root?).map(&:permitted?)
|
7
|
+
a << false
|
8
|
+
a.all?
|
9
|
+
end
|
10
|
+
|
11
|
+
def allow!
|
12
|
+
ancestors.reject(&:persisted?).each(&:allow!)
|
13
|
+
@parent.resources << type
|
14
|
+
@parent.resources.uniq!
|
15
|
+
@parent.add_subject({
|
16
|
+
id: id,
|
17
|
+
type: type
|
18
|
+
})
|
19
|
+
end
|
20
|
+
|
21
|
+
def deny!
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
def persisted?
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
def array_class
|
30
|
+
Sanction::Whitelist::NullList
|
31
|
+
end
|
32
|
+
|
33
|
+
alias :null_array_class :array_class
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/sanction.rb
CHANGED
@@ -1,5 +1,36 @@
|
|
1
|
+
require "active_support"
|
2
|
+
require "active_support/core_ext"
|
1
3
|
require "sanction/version"
|
2
4
|
|
3
5
|
module Sanction
|
4
|
-
|
6
|
+
|
7
|
+
autoload :AttachedList, 'sanction/attached_list'
|
8
|
+
autoload :Tree, 'sanction/tree'
|
9
|
+
autoload :Node, 'sanction/node'
|
10
|
+
autoload :Permission, 'sanction/permission'
|
11
|
+
|
12
|
+
module Whitelist
|
13
|
+
autoload :List, 'sanction/whitelist/list'
|
14
|
+
autoload :Node, 'sanction/whitelist/node'
|
15
|
+
autoload :NullList, 'sanction/whitelist/null_list'
|
16
|
+
autoload :NullNode, 'sanction/whitelist/null_node'
|
17
|
+
end
|
18
|
+
|
19
|
+
module Blacklist
|
20
|
+
autoload :List, 'sanction/blacklist/list'
|
21
|
+
autoload :Node, 'sanction/blacklist/node'
|
22
|
+
autoload :NullList, 'sanction/blacklist/null_list'
|
23
|
+
autoload :NullNode, 'sanction/blacklist/null_node'
|
24
|
+
end
|
25
|
+
|
26
|
+
extend self
|
27
|
+
|
28
|
+
def build(hash)
|
29
|
+
"sanction/#{hash[:mode]}/node".classify.constantize.new(hash)
|
30
|
+
end
|
31
|
+
|
32
|
+
def permission(permission, *predicates)
|
33
|
+
Sanction::Permission.new(permission, *predicates)
|
34
|
+
end
|
35
|
+
|
5
36
|
end
|
data/sanction.gemspec
CHANGED
@@ -6,11 +6,11 @@ require 'sanction/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "sanction"
|
8
8
|
spec.version = Sanction::VERSION
|
9
|
-
spec.authors = ["
|
10
|
-
spec.email = ["
|
11
|
-
spec.summary =
|
12
|
-
spec.description =
|
13
|
-
spec.homepage = "http://
|
9
|
+
spec.authors = ["Adam Carlile", "John Maxwell"]
|
10
|
+
spec.email = ["adam.carlile@boardintelligence.co.uk", "john.maxwell@boardintelligence.co.uk"]
|
11
|
+
spec.summary = "A permissions gem for people who love JSON"
|
12
|
+
spec.description = "Provides a JSON format for describing complex nested permission sets"
|
13
|
+
spec.homepage = "http://github.com/boardiq/sanction"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
@@ -18,6 +18,11 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
+
spec.add_dependency "activesupport"
|
22
|
+
|
23
|
+
spec.add_development_dependency "minitest"
|
21
24
|
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
-
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "pry"
|
27
|
+
spec.add_development_dependency "awesome_print"
|
23
28
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# Issues lifted from the web app
|
4
|
+
|
5
|
+
describe 'application issues' do
|
6
|
+
|
7
|
+
let(:permissions_hash) { {} }
|
8
|
+
let(:permissions) { Sanction.build(permissions_hash) }
|
9
|
+
let(:predicates) { [] }
|
10
|
+
let(:permission) { Sanction::Permission.new(permissions, *predicates)}
|
11
|
+
|
12
|
+
describe 'regular user with one allowed bookcase, but no allowed shelves' do
|
13
|
+
let(:permissions_hash) do
|
14
|
+
{
|
15
|
+
mode: "whitelist",
|
16
|
+
scope: [:read],
|
17
|
+
subjects: [
|
18
|
+
{
|
19
|
+
id: "948b9ace-784f-4326-aeb9-a2a0587d75b9",
|
20
|
+
type: 'bookcase',
|
21
|
+
mode: "whitelist",
|
22
|
+
scope: [:read, :manage],
|
23
|
+
resources: []
|
24
|
+
}
|
25
|
+
],
|
26
|
+
resources: [:bookcase]
|
27
|
+
}
|
28
|
+
end
|
29
|
+
let(:predicates) { [Bookcase.new('948b9ace-784f-4326-aeb9-a2a0587d75b9')] }
|
30
|
+
|
31
|
+
it 'should not allow access to any shelves' do
|
32
|
+
permission.path[:shelf].permitted?.must_equal false
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "allowing a new shelf" do
|
36
|
+
before do
|
37
|
+
permission.path[:shelf][1234].allow!
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should allow access to the shelf' do
|
41
|
+
permission.path[:shelf][1234].permitted?.must_equal true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe 'admin user with one banned bookcase' do
|
47
|
+
|
48
|
+
let(:permissions_hash) do
|
49
|
+
{
|
50
|
+
mode: 'blacklist',
|
51
|
+
scope: [:read, :manage],
|
52
|
+
subjects: [
|
53
|
+
{
|
54
|
+
id: 'f23175aa-014b-4796-aaef-878df597e7f1',
|
55
|
+
type: 'bookcase',
|
56
|
+
mode: 'whitelist'
|
57
|
+
}
|
58
|
+
]
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
let(:predicates) { [User.new(32)] }
|
63
|
+
|
64
|
+
it 'should allow access for a user and a random id' do
|
65
|
+
permission.permitted?.must_equal true
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'disalowing neseted objects' do
|
69
|
+
before do
|
70
|
+
permission.path.root[:bookcase][12][:shelf][10][:pack][14].deny!
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should not be viewable' do
|
74
|
+
permission.path.root[:bookcase][12][:shelf][10][:pack][14].permitted?.must_equal false
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe 'disalowing a user' do
|
79
|
+
before do
|
80
|
+
permission.path.root[:user][12].deny!
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should prevent access to user 12' do
|
84
|
+
permission.path.root[:user][12].permitted?.must_equal false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
end
|
data/spec/node_spec.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Sanction::Node do
|
4
|
+
|
5
|
+
let(:permissions_hash) do
|
6
|
+
{
|
7
|
+
mode: 'whitelist',
|
8
|
+
scope: ['manage', 'read'],
|
9
|
+
subjects: [
|
10
|
+
{
|
11
|
+
id: 6,
|
12
|
+
type: 'bookcase',
|
13
|
+
scope: []
|
14
|
+
}
|
15
|
+
]
|
16
|
+
}
|
17
|
+
end
|
18
|
+
let(:permissions) { Sanction.build(permissions_hash) }
|
19
|
+
|
20
|
+
it 'should let me find id: 6' do
|
21
|
+
permissions.find('bookcase', 6).id.must_equal 6
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'changing root node type' do
|
25
|
+
|
26
|
+
it 'should return the root node as being a blacklist' do
|
27
|
+
perm = permissions.root.change_type!(:blacklist)
|
28
|
+
perm.mode.must_equal 'blacklist'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'with complex permissons' do
|
33
|
+
let(:permissions_hash) { PERMISSIONS }
|
34
|
+
|
35
|
+
describe 'changing a node type' do
|
36
|
+
|
37
|
+
it 'should return the node as being changed' do
|
38
|
+
perm = permissions.find('bookcase', 2).change_type!(:blacklist)
|
39
|
+
perm.find('bookcase', 2).mode.must_equal 'blacklist'
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should return root for a missing id and type' do
|
45
|
+
permissions.find('test', 5).root?.must_equal true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Sanction::Permission do
|
4
|
+
|
5
|
+
let(:permissions) { Sanction.build(permissions_hash) }
|
6
|
+
let(:predicates) { [] }
|
7
|
+
let(:permission) { Sanction::Permission.new(permissions, *predicates)}
|
8
|
+
|
9
|
+
let(:bookcase) { Bookcase.new(6) }
|
10
|
+
let(:shelf) { Shelf.new(4) }
|
11
|
+
let(:pack) { Pack.new(5) }
|
12
|
+
|
13
|
+
describe "With simple whitelist permissions" do
|
14
|
+
|
15
|
+
let(:permissions_hash) do
|
16
|
+
{
|
17
|
+
mode: 'whitelist',
|
18
|
+
scope: ['manage', 'read'],
|
19
|
+
resources: ['bookcase'],
|
20
|
+
subjects: [
|
21
|
+
{
|
22
|
+
id: 6,
|
23
|
+
type: 'bookcase',
|
24
|
+
scope: []
|
25
|
+
}
|
26
|
+
]
|
27
|
+
}
|
28
|
+
end
|
29
|
+
let(:predicates) { [bookcase] }
|
30
|
+
|
31
|
+
it 'should be permitted' do
|
32
|
+
permission.permitted?.must_equal true
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should have scope manage' do
|
36
|
+
permission.permitted_with_scope?(:manage).must_equal true
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should have scope read' do
|
40
|
+
permission.permitted_with_scope?(:read).must_equal true
|
41
|
+
end
|
42
|
+
|
43
|
+
describe 'With a class predicate' do
|
44
|
+
let(:predicates) { [Shelf] }
|
45
|
+
|
46
|
+
it 'should not be permitted' do
|
47
|
+
permission.permitted?.must_equal false
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "With simple blacklist permissions" do
|
55
|
+
let(:permissions_hash) do
|
56
|
+
{
|
57
|
+
mode: 'blacklist',
|
58
|
+
scope: ['manage', 'read'],
|
59
|
+
subjects: [
|
60
|
+
{
|
61
|
+
id: 6,
|
62
|
+
type: 'bookcase',
|
63
|
+
scope: []
|
64
|
+
}
|
65
|
+
]
|
66
|
+
}
|
67
|
+
end
|
68
|
+
let(:predicates) { [bookcase] }
|
69
|
+
|
70
|
+
it 'should not be permitted' do
|
71
|
+
permission.permitted?.must_equal false
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should not be permitted for a scope of manage' do
|
75
|
+
permission.permitted_with_scope?(:manage).must_equal false
|
76
|
+
end
|
77
|
+
|
78
|
+
describe 'With a class predicate not blacklisted' do
|
79
|
+
let(:predicates) { [Shelf] }
|
80
|
+
|
81
|
+
it 'should be permitted' do
|
82
|
+
permission.permitted?.must_equal true
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should be permitted with a scope of manage' do
|
86
|
+
permission.permitted_with_scope?(:manage).must_equal true
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "With complex whitelist permissions" do
|
94
|
+
let(:permissions_hash) { PERMISSIONS }
|
95
|
+
let(:bookcase) { Bookcase.new(2) }
|
96
|
+
let(:predicates) { [bookcase, shelf] }
|
97
|
+
|
98
|
+
describe "with a blacklisted mode shelf" do
|
99
|
+
let(:shelf) { Shelf.new(7) }
|
100
|
+
|
101
|
+
describe "with a pack on the blacklist" do
|
102
|
+
let(:pack) { Pack.new(8) }
|
103
|
+
let(:predicates) { [bookcase, shelf, pack] }
|
104
|
+
|
105
|
+
it 'should not be permitted' do
|
106
|
+
permission.permitted?.must_equal false
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "with a pack not on the blacklist" do
|
112
|
+
let(:pack) { Pack.new(100) }
|
113
|
+
let(:predicates) { [bookcase, shelf, pack] }
|
114
|
+
|
115
|
+
it 'should be permitted' do
|
116
|
+
permission.permitted?.must_equal true
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'should have its parents scopes' do
|
120
|
+
permission.permitted_with_scope?(:manage).must_equal true
|
121
|
+
permission.permitted_with_scope?(:read).must_equal true
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "with a whitelisted shelf" do
|
129
|
+
let(:shelf) { Shelf.new(4) }
|
130
|
+
|
131
|
+
it 'should be permitted' do
|
132
|
+
permission.permitted?.must_equal true
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "with a whitelisted pack" do
|
136
|
+
let(:pack) { Pack.new(5) }
|
137
|
+
let(:predicates) { [bookcase, shelf, pack] }
|
138
|
+
|
139
|
+
it 'should be permitted' do
|
140
|
+
permission.permitted?.must_equal true
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'should have a read and manage scope' do
|
144
|
+
permission.permitted_with_scope?(:manage).must_equal true
|
145
|
+
permission.permitted_with_scope?(:read).must_equal true
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "with an incorrect object graph with a whitelisted incorrect parent" do
|
155
|
+
let(:permissions_hash) { PERMISSIONS }
|
156
|
+
let(:bookcase) { Bookcase.new(2) }
|
157
|
+
let(:shelf) { Shelf.new(6) }
|
158
|
+
let(:predicates) { [bookcase, shelf] }
|
159
|
+
|
160
|
+
it 'should not allow the shelf to be permitted' do
|
161
|
+
permission.permitted?.must_equal false
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
describe "With missing blacklist permissions" do
|
167
|
+
let(:permissions_hash) { PERMISSIONS }
|
168
|
+
let(:bookcase) { Bookcase.new(1) }
|
169
|
+
let(:predicates) { [bookcase, shelf] }
|
170
|
+
|
171
|
+
describe "with a blacklisted shelf" do
|
172
|
+
let(:shelf) { Shelf.new(6) }
|
173
|
+
|
174
|
+
it 'should not be permitted' do
|
175
|
+
permission.permitted?.must_equal false
|
176
|
+
end
|
177
|
+
|
178
|
+
describe "with a pack within a blacklisted shelf" do
|
179
|
+
let(:pack) { Pack.new(98) }
|
180
|
+
let(:predicates) { [bookcase, shelf, pack] }
|
181
|
+
|
182
|
+
it 'should not be permitted' do
|
183
|
+
permission.permitted?.must_equal false
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'should not have a read scope' do
|
187
|
+
permission.permitted_with_scope?(:read).must_equal false
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
describe 'with a non blacklisted shelf' do
|
195
|
+
let(:shelf) { Shelf.new(31) }
|
196
|
+
|
197
|
+
it 'should be permitted' do
|
198
|
+
permission.permitted?.must_equal true
|
199
|
+
end
|
200
|
+
|
201
|
+
describe 'with a non blacklisted pack' do
|
202
|
+
let(:pack) { Pack.new(10) }
|
203
|
+
let(:predicates) { [bookcase, shelf, pack] }
|
204
|
+
|
205
|
+
it 'should be permitted' do
|
206
|
+
permission.permitted?.must_equal true
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'should have a read scope' do
|
210
|
+
permission.permitted_with_scope?(:read).must_equal true
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|