Failed Conditions
Push — ng ( 8b0de7...c3dfca )
by Florent
06:04
created

ClientAssertionJwtAuthenticationMethodTest   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 470
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 27
lcom 1
cbo 3
dl 0
loc 470
rs 10
c 0
b 0
f 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
A genericCalls() 0 10 1
A theClientIdCannotBeFoundInTheRequest() 0 11 1
A theClientAssertionTypeIsNotSupported() 0 13 1
A theClientAssertionIsMissing() 0 17 2
A theClientAssertionIsInvalid() 0 18 2
A theClientAssertionSignedByTheClientIsInvalidBecauseOfMissingClaims() 0 18 2
A theClientAssertionIsValidAndTheClientIdIsRetrieved() 0 13 1
A theClientConfigurationCanBeCheckedWithClientSecretJwt() 0 11 1
A theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfBothJwksAndJwksUriAreSet() 0 10 1
A theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfNoneOfTheJwksAndJwksUriAreSet() 0 8 1
A theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksIsNotAValidKeySet() 0 9 1
A theClientConfigurationCanBeCheckedWithPrivateKeyJwtIfJwksIsValid() 0 12 1
A theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriFactoryIsNotAvailable() 0 9 1
A theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriIsNotValid() 0 13 1
A theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriCannotBeReached() 0 14 1
A theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriDoesNotContainAValidKeySet() 0 17 1
A theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriDoesContainAnEmptyKeySet() 0 17 1
A theClientConfigurationCanBeCheckedWithPrivateKeyJwt() 0 20 1
A getMethod() 0 22 2
A getJkuFactory() 0 8 1
A getHttpClient() 0 6 1
B createValidClientAssertionSignedByTheClient() 0 28 1
A createInvalidClientAssertionSignedByTheClient() 0 23 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\KeyManagement\JKUFactory;
23
use Jose\Component\Signature\Algorithm\HS256;
24
use Jose\Component\Signature\Algorithm\RS256;
25
use Jose\Component\Signature\JWSBuilder;
26
use Jose\Component\Signature\JWSTokenSupport;
27
use Jose\Component\Signature\JWSVerifier;
28
use Jose\Component\Signature\Serializer\CompactSerializer;
29
use OAuth2Framework\Component\Core\Client\Client;
30
use OAuth2Framework\Component\Core\Client\ClientId;
31
use OAuth2Framework\Component\Core\DataBag\DataBag;
32
use OAuth2Framework\Component\Core\Exception\OAuth2Exception;
33
use OAuth2Framework\Component\Core\UserAccount\UserAccountId;
34
use OAuth2Framework\Component\ClientAuthentication\AuthenticationMethodManager;
35
use OAuth2Framework\Component\ClientAuthentication\ClientAssertionJwt;
36
use PHPUnit\Framework\TestCase;
37
use Psr\Http\Message\ServerRequestInterface;
38
use Zend\Diactoros\Response;
39
40
/**
41
 * @group TokenEndpoint
42
 * @group ClientAuthentication
43
 */
44
class ClientAssertionJwtAuthenticationMethodTest extends TestCase
45
{
46
    /**
47
     * @test
48
     */
49
    public function genericCalls()
50
    {
51
        $method = $this->getMethod();
52
53
        self::assertEquals([], $method->getSchemesParameters());
54
        self::assertEquals(['client_secret_jwt', 'private_key_jwt'], $method->getSupportedMethods());
55
        self::assertEquals(['HS256', 'RS256'], $method->getSupportedSignatureAlgorithms());
56
        self::assertEquals([], $method->getSupportedKeyEncryptionAlgorithms());
57
        self::assertEquals([], $method->getSupportedContentEncryptionAlgorithms());
58
    }
59
60
    /**
61
     * @test
62
     */
63
    public function theClientIdCannotBeFoundInTheRequest()
64
    {
65
        $method = $this->getMethod();
66
        $request = $this->prophesize(ServerRequestInterface::class);
67
        $request->getHeader('Authorization')->willReturn([]);
68
        $request->getParsedBody()->willReturn([]);
69
70
        $clientId = $method->findClientIdAndCredentials($request->reveal(), $credentials);
71
        self::assertNull($clientId);
72
        self::assertNull($credentials);
73
    }
74
75
    /**
76
     * @test
77
     */
78
    public function theClientAssertionTypeIsNotSupported()
79
    {
80
        $method = $this->getMethod();
81
        $request = $this->prophesize(ServerRequestInterface::class);
82
        $request->getHeader('Authorization')->willReturn([]);
83
        $request->getParsedBody()->willReturn([
84
            'client_assertion_type' => 'foo',
85
        ]);
86
87
        $clientId = $method->findClientIdAndCredentials($request->reveal(), $credentials);
88
        self::assertNull($clientId);
89
        self::assertNull($credentials);
90
    }
91
92
    /**
93
     * @test
94
     */
95
    public function theClientAssertionIsMissing()
96
    {
97
        $method = $this->getMethod();
98
        $request = $this->prophesize(ServerRequestInterface::class);
99
        $request->getHeader('Authorization')->willReturn([]);
100
        $request->getParsedBody()->willReturn([
101
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
102
        ]);
103
104
        try {
105
            $method->findClientIdAndCredentials($request->reveal(), $credentials);
106
            $this->fail('An OAuth2 exception should be thrown.');
107
        } catch (OAuth2Exception $e) {
108
            self::assertEquals('invalid_request', $e->getMessage());
109
            self::assertEquals('Parameter "client_assertion" is missing.', $e->getErrorDescription());
110
        }
111
    }
112
113
    /**
114
     * @test
115
     */
116
    public function theClientAssertionIsInvalid()
117
    {
118
        $method = $this->getMethod();
119
        $request = $this->prophesize(ServerRequestInterface::class);
120
        $request->getHeader('Authorization')->willReturn([]);
121
        $request->getParsedBody()->willReturn([
122
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
123
            'client_assertion' => 'foo',
124
        ]);
125
126
        try {
127
            $method->findClientIdAndCredentials($request->reveal(), $credentials);
128
            $this->fail('An OAuth2 exception should be thrown.');
129
        } catch (OAuth2Exception $e) {
130
            self::assertEquals('invalid_request', $e->getMessage());
131
            self::assertEquals('Unable to load, decrypt or verify the client assertion.', $e->getErrorDescription());
132
        }
133
    }
134
135
    /**
136
     * @test
137
     */
138
    public function theClientAssertionSignedByTheClientIsInvalidBecauseOfMissingClaims()
139
    {
140
        $method = $this->getMethod();
141
        $request = $this->prophesize(ServerRequestInterface::class);
142
        $request->getHeader('Authorization')->willReturn([]);
143
        $request->getParsedBody()->willReturn([
144
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
145
            'client_assertion' => $this->createInvalidClientAssertionSignedByTheClient(),
146
        ]);
147
148
        try {
149
            $method->findClientIdAndCredentials($request->reveal(), $credentials);
150
            $this->fail('An OAuth2 exception should be thrown.');
151
        } catch (OAuth2Exception $e) {
152
            self::assertEquals('invalid_request', $e->getMessage());
153
            self::assertEquals('The following claim(s) is/are mandatory: "iss, sub, aud, exp".', $e->getErrorDescription());
154
        }
155
    }
156
157
    /**
158
     * @test
159
     */
160
    public function theClientAssertionIsValidAndTheClientIdIsRetrieved()
161
    {
162
        $method = $this->getMethod();
163
        $request = $this->prophesize(ServerRequestInterface::class);
164
        $request->getHeader('Authorization')->willReturn([]);
165
        $request->getParsedBody()->willReturn([
166
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
167
            'client_assertion' => $this->createValidClientAssertionSignedByTheClient(),
168
        ]);
169
170
        $clientId = $method->findClientIdAndCredentials($request->reveal(), $credentials);
171
        self::assertEquals('ClientId', $clientId->getValue());
172
    }
173
174
    /**
175
     * @test
176
     */
177
    /*public function theClientUsesAnotherAuthenticationMethod()
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
178
    {
179
        $method = $this->getMethod();
180
        $manager = new AuthenticationMethodManager();
181
        $method->add($method);
182
        $client = Client::createEmpty();
183
        $client = $client->create(
184
            ClientId::create('CLIENT_ID'),
185
            DataBag::create([
186
                'client_secret' => 'CLIENT_SECRET',
187
                'token_endpoint_auth_method' => 'client_secret_post',
188
            ]),
189
            UserAccountId::create('USER_ACCOUNT_ID')
190
        );
191
        $request = $this->prophesize(ServerRequestInterface::class);
192
        $request->getParsedBody()->willReturn([
193
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
194
            'client_assertion'      => 'CLIENT_SECRET',
195
        ]);
196
197
        self::assertFalse($method->isClientAuthenticated($request->reveal(), $client, $method, 'CLIENT_SECRET'));
198
    }*/
199
200
    /**
201
     * @test
202
     */
203
    public function theClientConfigurationCanBeCheckedWithClientSecretJwt()
204
    {
205
        $method = $this->getMethod();
206
        $commandParameters = DataBag::create([
207
            'token_endpoint_auth_method' => 'client_secret_jwt',
208
        ]);
209
        $validatedParameters = $method->checkClientConfiguration($commandParameters, DataBag::create([]));
210
211
        self::assertTrue($validatedParameters->has('client_secret'));
212
        self::assertTrue($validatedParameters->has('client_secret_expires_at'));
213
    }
214
215
    /**
216
     * @test
217
     * @expectedException \InvalidArgumentException
218
     * @expectedExceptionMessage Either the parameter "jwks" or "jwks_uri" must be set.
219
     */
220
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfBothJwksAndJwksUriAreSet()
221
    {
222
        $method = $this->getMethod();
223
        $commandParameters = DataBag::create([
224
            'token_endpoint_auth_method' => 'private_key_jwt',
225
            'jwks' => 'foo',
226
            'jwks_uri' => 'bar',
227
        ]);
228
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
229
    }
230
231
    /**
232
     * @test
233
     * @expectedException \InvalidArgumentException
234
     * @expectedExceptionMessage Either the parameter "jwks" or "jwks_uri" must be set.
235
     */
236
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfNoneOfTheJwksAndJwksUriAreSet()
237
    {
238
        $method = $this->getMethod();
239
        $commandParameters = DataBag::create([
240
            'token_endpoint_auth_method' => 'private_key_jwt',
241
        ]);
242
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
243
    }
244
245
    /**
246
     * @test
247
     * @expectedException \InvalidArgumentException
248
     * @expectedExceptionMessage The parameter "jwks" must be a valid JWKSet object.
249
     */
250
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksIsNotAValidKeySet()
251
    {
252
        $method = $this->getMethod();
253
        $commandParameters = DataBag::create([
254
            'token_endpoint_auth_method' => 'private_key_jwt',
255
            'jwks' => 'foo',
256
        ]);
257
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
258
    }
259
260
    /**
261
     * @test
262
     */
263
    public function theClientConfigurationCanBeCheckedWithPrivateKeyJwtIfJwksIsValid()
264
    {
265
        $method = $this->getMethod();
266
        $commandParameters = DataBag::create([
267
            'token_endpoint_auth_method' => 'private_key_jwt',
268
            'jwks' => '{"keys":[{"kty":"oct","k":"bJzb8RaN7TzPz001PeF0lw0ZoUJqbazGxMvBd_xzfms"},{"kty":"oct","k":"dIx5cdLn-dAgNkvfZSiroJuy5oykHO4hDnYpmwlMq6A"}]}',
269
        ]);
270
        $validatedParameters = $method->checkClientConfiguration($commandParameters, DataBag::create([]));
271
272
        self::assertTrue($validatedParameters->has('jwks'));
273
        self::assertEquals('{"keys":[{"kty":"oct","k":"bJzb8RaN7TzPz001PeF0lw0ZoUJqbazGxMvBd_xzfms"},{"kty":"oct","k":"dIx5cdLn-dAgNkvfZSiroJuy5oykHO4hDnYpmwlMq6A"}]}', $validatedParameters->get('jwks'));
274
    }
275
276
    /**
277
     * @test
278
     * @expectedException \InvalidArgumentException
279
     * @expectedExceptionMessage Distant key sets cannot be used. Please use "jwks" instead of "jwks_uri".
280
     */
281
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriFactoryIsNotAvailable()
282
    {
283
        $method = $this->getMethod();
284
        $commandParameters = DataBag::create([
285
            'token_endpoint_auth_method' => 'private_key_jwt',
286
            'jwks_uri' => 'foo',
287
        ]);
288
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
289
    }
290
291
    /**
292
     * @test
293
     * @expectedException \InvalidArgumentException
294
     * @expectedExceptionMessage The parameter "jwks_uri" must be a valid uri to a JWKSet.
295
     */
296
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriIsNotValid()
297
    {
298
        $method = clone $this->getMethod();
299
        $httpClient = $this->getHttpClient();
300
        $method->enableJkuSupport(
301
            $this->getJkuFactory($httpClient)
302
        );
303
        $commandParameters = DataBag::create([
304
            'token_endpoint_auth_method' => 'private_key_jwt',
305
            'jwks_uri' => 'foo',
306
        ]);
307
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
308
    }
309
310
    /**
311
     * @test
312
     * @expectedException \InvalidArgumentException
313
     * @expectedExceptionMessage The parameter "jwks_uri" must be a valid uri to a JWKSet.
314
     */
315
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriCannotBeReached()
316
    {
317
        $method = clone $this->getMethod();
318
        $httpClient = $this->getHttpClient();
319
        $httpClient->addResponse(new Response('php://memory', 404));
320
        $method->enableJkuSupport(
321
            $this->getJkuFactory($httpClient)
322
        );
323
        $commandParameters = DataBag::create([
324
            'token_endpoint_auth_method' => 'private_key_jwt',
325
            'jwks_uri' => 'https://www.foo.com/bad-url.jwkset',
326
        ]);
327
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
328
    }
329
330
    /**
331
     * @test
332
     * @expectedException \InvalidArgumentException
333
     * @expectedExceptionMessage The parameter "jwks_uri" must be a valid uri to a JWKSet.
334
     */
335
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriDoesNotContainAValidKeySet()
336
    {
337
        $method = clone $this->getMethod();
338
        $httpClient = $this->getHttpClient();
339
        $stream = fopen('php://memory', 'w+');
340
        fwrite($stream, 'Hello World!');
341
        rewind($stream);
342
        $httpClient->addResponse(new Response($stream, 200));
343
        $method->enableJkuSupport(
344
            $this->getJkuFactory($httpClient)
345
        );
346
        $commandParameters = DataBag::create([
347
            'token_endpoint_auth_method' => 'private_key_jwt',
348
            'jwks_uri' => 'https://www.foo.com/index.html',
349
        ]);
350
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
351
    }
352
353
    /**
354
     * @test
355
     * @expectedException \InvalidArgumentException
356
     * @expectedExceptionMessage The distant key set is empty.
357
     */
358
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriDoesContainAnEmptyKeySet()
359
    {
360
        $method = clone $this->getMethod();
361
        $httpClient = $this->getHttpClient();
362
        $stream = fopen('php://memory', 'w+');
363
        fwrite($stream, '{"keys":[]}');
364
        rewind($stream);
365
        $httpClient->addResponse(new Response($stream, 200));
366
        $method->enableJkuSupport(
367
            $this->getJkuFactory($httpClient)
368
        );
369
        $commandParameters = DataBag::create([
370
            'token_endpoint_auth_method' => 'private_key_jwt',
371
            'jwks_uri' => 'https://www.foo.com/index.html',
372
        ]);
373
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
374
    }
375
376
    /**
377
     * @test
378
     */
379
    public function theClientConfigurationCanBeCheckedWithPrivateKeyJwt()
380
    {
381
        $method = clone $this->getMethod();
382
        $httpClient = $this->getHttpClient();
383
        $stream = fopen('php://memory', 'w+');
384
        fwrite($stream, '{"keys":[{"kty":"oct","k":"bJzb8RaN7TzPz001PeF0lw0ZoUJqbazGxMvBd_xzfms"},{"kty":"oct","k":"dIx5cdLn-dAgNkvfZSiroJuy5oykHO4hDnYpmwlMq6A"}]}');
385
        rewind($stream);
386
        $httpClient->addResponse(new Response($stream, 200));
387
        $method->enableJkuSupport(
388
            $this->getJkuFactory($httpClient)
389
        );
390
        $commandParameters = DataBag::create([
391
            'token_endpoint_auth_method' => 'private_key_jwt',
392
            'jwks_uri' => 'https://www.foo.com/keyset',
393
        ]);
394
        $validatedParameters = $method->checkClientConfiguration($commandParameters, DataBag::create([]));
395
396
        self::assertTrue($validatedParameters->has('jwks_uri'));
397
        self::assertEquals('https://www.foo.com/keyset', $validatedParameters->get('jwks_uri'));
398
    }
399
400
    /**
401
     * @var null|ClientAssertionJwt
402
     */
403
    private $method = null;
404
405
    /**
406
     * @return ClientAssertionJwt
407
     */
408
    private function getMethod(): ClientAssertionJwt
409
    {
410
        if (null === $this->method) {
411
            $this->method = new ClientAssertionJwt(
412
                new StandardConverter(),
413
                new JWSVerifier(
414
                    AlgorithmManager::create([
415
                        new HS256(),
416
                        new RS256(),
417
                    ])
418
                ),
419
                HeaderCheckerManager::create(
420
                    [],
421
                    [new JWSTokenSupport()]
422
                ),
423
                ClaimCheckerManager::create([]),
424
                3600
425
            );
426
        }
427
428
        return $this->method;
429
    }
430
431
    /**
432
     * @param \Http\Mock\Client $client
433
     *
434
     * @return JKUFactory
435
     */
436
    private function getJkuFactory(\Http\Mock\Client $client):JKUFactory
437
    {
438
        return new JKUFactory(
439
            new StandardConverter(),
440
            $client,
441
            new DiactorosMessageFactory()
442
        );
443
    }
444
445
    /**
446
     * @return \Http\Mock\Client
447
     */
448
    private function getHttpClient(): \Http\Mock\Client
449
    {
450
        return new \Http\Mock\Client(
451
            new DiactorosMessageFactory()
452
        );
453
    }
454
455
    /**
456
     * @return string
457
     */
458
    private function createValidClientAssertionSignedByTheClient(): string
459
    {
460
        $jsonConverter = new StandardConverter();
461
        $jwsBuilder = new JWSBuilder(
462
            $jsonConverter,
463
            AlgorithmManager::create([
464
                new HS256(),
465
            ])
466
        );
467
468
        $jws = $jwsBuilder
469
            ->create()
470
            ->withPayload($jsonConverter->encode([
471
                'iss' => 'ClientId',
472
                'sub' => 'ClientId',
473
                'aud' => 'My Server',
474
                'exp' => time()+3600,
475
            ]))
476
            ->addSignature(
477
                JWK::createFromJson('{"kty":"oct","k":"bJzb8RaN7TzPz001PeF0lw0ZoUJqbazGxMvBd_xzfms"}'),
478
                ['alg' => 'HS256']
479
            )
480
            ->build();
481
482
        $serializer = new CompactSerializer($jsonConverter);
483
484
        return $serializer->serialize($jws, 0);
485
    }
486
487
    /**
488
     * @return string
489
     */
490
    private function createInvalidClientAssertionSignedByTheClient(): string
491
    {
492
        $jsonConverter = new StandardConverter();
493
        $jwsBuilder = new JWSBuilder(
494
            $jsonConverter,
495
            AlgorithmManager::create([
496
                new HS256(),
497
            ])
498
        );
499
500
        $jws = $jwsBuilder
501
            ->create()
502
            ->withPayload($jsonConverter->encode([]))
503
            ->addSignature(
504
                JWK::createFromJson('{"kty":"oct","k":"bJzb8RaN7TzPz001PeF0lw0ZoUJqbazGxMvBd_xzfms"}'),
505
                ['alg' => 'HS256']
506
            )
507
            ->build();
508
509
        $serializer = new CompactSerializer($jsonConverter);
510
511
        return $serializer->serialize($jws, 0);
512
    }
513
}
514