hoodoo 1.0.4 → 1.0.5

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.
@@ -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