Failed Conditions
Push — master ( 393e29...5c5e5d )
by Florent
05:55
created

buildRequest()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 9
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2018 Spomky-Labs
9
 *
10
 * This software may be modified and distributed under the terms
11
 * of the MIT license.  See the LICENSE file for details.
12
 */
13
14
namespace OAuth2Framework\Component\ClientAuthentication\Tests;
15
16
use Http\Message\MessageFactory\DiactorosMessageFactory;
17
use Jose\Component\Checker\ClaimCheckerManager;
18
use Jose\Component\Checker\HeaderCheckerManager;
19
use Jose\Component\Core\AlgorithmManager;
20
use Jose\Component\Core\Converter\StandardConverter;
21
use Jose\Component\Core\JWK;
22
use Jose\Component\Core\JWKSet;
23
use Jose\Component\Encryption\Algorithm\ContentEncryption\A256CBCHS512;
24
use Jose\Component\Encryption\Algorithm\KeyEncryption\RSAOAEP256;
25
use Jose\Component\Encryption\Compression\CompressionMethodManager;
26
use Jose\Component\Encryption\Compression\Deflate;
27
use Jose\Component\Encryption\JWEBuilder;
28
use Jose\Component\Encryption\JWEDecrypter;
29
use Jose\Component\Encryption\JWELoader;
30
use Jose\Component\Encryption\JWETokenSupport;
31
use Jose\Component\Encryption\Serializer\JWESerializerManager;
32
use Jose\Component\KeyManagement\JKUFactory;
33
use Jose\Component\Signature\Algorithm\HS256;
34
use Jose\Component\Signature\Algorithm\RS256;
35
use Jose\Component\Signature\JWS;
36
use Jose\Component\Signature\JWSBuilder;
37
use Jose\Component\Signature\JWSTokenSupport;
38
use Jose\Component\Signature\JWSVerifier;
39
use Jose\Component\Signature\Serializer\CompactSerializer;
40
use OAuth2Framework\Component\Core\Client\Client;
41
use OAuth2Framework\Component\Core\Client\ClientId;
42
use OAuth2Framework\Component\Core\DataBag\DataBag;
43
use OAuth2Framework\Component\Core\Message\OAuth2Message;
44
use OAuth2Framework\Component\Core\UserAccount\UserAccountId;
45
use OAuth2Framework\Component\ClientAuthentication\AuthenticationMethodManager;
46
use OAuth2Framework\Component\ClientAuthentication\ClientAssertionJwt;
47
use OAuth2Framework\Component\Core\TrustedIssuer\TrustedIssuer;
48
use OAuth2Framework\Component\Core\TrustedIssuer\TrustedIssuerRepository;
49
use PHPUnit\Framework\TestCase;
50
use Prophecy\Prophecy\ObjectProphecy;
51
use Psr\Http\Message\ServerRequestInterface;
52
use Psr\Http\Message\StreamInterface;
53
use Zend\Diactoros\Response;
54
55
/**
56
 * @group TokenEndpoint
57
 * @group ClientAuthentication
58
 */
59
final class ClientAssertionJwtAuthenticationMethodTest extends TestCase
60
{
61
    /**
62
     * @test
63
     */
64
    public function genericCalls()
65
    {
66
        $method = $this->getMethod();
67
68
        self::assertEquals([], $method->getSchemesParameters());
69
        self::assertEquals(['client_secret_jwt', 'private_key_jwt'], $method->getSupportedMethods());
70
        self::assertEquals(['HS256', 'RS256'], $method->getSupportedSignatureAlgorithms());
71
        self::assertEquals([], $method->getSupportedKeyEncryptionAlgorithms());
72
        self::assertEquals([], $method->getSupportedContentEncryptionAlgorithms());
73
    }
74
75
    /**
76
     * @test
77
     */
78
    public function genericCallsWithEncryptionSupport()
79
    {
80
        $method = $this->getMethodWithEncryptionSupport(false);
81
82
        self::assertEquals([], $method->getSchemesParameters());
83
        self::assertEquals(['client_secret_jwt', 'private_key_jwt'], $method->getSupportedMethods());
84
        self::assertEquals(['HS256', 'RS256'], $method->getSupportedSignatureAlgorithms());
85
        self::assertEquals(['RSA-OAEP-256'], $method->getSupportedKeyEncryptionAlgorithms());
86
        self::assertEquals(['A256CBC-HS512'], $method->getSupportedContentEncryptionAlgorithms());
87
    }
88
89
    /**
90
     * @test
91
     */
92
    public function theClientIdCannotBeFoundInTheRequest()
93
    {
94
        $method = $this->getMethod();
95
        $request = $this->buildRequest([]);
96
        $request->getParsedBody()->willReturn([]);
97
        $request->getHeader('Authorization')->willReturn([]);
98
99
        $clientId = $method->findClientIdAndCredentials($request->reveal(), $credentials);
100
        self::assertNull($clientId);
101
        self::assertNull($credentials);
102
    }
103
104
    /**
105
     * @test
106
     */
107
    public function theClientAssertionTypeIsNotSupported()
108
    {
109
        $method = $this->getMethod();
110
        $request = $this->buildRequest([
111
            'client_assertion_type' => 'foo',
112
        ]);
113
114
        $request->getHeader('Authorization')->willReturn([]);
115
        $clientId = $method->findClientIdAndCredentials($request->reveal(), $credentials);
116
        self::assertNull($clientId);
117
        self::assertNull($credentials);
118
    }
119
120
    /**
121
     * @test
122
     */
123
    public function theClientAssertionIsMissing()
124
    {
125
        $method = $this->getMethod();
126
        $request = $this->buildRequest([
127
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
128
        ]);
129
130
        $request->getHeader('Authorization')->willReturn([]);
131
        try {
132
            $method->findClientIdAndCredentials($request->reveal(), $credentials);
133
            $this->fail('An OAuth2 exception should be thrown.');
134
        } catch (OAuth2Message $e) {
135
            self::assertEquals('invalid_request', $e->getMessage());
136
            self::assertEquals('Parameter "client_assertion" is missing.', $e->getErrorDescription());
137
        }
138
    }
139
140
    /**
141
     * @test
142
     */
143
    public function theClientAssertionIsInvalid()
144
    {
145
        $method = $this->getMethod();
146
        $request = $this->buildRequest([
147
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
148
            'client_assertion' => 'foo',
149
        ]);
150
151
        $request->getHeader('Authorization')->willReturn([]);
152
        try {
153
            $method->findClientIdAndCredentials($request->reveal(), $credentials);
154
            $this->fail('An OAuth2 exception should be thrown.');
155
        } catch (OAuth2Message $e) {
156
            self::assertEquals('invalid_request', $e->getMessage());
157
            self::assertEquals('Unable to load, decrypt or verify the client assertion.', $e->getErrorDescription());
158
        }
159
    }
160
161
    /**
162
     * @test
163
     */
164
    public function theClientAssertionSignedByTheClientIsInvalidBecauseOfMissingClaims()
165
    {
166
        $assertion = $this->serializeJWS(
167
            $this->createInvalidClientAssertionSignedByTheClient()
168
        );
169
        $method = $this->getMethod();
170
        $request = $this->buildRequest([
171
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
172
            'client_assertion' => $assertion,
173
        ]);
174
175
        $request->getHeader('Authorization')->willReturn([]);
176
        try {
177
            $method->findClientIdAndCredentials($request->reveal(), $credentials);
178
            $this->fail('An OAuth2 exception should be thrown.');
179
        } catch (OAuth2Message $e) {
180
            self::assertEquals('invalid_request', $e->getMessage());
181
            self::assertEquals('The following claim(s) is/are mandatory: "iss, sub, aud, exp".', $e->getErrorDescription());
182
        }
183
    }
184
185
    /**
186
     * @test
187
     */
188
    public function theClientAssertionSignedByTheClientIsRejectedBecauseEncryptionIsMandatory()
189
    {
190
        $assertion = $this->serializeJWS(
191
            $this->createInvalidClientAssertionSignedByTheClient()
192
        );
193
        $method = $this->getMethodWithEncryptionSupport(true);
194
        $request = $this->buildRequest([
195
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
196
            'client_assertion' => $assertion,
197
        ]);
198
199
        $request->getHeader('Authorization')->willReturn([]);
200
        try {
201
            $method->findClientIdAndCredentials($request->reveal(), $credentials);
202
            $this->fail('An OAuth2 exception should be thrown.');
203
        } catch (OAuth2Message $e) {
204
            self::assertEquals('invalid_request', $e->getMessage());
205
            self::assertEquals('The encryption of the assertion is mandatory but the decryption of the assertion failed.', $e->getErrorDescription());
206
        }
207
    }
208
209
    /**
210
     * @test
211
     */
212
    public function theEncryptedClientAssertionSignedAndEncryptedByTheClientIsInvalidBecauseOfMissingClaims()
213
    {
214
        $assertion = $this->encryptAssertion(
215
            $this->serializeJWS(
216
                $this->createInvalidClientAssertionSignedByTheClient()
217
            )
218
        );
219
        $method = $this->getMethodWithEncryptionSupport(false);
220
        $request = $this->buildRequest([
221
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
222
            'client_assertion' => $assertion,
223
        ]);
224
225
        $request->getHeader('Authorization')->willReturn([]);
226
        try {
227
            $method->findClientIdAndCredentials($request->reveal(), $credentials);
228
            $this->fail('An OAuth2 exception should be thrown.');
229
        } catch (OAuth2Message $e) {
230
            self::assertEquals('invalid_request', $e->getMessage());
231
            self::assertEquals('The following claim(s) is/are mandatory: "iss, sub, aud, exp".', $e->getErrorDescription());
232
        }
233
    }
234
235
    /**
236
     * @test
237
     */
238
    public function theClientAssertionIsValidAndTheClientIdIsRetrieved()
239
    {
240
        $assertion = $this->serializeJWS(
241
            $this->createValidClientAssertionSignedByTheClient()
242
        );
243
        $method = $this->getMethod();
244
        $request = $this->buildRequest([
245
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
246
            'client_assertion' => $assertion,
247
        ]);
248
249
        $request->getHeader('Authorization')->willReturn([]);
250
        $clientId = $method->findClientIdAndCredentials($request->reveal(), $credentials);
251
        self::assertEquals('ClientId', $clientId->getValue());
252
    }
253
254
    /**
255
     * @test
256
     */
257
    public function theEncryptedClientAssertionIsValidAndTheClientIdIsRetrieved()
258
    {
259
        $assertion = $this->encryptAssertion(
260
            $this->serializeJWS(
261
                $this->createValidClientAssertionSignedByTheClient()
262
            )
263
        );
264
        $method = $this->getMethodWithEncryptionSupport(false);
265
        $request = $this->buildRequest([
266
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
267
            'client_assertion' => $assertion,
268
        ]);
269
        $request->getHeader('Authorization')->willReturn([]);
270
271
        $clientId = $method->findClientIdAndCredentials($request->reveal(), $credentials);
272
        self::assertEquals('ClientId', $clientId->getValue());
273
    }
274
275
    /**
276
     * @test
277
     */
278
    public function theClientUsesAnotherAuthenticationMethod()
279
    {
280
        $jws = $this->createInvalidClientAssertionSignedByTheClient();
281
        $assertion = $this->encryptAssertion(
282
            $this->serializeJWS(
283
                $jws
284
            )
285
        );
286
        $method = $this->getMethodWithEncryptionSupport(false);
287
        $manager = new AuthenticationMethodManager();
288
        $manager->add($method);
289
        $client = Client::createEmpty();
290
        $client = $client->create(
291
            ClientId::create('CLIENT_ID'),
292
            DataBag::create([
293
                'client_secret' => 'CLIENT_SECRET',
294
                'token_endpoint_auth_method' => 'client_secret_post',
295
            ]),
296
            UserAccountId::create('USER_ACCOUNT_ID')
297
        );
298
        $request = $this->buildRequest([
299
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
300
            'client_assertion' => $assertion,
301
        ]);
302
303
        self::assertFalse($manager->isClientAuthenticated($request->reveal(), $client, $method, $jws));
304
    }
305
306
    /**
307
     * @test
308
     */
309
    public function theClientWithPrivateKeyIsAuthenticated()
310
    {
311
        $jws = $this->createValidClientAssertionSignedByTheClient();
312
        $assertion = $this->encryptAssertion(
313
            $this->serializeJWS(
314
                $jws
315
            )
316
        );
317
        $method = $this->getMethodWithEncryptionSupport(false);
318
        $manager = new AuthenticationMethodManager();
319
        $manager->add($method);
320
        $client = Client::createEmpty();
321
        $client = $client->create(
322
            ClientId::create('CLIENT_ID'),
323
            DataBag::create([
324
                'token_endpoint_auth_method' => 'private_key_jwt',
325
                'jwks' => '{"keys":[{"kty":"oct","k":"U0VDUkVU"}]}',
326
            ]),
327
            UserAccountId::create('USER_ACCOUNT_ID')
328
        );
329
        $request = $this->buildRequest([
330
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
331
            'client_assertion' => $assertion,
332
        ]);
333
334
        self::assertTrue($manager->isClientAuthenticated($request->reveal(), $client, $method, $jws));
335
    }
336
337
    /**
338
     * @test
339
     */
340
    public function theClientWithClientSecretIsAuthenticated()
341
    {
342
        $jws = $this->createValidClientAssertionSignedByTheClient();
343
        $assertion = $this->encryptAssertion(
344
            $this->serializeJWS(
345
                $jws
346
            )
347
        );
348
        $method = $this->getMethodWithEncryptionSupport(false);
349
        $manager = new AuthenticationMethodManager();
350
        $manager->add($method);
351
        $client = Client::createEmpty();
352
        $client = $client->create(
353
            ClientId::create('CLIENT_ID'),
354
            DataBag::create([
355
                'token_endpoint_auth_method' => 'client_secret_jwt',
356
                'client_secret' => 'SECRET',
357
            ]),
358
            UserAccountId::create('USER_ACCOUNT_ID')
359
        );
360
        $request = $this->buildRequest([
361
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
362
            'client_assertion' => $assertion,
363
        ]);
364
365
        self::assertTrue($manager->isClientAuthenticated($request->reveal(), $client, $method, $jws));
366
    }
367
368
    /**
369
     * @test
370
     */
371
    public function theClientWithTrustedIssuerAssertionIsAuthenticated()
372
    {
373
        $jws = $this->createValidClientAssertionSignedByATrustedIssuer();
374
        $assertion = $this->serializeJWS(
375
            $jws
376
        );
377
        $method = $this->getMethodWithTrustedIssuerSupport();
378
        $manager = new AuthenticationMethodManager();
379
        $manager->add($method);
380
        $client = Client::createEmpty();
381
        $client = $client->create(
382
            ClientId::create('CLIENT_ID'),
383
            DataBag::create([
384
                'token_endpoint_auth_method' => 'client_secret_jwt',
385
            ]),
386
            UserAccountId::create('USER_ACCOUNT_ID')
387
        );
388
        $request = $this->buildRequest([
389
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
390
            'client_assertion' => $assertion,
391
        ]);
392
393
        self::assertTrue($manager->isClientAuthenticated($request->reveal(), $client, $method, $jws));
394
    }
395
396
    /**
397
     * @test
398
     */
399
    public function theClientConfigurationCanBeCheckedWithClientSecretJwt()
400
    {
401
        $method = $this->getMethod();
402
        $commandParameters = DataBag::create([
403
            'token_endpoint_auth_method' => 'client_secret_jwt',
404
        ]);
405
        $validatedParameters = $method->checkClientConfiguration($commandParameters, DataBag::create([]));
406
407
        self::assertTrue($validatedParameters->has('client_secret'));
408
        self::assertTrue($validatedParameters->has('client_secret_expires_at'));
409
    }
410
411
    /**
412
     * @test
413
     * @expectedException \InvalidArgumentException
414
     * @expectedExceptionMessage Either the parameter "jwks" or "jwks_uri" must be set.
415
     */
416
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfBothJwksAndJwksUriAreSet()
417
    {
418
        $method = $this->getMethod();
419
        $commandParameters = DataBag::create([
420
            'token_endpoint_auth_method' => 'private_key_jwt',
421
            'jwks' => 'foo',
422
            'jwks_uri' => 'bar',
423
        ]);
424
        $validatedParameters = $method->checkClientConfiguration($commandParameters, DataBag::create([]));
425
426
        self::assertTrue($validatedParameters->has('token_endpoint_auth_method'));
427
        self::assertEquals('private_key_jwt', $validatedParameters->get('token_endpoint_auth_method'));
428
    }
429
430
    /**
431
     * @test
432
     * @expectedException \InvalidArgumentException
433
     * @expectedExceptionMessage Either the parameter "jwks" or "jwks_uri" must be set.
434
     */
435
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfBothJwksAndJwksUriAreNotSetBecauseTrustedIssuerSupportIsDisabled()
436
    {
437
        $method = $this->getMethod();
438
        $commandParameters = DataBag::create([
439
            'token_endpoint_auth_method' => 'private_key_jwt',
440
        ]);
441
        $validatedParameters = $method->checkClientConfiguration($commandParameters, DataBag::create([]));
442
443
        self::assertTrue($validatedParameters->has('token_endpoint_auth_method'));
444
        self::assertEquals('private_key_jwt', $validatedParameters->get('token_endpoint_auth_method'));
445
    }
446
447
    /**
448
     * @test
449
     */
450
    public function theClientConfigurationCanBeCheckedWithPrivateKeyJwtIfBothJwksAndJwksUriAreNotSetBecauseTrustedIssuerSupportIsEnabled()
451
    {
452
        $method = $this->getMethodWithTrustedIssuerSupport();
453
        $commandParameters = DataBag::create([
454
            'token_endpoint_auth_method' => 'private_key_jwt',
455
        ]);
456
        $validatedParameters = $method->checkClientConfiguration($commandParameters, DataBag::create([]));
457
458
        self::assertTrue($validatedParameters->has('token_endpoint_auth_method'));
459
        self::assertEquals('private_key_jwt', $validatedParameters->get('token_endpoint_auth_method'));
460
    }
461
462
    /**
463
     * @test
464
     * @expectedException \InvalidArgumentException
465
     * @expectedExceptionMessage The parameter "jwks" must be a valid JWKSet object.
466
     */
467
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksIsNotAValidKeySet()
468
    {
469
        $method = $this->getMethod();
470
        $commandParameters = DataBag::create([
471
            'token_endpoint_auth_method' => 'private_key_jwt',
472
            'jwks' => 'foo',
473
        ]);
474
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
475
    }
476
477
    /**
478
     * @test
479
     */
480
    public function theClientConfigurationCanBeCheckedWithPrivateKeyJwtIfJwksIsValid()
481
    {
482
        $method = $this->getMethod();
483
        $commandParameters = DataBag::create([
484
            'token_endpoint_auth_method' => 'private_key_jwt',
485
            'jwks' => '{"keys":[{"kty":"oct","k":"U0VDUkVU"}]}',
486
        ]);
487
        $validatedParameters = $method->checkClientConfiguration($commandParameters, DataBag::create([]));
488
489
        self::assertTrue($validatedParameters->has('jwks'));
490
        self::assertEquals('{"keys":[{"kty":"oct","k":"U0VDUkVU"}]}', $validatedParameters->get('jwks'));
491
    }
492
493
    /**
494
     * @test
495
     * @expectedException \InvalidArgumentException
496
     * @expectedExceptionMessage Distant key sets cannot be used. Please use "jwks" instead of "jwks_uri".
497
     */
498
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriFactoryIsNotAvailable()
499
    {
500
        $method = $this->getMethod();
501
        $commandParameters = DataBag::create([
502
            'token_endpoint_auth_method' => 'private_key_jwt',
503
            'jwks_uri' => 'foo',
504
        ]);
505
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
506
    }
507
508
    /**
509
     * @test
510
     * @expectedException \InvalidArgumentException
511
     * @expectedExceptionMessage The parameter "jwks_uri" must be a valid uri to a JWKSet.
512
     */
513
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriIsNotValid()
514
    {
515
        $method = clone $this->getMethod();
516
        $httpClient = $this->getHttpClient();
517
        $method->enableJkuSupport(
518
            $this->getJkuFactory($httpClient)
519
        );
520
        $commandParameters = DataBag::create([
521
            'token_endpoint_auth_method' => 'private_key_jwt',
522
            'jwks_uri' => 'foo',
523
        ]);
524
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
525
    }
526
527
    /**
528
     * @test
529
     * @expectedException \InvalidArgumentException
530
     * @expectedExceptionMessage The parameter "jwks_uri" must be a valid uri to a JWKSet.
531
     */
532
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriCannotBeReached()
533
    {
534
        $method = clone $this->getMethod();
535
        $httpClient = $this->getHttpClient();
536
        $httpClient->addResponse(new Response('php://memory', 404));
537
        $method->enableJkuSupport(
538
            $this->getJkuFactory($httpClient)
539
        );
540
        $commandParameters = DataBag::create([
541
            'token_endpoint_auth_method' => 'private_key_jwt',
542
            'jwks_uri' => 'https://www.foo.com/bad-url.jwkset',
543
        ]);
544
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
545
    }
546
547
    /**
548
     * @test
549
     * @expectedException \InvalidArgumentException
550
     * @expectedExceptionMessage The parameter "jwks_uri" must be a valid uri to a JWKSet.
551
     */
552
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriDoesNotContainAValidKeySet()
553
    {
554
        $method = clone $this->getMethod();
555
        $httpClient = $this->getHttpClient();
556
        $stream = fopen('php://memory', 'w+');
557
        fwrite($stream, 'Hello World!');
558
        rewind($stream);
559
        $httpClient->addResponse(new Response($stream, 200));
560
        $method->enableJkuSupport(
561
            $this->getJkuFactory($httpClient)
562
        );
563
        $commandParameters = DataBag::create([
564
            'token_endpoint_auth_method' => 'private_key_jwt',
565
            'jwks_uri' => 'https://www.foo.com/index.html',
566
        ]);
567
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
568
    }
569
570
    /**
571
     * @test
572
     * @expectedException \InvalidArgumentException
573
     * @expectedExceptionMessage The distant key set is empty.
574
     */
575
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriDoesContainAnEmptyKeySet()
576
    {
577
        $method = clone $this->getMethod();
578
        $httpClient = $this->getHttpClient();
579
        $stream = fopen('php://memory', 'w+');
580
        fwrite($stream, '{"keys":[]}');
581
        rewind($stream);
582
        $httpClient->addResponse(new Response($stream, 200));
583
        $method->enableJkuSupport(
584
            $this->getJkuFactory($httpClient)
585
        );
586
        $commandParameters = DataBag::create([
587
            'token_endpoint_auth_method' => 'private_key_jwt',
588
            'jwks_uri' => 'https://www.foo.com/index.html',
589
        ]);
590
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
591
    }
592
593
    /**
594
     * @test
595
     */
596
    public function theClientConfigurationCanBeCheckedWithPrivateKeyJwt()
597
    {
598
        $method = clone $this->getMethod();
599
        $httpClient = $this->getHttpClient();
600
        $stream = fopen('php://memory', 'w+');
601
        fwrite($stream, '{"keys":[{"kty":"oct","k":"U0VDUkVU"}]}');
602
        rewind($stream);
603
        $httpClient->addResponse(new Response($stream, 200));
604
        $method->enableJkuSupport(
605
            $this->getJkuFactory($httpClient)
606
        );
607
        $commandParameters = DataBag::create([
608
            'token_endpoint_auth_method' => 'private_key_jwt',
609
            'jwks_uri' => 'https://www.foo.com/keyset',
610
        ]);
611
        $validatedParameters = $method->checkClientConfiguration($commandParameters, DataBag::create([]));
612
613
        self::assertTrue($validatedParameters->has('jwks_uri'));
614
        self::assertEquals('https://www.foo.com/keyset', $validatedParameters->get('jwks_uri'));
615
    }
616
617
    /**
618
     * @var null|ClientAssertionJwt
619
     */
620
    private $method = null;
621
622
    /**
623
     * @return ClientAssertionJwt
624
     */
625
    private function getMethod(): ClientAssertionJwt
626
    {
627
        if (null === $this->method) {
628
            $this->method = new ClientAssertionJwt(
629
                new StandardConverter(),
630
                new JWSVerifier(AlgorithmManager::create([new HS256(), new RS256()])),
631
                HeaderCheckerManager::create([], [new JWSTokenSupport()]),
632
                ClaimCheckerManager::create([]),
633
                3600
634
            );
635
        }
636
637
        return $this->method;
638
    }
639
640
    /**
641
     * @param bool $isRequired
642
     *
643
     * @return ClientAssertionJwt
644
     */
645
    private function getMethodWithEncryptionSupport(bool $isRequired): ClientAssertionJwt
646
    {
647
        $method = clone $this->getMethod();
648
649
        $method->enableEncryptedAssertions(
650
            new JWELoader(
651
                JWESerializerManager::create([new \Jose\Component\Encryption\Serializer\CompactSerializer(new StandardConverter())]),
652
                new JWEDecrypter(
653
                    AlgorithmManager::create([new RSAOAEP256()]),
654
                    AlgorithmManager::create([new A256CBCHS512()]),
655
                    CompressionMethodManager::create([new Deflate()])
656
                ),
657
                HeaderCheckerManager::create([], [new JWETokenSupport()])
658
            ),
659
            JWKSet::createFromKeys([JWK::create([
660
                'kty' => 'RSA',
661
                'kid' => '[email protected]',
662
                'use' => 'enc',
663
                'n' => 'wbdxI55VaanZXPY29Lg5hdmv2XhvqAhoxUkanfzf2-5zVUxa6prHRrI4pP1AhoqJRlZfYtWWd5mmHRG2pAHIlh0ySJ9wi0BioZBl1XP2e-C-FyXJGcTy0HdKQWlrfhTm42EW7Vv04r4gfao6uxjLGwfpGrZLarohiWCPnkNrg71S2CuNZSQBIPGjXfkmIy2tl_VWgGnL22GplyXj5YlBLdxXp3XeStsqo571utNfoUTU8E4qdzJ3U1DItoVkPGsMwlmmnJiwA7sXRItBCivR4M5qnZtdw-7v4WuR4779ubDuJ5nalMv2S66-RPcnFAzWSKxtBDnFJJDGIUe7Tzizjg1nms0Xq_yPub_UOlWn0ec85FCft1hACpWG8schrOBeNqHBODFskYpUc2LC5JA2TaPF2dA67dg1TTsC_FupfQ2kNGcE1LgprxKHcVWYQb86B-HozjHZcqtauBzFNV5tbTuB-TpkcvJfNcFLlH3b8mb-H_ox35FjqBSAjLKyoeqfKTpVjvXhd09knwgJf6VKq6UC418_TOljMVfFTWXUxlnfhOOnzW6HSSzD1c9WrCuVzsUMv54szidQ9wf1cYWf3g5qFDxDQKis99gcDaiCAwM3yEBIzuNeeCa5dartHDb1xEB_HcHSeYbghbMjGfasvKn0aZRsnTyC0xhWBlsolZE',
664
                'e' => 'AQAB',
665
                'alg' => 'RSA-OAEP-256',
666
                'd' => 'n7fzJc3_WG59VEOBTkayzuSMM780OJQuZjN_KbH8lOZG25ZoA7T4Bxcc0xQn5oZE5uSCIwg91oCt0JvxPcpmqzaJZg1nirjcWZ-oBtVk7gCAWq-B3qhfF3izlbkosrzjHajIcY33HBhsy4_WerrXg4MDNE4HYojy68TcxT2LYQRxUOCf5TtJXvM8olexlSGtVnQnDRutxEUCwiewfmmrfveEogLx9EA-KMgAjTiISXxqIXQhWUQX1G7v_mV_Hr2YuImYcNcHkRvp9E7ook0876DhkO8v4UOZLwA1OlUX98mkoqwc58A_Y2lBYbVx1_s5lpPsEqbbH-nqIjh1fL0gdNfihLxnclWtW7pCztLnImZAyeCWAG7ZIfv-Rn9fLIv9jZ6r7r-MSH9sqbuziHN2grGjD_jfRluMHa0l84fFKl6bcqN1JWxPVhzNZo01yDF-1LiQnqUYSepPf6X3a2SOdkqBRiquE6EvLuSYIDpJq3jDIsgoL8Mo1LoomgiJxUwL_GWEOGu28gplyzm-9Q0U0nyhEf1uhSR8aJAQWAiFImWH5W_IQT9I7-yrindr_2fWQ_i1UgMsGzA7aOGzZfPljRy6z-tY_KuBG00-28S_aWvjyUc-Alp8AUyKjBZ-7CWH32fGWK48j1t-zomrwjL_mnhsPbGs0c9WsWgRzI-K8gE',
667
                'p' => '7_2v3OQZzlPFcHyYfLABQ3XP85Es4hCdwCkbDeltaUXgVy9l9etKghvM4hRkOvbb01kYVuLFmxIkCDtpi-zLCYAdXKrAK3PtSbtzld_XZ9nlsYa_QZWpXB_IrtFjVfdKUdMz94pHUhFGFj7nr6NNxfpiHSHWFE1zD_AC3mY46J961Y2LRnreVwAGNw53p07Db8yD_92pDa97vqcZOdgtybH9q6uma-RFNhO1AoiJhYZj69hjmMRXx-x56HO9cnXNbmzNSCFCKnQmn4GQLmRj9sfbZRqL94bbtE4_e0Zrpo8RNo8vxRLqQNwIy85fc6BRgBJomt8QdQvIgPgWCv5HoQ',
668
                'q' => 'zqOHk1P6WN_rHuM7ZF1cXH0x6RuOHq67WuHiSknqQeefGBA9PWs6ZyKQCO-O6mKXtcgE8_Q_hA2kMRcKOcvHil1hqMCNSXlflM7WPRPZu2qCDcqssd_uMbP-DqYthH_EzwL9KnYoH7JQFxxmcv5An8oXUtTwk4knKjkIYGRuUwfQTus0w1NfjFAyxOOiAQ37ussIcE6C6ZSsM3n41UlbJ7TCqewzVJaPJN5cxjySPZPD3Vp01a9YgAD6a3IIaKJdIxJS1ImnfPevSJQBE79-EXe2kSwVgOzvt-gsmM29QQ8veHy4uAqca5dZzMs7hkkHtw1z0jHV90epQJJlXXnH8Q',
669
                'dp' => '19oDkBh1AXelMIxQFm2zZTqUhAzCIr4xNIGEPNoDt1jK83_FJA-xnx5kA7-1erdHdms_Ef67HsONNv5A60JaR7w8LHnDiBGnjdaUmmuO8XAxQJ_ia5mxjxNjS6E2yD44USo2JmHvzeeNczq25elqbTPLhUpGo1IZuG72FZQ5gTjXoTXC2-xtCDEUZfaUNh4IeAipfLugbpe0JAFlFfrTDAMUFpC3iXjxqzbEanflwPvj6V9iDSgjj8SozSM0dLtxvu0LIeIQAeEgT_yXcrKGmpKdSO08kLBx8VUjkbv_3Pn20Gyu2YEuwpFlM_H1NikuxJNKFGmnAq9LcnwwT0jvoQ',
670
                'dq' => 'S6p59KrlmzGzaQYQM3o0XfHCGvfqHLYjCO557HYQf72O9kLMCfd_1VBEqeD-1jjwELKDjck8kOBl5UvohK1oDfSP1DleAy-cnmL29DqWmhgwM1ip0CCNmkmsmDSlqkUXDi6sAaZuntyukyflI-qSQ3C_BafPyFaKrt1fgdyEwYa08pESKwwWisy7KnmoUvaJ3SaHmohFS78TJ25cfc10wZ9hQNOrIChZlkiOdFCtxDqdmCqNacnhgE3bZQjGp3n83ODSz9zwJcSUvODlXBPc2AycH6Ci5yjbxt4Ppox_5pjm6xnQkiPgj01GpsUssMmBN7iHVsrE7N2iznBNCeOUIQ',
671
                'qi' => 'FZhClBMywVVjnuUud-05qd5CYU0dK79akAgy9oX6RX6I3IIIPckCciRrokxglZn-omAY5CnCe4KdrnjFOT5YUZE7G_Pg44XgCXaarLQf4hl80oPEf6-jJ5Iy6wPRx7G2e8qLxnh9cOdf-kRqgOS3F48Ucvw3ma5V6KGMwQqWFeV31XtZ8l5cVI-I3NzBS7qltpUVgz2Ju021eyc7IlqgzR98qKONl27DuEES0aK0WE97jnsyO27Yp88Wa2RiBrEocM89QZI1seJiGDizHRUP4UZxw9zsXww46wy0P6f9grnYp7t8LkyDDk8eoI4KX6SNMNVcyVS9IWjlq8EzqZEKIA',
672
            ])]),
673
            $isRequired
674
        );
675
676
        return $method;
677
    }
678
679
    /**
680
     * @return ClientAssertionJwt
681
     */
682
    private function getMethodWithTrustedIssuerSupport(): ClientAssertionJwt
683
    {
684
        $method = clone $this->getMethod();
685
686
        $trustedIssuer = $this->prophesize(TrustedIssuer::class);
687
        $trustedIssuer->name()->willReturn('TRUSTED_ISSUER');
688
        $trustedIssuer->getAllowedAssertionTypes()->willReturn(['urn:ietf:params:oauth:client-assertion-type:jwt-bearer']);
689
        $trustedIssuer->getAllowedSignatureAlgorithms()->willReturn(['RS256']);
690
        $trustedIssuer->getJWKSet()->willReturn(JWKSet::createFromKeys([JWK::create([
691
            'kty' => 'RSA',
692
            'n' => '33WRDEG5rN7daMgI2N5H8cPwTeQPOnz34uG2fe0yKyHjJDGE2XoESRpu5LelSPdYM_r4AWMFWoDWPd-7xaq7uFEkM8c6zaQIgj4uEiq-pBMvH-e805SFbYOKYqfQe4eeXAk4OrQwcUkSrlGskf6YUaw_3IwbPgzEDTgTZFVtQlE',
693
            'e' => 'AQAB',
694
            'alg' => 'RS256',
695
            'use' => 'sig',
696
        ])]));
697
698
        $trustedIssuerRepository = $this->prophesize(TrustedIssuerRepository::class);
699
        $trustedIssuerRepository->find('TRUSTED_ISSUER')->willReturn($trustedIssuer->reveal());
700
701
        $method->enableTrustedIssuerSupport($trustedIssuerRepository->reveal());
702
703
        return $method;
704
    }
705
706
    /**
707
     * @param \Http\Mock\Client $client
708
     *
709
     * @return JKUFactory
710
     */
711
    private function getJkuFactory(\Http\Mock\Client $client): JKUFactory
712
    {
713
        return new JKUFactory(
714
            new StandardConverter(),
715
            $client,
716
            new DiactorosMessageFactory()
717
        );
718
    }
719
720
    /**
721
     * @return \Http\Mock\Client
722
     */
723
    private function getHttpClient(): \Http\Mock\Client
724
    {
725
        return new \Http\Mock\Client(
726
            new DiactorosMessageFactory()
727
        );
728
    }
729
730
    /**
731
     * @param JWS $jws
732
     *
733
     * @return string
734
     *
735
     * @throws \Exception
736
     */
737
    private function serializeJWS(JWS $jws): string
738
    {
739
        $jsonConverter = new StandardConverter();
740
        $serializer = new CompactSerializer($jsonConverter);
741
742
        return $serializer->serialize($jws, 0);
743
    }
744
745
    /**
746
     * @return JWS
747
     */
748
    private function createValidClientAssertionSignedByTheClient(): JWS
749
    {
750
        $jsonConverter = new StandardConverter();
751
        $jwsBuilder = new JWSBuilder(
752
            $jsonConverter,
753
            AlgorithmManager::create([
754
                new HS256(),
755
            ])
756
        );
757
758
        return $jwsBuilder
759
            ->create()
760
            ->withPayload($jsonConverter->encode([
761
                'iss' => 'ClientId',
762
                'sub' => 'ClientId',
763
                'aud' => 'My Server',
764
                'exp' => time() + 3600,
765
            ]))
766
            ->addSignature(
767
                JWK::createFromJson('{"kty":"oct","k":"U0VDUkVU"}'),
768
                ['alg' => 'HS256']
769
            )
770
            ->build();
771
    }
772
773
    /**
774
     * @return JWS
775
     */
776
    private function createValidClientAssertionSignedByATrustedIssuer(): JWS
777
    {
778
        $jsonConverter = new StandardConverter();
779
        $jwsBuilder = new JWSBuilder(
780
            $jsonConverter,
781
            AlgorithmManager::create([
782
                new RS256(),
783
            ])
784
        );
785
786
        return $jwsBuilder
787
            ->create()
788
            ->withPayload($jsonConverter->encode([
789
                'iss' => 'TRUSTED_ISSUER',
790
                'sub' => 'ClientId',
791
                'aud' => 'My Server',
792
                'exp' => time() + 3600,
793
            ]))
794
            ->addSignature(
795
                JWK::create([
796
                    'kty' => 'RSA',
797
                    'n' => '33WRDEG5rN7daMgI2N5H8cPwTeQPOnz34uG2fe0yKyHjJDGE2XoESRpu5LelSPdYM_r4AWMFWoDWPd-7xaq7uFEkM8c6zaQIgj4uEiq-pBMvH-e805SFbYOKYqfQe4eeXAk4OrQwcUkSrlGskf6YUaw_3IwbPgzEDTgTZFVtQlE',
798
                    'e' => 'AQAB',
799
                    'p' => '9Vovb8pySyOZUoTrNMD6JmTsDa12u9y4_HImQuKD0rerVo2y5y7D_r00i1MhGHkBrI3W2PsubIiZgKp1f0oQfQ',
800
                    'd' => 'jrDrO3Fo2GvD5Jn_lER0mnxtIb_kvYt5WyaYutbRN1u_SKhaVeklfWzkrSZb5DkV2LOE1JXfoEgvBnms1O9OSJXwqDrFF7NDebw95g6JzI-SbkIHw0Cb-_E9K92FjvW3Bi8j9PKIa8c_dpwIAIirc_q8uhSTf4WoIOHSFbSaQPE',
801
                    'q' => '6Sgna9gQw4dXN0jBSjOZSjl4S2_H3wHatclrvlYfbJVU6GlIlqWGaUkdFvCuEr9iXJAY4zpEQ4P370EZtsyVZQ',
802
                    'dp' => '5m79fpE1Jz0YE1ijT7ivOMAws-fnTCnR08eiB8-W36GBWplbHaXejrJFV1WMD-AWomnVD5VZ1LW29hEiqZp2QQ',
803
                    'dq' => 'JV2pC7CB50QeZx7C02h3jZyuObC9YHEEoxOXr9ZPjPBVvjV5S6NVajQsdEu4Kgr_8YOqaWgiHovcxTwyqcgZvQ',
804
                    'qi' => 'VZykPj-ugKQxuWTSE-hA-nJqkl7FzjfzHte4QYUSHLHFq6oLlHhgUoJ_4oFLaBmCvgZLAFRDDD6pnd5Fgzt9ow',
805
                ]),
806
                ['alg' => 'RS256']
807
            )
808
            ->build();
809
    }
810
811
    /**
812
     * @return JWS
813
     *
814
     * @throws \Exception
815
     */
816
    private function createInvalidClientAssertionSignedByTheClient(): JWS
817
    {
818
        $jsonConverter = new StandardConverter();
819
        $jwsBuilder = new JWSBuilder(
820
            $jsonConverter,
821
            AlgorithmManager::create([
822
                new HS256(),
823
            ])
824
        );
825
826
        return $jwsBuilder
827
            ->create()
828
            ->withPayload($jsonConverter->encode([]))
829
            ->addSignature(
830
                JWK::createFromJson('{"kty":"oct","k":"U0VDUkVU"}'),
831
                ['alg' => 'HS256']
832
            )
833
            ->build();
834
    }
835
836
    /**
837
     * @return JWS
838
     */
839
    private function createInvalidClientAssertionSignedByATrustedIssuer(): JWS
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
840
    {
841
        $jsonConverter = new StandardConverter();
842
        $jwsBuilder = new JWSBuilder(
843
            $jsonConverter,
844
            AlgorithmManager::create([
845
                new RS256(),
846
            ])
847
        );
848
849
        return $jwsBuilder
850
            ->create()
851
            ->withPayload($jsonConverter->encode([
852
            ]))
853
            ->addSignature(
854
                JWK::create([
855
                    'kty' => 'RSA',
856
                    'n' => '33WRDEG5rN7daMgI2N5H8cPwTeQPOnz34uG2fe0yKyHjJDGE2XoESRpu5LelSPdYM_r4AWMFWoDWPd-7xaq7uFEkM8c6zaQIgj4uEiq-pBMvH-e805SFbYOKYqfQe4eeXAk4OrQwcUkSrlGskf6YUaw_3IwbPgzEDTgTZFVtQlE',
857
                    'e' => 'AQAB',
858
                    'p' => '9Vovb8pySyOZUoTrNMD6JmTsDa12u9y4_HImQuKD0rerVo2y5y7D_r00i1MhGHkBrI3W2PsubIiZgKp1f0oQfQ',
859
                    'd' => 'jrDrO3Fo2GvD5Jn_lER0mnxtIb_kvYt5WyaYutbRN1u_SKhaVeklfWzkrSZb5DkV2LOE1JXfoEgvBnms1O9OSJXwqDrFF7NDebw95g6JzI-SbkIHw0Cb-_E9K92FjvW3Bi8j9PKIa8c_dpwIAIirc_q8uhSTf4WoIOHSFbSaQPE',
860
                    'q' => '6Sgna9gQw4dXN0jBSjOZSjl4S2_H3wHatclrvlYfbJVU6GlIlqWGaUkdFvCuEr9iXJAY4zpEQ4P370EZtsyVZQ',
861
                    'dp' => '5m79fpE1Jz0YE1ijT7ivOMAws-fnTCnR08eiB8-W36GBWplbHaXejrJFV1WMD-AWomnVD5VZ1LW29hEiqZp2QQ',
862
                    'dq' => 'JV2pC7CB50QeZx7C02h3jZyuObC9YHEEoxOXr9ZPjPBVvjV5S6NVajQsdEu4Kgr_8YOqaWgiHovcxTwyqcgZvQ',
863
                    'qi' => 'VZykPj-ugKQxuWTSE-hA-nJqkl7FzjfzHte4QYUSHLHFq6oLlHhgUoJ_4oFLaBmCvgZLAFRDDD6pnd5Fgzt9ow',
864
                ]),
865
                ['alg' => 'RS256']
866
            )
867
            ->build();
868
    }
869
870
    /**
871
     * @param string $assertion
872
     *
873
     * @return string
874
     *
875
     * @throws \Exception
876
     */
877
    private function encryptAssertion(string $assertion): string
878
    {
879
        $jsonConverter = new StandardConverter();
880
        $jweBuilder = new JWEBuilder(
881
            $jsonConverter,
882
            AlgorithmManager::create([new RSAOAEP256()]),
883
            AlgorithmManager::create([new A256CBCHS512()]),
884
            CompressionMethodManager::create([new Deflate()])
885
        );
886
        $jwe = $jweBuilder->create()
887
            ->withPayload($assertion)
888
            ->withSharedProtectedHeader(['alg' => 'RSA-OAEP-256', 'enc' => 'A256CBC-HS512'])
889
            ->addRecipient(JWK::create([
890
                'kty' => 'RSA',
891
                'kid' => '[email protected]',
892
                'use' => 'enc',
893
                'n' => 'wbdxI55VaanZXPY29Lg5hdmv2XhvqAhoxUkanfzf2-5zVUxa6prHRrI4pP1AhoqJRlZfYtWWd5mmHRG2pAHIlh0ySJ9wi0BioZBl1XP2e-C-FyXJGcTy0HdKQWlrfhTm42EW7Vv04r4gfao6uxjLGwfpGrZLarohiWCPnkNrg71S2CuNZSQBIPGjXfkmIy2tl_VWgGnL22GplyXj5YlBLdxXp3XeStsqo571utNfoUTU8E4qdzJ3U1DItoVkPGsMwlmmnJiwA7sXRItBCivR4M5qnZtdw-7v4WuR4779ubDuJ5nalMv2S66-RPcnFAzWSKxtBDnFJJDGIUe7Tzizjg1nms0Xq_yPub_UOlWn0ec85FCft1hACpWG8schrOBeNqHBODFskYpUc2LC5JA2TaPF2dA67dg1TTsC_FupfQ2kNGcE1LgprxKHcVWYQb86B-HozjHZcqtauBzFNV5tbTuB-TpkcvJfNcFLlH3b8mb-H_ox35FjqBSAjLKyoeqfKTpVjvXhd09knwgJf6VKq6UC418_TOljMVfFTWXUxlnfhOOnzW6HSSzD1c9WrCuVzsUMv54szidQ9wf1cYWf3g5qFDxDQKis99gcDaiCAwM3yEBIzuNeeCa5dartHDb1xEB_HcHSeYbghbMjGfasvKn0aZRsnTyC0xhWBlsolZE',
894
                'e' => 'AQAB',
895
                'alg' => 'RSA-OAEP-256',
896
            ]))
897
            ->build();
898
899
        $serializer = new \Jose\Component\Encryption\Serializer\CompactSerializer($jsonConverter);
900
901
        return $serializer->serialize($jwe, 0);
902
    }
903
904
    private function buildRequest(array $data): ObjectProphecy
905
    {
906
        $body = $this->prophesize(StreamInterface::class);
907
        $body->getContents()->willReturn(http_build_query($data));
908
        $request = $this->prophesize(ServerRequestInterface::class);
909
        $request->hasHeader('Content-Type')->willReturn(true);
910
        $request->getHeader('Content-Type')->willReturn(['application/x-www-form-urlencoded']);
911
        $request->getBody()->willReturn($body->reveal());
912
        $request->getParsedBody()->willReturn([]);
913
914
        return $request;
915
    }
916
}
917