hoodoo 1.0.4 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,651 @@
1
+ # The X-Assume-Identity-Of secured HTTP header allows a caller to
2
+ # masquerade as some other identity according to scoping limitations.
3
+ # Since this bypasses some of the deliberate restrictions on identity
4
+ # through a Caller, it's important to test it extensively so this
5
+ # file exists for that purpose alone.
6
+ #
7
+ # The inter-resource local and remote specs are also involved to make
8
+ # sure that the header value is auto-propagated across inter-resource
9
+ # calls. This is obviously vital, else the identity of a caller may
10
+ # seem to change across the inter-resource boundary.
11
+
12
+ require 'spec_helper'
13
+
14
+ # ============================================================================
15
+
16
+ class RSpecAssumedIdentityImplementation < Hoodoo::Services::Implementation
17
+
18
+ # This implementation returns the identity hash from the Hoodoo session
19
+ # information under the body data key of "identity". If there is a
20
+ # "caller_id" key present in the Hoodoo identity is it removed first.
21
+ #
22
+ def show( context )
23
+ identity_hash = Hoodoo::Utilities.stringify( context.session.identity.to_h )
24
+ identity_hash.delete( 'caller_id' )
25
+
26
+ context.response.body = {
27
+ 'identity' => identity_hash
28
+ }
29
+ end
30
+ end
31
+
32
+ class RSpecAssumedIdentityInterface < Hoodoo::Services::Interface
33
+ interface :RSpecAssumedIdentity do
34
+ endpoint :rspec_assumed_identity, RSpecAssumedIdentityImplementation
35
+ actions :show
36
+ end
37
+ end
38
+
39
+ class RSpecAssumedIdentityService < Hoodoo::Services::Service
40
+ comprised_of RSpecAssumedIdentityInterface
41
+ end
42
+
43
+ # ============================================================================
44
+
45
+ describe Hoodoo::Services::Middleware do
46
+
47
+ before :each do
48
+ @old_test_session = Hoodoo::Services::Middleware.test_session()
49
+
50
+ # Set up a permissive test session that's ready to have scoping and
51
+ # identity information updated.
52
+ #
53
+ @test_session = @old_test_session.dup
54
+
55
+ permissions = Hoodoo::Services::Permissions.new # (this is "default-else-deny")
56
+ permissions.set_default_fallback( Hoodoo::Services::Permissions::ALLOW )
57
+
58
+ @test_session.permissions = permissions
59
+ @test_session.identity = OpenStruct.new
60
+ @test_session.scoping = @test_session.scoping.dup
61
+ end
62
+
63
+ after :each do
64
+ Hoodoo::Services::Middleware.set_test_session( @old_test_session )
65
+ end
66
+
67
+ def app
68
+ Rack::Builder.new do
69
+ use Hoodoo::Services::Middleware
70
+ run RSpecAssumedIdentityService.new
71
+ end
72
+ end
73
+
74
+ # Calls the test service using a given identity Hash (which it encodes
75
+ # for the HTTP header) and expecting the given HTTP status code (as a
76
+ # String or Integer). If expecting 200, the response from the test
77
+ # service is examined as the "identity" key in the response body should
78
+ # yield the same identity hash that was given on input, if the
79
+ # middleware correctly decoded and transferred the encoded value all
80
+ # the way through to the called service.
81
+ #
82
+ # +identity_hash+:: Hash for the 'assume identity' header, String keys
83
+ # and values only
84
+ #
85
+ # +expected_status+:: HTTP status to expect(), as a String or Integer.
86
+ #
87
+ # Always returns the JSON-parsed response body for further examination.
88
+ #
89
+ def show( identity_hash, expected_status )
90
+ get(
91
+ '/v1/rspec_assumed_identity/hello',
92
+ nil,
93
+ {
94
+ 'CONTENT_TYPE' => 'application/json; charset=utf-8',
95
+ 'HTTP_X_ASSUME_IDENTITY_OF' => URI.encode_www_form( identity_hash )
96
+ }
97
+ )
98
+
99
+ expect( last_response.status ).to eq( expected_status )
100
+ result = JSON.parse( last_response.body )
101
+
102
+ if ( expected_status == 200 )
103
+ expect( result[ 'identity' ] ).to eq( identity_hash )
104
+ end
105
+
106
+ return result
107
+ end
108
+
109
+ # ==========================================================================
110
+
111
+ # Tested implicitly elsewhere and explicitly here to make sure.
112
+ #
113
+ context 'X-Assume-Identity-Of prohibited' do
114
+ before :each do
115
+ @test_session.scoping.authorised_http_headers = []
116
+ Hoodoo::Services::Middleware.set_test_session( @test_session )
117
+ end
118
+
119
+ it 'rejects usage attempts' do
120
+ result = show( { 'account_id' => 'bad' }, 403 )
121
+
122
+ # We expect a generic - arguably even confusing - 'platform.forbidden'
123
+ # message to avoid information disclosure that someone has guessed /
124
+ # potentially correctly tried to use a secured header, but for their
125
+ # caller credentials prohibiting its use.
126
+ #
127
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
128
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
129
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'Action not authorized' )
130
+ end
131
+ end
132
+
133
+ # ==========================================================================
134
+
135
+ context 'X-Assume-Identity-Of allowed' do
136
+ context 'with empty rules' do
137
+ before :each do
138
+ @test_session.scoping.authorised_http_headers = [ 'X-Assume-Identity-Of' ]
139
+ @test_session.scoping.authorised_identities = {}
140
+
141
+ Hoodoo::Services::Middleware.set_test_session( @test_session )
142
+ end
143
+
144
+ it 'rejects any values' do
145
+ result = show( { 'account_id' => '1', 'member_id' => 1 }, 403 )
146
+
147
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
148
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
149
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests prohibited identity name(s)' )
150
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'account_id\\,member_id' )
151
+ end
152
+ end
153
+
154
+ context 'with flat rules' do
155
+ before :each do
156
+ @test_session.scoping.authorised_http_headers = [ 'X-Assume-Identity-Of' ]
157
+ @test_session.scoping.authorised_identities =
158
+ {
159
+ 'account_id' => [ '20', '21', '22' ],
160
+ 'member_id' => [ '1', '2', '3', '4', '5', '6' ],
161
+ 'device_id' => [ 'A', 'B' ]
162
+ }
163
+
164
+ Hoodoo::Services::Middleware.set_test_session( @test_session )
165
+ end
166
+
167
+ it 'rejects bad account ID' do
168
+ result = show( { 'account_id' => 'bad' }, 403 )
169
+
170
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
171
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
172
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests a prohibited identity quantity' )
173
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'account_id,bad' )
174
+ end
175
+
176
+ it 'rejects bad member ID' do
177
+ result = show( { 'member_id' => 'bad' }, 403 )
178
+
179
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
180
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
181
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests a prohibited identity quantity' )
182
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'member_id,bad' )
183
+ end
184
+
185
+ it 'rejects bad device ID' do
186
+ result = show( { 'device_id' => 'bad' }, 403 )
187
+
188
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
189
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
190
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests a prohibited identity quantity' )
191
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'device_id,bad' )
192
+ end
193
+
194
+ # Belt-and-braces check that multiple bad items are still rejected,
195
+ # but don't have any expectations about which one gets picked out
196
+ # in the 'reference' field.
197
+ #
198
+ it 'rejects bad combinations' do
199
+ result = show( { 'account_id' => 'bad', 'member_id' => 'bad', 'device_id' => 'bad' }, 403 )
200
+
201
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
202
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
203
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests a prohibited identity quantity' )
204
+ end
205
+
206
+ it 'rejects bad IDs amongst good' do
207
+ result = show( { 'account_id' => '21', 'member_id' => 'bad', 'device_id' => 'A' }, 403 )
208
+
209
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
210
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
211
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests a prohibited identity quantity' )
212
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'member_id,bad' )
213
+ end
214
+
215
+ # Each 'show' must be in its own test so that the test session data
216
+ # gets reset in between; otherwise, the *same* session identity is
217
+ # being successively merged/updated under test, since it's a single
218
+ # object that's reused rather than a new loaded-in session.
219
+ #
220
+ it 'accepts one good ID (1)' do
221
+ result = show( { 'account_id' => '22' }, 200 )
222
+ end
223
+ it 'accepts one good ID (2)' do
224
+ result = show( { 'member_id' => '1' }, 200 )
225
+ end
226
+ it 'accepts one good ID (3)' do
227
+ result = show( { 'device_id' => 'B' }, 200 )
228
+ end
229
+
230
+ it 'accepts many good IDs' do
231
+ result = show( { 'account_id' => '22', 'member_id' => '1', 'device_id' => 'B' }, 200 )
232
+ end
233
+
234
+ it 'accepts encoded names' do
235
+ get(
236
+ '/v1/rspec_assumed_identity/hello',
237
+ nil,
238
+ {
239
+ 'CONTENT_TYPE' => 'application/json; charset=utf-8',
240
+ 'HTTP_X_ASSUME_IDENTITY_OF' => 'a%63%63ount_id=22'
241
+ }
242
+ )
243
+
244
+ expect( last_response.status ).to eq( 200 )
245
+ end
246
+
247
+ it 'accepts encoded values' do
248
+ get(
249
+ '/v1/rspec_assumed_identity/hello',
250
+ nil,
251
+ {
252
+ 'CONTENT_TYPE' => 'application/json; charset=utf-8',
253
+ 'HTTP_X_ASSUME_IDENTITY_OF' => 'account_id=%32%32'
254
+ }
255
+ )
256
+
257
+ expect( last_response.status ).to eq( 200 )
258
+ end
259
+
260
+ it 'rejects an unknown name' do
261
+ result = show( { 'another_id' => 'A155C' }, 403 )
262
+
263
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
264
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
265
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests prohibited identity name(s)' )
266
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'another_id' )
267
+ end
268
+
269
+ it 'rejects unknown names' do
270
+ result = show( { 'another_id' => 'A155C', 'additional_id' => 'iiv' }, 403 )
271
+
272
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
273
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
274
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests prohibited identity name(s)' )
275
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'additional_id\\,another_id' )
276
+ end
277
+
278
+ it 'rejects an unknown name amongst a known name' do
279
+ result = show( { 'another_id' => 'A155C', 'account_id' => '22' }, 403 )
280
+
281
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
282
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
283
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests prohibited identity name(s)' )
284
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'another_id' )
285
+ end
286
+
287
+ it 'rejects an unknown name amongst known names' do
288
+ result = show( { 'another_id' => 'A155C', 'account_id' => '22', 'member_id' => '1' }, 403 )
289
+
290
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
291
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
292
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests prohibited identity name(s)' )
293
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'another_id' )
294
+ end
295
+ end
296
+
297
+ context 'with deep rules' do
298
+ before :each do
299
+ @test_session.scoping.authorised_http_headers = [ 'X-Assume-Identity-Of' ]
300
+ @test_session.scoping.authorised_identities =
301
+ {
302
+ 'account_id' =>
303
+ {
304
+ '20' => { 'member_id' => [ '1', '2' ] },
305
+ '21' => { 'member_id' => [ '3', '4' ] },
306
+ '22' =>
307
+ {
308
+ 'member_id' =>
309
+ {
310
+ '5' => { 'device_id' => [ 'A' ] },
311
+ '6' => { 'device_id' => [ 'B' ] }
312
+ }
313
+ }
314
+ }
315
+ }
316
+
317
+ Hoodoo::Services::Middleware.set_test_session( @test_session )
318
+ end
319
+
320
+ it 'rejects bad account ID' do
321
+ result = show( { 'account_id' => 'bad' }, 403 )
322
+
323
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
324
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
325
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests a prohibited identity quantity' )
326
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'account_id,bad' )
327
+ end
328
+
329
+ it 'rejects bad member ID' do
330
+ result = show( { 'account_id' => '20', 'member_id' => 'bad' }, 403 )
331
+
332
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
333
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
334
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests a prohibited identity quantity' )
335
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'member_id,bad' )
336
+ end
337
+
338
+ it 'rejects bad device ID' do
339
+ result = show( { 'account_id' => '22', 'member_id' => '5', 'device_id' => 'bad' }, 403 )
340
+
341
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
342
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
343
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests a prohibited identity quantity' )
344
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'device_id,bad' )
345
+ end
346
+
347
+ it 'rejects attempt to use device ID when not listed in rules' do
348
+ result = show( { 'account_id' => '21', 'member_id' => '4', 'device_id' => 'A' }, 403 )
349
+
350
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
351
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
352
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests prohibited identity name(s)' )
353
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'device_id' )
354
+ end
355
+
356
+ it 'rejects an ID that is present but listed under a different key' do
357
+ result = show( { 'account_id' => '20', 'member_id' => '4' }, 403 )
358
+
359
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
360
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
361
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests a prohibited identity quantity' )
362
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'member_id,4' )
363
+ end
364
+
365
+ it 'rejects an ID that is present but not top-level' do
366
+ result = show( { 'member_id' => '1' }, 403 )
367
+
368
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
369
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
370
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests prohibited identity name(s)' )
371
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'member_id' )
372
+ end
373
+
374
+ # Each 'show' must be in its own test so that the test session data
375
+ # gets reset in between; otherwise, the *same* session identity is
376
+ # being successively merged/updated under test, since it's a single
377
+ # object that's reused rather than a new loaded-in session.
378
+ #
379
+ it 'accepts a subset of good IDs (1)' do
380
+ result = show( { 'account_id' => '22' }, 200 )
381
+ end
382
+ it 'accepts a subset of good IDs (2)' do
383
+ result = show( { 'account_id' => '22', 'member_id' => '5' }, 200 )
384
+ end
385
+ it 'accepts many good IDs (1)' do
386
+ result = show( { 'account_id' => '20', 'member_id' => '2' }, 200 )
387
+ end
388
+ it 'accepts many good IDs (2)' do
389
+ result = show( { 'account_id' => '22', 'member_id' => '6', 'device_id' => 'B' }, 200 )
390
+ end
391
+
392
+ it 'rejects an unknown name' do
393
+ result = show( { 'another_id' => 'A155C' }, 403 )
394
+
395
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
396
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
397
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests prohibited identity name(s)' )
398
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'another_id' )
399
+ end
400
+
401
+ it 'rejects unknown names' do
402
+ result = show( { 'another_id' => 'A155C', 'additional_id' => 'iiv' }, 403 )
403
+
404
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
405
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
406
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests prohibited identity name(s)' )
407
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'additional_id\\,another_id' )
408
+ end
409
+
410
+ it 'rejects an unknown name amongst a known name' do
411
+ result = show( { 'another_id' => 'A155C', 'account_id' => '22' }, 403 )
412
+
413
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
414
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
415
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests prohibited identity name(s)' )
416
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'another_id' )
417
+ end
418
+
419
+ it 'rejects an unknown name amongst known names' do
420
+ result = show( { 'another_id' => 'A155C', 'account_id' => '22', 'member_id' => '6' }, 403 )
421
+
422
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
423
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
424
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'X-Assume-Identity-Of header value requests prohibited identity name(s)' )
425
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'another_id' )
426
+ end
427
+
428
+ end
429
+
430
+ context 'with malformed rules' do
431
+ def set_rules( rules )
432
+ @test_session.scoping.authorised_http_headers = [ 'X-Assume-Identity-Of' ]
433
+ @test_session.scoping.authorised_identities = rules
434
+ Hoodoo::Services::Middleware.set_test_session( @test_session )
435
+ end
436
+
437
+ def expect_malformed( result )
438
+ expect( result[ 'kind' ] ).to eq( 'Errors' )
439
+ expect( result[ 'errors' ][ 0 ][ 'code' ] ).to eq( 'generic.malformed' )
440
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( "X-Assume-Identity-Of header cannot be processed because of malformed scoping rules in Session's associated Caller" )
441
+ end
442
+
443
+ it 'rejects top-level non-Hash (1)' do
444
+ set_rules( [ '1', '2', '3' ] )
445
+ result = show( { 'account_id' => '21' }, 422 )
446
+ expect_malformed( result )
447
+ end
448
+
449
+ it 'rejects top-level non-Hash (2)' do
450
+ set_rules( 'String' )
451
+ result = show( { 'account_id' => '21' }, 422 )
452
+ expect_malformed( result )
453
+ end
454
+
455
+ it 'rejects top-level value where array was expected' do
456
+ set_rules( {
457
+ 'account_id' => '21'
458
+ } )
459
+
460
+ result = show( { 'account_id' => '21' }, 422 )
461
+ expect_malformed( result )
462
+ end
463
+
464
+ it 'rejects intermediate value where Hash was expected' do
465
+ set_rules( {
466
+ 'account_id' =>
467
+ {
468
+ '20' => 'member_id'
469
+ }
470
+ } )
471
+
472
+ result = show( { 'account_id' => '20', 'member_id' => '1' }, 422 )
473
+ expect_malformed( result )
474
+ end
475
+
476
+ it 'rejects deep value where Array was expected' do
477
+ set_rules( {
478
+ 'account_id' =>
479
+ {
480
+ '22' =>
481
+ {
482
+ 'member_id' =>
483
+ {
484
+ '5' => { 'device_id' => 'A' }
485
+ }
486
+ }
487
+ }
488
+ } )
489
+
490
+ result = show( { 'account_id' => '22', 'member_id' => '5', 'device_id' => 'A' }, 422 )
491
+ expect_malformed( result )
492
+ end
493
+ end
494
+
495
+ # All of these should result in 403 cases because they parse into
496
+ # identity hashes with either an empty key or empty value, or a
497
+ # key or value that's not allowed by the rules. The exception is
498
+ # for the pure empty string header value or empty Hash, which
499
+ # gets caught explicitly as malformed input.
500
+ #
501
+ context 'but header value is malformed' do
502
+ before :each do
503
+ @test_session.scoping.authorised_http_headers = [ 'X-Assume-Identity-Of' ]
504
+ @test_session.scoping.authorised_identities =
505
+ {
506
+ 'account_id' => [ '20', '21', '22' ],
507
+ 'member_id' => [ '1', '2', '3', '4', '5', '6' ],
508
+ 'device_id' => [ 'A', 'B' ]
509
+ }
510
+
511
+ Hoodoo::Services::Middleware.set_test_session( @test_session )
512
+ end
513
+
514
+ it 'rejects explicit empty hashes (1)' do
515
+ get(
516
+ '/v1/rspec_assumed_identity/hello',
517
+ nil,
518
+ {
519
+ 'CONTENT_TYPE' => 'application/json; charset=utf-8',
520
+ 'HTTP_X_ASSUME_IDENTITY_OF' => ''
521
+ }
522
+ )
523
+
524
+ expect( last_response.status ).to eq( 422 )
525
+ end
526
+
527
+ # Likewise via a Hash.
528
+ #
529
+ it 'rejects explicit empty hashes (2)' do
530
+ result = show( {}, 422 )
531
+ end
532
+
533
+ it 'rejects no-key headers (1)' do
534
+ get(
535
+ '/v1/rspec_assumed_identity/hello',
536
+ nil,
537
+ {
538
+ 'CONTENT_TYPE' => 'application/json; charset=utf-8',
539
+ 'HTTP_X_ASSUME_IDENTITY_OF' => '=22'
540
+ }
541
+ )
542
+
543
+ expect( last_response.status ).to eq( 403 )
544
+
545
+ result = JSON.parse( last_response.body )
546
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( '' )
547
+ end
548
+
549
+ # Likewise via a Hash.
550
+ #
551
+ it 'rejects no-key headers (2)' do
552
+ result = show( { '' => '22' }, 403 )
553
+ end
554
+
555
+ it 'rejects no-key value (1)' do
556
+ get(
557
+ '/v1/rspec_assumed_identity/hello',
558
+ nil,
559
+ {
560
+ 'CONTENT_TYPE' => 'application/json; charset=utf-8',
561
+ 'HTTP_X_ASSUME_IDENTITY_OF' => 'account_id='
562
+ }
563
+ )
564
+
565
+ expect( last_response.status ).to eq( 403 )
566
+
567
+ result = JSON.parse( last_response.body )
568
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'account_id,' )
569
+ end
570
+
571
+ # Likewise via a Hash.
572
+ #
573
+ it 'rejects no-key value (2)' do
574
+ result = show( { 'account_id' => '' }, 403 )
575
+ end
576
+
577
+ it 'rejects a non-KVP value' do
578
+ get(
579
+ '/v1/rspec_assumed_identity/hello',
580
+ nil,
581
+ {
582
+ 'CONTENT_TYPE' => 'application/json; charset=utf-8',
583
+ 'HTTP_X_ASSUME_IDENTITY_OF' => 'foo'
584
+ }
585
+ )
586
+
587
+ expect( last_response.status ).to eq( 403 )
588
+
589
+ result = JSON.parse( last_response.body )
590
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'foo' )
591
+ end
592
+
593
+ it 'rejects a badly encoded value' do
594
+ get(
595
+ '/v1/rspec_assumed_identity/hello',
596
+ nil,
597
+ {
598
+ 'CONTENT_TYPE' => 'application/json; charset=utf-8',
599
+ 'HTTP_X_ASSUME_IDENTITY_OF' => 'account_id=%4'
600
+ }
601
+ )
602
+
603
+ expect( last_response.status ).to eq( 403 )
604
+
605
+ result = JSON.parse( last_response.body )
606
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'account_id,%4' )
607
+ end
608
+
609
+ it 'rejects an encoded "=" because it is taken as a literal, not a separator' do
610
+ get(
611
+ '/v1/rspec_assumed_identity/hello',
612
+ nil,
613
+ {
614
+ 'CONTENT_TYPE' => 'application/json; charset=utf-8',
615
+ 'HTTP_X_ASSUME_IDENTITY_OF' => 'account_id%3d22'
616
+ }
617
+ )
618
+
619
+ expect( last_response.status ).to eq( 403 )
620
+
621
+ result = JSON.parse( last_response.body )
622
+ expect( result[ 'errors' ][ 0 ][ 'reference' ] ).to eq( 'account_id=22' )
623
+ end
624
+ end
625
+ end
626
+
627
+ # ==========================================================================
628
+
629
+ context 'code coverage' do
630
+ before :each do
631
+ @test_session.scoping.authorised_http_headers = [ 'X-Assume-Identity-Of' ]
632
+ @test_session.scoping.authorised_identities = { 'account_id' => [ '1' ] }
633
+ Hoodoo::Services::Middleware.set_test_session( @test_session )
634
+ end
635
+
636
+ it 'internally self-checks' do
637
+ allow_any_instance_of( Hoodoo::Services::Middleware ).to receive( :deal_with_x_assume_identity_of ) do | instance, interaction |
638
+ instance.send(
639
+ :validate_x_assume_identity_of,
640
+ interaction,
641
+ { 'account_id' => 23 },
642
+ @test_session.scoping.authorised_identities
643
+ )
644
+ end
645
+
646
+ result = show( { 'account_id' => '1' }, 500 )
647
+ expect( result[ 'errors' ][ 0 ][ 'message' ] ).to eq( 'Internal error - internal validation input value for X-Assume-Identity-Of is not a String' )
648
+ end
649
+ end
650
+
651
+ end