ey_gatekeeper 0.1.34
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/lib/ey_gatekeeper/access_control_list.rb +133 -0
- data/lib/ey_gatekeeper/client/consumer.rb +63 -0
- data/lib/ey_gatekeeper/client/middlewares/authentication.rb +77 -0
- data/lib/ey_gatekeeper/client/middlewares/service_token_authentication.rb +39 -0
- data/lib/ey_gatekeeper/client/server_response.rb +37 -0
- data/lib/ey_gatekeeper/client.rb +44 -0
- data/lib/ey_gatekeeper/responses.rb +67 -0
- data/lib/ey_gatekeeper/token.rb +29 -0
- data/lib/ey_gatekeeper/util.rb +23 -0
- data/lib/ey_gatekeeper/version.rb +5 -0
- data/lib/ey_gatekeeper.rb +54 -0
- data/spec/access_control_list_spec.rb +192 -0
- data/spec/auth_service_spec.rb +332 -0
- data/spec/consumer_spec.rb +80 -0
- data/spec/fallback_spec.rb +76 -0
- data/spec/impersonation_spec.rb +50 -0
- data/spec/responses_spec.rb +23 -0
- data/spec/service_token_authentication_spec.rb +10 -0
- data/spec/spec_helper.rb +133 -0
- metadata +129 -0
@@ -0,0 +1,192 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EY::GateKeeper::AccessControlList do
|
4
|
+
context "with a control for a collection" do
|
5
|
+
it "allows access to the collection" do
|
6
|
+
acl('xdna://foobar' => 'GET').allow?('GET', '/foobar').should be_true
|
7
|
+
end
|
8
|
+
|
9
|
+
it "denies access to the collection for a method that's not listed" do
|
10
|
+
acl('xdna://foobar' => 'GET').allow?('POST', '/foobar').should be_false
|
11
|
+
end
|
12
|
+
|
13
|
+
it "denies access to a collection with a longer name" do
|
14
|
+
acl('xdna://foobarbaz' => 'GET').allow?('GET', '/foobar').should be_false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context "with conflicting controls" do
|
19
|
+
it "allows access based on the more specific control" do
|
20
|
+
acl('xdna://foobar' => [], 'xdna://foobar/baz' => 'GET').allow?('GET', '/foobar/baz').should be_true
|
21
|
+
end
|
22
|
+
|
23
|
+
it "denies access based on the less specific control" do
|
24
|
+
acl = acl('xdna://foobar' => [], 'xdna://foobar/baz' => 'GET')
|
25
|
+
acl.allow?('GET', '/foobar').should be_false
|
26
|
+
acl.allow?('GET', '/foobar/foo').should be_false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "with a control requiring the search parameter" do
|
31
|
+
it "allows access to the collection when the search parameter is specified" do
|
32
|
+
acl('xdna://foobar?search' => 'GET').allow?('GET', '/foobar?search=foo').should be_true
|
33
|
+
end
|
34
|
+
|
35
|
+
it "allows access to the collection when the search parameter is specified with other parameters" do
|
36
|
+
acl('xdna://foobar?search' => 'GET').allow?('GET', '/foobar?search=foo&limit=10').should be_true
|
37
|
+
end
|
38
|
+
|
39
|
+
it "denies access to the collection when the search parameter is not specified" do
|
40
|
+
acl('xdna://foobar?search' => 'GET').allow?('GET', '/foobar').should be_false
|
41
|
+
end
|
42
|
+
|
43
|
+
it "denies access to the collection when a parameter other than search is specified" do
|
44
|
+
acl('xdna://foobar?search' => 'GET').allow?('GET', '/foobar?limit=10').should be_false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "with a control requiring the search and derp parameters" do
|
49
|
+
it "allows access to the collection when the both parameters are specified" do
|
50
|
+
acl('xdna://foobar?search&derp' => 'GET').allow?('GET', '/foobar?search=foo&derp=bar').should be_true
|
51
|
+
end
|
52
|
+
|
53
|
+
it "allows access to the collection when both parameters are specified with other parameters" do
|
54
|
+
acl('xdna://foobar?search&derp' => 'GET').allow?('GET', '/foobar?search=foo&derp=bar&limit=10').should be_true
|
55
|
+
end
|
56
|
+
|
57
|
+
it "denies access to the collection when both parameters are not specified" do
|
58
|
+
acl('xdna://foobar?search&derp' => 'GET').allow?('GET', '/foobar').should be_false
|
59
|
+
end
|
60
|
+
|
61
|
+
it "denies access to the collection when a parameter other than the required two are specified" do
|
62
|
+
acl('xdna://foobar?search&derp' => 'GET').allow?('GET', '/foobar?limit=10').should be_false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "with a global control" do
|
67
|
+
it "allows access" do
|
68
|
+
acl('xdna://' => 'GET').allow?('GET', '/whatever').should be_true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "with underscores" do
|
73
|
+
it "doesn't barf" do
|
74
|
+
expect {
|
75
|
+
acl('xdna://cloudkit_token_auth_tokens?foo' => 'GET').allow?('GET', '/cloudkit_token_auth_tokens?foo=bar')
|
76
|
+
}.to_not raise_error
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "with spaces" do
|
81
|
+
it "doesn't barf" do
|
82
|
+
expect {
|
83
|
+
acl('xdna://pools' => 'GET').allow?('GET', '/pools/Foo Bar')
|
84
|
+
}.to_not raise_error
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "intersection" do
|
89
|
+
context "with 2 basic acls sets that match" do
|
90
|
+
subject { acl('xdna://foo' => 'GET') & acl('xdna://foo' => 'GET') }
|
91
|
+
|
92
|
+
it "should produce an acl with the same methods" do
|
93
|
+
subject.to_hash.should == { 'xdna://foo' => ['GET'] }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context "with 2 basic acls sets where one has no methods" do
|
98
|
+
subject { acl('xdna://foo' => 'GET') & acl('xdna://foo' => []) }
|
99
|
+
|
100
|
+
it "should produce an acl with no permissions" do
|
101
|
+
subject.to_hash.should == { 'xdna://foo' => [] }
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context "with 2 different acl sets with the same paths, but different methods" do
|
106
|
+
subject do
|
107
|
+
acl('xdna://foo' => 'GET') & acl('xdna://foo' => ['GET','PUT'])
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should produce an acl set with the least permissive methods" do
|
111
|
+
subject.to_hash.should == { 'xdna://foo' => ['GET'] }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "with 2 different acl sets with different path" do
|
116
|
+
subject do
|
117
|
+
acl('xdna://foo' => 'GET') & acl('xdna://bar' => ['GET','PUT'])
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should produce an empty list" do
|
121
|
+
subject.to_hash.should == {}
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context "with multiple acess controls in each list" do
|
126
|
+
subject do
|
127
|
+
acl('xdna://foo' => 'GET', 'xdna://bar' => 'GET') &
|
128
|
+
acl('xdna://bar' => ['GET','PUT'])
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should produce an acl set with the least permissive methods" do
|
132
|
+
subject.to_hash.should == {
|
133
|
+
'xdna://bar' => ['GET']
|
134
|
+
}
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context "with paths that are subpaths of others" do
|
139
|
+
context "in an easy case" do
|
140
|
+
subject do
|
141
|
+
acl('xdna://' => ['GET', 'PUT']) &
|
142
|
+
acl('xdna://foos' => 'GET')
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should produce an acl set that allows GET on /foos" do
|
146
|
+
subject.to_hash.should == {
|
147
|
+
'xdna://foos' => ['GET']
|
148
|
+
}
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context "with a more interesting case" do
|
153
|
+
subject do
|
154
|
+
acl('xdna://' => ['GET', 'PUT']) &
|
155
|
+
acl('xdna://foos' => 'GET', 'xdna://foos/bar' => 'PUT')
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should produce an acl set that allows things" do
|
159
|
+
subject.to_hash.should == {
|
160
|
+
'xdna://foos' => ['GET'],
|
161
|
+
'xdna://foos/bar' => ['PUT']
|
162
|
+
}
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
context "with a path that starts with part of a another path" do
|
167
|
+
subject do
|
168
|
+
acl('xdna://foos' => ['GET']) &
|
169
|
+
acl('xdna://foosbars' => ['GET'])
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should produce the right acl set" do
|
173
|
+
subject.to_hash.should == {}
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "#to_hash" do
|
180
|
+
it "converts to a hash" do
|
181
|
+
acl('xdna://foo' => ['GET', 'PUT'], 'xdna://bar' => [], 'xdna://baz?foo' => ['POST']).to_hash.should == {
|
182
|
+
'xdna://foo' => ['GET', 'PUT'],
|
183
|
+
'xdna://bar' => [],
|
184
|
+
'xdna://baz?foo' => ['POST']
|
185
|
+
}
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def acl(data)
|
190
|
+
described_class.new(data)
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,332 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "authentication and authorization behavior" do
|
4
|
+
context "with roles" do
|
5
|
+
before { create_test_roles }
|
6
|
+
|
7
|
+
context "a key with the role Test1" do
|
8
|
+
before do
|
9
|
+
create_key_with_roles("Test1")
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:key) { find_key(test_key, test_secret) }
|
13
|
+
|
14
|
+
specify { key['access_controls'].should == { "xdna://" => [] } }
|
15
|
+
specify { key['gatekeeper_roles'].should == ["/gatekeeper_roles/Test1"] }
|
16
|
+
|
17
|
+
let(:token) { find_token(get_token(test_key, test_secret)["token"]).parsed_content }
|
18
|
+
|
19
|
+
specify { token['access_controls'].should == test_role_1_access_controls.merge(key['access_controls']) }
|
20
|
+
end
|
21
|
+
|
22
|
+
context "a key with the roles [Test1, Test2]" do
|
23
|
+
before do
|
24
|
+
create_key_with_roles("Test1","Test2")
|
25
|
+
end
|
26
|
+
|
27
|
+
let(:key) { find_key(test_key, test_secret) }
|
28
|
+
|
29
|
+
specify { key['access_controls'].should == { "xdna://" => [] } }
|
30
|
+
specify { key['gatekeeper_roles'].should == ["/gatekeeper_roles/Test1", "/gatekeeper_roles/Test2"] }
|
31
|
+
|
32
|
+
let(:token) { find_token(get_token(test_key, test_secret)["token"]).parsed_content }
|
33
|
+
|
34
|
+
specify { token['access_controls'].should == test_role_1_access_controls.merge(test_role_2_access_controls.merge(key['access_controls'])) }
|
35
|
+
end
|
36
|
+
|
37
|
+
context "a key with the roles [Test2, Test1]" do
|
38
|
+
before do
|
39
|
+
create_key_with_roles("Test2","Test1")
|
40
|
+
end
|
41
|
+
|
42
|
+
let(:key) { find_key(test_key, test_secret) }
|
43
|
+
|
44
|
+
specify { key['access_controls'].should == { "xdna://" => [] } }
|
45
|
+
specify { key['gatekeeper_roles'].should == ["/gatekeeper_roles/Test2", "/gatekeeper_roles/Test1"] }
|
46
|
+
|
47
|
+
let(:token) { find_token(get_token(test_key, test_secret)["token"]).parsed_content }
|
48
|
+
|
49
|
+
specify { token['access_controls'].should == test_role_2_access_controls.merge(test_role_1_access_controls.merge(key['access_controls'])) }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "allows a request to /cloudkit-meta with no token" do
|
54
|
+
get '/cloudkit-meta'
|
55
|
+
|
56
|
+
last_response.status.should == 200
|
57
|
+
end
|
58
|
+
|
59
|
+
it "allows a request to /cloudkit-meta with a token that has no access" do
|
60
|
+
token_data = get_token
|
61
|
+
restrict_token(token_data['token'])
|
62
|
+
|
63
|
+
get '/cloudkit-meta', {}, {
|
64
|
+
'HTTP_AUTH_TOKEN' => token_data['token']
|
65
|
+
}
|
66
|
+
|
67
|
+
last_response.status.should == 200
|
68
|
+
end
|
69
|
+
|
70
|
+
it "returns 401 with a header set when invalid authentication info is given" do
|
71
|
+
get '/_authenticate', {}, {
|
72
|
+
'HTTP_AUTHORIZATION' => 'foobar'
|
73
|
+
}
|
74
|
+
|
75
|
+
last_response.status.should == 401
|
76
|
+
last_response.headers['X-Gatekeeper-Authentication-Error'].should == 'invalid authentication info'
|
77
|
+
end
|
78
|
+
|
79
|
+
it "returns 401 with a header set when no authentication info is given" do
|
80
|
+
get '/_authenticate'
|
81
|
+
|
82
|
+
last_response.status.should == 401
|
83
|
+
last_response.headers['X-Gatekeeper-Authentication-Error'].should == 'no authentication info'
|
84
|
+
end
|
85
|
+
|
86
|
+
it "returns 200 with token data when valid authentication info is given" do
|
87
|
+
token_data = get_token
|
88
|
+
token_data.should include('expires')
|
89
|
+
token_data.should include('token')
|
90
|
+
end
|
91
|
+
|
92
|
+
it "returns 401 with a header set when no token is specifed" do
|
93
|
+
get '/foobars'
|
94
|
+
|
95
|
+
last_response.status.should == 401
|
96
|
+
last_response.headers['X-Gatekeeper-Authentication-Error'].should == 'no token specified'
|
97
|
+
end
|
98
|
+
|
99
|
+
it "returns 401 with a header set when an invalid token is specified" do
|
100
|
+
get '/foobars', {}, {
|
101
|
+
'HTTP_AUTH_TOKEN' => 'foobar'
|
102
|
+
}
|
103
|
+
|
104
|
+
last_response.status.should == 401
|
105
|
+
last_response.headers['X-Gatekeeper-Authentication-Error'].should == 'invalid token specified'
|
106
|
+
end
|
107
|
+
|
108
|
+
it "passes the request through when a valid token is specified" do
|
109
|
+
token_data = get_token
|
110
|
+
|
111
|
+
get '/domains', {}, {
|
112
|
+
'HTTP_AUTH_TOKEN' => token_data['token']
|
113
|
+
}
|
114
|
+
|
115
|
+
last_response.status.should == 200
|
116
|
+
end
|
117
|
+
|
118
|
+
it "passes the request through when a valid impersonation token is specified" do
|
119
|
+
token_data = get_token
|
120
|
+
impersonation_token_data = get_token
|
121
|
+
|
122
|
+
get '/domains', {}, {
|
123
|
+
'HTTP_AUTH_TOKEN' => token_data['token'],
|
124
|
+
'HTTP_X_IMPERSONATION_TOKEN' => impersonation_token_data['token']
|
125
|
+
}
|
126
|
+
|
127
|
+
last_response.status.should == 200
|
128
|
+
end
|
129
|
+
|
130
|
+
it "returns 401 with a header set when an invalid impersonation token is specified" do
|
131
|
+
token_data = get_token
|
132
|
+
|
133
|
+
get '/domains', {}, {
|
134
|
+
'HTTP_AUTH_TOKEN' => token_data['token'],
|
135
|
+
'HTTP_X_IMPERSONATION_TOKEN' => 'foobar'
|
136
|
+
}
|
137
|
+
|
138
|
+
last_response.status.should == 401
|
139
|
+
last_response.headers['X-Gatekeeper-Authentication-Error'].should == 'invalid impersonation token specified'
|
140
|
+
end
|
141
|
+
|
142
|
+
it "returns 401 with a header set when an invalid token is specified with a valid impersonation token" do
|
143
|
+
impersonation_token_data = get_token
|
144
|
+
|
145
|
+
get '/domains', {}, {
|
146
|
+
'HTTP_AUTH_TOKEN' => 'foobar',
|
147
|
+
'HTTP_X_IMPERSONATION_TOKEN' => impersonation_token_data['token']
|
148
|
+
}
|
149
|
+
|
150
|
+
last_response.status.should == 401
|
151
|
+
last_response.headers['X-Gatekeeper-Authentication-Error'].should == 'invalid token specified'
|
152
|
+
end
|
153
|
+
|
154
|
+
it "returns 401 with a header set when an expired token is specified" do
|
155
|
+
token_data = get_token
|
156
|
+
|
157
|
+
expire_token(token_data['token'])
|
158
|
+
|
159
|
+
get '/domains', {}, {
|
160
|
+
'HTTP_AUTH_TOKEN' => token_data['token'],
|
161
|
+
}
|
162
|
+
|
163
|
+
last_response.status.should == 401
|
164
|
+
last_response.headers['X-Gatekeeper-Authentication-Error'].should == 'expired token specified'
|
165
|
+
end
|
166
|
+
|
167
|
+
it "returns 401 with a header set when an expired impersonation token is specified" do
|
168
|
+
token_data = get_token
|
169
|
+
impersonation_token_data = get_token
|
170
|
+
expire_token(impersonation_token_data['token'])
|
171
|
+
|
172
|
+
get '/domains', {}, {
|
173
|
+
'HTTP_AUTH_TOKEN' => token_data['token'],
|
174
|
+
'HTTP_X_IMPERSONATION_TOKEN' => impersonation_token_data['token']
|
175
|
+
}
|
176
|
+
|
177
|
+
last_response.status.should == 401
|
178
|
+
last_response.headers['X-Gatekeeper-Authentication-Error'].should == 'expired impersonation token specified'
|
179
|
+
end
|
180
|
+
|
181
|
+
it "returns 401 with a header set when an expired token and a non-expired impersonation token are specified" do
|
182
|
+
token_data = get_token
|
183
|
+
impersonation_token_data = get_token
|
184
|
+
expire_token(token_data['token'])
|
185
|
+
|
186
|
+
get '/domains', {}, {
|
187
|
+
'HTTP_AUTH_TOKEN' => token_data['token'],
|
188
|
+
'HTTP_X_IMPERSONATION_TOKEN' => impersonation_token_data['token']
|
189
|
+
}
|
190
|
+
|
191
|
+
last_response.status.should == 401
|
192
|
+
last_response.headers['X-Gatekeeper-Authentication-Error'].should == 'expired token specified'
|
193
|
+
end
|
194
|
+
|
195
|
+
it "returns 401 with a header set when using a valid token without sufficient access" do
|
196
|
+
token_data = get_token
|
197
|
+
restrict_token(token_data['token'])
|
198
|
+
|
199
|
+
get '/domains', {}, {
|
200
|
+
'HTTP_AUTH_TOKEN' => token_data['token']
|
201
|
+
}
|
202
|
+
|
203
|
+
last_response.status.should == 401
|
204
|
+
last_response.headers['X-Gatekeeper-Authentication-Error'].should == 'token has insufficient access'
|
205
|
+
end
|
206
|
+
|
207
|
+
it "returns 401 with a header set when using a valid impersonation token without sufficient access" do
|
208
|
+
token_data = get_token
|
209
|
+
impersonation_token_data = get_token
|
210
|
+
restrict_token(impersonation_token_data['token'])
|
211
|
+
|
212
|
+
get '/domains', {}, {
|
213
|
+
'HTTP_AUTH_TOKEN' => token_data['token'],
|
214
|
+
'HTTP_X_IMPERSONATION_TOKEN' => impersonation_token_data['token']
|
215
|
+
}
|
216
|
+
|
217
|
+
last_response.status.should == 401
|
218
|
+
last_response.headers['X-Gatekeeper-Authentication-Error'].should == 'impersonation token has insufficient access'
|
219
|
+
end
|
220
|
+
|
221
|
+
describe "/_effective_access_controls" do
|
222
|
+
it "returns the effective access controls when given a valid token" do
|
223
|
+
token_data = get_token
|
224
|
+
|
225
|
+
get '/_effective_access_controls', {}, {
|
226
|
+
'HTTP_AUTH_TOKEN' => token_data['token']
|
227
|
+
}
|
228
|
+
|
229
|
+
last_response.status.should == 200
|
230
|
+
last_response.content_type.should == 'application/json'
|
231
|
+
JSON.parse(last_response.body).should == {
|
232
|
+
"xdna://"=>["GET", "HEAD", "PUT", "POST", "DELETE", "OPTIONS"]
|
233
|
+
}
|
234
|
+
end
|
235
|
+
|
236
|
+
it "returns the effective access controls when given a valid token and a valid impersonation token" do
|
237
|
+
token_data = get_token
|
238
|
+
impersonation_token_data = get_token
|
239
|
+
restrict_token(impersonation_token_data['token'])
|
240
|
+
|
241
|
+
get '/_effective_access_controls', {}, {
|
242
|
+
'HTTP_AUTH_TOKEN' => token_data['token'],
|
243
|
+
'HTTP_X_IMPERSONATION_TOKEN' => impersonation_token_data['token']
|
244
|
+
}
|
245
|
+
|
246
|
+
last_response.status.should == 200
|
247
|
+
last_response.content_type.should == 'application/json'
|
248
|
+
JSON.parse(last_response.body).should == {}
|
249
|
+
end
|
250
|
+
|
251
|
+
it "returns 401 with a header set when no token is specifed" do
|
252
|
+
get '/_effective_access_controls'
|
253
|
+
|
254
|
+
last_response.status.should == 401
|
255
|
+
last_response.headers['X-Gatekeeper-Authentication-Error'].should == 'no token specified'
|
256
|
+
end
|
257
|
+
|
258
|
+
it "returns 401 with a header set when an invalid token is specified" do
|
259
|
+
get '/_effective_access_controls', {}, {
|
260
|
+
'HTTP_AUTH_TOKEN' => 'foobar'
|
261
|
+
}
|
262
|
+
|
263
|
+
last_response.status.should == 401
|
264
|
+
last_response.headers['X-Gatekeeper-Authentication-Error'].should == 'invalid token specified'
|
265
|
+
end
|
266
|
+
|
267
|
+
it "returns 401 with a header set when an invalid impersonation token is specified" do
|
268
|
+
token_data = get_token
|
269
|
+
|
270
|
+
get '/_effective_access_controls', {}, {
|
271
|
+
'HTTP_AUTH_TOKEN' => token_data['token'],
|
272
|
+
'HTTP_X_IMPERSONATION_TOKEN' => 'foobar'
|
273
|
+
}
|
274
|
+
|
275
|
+
last_response.status.should == 401
|
276
|
+
last_response.headers['X-Gatekeeper-Authentication-Error'].should == 'invalid impersonation token specified'
|
277
|
+
end
|
278
|
+
|
279
|
+
it "returns 401 with a header set when an invalid token is specified with a valid impersonation token" do
|
280
|
+
impersonation_token_data = get_token
|
281
|
+
|
282
|
+
get '/_effective_access_controls', {}, {
|
283
|
+
'HTTP_AUTH_TOKEN' => 'foobar',
|
284
|
+
'HTTP_X_IMPERSONATION_TOKEN' => impersonation_token_data['token']
|
285
|
+
}
|
286
|
+
|
287
|
+
last_response.status.should == 401
|
288
|
+
last_response.headers['X-Gatekeeper-Authentication-Error'].should == 'invalid token specified'
|
289
|
+
end
|
290
|
+
|
291
|
+
it "returns 401 with a header set when an expired token is specified" do
|
292
|
+
token_data = get_token
|
293
|
+
|
294
|
+
expire_token(token_data['token'])
|
295
|
+
|
296
|
+
get '/_effective_access_controls', {}, {
|
297
|
+
'HTTP_AUTH_TOKEN' => token_data['token'],
|
298
|
+
}
|
299
|
+
|
300
|
+
last_response.status.should == 401
|
301
|
+
last_response.headers['X-Gatekeeper-Authentication-Error'].should == 'expired token specified'
|
302
|
+
end
|
303
|
+
|
304
|
+
it "returns 401 with a header set when an expired impersonation token is specified" do
|
305
|
+
token_data = get_token
|
306
|
+
impersonation_token_data = get_token
|
307
|
+
expire_token(impersonation_token_data['token'])
|
308
|
+
|
309
|
+
get '/_effective_access_controls', {}, {
|
310
|
+
'HTTP_AUTH_TOKEN' => token_data['token'],
|
311
|
+
'HTTP_X_IMPERSONATION_TOKEN' => impersonation_token_data['token']
|
312
|
+
}
|
313
|
+
|
314
|
+
last_response.status.should == 401
|
315
|
+
last_response.headers['X-Gatekeeper-Authentication-Error'].should == 'expired impersonation token specified'
|
316
|
+
end
|
317
|
+
|
318
|
+
it "returns 401 with a header set when an expired token and a non-expired impersonation token are specified" do
|
319
|
+
token_data = get_token
|
320
|
+
impersonation_token_data = get_token
|
321
|
+
expire_token(token_data['token'])
|
322
|
+
|
323
|
+
get '/_effective_access_controls', {}, {
|
324
|
+
'HTTP_AUTH_TOKEN' => token_data['token'],
|
325
|
+
'HTTP_X_IMPERSONATION_TOKEN' => impersonation_token_data['token']
|
326
|
+
}
|
327
|
+
|
328
|
+
last_response.status.should == 401
|
329
|
+
last_response.headers['X-Gatekeeper-Authentication-Error'].should == 'expired token specified'
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
describe EY::GateKeeper::Client::Consumer do
|
4
|
+
subject { EY::GateKeeper.new }
|
5
|
+
|
6
|
+
it "properly authenticates with the master user" do
|
7
|
+
subject.get('/domains').status.should == 200
|
8
|
+
end
|
9
|
+
|
10
|
+
it "properly clears the mock store" do
|
11
|
+
subject.post('/test_class', {}, { 'foo' => 'bar' }.to_json)
|
12
|
+
JSON.parse(subject.get('/test_class').body)['total'].should == 1
|
13
|
+
|
14
|
+
EY::GateKeeper.reset!
|
15
|
+
|
16
|
+
subject.get('/test_class').status.should == 401
|
17
|
+
end
|
18
|
+
|
19
|
+
it "automatically fetches a new token when the client sees it has expired" do
|
20
|
+
subject.get('/domains').status.should == 200
|
21
|
+
first_token = subject.token.token
|
22
|
+
|
23
|
+
subject.token.token_data['expires'] = 0
|
24
|
+
|
25
|
+
subject.get('/domains').status.should == 200
|
26
|
+
subject.token.token.should_not == first_token
|
27
|
+
end
|
28
|
+
|
29
|
+
it "automatically fetches a new token when the server says it has expired" do
|
30
|
+
subject.get('/domains').status.should == 200
|
31
|
+
first_token = subject.token.token
|
32
|
+
|
33
|
+
expire_token(first_token)
|
34
|
+
|
35
|
+
subject.get('/domains').status.should == 200
|
36
|
+
subject.token.token.should_not == first_token
|
37
|
+
end
|
38
|
+
|
39
|
+
it "doesn't automatically fetch a new token when the server says it has insufficient access" do
|
40
|
+
subject.get('/domains').status.should == 200
|
41
|
+
token = subject.token.token
|
42
|
+
|
43
|
+
restrict_token(token)
|
44
|
+
|
45
|
+
subject.get('/domains').status.should == 401
|
46
|
+
subject.token.token.should == token
|
47
|
+
end
|
48
|
+
|
49
|
+
it "doesn't automatically fetch a new token when the server says the impersonation token has insufficient access" do
|
50
|
+
impersonation_token_data = get_token
|
51
|
+
subject.impersonate! impersonation_token_data['token']
|
52
|
+
|
53
|
+
subject.get('/domains').status.should == 200
|
54
|
+
token = subject.token.token
|
55
|
+
|
56
|
+
restrict_token(impersonation_token_data['token'])
|
57
|
+
|
58
|
+
subject.get('/domains').status.should == 401
|
59
|
+
subject.token.token.should == token
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "#effective_access_controls" do
|
63
|
+
it "returns the effective access controls of the session" do
|
64
|
+
subject.effective_access_controls.should == {
|
65
|
+
"xdna://"=>["GET", "HEAD", "PUT", "POST", "DELETE", "OPTIONS"]
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
it "returns an empty hash if something goes wrong" do
|
70
|
+
subject.get('/domains').status.should == 200
|
71
|
+
subject.token.token_data['token'] = 'foobar'
|
72
|
+
|
73
|
+
subject.effective_access_controls.should == {}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def auth_store
|
78
|
+
@store ||= EY::GateKeeper::AuthStore.new
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
describe EY::GateKeeper::Client::Consumer do
|
4
|
+
|
5
|
+
context "with invalid credentials" do
|
6
|
+
subject { EY::GateKeeper.new('foo', 'bar') }
|
7
|
+
it "responds with a 401" do
|
8
|
+
subject.get('/domains').status.should == 401
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context "with the master credentials" do
|
13
|
+
subject { EY::GateKeeper.new }
|
14
|
+
it "responds with a 200" do
|
15
|
+
subject.get('/domains').status.should == 200
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "with valid credentials" do
|
20
|
+
subject { EY::GateKeeper.new('foo','bar') }
|
21
|
+
before do
|
22
|
+
key_data = {
|
23
|
+
'key' => 'foo',
|
24
|
+
'secret' => 'bar',
|
25
|
+
'access_controls' => {
|
26
|
+
'xdna://domains' => ['GET'],
|
27
|
+
'xdna://domains/1' => ['GET', 'PUT'],
|
28
|
+
'xdna://domains/2' => ['PUT'],
|
29
|
+
'xdna://pools?search' => ['GET'],
|
30
|
+
'xdna://pools/1' => ['PUT', 'GET'],
|
31
|
+
'xdna://pools/2' => ['PUT']
|
32
|
+
},
|
33
|
+
'cloudkit_user' => 'xcloud',
|
34
|
+
'user' => 'id://foo'
|
35
|
+
}.to_json
|
36
|
+
store = EY::GateKeeper::AuthStore.new
|
37
|
+
store.post('/gatekeeper_credentials', :json => key_data)
|
38
|
+
end
|
39
|
+
|
40
|
+
context "with permissions to get a collection" do
|
41
|
+
it "responds with a 200" do
|
42
|
+
subject.get('/domains').status.should == 200
|
43
|
+
end
|
44
|
+
|
45
|
+
it "filters results" do
|
46
|
+
subject.put('/domains/1', {}, { 'foo' => 'bar' }.to_json)
|
47
|
+
subject.put('/domains/2', {}, { 'foo' => 'bar' }.to_json)
|
48
|
+
|
49
|
+
JSON.parse(subject.get('/domains').body)['uris'].should == ['/domains/1']
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "with permissions to only search a collection" do
|
54
|
+
before do
|
55
|
+
subject.put('/pools/1', {}, { 'foo' => 'bar' }.to_json)
|
56
|
+
subject.put('/pools/2', {}, { 'foo' => 'bar' }.to_json)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "responds with a 401 when getting the collection itself" do
|
60
|
+
subject.get('/pools').status.should == 401
|
61
|
+
end
|
62
|
+
|
63
|
+
it "filters results" do
|
64
|
+
query = URI.escape({ 'foo' => 'bar' }.to_json)
|
65
|
+
JSON.parse(subject.get("/pools?search=#{query}").body)['uris'].should == ['/pools/1']
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "without permissions to get a collection" do
|
70
|
+
it "responds with a 401" do
|
71
|
+
subject.get('/instances').status.should == 401
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|