1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace AerialShip\SamlSPBundle\Tests\Security\Http; |
4
|
|
|
|
5
|
|
|
use AerialShip\LightSaml\Model\Assertion\Attribute; |
6
|
|
|
use AerialShip\LightSaml\Model\Assertion\AuthnStatement; |
7
|
|
|
use AerialShip\LightSaml\Model\Assertion\NameID; |
8
|
|
|
use AerialShip\SamlSPBundle\Bridge\SamlSpInfo; |
9
|
|
|
use AerialShip\SamlSPBundle\RelyingParty\RelyingPartyInterface; |
10
|
|
|
use AerialShip\SamlSPBundle\Security\Core\Authentication\Token\SamlSpToken; |
11
|
|
|
use AerialShip\SamlSPBundle\Security\Http\Firewall\SamlSpAuthenticationListener; |
12
|
|
|
use Symfony\Component\HttpFoundation\ParameterBag; |
13
|
|
|
use Symfony\Component\HttpFoundation\RedirectResponse; |
14
|
|
|
use Symfony\Component\HttpFoundation\Request; |
15
|
|
|
use Symfony\Component\HttpFoundation\Response; |
16
|
|
|
use Symfony\Component\HttpFoundation\Session\SessionInterface; |
17
|
|
|
use Symfony\Component\HttpKernel\Event\GetResponseEvent; |
18
|
|
|
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; |
19
|
|
|
use Symfony\Component\Security\Core\SecurityContextInterface; |
20
|
|
|
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; |
21
|
|
|
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; |
22
|
|
|
use Symfony\Component\Security\Http\HttpUtils; |
23
|
|
|
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; |
24
|
|
|
|
25
|
|
|
class SamlSpAuthenticationListenerTest extends \PHPUnit_Framework_TestCase |
26
|
|
|
{ |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @test |
30
|
|
|
*/ |
31
|
|
|
public function shouldBeConstructedWithRequiredSetOfArguments() |
32
|
|
|
{ |
33
|
|
|
new SamlSpAuthenticationListener( |
34
|
|
|
$this->createSecurityContextMock(), |
|
|
|
|
35
|
|
|
$this->createAuthenticationManagerMock(), |
|
|
|
|
36
|
|
|
$this->createSessionAuthenticationStrategyMock(), |
|
|
|
|
37
|
|
|
$this->createHttpUtilsMock(), |
|
|
|
|
38
|
|
|
'providerKey', |
39
|
|
|
$this->createAuthenticationSuccessHandlerMock(), |
|
|
|
|
40
|
|
|
$this->createAuthenticationFailureHandlerMock(), |
|
|
|
|
41
|
|
|
$options = array() |
42
|
|
|
); |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @test |
47
|
|
|
* @expectedException \AerialShip\SamlSPBundle\Error\RelyingPartyNotSetException |
48
|
|
|
*/ |
49
|
|
|
public function throwIfRelyingPartyNotSet() |
50
|
|
|
{ |
51
|
|
|
$requestMock = $this->createRequestStub( |
52
|
|
|
$hasSessionReturn = true, |
53
|
|
|
$hasPreviousSessionReturn = true, |
54
|
|
|
$duplicatedRequestMock = $this->createRequestMock() |
55
|
|
|
); |
56
|
|
|
$duplicatedRequestMock->attributes = new ParameterBag(); |
57
|
|
|
|
58
|
|
|
$eventMock = $this->createGetResponseEventStub($requestMock); |
|
|
|
|
59
|
|
|
|
60
|
|
|
$listener = new SamlSpAuthenticationListener( |
61
|
|
|
$this->createSecurityContextMock(), |
|
|
|
|
62
|
|
|
$this->createAuthenticationManagerMock(), |
|
|
|
|
63
|
|
|
$this->createSessionAuthenticationStrategyMock(), |
|
|
|
|
64
|
|
|
$this->createHttpUtilsMock(), |
|
|
|
|
65
|
|
|
'providerKey', |
66
|
|
|
$this->createAuthenticationSuccessHandlerMock(), |
|
|
|
|
67
|
|
|
$this->createAuthenticationFailureHandlerMock(), |
|
|
|
|
68
|
|
|
$options = array('require_previous_session'=>false) |
69
|
|
|
); |
70
|
|
|
|
71
|
|
|
$listener->handle($eventMock); |
|
|
|
|
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
|
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @test |
78
|
|
|
*/ |
79
|
|
|
public function shouldDuplicateRequestAndPassItToRelyingPartyManageMethod() |
80
|
|
|
{ |
81
|
|
|
$requestMock = $this->createRequestStub( |
82
|
|
|
$hasSessionReturn = true, |
83
|
|
|
$hasPreviousSessionReturn = true, |
84
|
|
|
$duplicatedRequestMock = $this->createRequestMock() |
85
|
|
|
); |
86
|
|
|
$duplicatedRequestMock->attributes = new ParameterBag(); |
87
|
|
|
|
88
|
|
|
$relyingPartyMock = $this->createRelyingPartyMock(); |
89
|
|
|
$relyingPartyMock |
|
|
|
|
90
|
|
|
->expects($this->any()) |
91
|
|
|
->method('supports') |
92
|
|
|
->will($this->returnValue(true)) |
93
|
|
|
; |
94
|
|
|
$relyingPartyMock |
95
|
|
|
->expects($this->once()) |
96
|
|
|
->method('manage') |
97
|
|
|
->with($this->equalTo($duplicatedRequestMock)) |
98
|
|
|
->will($this->returnValue(new RedirectResponse('http://example.com/saml/idp'))) |
99
|
|
|
; |
100
|
|
|
|
101
|
|
|
$eventMock = $this->createGetResponseEventStub($requestMock); |
|
|
|
|
102
|
|
|
|
103
|
|
|
$listener = new SamlSpAuthenticationListener( |
104
|
|
|
$this->createSecurityContextMock(), |
|
|
|
|
105
|
|
|
$this->createAuthenticationManagerMock(), |
|
|
|
|
106
|
|
|
$this->createSessionAuthenticationStrategyMock(), |
|
|
|
|
107
|
|
|
$this->createHttpUtilsStub($checkRequestPathReturn = true), |
|
|
|
|
108
|
|
|
'providerKey', |
109
|
|
|
$this->createAuthenticationSuccessHandlerMock(), |
|
|
|
|
110
|
|
|
$this->createAuthenticationFailureHandlerMock(), |
|
|
|
|
111
|
|
|
$options = array('require_previous_session'=>false) |
112
|
|
|
); |
113
|
|
|
|
114
|
|
|
$listener->setRelyingParty($relyingPartyMock); |
|
|
|
|
115
|
|
|
|
116
|
|
|
$listener->handle($eventMock); |
|
|
|
|
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* @test |
122
|
|
|
*/ |
123
|
|
|
public function shouldAddOptionsToDuplicatedRequest() |
124
|
|
|
{ |
125
|
|
|
$requestOptions = array( |
126
|
|
|
'login_path' => '/saml/sp/login', |
127
|
|
|
'check_path' => '/saml/sp/check', |
128
|
|
|
'logout_path' => '/saml/sp/logout', |
129
|
|
|
'metadata_path' => '/saml/sp/FederationMetadata.xml', |
130
|
|
|
'discovery_path' => '/saml/sp/discovery', |
131
|
|
|
'failure_path' => 'saml/sp/failure' |
132
|
|
|
); |
133
|
|
|
|
134
|
|
|
$duplicatedRequestMock = $this->createRequestMock(); |
135
|
|
|
$duplicatedRequestMock->attributes = new ParameterBag(); |
136
|
|
|
|
137
|
|
|
$requestMock = $this->createRequestStub( |
138
|
|
|
$hasSessionReturn = true, |
139
|
|
|
$hasPreviousSessionReturn = true, |
140
|
|
|
$duplicateReturn = $duplicatedRequestMock |
141
|
|
|
); |
142
|
|
|
|
143
|
|
|
$relyingPartyMock = $this->createRelyingPartyStub( |
144
|
|
|
$supportsReturn = true, |
145
|
|
|
$manageReturn = new RedirectResponse('http://example.com/saml/idp') |
146
|
|
|
); |
147
|
|
|
|
148
|
|
|
$eventMock = $this->createGetResponseEventStub($requestMock); |
|
|
|
|
149
|
|
|
|
150
|
|
|
$listener = new SamlSpAuthenticationListener( |
151
|
|
|
$this->createSecurityContextMock(), |
|
|
|
|
152
|
|
|
$this->createAuthenticationManagerMock(), |
|
|
|
|
153
|
|
|
$this->createSessionAuthenticationStrategyMock(), |
|
|
|
|
154
|
|
|
$this->createHttpUtilsStub($checkRequestPathReturn = true), |
|
|
|
|
155
|
|
|
'providerKey', |
156
|
|
|
$this->createAuthenticationSuccessHandlerMock(), |
|
|
|
|
157
|
|
|
$this->createAuthenticationFailureHandlerMock(), |
|
|
|
|
158
|
|
|
$options = array_merge(array('require_previous_session'=>false), $requestOptions) |
159
|
|
|
); |
160
|
|
|
|
161
|
|
|
$listener->setRelyingParty($relyingPartyMock); |
|
|
|
|
162
|
|
|
|
163
|
|
|
$listener->handle($eventMock); |
|
|
|
|
164
|
|
|
|
165
|
|
|
$this->assertSame( |
166
|
|
|
$requestOptions, |
167
|
|
|
$duplicatedRequestMock->attributes->all() |
168
|
|
|
); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* @test |
174
|
|
|
*/ |
175
|
|
|
public function shouldCreateTokenFromIDPResponseAndPassItToAuthenticationManager() |
176
|
|
|
{ |
177
|
|
|
$requestMock = $this->createRequestStub( |
178
|
|
|
$hasSessionReturn = true, |
179
|
|
|
$hasPreviousSessionReturn = true, |
180
|
|
|
$duplicateReturn = $this->createRequestMock(), |
181
|
|
|
$getSessionReturn = $this->createSessionMock() |
182
|
|
|
); |
183
|
|
|
$duplicateReturn->attributes = new ParameterBag(); |
184
|
|
|
|
185
|
|
|
$nameID = new NameID(); |
186
|
|
|
$nameID->setValue('name.id'); |
187
|
|
|
$attribute1 = new Attribute(); |
188
|
|
|
$attribute1->setName('common.name'); |
189
|
|
|
$attribute1->setValues(array('my common name')); |
190
|
|
|
$authnStatement = new AuthnStatement(); |
191
|
|
|
$authnStatement->setSessionIndex('1234567890'); |
192
|
|
|
|
193
|
|
|
$relyingPartyMock = $this->createRelyingPartyStub( |
194
|
|
|
$supportsReturn = true, |
195
|
|
|
$manageReturnSamlSpInfo = new SamlSpInfo( |
196
|
|
|
'idp1', |
197
|
|
|
$nameID, |
198
|
|
|
array($attribute1), |
199
|
|
|
$authnStatement |
200
|
|
|
) |
201
|
|
|
); |
202
|
|
|
|
203
|
|
|
$httpUtilsStub = $this->createHttpUtilsStub( |
204
|
|
|
$checkRequestPathReturn = true, |
205
|
|
|
$createRedirectResponseReturn = new RedirectResponse('uri') |
206
|
|
|
); |
207
|
|
|
|
208
|
|
|
$testCase = $this; |
209
|
|
|
$authenticationManagerMock = $this->createAuthenticationManagerMock(); |
210
|
|
|
$authenticationManagerMock |
|
|
|
|
211
|
|
|
->expects($this->once()) |
212
|
|
|
->method('authenticate') |
213
|
|
|
->with($this->isInstanceOf('AerialShip\SamlSPBundle\Security\Core\Authentication\Token\SamlSpToken')) |
214
|
|
|
->will($this->returnCallback(function (SamlSpToken $actualToken) use ($testCase, $manageReturnSamlSpInfo) { |
215
|
|
|
$samlInfo = $actualToken->getSamlSpInfo(); |
216
|
|
|
$testCase->assertNotNull($samlInfo); |
217
|
|
|
$testCase->assertNotNull($samlInfo->getNameID()); |
218
|
|
|
$testCase->assertEquals('name.id', $samlInfo->getNameID()->getValue()); |
219
|
|
|
$testCase->assertNotNull($samlInfo->getAttributes()); |
220
|
|
|
$testCase->assertCount(1, $samlInfo->getAttributes()); |
221
|
|
|
$testCase->assertEquals($manageReturnSamlSpInfo, $actualToken->getSamlSpInfo()); |
222
|
|
|
return $actualToken; |
223
|
|
|
})) |
224
|
|
|
; |
225
|
|
|
|
226
|
|
|
$eventMock = $this->createGetResponseEventStub($requestMock); |
|
|
|
|
227
|
|
|
|
228
|
|
|
$listener = new SamlSpAuthenticationListener( |
229
|
|
|
$this->createSecurityContextMock(), |
|
|
|
|
230
|
|
|
$authenticationManagerMock, |
|
|
|
|
231
|
|
|
$this->createSessionAuthenticationStrategyMock(), |
|
|
|
|
232
|
|
|
$httpUtilsStub, |
|
|
|
|
233
|
|
|
'providerKey', |
234
|
|
|
$this->createAuthenticationSuccessHandlerStub(), |
|
|
|
|
235
|
|
|
$this->createAuthenticationFailureHandlerMock(), |
|
|
|
|
236
|
|
|
$options = array() |
237
|
|
|
); |
238
|
|
|
|
239
|
|
|
$listener->setRelyingParty($relyingPartyMock); |
|
|
|
|
240
|
|
|
|
241
|
|
|
$listener->handle($eventMock); |
|
|
|
|
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* @return \PHPUnit_Framework_MockObject_MockObject|Request |
247
|
|
|
*/ |
248
|
|
|
private function createRequestMock() |
249
|
|
|
{ |
250
|
|
|
return $this->getMock('Symfony\Component\HttpFoundation\Request', array(), array(), '', false, false); |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* @param bool $hasSessionReturn |
255
|
|
|
* @param bool $hasPreviousSession |
256
|
|
|
* @param mixed $duplicateReturn |
257
|
|
|
* @param mixed $getSessionReturn |
258
|
|
|
* @return \PHPUnit_Framework_MockObject_MockObject|Request |
259
|
|
|
*/ |
260
|
|
|
private function createRequestStub($hasSessionReturn = null, $hasPreviousSession = null, $duplicateReturn = null, $getSessionReturn = null) |
261
|
|
|
{ |
262
|
|
|
$requestMock = $this->createRequestMock(); |
263
|
|
|
|
264
|
|
|
$requestMock |
|
|
|
|
265
|
|
|
->expects($this->any()) |
266
|
|
|
->method('hasSession') |
267
|
|
|
->will($this->returnValue($hasSessionReturn)) |
268
|
|
|
; |
269
|
|
|
$requestMock |
270
|
|
|
->expects($this->any()) |
271
|
|
|
->method('hasPreviousSession') |
272
|
|
|
->will($this->returnValue($hasPreviousSession)) |
273
|
|
|
; |
274
|
|
|
$requestMock |
275
|
|
|
->expects($this->any()) |
276
|
|
|
->method('duplicate') |
277
|
|
|
->will($this->returnValue($duplicateReturn)) |
278
|
|
|
; |
279
|
|
|
$requestMock |
280
|
|
|
->expects($this->any()) |
281
|
|
|
->method('getSession') |
282
|
|
|
->will($this->returnValue($getSessionReturn)) |
283
|
|
|
; |
284
|
|
|
|
285
|
|
|
return $requestMock; |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* @return \PHPUnit_Framework_MockObject_MockObject|RelyingPartyInterface |
290
|
|
|
*/ |
291
|
|
|
private function createRelyingPartyMock() |
292
|
|
|
{ |
293
|
|
|
return $this->getMock('AerialShip\SamlSPBundle\RelyingParty\RelyingPartyInterface'); |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
private function createRelyingPartyStub($supportsReturn = null, $manageReturn = null) |
297
|
|
|
{ |
298
|
|
|
$relyingPartyMock = $this->createRelyingPartyMock(); |
299
|
|
|
|
300
|
|
|
$relyingPartyMock |
|
|
|
|
301
|
|
|
->expects($this->any()) |
302
|
|
|
->method('supports') |
303
|
|
|
->will($this->returnValue($supportsReturn)) |
304
|
|
|
; |
305
|
|
|
$relyingPartyMock |
306
|
|
|
->expects($this->any()) |
307
|
|
|
->method('manage') |
308
|
|
|
->will($this->returnValue($manageReturn)) |
309
|
|
|
; |
310
|
|
|
|
311
|
|
|
return $relyingPartyMock; |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
/** |
315
|
|
|
* @return \PHPUnit_Framework_MockObject_MockObject|GetResponseEvent |
316
|
|
|
*/ |
317
|
|
|
private function createGetResponseEventMock() |
318
|
|
|
{ |
319
|
|
|
return $this->getMock('Symfony\Component\HttpKernel\Event\GetResponseEvent', array(), array(), '', false); |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
/** |
323
|
|
|
* @param Request|null $request |
324
|
|
|
* @return \PHPUnit_Framework_MockObject_MockObject|GetResponseEvent |
325
|
|
|
*/ |
326
|
|
|
private function createGetResponseEventStub($request = null) |
327
|
|
|
{ |
328
|
|
|
$getResponseEventMock = $this->createGetResponseEventMock(); |
329
|
|
|
|
330
|
|
|
$getResponseEventMock |
|
|
|
|
331
|
|
|
->expects($this->any()) |
332
|
|
|
->method('getRequest') |
333
|
|
|
->will($this->returnValue($request)) |
334
|
|
|
; |
335
|
|
|
|
336
|
|
|
return $getResponseEventMock; |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
|
340
|
|
|
/** |
341
|
|
|
* @return \PHPUnit_Framework_MockObject_MockObject|SessionInterface |
342
|
|
|
*/ |
343
|
|
|
private function createSessionMock() |
344
|
|
|
{ |
345
|
|
|
return $this->getMock('Symfony\Component\HttpFoundation\Session\SessionInterface'); |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
/** |
349
|
|
|
* @return \PHPUnit_Framework_MockObject_MockObject|SecurityContextInterface |
350
|
|
|
*/ |
351
|
|
|
private function createSecurityContextMock() |
352
|
|
|
{ |
353
|
|
|
return $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface'); |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
/** |
357
|
|
|
* @return \PHPUnit_Framework_MockObject_MockObject|AuthenticationManagerInterface |
358
|
|
|
*/ |
359
|
|
|
private function createAuthenticationManagerMock() |
360
|
|
|
{ |
361
|
|
|
return $this->getMock('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface'); |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
/** |
365
|
|
|
* @return \PHPUnit_Framework_MockObject_MockObject|SessionAuthenticationStrategyInterface |
366
|
|
|
*/ |
367
|
|
|
private function createSessionAuthenticationStrategyMock() |
368
|
|
|
{ |
369
|
|
|
return $this->getMock('Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface'); |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
/** |
373
|
|
|
* @return \PHPUnit_Framework_MockObject_MockObject|HttpUtils |
374
|
|
|
*/ |
375
|
|
|
private function createHttpUtilsMock() |
376
|
|
|
{ |
377
|
|
|
return $this->getMock('Symfony\Component\Security\Http\HttpUtils'); |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
/** |
381
|
|
|
* @param null $checkRequestPathResult |
382
|
|
|
* @param null $createRedirectResponseReturn |
383
|
|
|
* @return \PHPUnit_Framework_MockObject_MockObject|HttpUtils |
384
|
|
|
*/ |
385
|
|
|
private function createHttpUtilsStub($checkRequestPathResult = null, $createRedirectResponseReturn = null) |
386
|
|
|
{ |
387
|
|
|
$httpUtilsMock = $this->createHttpUtilsMock(); |
388
|
|
|
|
389
|
|
|
$httpUtilsMock |
|
|
|
|
390
|
|
|
->expects($this->any()) |
391
|
|
|
->method('checkRequestPath') |
392
|
|
|
->will($this->returnValue($checkRequestPathResult)) |
393
|
|
|
; |
394
|
|
|
$httpUtilsMock |
395
|
|
|
->expects($this->any()) |
396
|
|
|
->method('createRedirectResponse') |
397
|
|
|
->will($this->returnValue($createRedirectResponseReturn)) |
398
|
|
|
; |
399
|
|
|
|
400
|
|
|
return $httpUtilsMock; |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
|
404
|
|
|
/** |
405
|
|
|
* @return \PHPUnit_Framework_MockObject_MockObject|AuthenticationSuccessHandlerInterface |
406
|
|
|
*/ |
407
|
|
|
private function createAuthenticationSuccessHandlerMock() |
408
|
|
|
{ |
409
|
|
|
return $this->getMock('Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface'); |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
/** |
413
|
|
|
* @return \PHPUnit_Framework_MockObject_MockObject|AuthenticationSuccessHandlerInterface |
414
|
|
|
*/ |
415
|
|
|
protected function createAuthenticationSuccessHandlerStub() |
416
|
|
|
{ |
417
|
|
|
$handlerMock = $this->createAuthenticationSuccessHandlerMock(); |
418
|
|
|
|
419
|
|
|
$handlerMock |
|
|
|
|
420
|
|
|
->expects($this->any()) |
421
|
|
|
->method('onAuthenticationSuccess') |
422
|
|
|
->will($this->returnValue(new Response())) |
423
|
|
|
; |
424
|
|
|
|
425
|
|
|
return $handlerMock; |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
/** |
429
|
|
|
* @return \PHPUnit_Framework_MockObject_MockObject|AuthenticationFailureHandlerInterface |
430
|
|
|
*/ |
431
|
|
|
private function createAuthenticationFailureHandlerMock() |
432
|
|
|
{ |
433
|
|
|
return $this->getMock('Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface'); |
434
|
|
|
} |
435
|
|
|
} |
436
|
|
|
|
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.