theSignatureVerificationFailed()   A
last analyzed

Complexity

Conditions 2
Paths 3

Size

Total Lines 24
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 19
c 0
b 0
f 0
nc 3
nop 0
dl 0
loc 24
rs 9.6333
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2019 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\AuthorizationEndpoint\Tests\AuthorizationRequest;
15
16
use Http\Mock\Client;
17
use Jose\Component\Checker\ClaimCheckerManager;
18
use Jose\Component\Core\AlgorithmManager;
19
use Jose\Component\Core\JWKSet;
20
use Jose\Component\Signature\JWS;
21
use Jose\Component\Signature\JWSVerifier;
22
use Nyholm\Psr7\Factory\Psr17Factory;
23
use OAuth2Framework\Component\AuthorizationEndpoint\AuthorizationRequest\AuthorizationRequestLoader;
24
use OAuth2Framework\Component\Core\Client\ClientRepository;
25
use OAuth2Framework\Component\Core\Message\OAuth2Error;
26
use PHPUnit\Framework\TestCase;
27
use Prophecy\Argument;
28
use Prophecy\PhpUnit\ProphecyTrait;
29
use Psr\Http\Message\RequestInterface;
30
31
/**
32
 * @group AuthorizationEndpoint
33
 * @group AuthorizationRequest
34
 *
35
 * @internal
36
 */
37
final class AuthorizationRequestLoaderTest extends TestCase
38
{
39
    use ProphecyTrait;
40
41
    /**
42
     * @var null|AuthorizationRequestLoader
43
     */
44
    private $authorizationRequestLoader;
45
46
    /**
47
     * @test
48
     */
49
    public function basicCalls()
50
    {
51
        $clientRepository = $this->prophesize(ClientRepository::class);
52
        $loader = $this->getAuthorizationRequestLoader(
53
            $clientRepository->reveal()
54
        );
55
        static::assertEquals([], $loader->getSupportedKeyEncryptionAlgorithms());
56
        static::assertEquals([], $loader->getSupportedContentEncryptionAlgorithms());
57
        static::assertEquals(['RS256'], $loader->getSupportedSignatureAlgorithms());
58
        static::assertTrue($loader->isRequestObjectSupportEnabled());
59
        static::assertTrue($loader->isRequestUriRegistrationRequired());
60
        static::assertTrue($loader->isRequestObjectReferenceSupportEnabled());
61
        static::assertFalse($loader->isEncryptedRequestSupportEnabled());
62
63
        //$this->authorizationRequestLoader->enableEncryptedRequestObjectSupport($jweLoader, $keyset, false);
64
    }
65
66
    /**
67
     * @test
68
     */
69
    public function theAuthorizationRequestMustContainAClientId()
70
    {
71
        $clientRepository = $this->prophesize(ClientRepository::class);
72
73
        try {
74
            $this->getAuthorizationRequestLoader($clientRepository->reveal())->load([]);
75
            static::fail('The expected exception has not been thrown.');
76
        } catch (OAuth2Error $e) {
77
            static::assertEquals(400, $e->getCode());
78
            static::assertEquals('invalid_request', $e->getMessage());
79
            static::assertEquals('Parameter "client_id" missing or invalid.', $e->getErrorDescription());
80
        }
81
    }
82
83
    /**
84
     * @test
85
     */
86
    public function theClientDoesNotExist()
87
    {
88
        $clientRepository = $this->prophesize(ClientRepository::class);
89
        $clientRepository->find(Argument::any())->willReturn(null)->shouldBeCalled();
90
91
        try {
92
            $this->getAuthorizationRequestLoader($clientRepository->reveal())->load([
93
                'client_id' => 'BAD_CLIENT_ID',
94
            ]);
95
            static::fail('The expected exception has not been thrown.');
96
        } catch (OAuth2Error $e) {
97
            static::assertEquals(400, $e->getCode());
98
            static::assertEquals('invalid_request', $e->getMessage());
99
            static::assertEquals('Parameter "client_id" missing or invalid.', $e->getErrorDescription());
100
        }
101
    }
102
103
    /**
104
     * @test
105
     */
106
    public function theClientIsDeleted()
107
    {
108
        $client = $this->prophesize(\OAuth2Framework\Component\Core\Client\Client::class);
109
        $client->isDeleted()->willReturn(true)->shouldBeCalled();
110
111
        $clientRepository = $this->prophesize(ClientRepository::class);
112
        $clientRepository->find(Argument::any())->willReturn($client->reveal())->shouldBeCalled();
113
114
        try {
115
            $this->getAuthorizationRequestLoader($clientRepository->reveal())->load([
116
                'client_id' => 'DELETED_CLIENT_ID',
117
            ]);
118
            static::fail('The expected exception has not been thrown.');
119
        } catch (OAuth2Error $e) {
120
            static::assertEquals(400, $e->getCode());
121
            static::assertEquals('invalid_request', $e->getMessage());
122
            static::assertEquals('Parameter "client_id" missing or invalid.', $e->getErrorDescription());
123
        }
124
    }
125
126
    /**
127
     * @test
128
     */
129
    public function theClientExistsAndTheParametersAreReturned()
130
    {
131
        $client = $this->prophesize(\OAuth2Framework\Component\Core\Client\Client::class);
132
        $client->isDeleted()->willReturn(false)->shouldBeCalled();
133
134
        $clientRepository = $this->prophesize(ClientRepository::class);
135
        $clientRepository->find(Argument::any())->willReturn($client->reveal())->shouldBeCalled();
136
137
        $authorization = $this->getAuthorizationRequestLoader($clientRepository->reveal())->load([
138
            'client_id' => 'CLIENT_ID',
139
        ]);
140
141
        static::assertTrue($authorization->hasQueryParam('client_id'));
142
        static::assertEquals('CLIENT_ID', $authorization->getQueryParam('client_id'));
143
        static::assertInstanceOf(\OAuth2Framework\Component\Core\Client\Client::class, $authorization->getClient());
144
    }
145
146
    /**
147
     * @test
148
     */
149
    public function theRequestObjectIsNotAValidAssertion()
150
    {
151
        $clientRepository = $this->prophesize(ClientRepository::class);
152
153
        try {
154
            $this->getAuthorizationRequestLoader($clientRepository->reveal())->load([
155
                'request' => 'INVALID_ASSERTION',
156
            ]);
157
            static::fail('The expected exception has not been thrown.');
158
        } catch (OAuth2Error $e) {
159
            static::assertEquals(400, $e->getCode());
160
            static::assertEquals('invalid_request_object', $e->getMessage());
161
            static::assertEquals('Unsupported input', $e->getErrorDescription());
162
        }
163
    }
164
165
    /**
166
     * @test
167
     */
168
    public function theRequestObjectPayloadDoesNotContainClaims()
169
    {
170
        $clientRepository = $this->prophesize(ClientRepository::class);
171
172
        try {
173
            $this->getAuthorizationRequestLoader($clientRepository->reveal())->load([
174
                'request' => 'eyJhbGciOiJub25lIn0.SEVMTE8gV09STEQh.',
175
            ]);
176
            static::fail('The expected exception has not been thrown.');
177
        } catch (OAuth2Error $e) {
178
            static::assertEquals(400, $e->getCode());
179
            static::assertEquals('invalid_request_object', $e->getMessage());
180
            static::assertEquals('Invalid assertion. The payload must contain claims.', $e->getErrorDescription());
181
        }
182
    }
183
184
    /**
185
     * @test
186
     */
187
    public function theRequestObjectParametersDoesNotContainAClientId()
188
    {
189
        $clientRepository = $this->prophesize(ClientRepository::class);
190
191
        try {
192
            $this->getAuthorizationRequestLoader($clientRepository->reveal())->load([
193
                'request' => 'eyJhbGciOiJub25lIn0.e30.',
194
            ]);
195
            static::fail('The expected exception has not been thrown.');
196
        } catch (OAuth2Error $e) {
197
            static::assertEquals(400, $e->getCode());
198
            static::assertEquals('invalid_request', $e->getMessage());
199
            static::assertEquals('Parameter "client_id" missing or invalid.', $e->getErrorDescription());
200
        }
201
    }
202
203
    /**
204
     * @test
205
     */
206
    public function theClientHasNoPublicKeysAndCannotUseTheRequestObjectParameters()
207
    {
208
        $client = $this->prophesize(\OAuth2Framework\Component\Core\Client\Client::class);
209
        $client->isDeleted()->willReturn(false)->shouldBeCalled();
210
        $client->has('jwks')->willReturn(false)->shouldBeCalled();
211
        $client->has('jwks_uri')->willReturn(false)->shouldBeCalled();
212
        $client->has('client_secret')->willReturn(false)->shouldBeCalled();
213
        $client->has('request_object_signing_alg')->willReturn(false)->shouldNotBeCalled();
214
215
        $clientRepository = $this->prophesize(ClientRepository::class);
216
        $clientRepository->find(Argument::any())->willReturn($client->reveal())->shouldBeCalled();
217
218
        try {
219
            $this->getAuthorizationRequestLoader($clientRepository->reveal())->load([
220
                'request' => 'eyJhbGciOiJub25lIn0.eyJjbGllbnRfaWQiOiJOT19LRVlfQ0xJRU5UX0lEIn0.',
221
            ]);
222
            static::fail('The expected exception has not been thrown.');
223
        } catch (OAuth2Error $e) {
224
            static::assertEquals(400, $e->getCode());
225
            static::assertEquals('invalid_request_object', $e->getMessage());
226
            static::assertEquals('The client has no key or key set.', $e->getErrorDescription());
227
        }
228
    }
229
230
    /**
231
     * @test
232
     */
233
    public function theClientDidNotReferencedAnySignatureAlgorithmAndTheUsedAlgorithmIsNotSupported()
234
    {
235
        $client = $this->prophesize(\OAuth2Framework\Component\Core\Client\Client::class);
236
        $client->isDeleted()->willReturn(false)->shouldBeCalled();
237
        $client->has('jwks')->willReturn(false)->shouldBeCalled();
238
        $client->has('jwks_uri')->willReturn(false)->shouldBeCalled();
239
        $client->has('client_secret')->willReturn(true)->shouldBeCalled();
240
        $client->get('client_secret')->willReturn('SECRET')->shouldBeCalled();
241
        $client->has('request_object_signing_alg')->willReturn(false);
242
        $client->getTokenEndpointAuthenticationMethod()->willReturn('client_secret_jwt');
243
244
        $clientRepository = $this->prophesize(ClientRepository::class);
245
        $clientRepository->find(Argument::any())->willReturn($client->reveal())->shouldBeCalled();
246
247
        try {
248
            $this->getAuthorizationRequestLoader($clientRepository->reveal())->load([
249
                'request' => 'eyJhbGciOiJub25lIn0.eyJjbGllbnRfaWQiOiJDTElFTlRfSUQifQ.',
250
            ]);
251
            static::fail('The expected exception has not been thrown.');
252
        } catch (OAuth2Error $e) {
253
            static::assertEquals(400, $e->getCode());
254
            static::assertEquals('invalid_request_object', $e->getMessage());
255
            static::assertEquals('The algorithm "none" is not allowed for request object signatures. Please use one of the following algorithm(s): RS256', $e->getErrorDescription());
256
        }
257
    }
258
259
    /**
260
     * @test
261
     */
262
    public function theSignatureAlgorithmIsNotAllowedForThatClient()
263
    {
264
        $client = $this->prophesize(\OAuth2Framework\Component\Core\Client\Client::class);
265
        $client->isDeleted()->willReturn(false)->shouldBeCalled();
266
        $client->has('jwks')->willReturn(false)->shouldBeCalled();
267
        $client->has('jwks_uri')->willReturn(false)->shouldBeCalled();
268
        $client->has('client_secret')->willReturn(true)->shouldBeCalled();
269
        $client->get('client_secret')->willReturn('SECRET')->shouldBeCalled();
270
        $client->has('request_object_signing_alg')->willReturn(true)->shouldBeCalled();
271
        $client->get('request_object_signing_alg')->willReturn('RS256')->shouldBeCalled();
272
        $client->getTokenEndpointAuthenticationMethod()->willReturn('client_secret_jwt');
273
274
        $clientRepository = $this->prophesize(ClientRepository::class);
275
        $clientRepository->find(Argument::any())->willReturn($client->reveal())->shouldBeCalled();
276
277
        try {
278
            $this->getAuthorizationRequestLoader($clientRepository->reveal())->load([
279
                'request' => 'eyJhbGciOiJub25lIn0.eyJjbGllbnRfaWQiOiJDTElFTlRfSUQifQ.',
280
            ]);
281
            static::fail('The expected exception has not been thrown.');
282
        } catch (OAuth2Error $e) {
283
            static::assertEquals(400, $e->getCode());
284
            static::assertEquals('invalid_request_object', $e->getMessage());
285
            static::assertEquals('Request Object signature algorithm not allowed for the client.', $e->getErrorDescription());
286
        }
287
    }
288
289
    /**
290
     * @test
291
     */
292
    public function theSignatureVerificationFailed()
293
    {
294
        $client = $this->prophesize(\OAuth2Framework\Component\Core\Client\Client::class);
295
        $client->isDeleted()->willReturn(false)->shouldBeCalled();
296
        $client->has('jwks')->willReturn(false)->shouldBeCalled();
297
        $client->has('jwks_uri')->willReturn(false)->shouldBeCalled();
298
        $client->has('client_secret')->willReturn(true)->shouldBeCalled();
299
        $client->get('client_secret')->willReturn('SECRET')->shouldBeCalled();
300
        $client->has('request_object_signing_alg')->willReturn(true)->shouldBeCalled();
301
        $client->get('request_object_signing_alg')->willReturn('RS256')->shouldBeCalled();
302
        $client->getTokenEndpointAuthenticationMethod()->willReturn('client_secret_jwt');
303
304
        $clientRepository = $this->prophesize(ClientRepository::class);
305
        $clientRepository->find(Argument::any())->willReturn($client->reveal())->shouldBeCalled();
306
307
        try {
308
            $this->getAuthorizationRequestLoader($clientRepository->reveal())->load([
309
                'request' => 'eyJhbGciOiJSUzI1NiJ9.eyJjbGllbnRfaWQiOiJDTElFTlRfSUQifQ.',
310
            ]);
311
            static::fail('The expected exception has not been thrown.');
312
        } catch (OAuth2Error $e) {
313
            static::assertEquals(400, $e->getCode());
314
            static::assertEquals('invalid_request_object', $e->getMessage());
315
            static::assertEquals('The verification of the request object failed.', $e->getErrorDescription());
316
        }
317
    }
318
319
    /**
320
     * @test
321
     */
322
    public function theAssertionIsVerified()
323
    {
324
        $client = $this->prophesize(\OAuth2Framework\Component\Core\Client\Client::class);
325
        $client->isDeleted()->willReturn(false)->shouldBeCalled();
326
        $client->has('jwks')->willReturn(false)->shouldBeCalled();
327
        $client->has('jwks_uri')->willReturn(false)->shouldBeCalled();
328
        $client->has('client_secret')->willReturn(true)->shouldBeCalled();
329
        $client->get('client_secret')->willReturn('SECRET')->shouldBeCalled();
330
        $client->has('request_object_signing_alg')->willReturn(true)->shouldBeCalled();
331
        $client->get('request_object_signing_alg')->willReturn('RS256')->shouldBeCalled();
332
        $client->getTokenEndpointAuthenticationMethod()->willReturn('client_secret_jwt');
333
334
        $clientRepository = $this->prophesize(ClientRepository::class);
335
        $clientRepository->find(Argument::any())->willReturn($client->reveal())->shouldBeCalled();
336
337
        $authorization = $this->getAuthorizationRequestLoader($clientRepository->reveal())->load([
338
            'request' => 'eyJhbGciOiJSUzI1NiJ9.eyJjbGllbnRfaWQiOiJDTElFTlRfSUQifQ.R09PRF9TSUdOQVRVUkU',
339
        ]);
340
341
        static::assertTrue($authorization->hasQueryParam('client_id'));
342
        static::assertTrue($authorization->hasQueryParam('request'));
343
        static::assertEquals('CLIENT_ID', $authorization->getQueryParam('client_id'));
344
        static::assertEquals('eyJhbGciOiJSUzI1NiJ9.eyJjbGllbnRfaWQiOiJDTElFTlRfSUQifQ.R09PRF9TSUdOQVRVUkU', $authorization->getQueryParam('request'));
345
        static::assertInstanceOf(\OAuth2Framework\Component\Core\Client\Client::class, $authorization->getClient());
346
    }
347
348
    /**
349
     * @test
350
     */
351
    public function theRequestObjectUriIsVerified()
352
    {
353
        $client = $this->prophesize(\OAuth2Framework\Component\Core\Client\Client::class);
354
        $client->isDeleted()->willReturn(false)->shouldBeCalled();
355
        $client->has('jwks')->willReturn(false)->shouldBeCalled();
356
        $client->has('jwks_uri')->willReturn(false)->shouldBeCalled();
357
        $client->has('client_secret')->willReturn(true)->shouldBeCalled();
358
        $client->get('client_secret')->willReturn('SECRET')->shouldBeCalled();
359
        $client->has('request_object_signing_alg')->willReturn(true)->shouldBeCalled();
360
        $client->get('request_object_signing_alg')->willReturn('RS256')->shouldBeCalled();
361
        $client->has('request_uris')->willReturn(true)->shouldBeCalled();
362
        $client->get('request_uris')->willReturn(['https://www.foo.bar/'])->shouldBeCalled();
363
        $client->getTokenEndpointAuthenticationMethod()->willReturn('client_secret_jwt');
364
365
        $clientRepository = $this->prophesize(ClientRepository::class);
366
        $clientRepository->find(Argument::any())->willReturn($client->reveal())->shouldBeCalled();
367
368
        $authorization = $this->getAuthorizationRequestLoader($clientRepository->reveal())->load([
369
            'request_uri' => 'https://www.foo.bar/eyJhbGciOiJSUzI1NiJ9.eyJjbGllbnRfaWQiOiJDTElFTlRfSUQifQ.R09PRF9TSUdOQVRVUkU',
370
        ]);
371
372
        static::assertTrue($authorization->hasQueryParam('client_id'));
373
        static::assertTrue($authorization->hasQueryParam('request_uri'));
374
        static::assertEquals('CLIENT_ID', $authorization->getQueryParam('client_id'));
375
        static::assertEquals('https://www.foo.bar/eyJhbGciOiJSUzI1NiJ9.eyJjbGllbnRfaWQiOiJDTElFTlRfSUQifQ.R09PRF9TSUdOQVRVUkU', $authorization->getQueryParam('request_uri'));
376
        static::assertInstanceOf(\OAuth2Framework\Component\Core\Client\Client::class, $authorization->getClient());
377
    }
378
379
    /**
380
     * @test
381
     */
382
    public function theRequestUriIsNotAllowedForTheClient()
383
    {
384
        $client = $this->prophesize(\OAuth2Framework\Component\Core\Client\Client::class);
385
        $client->isDeleted()->willReturn(false)->shouldBeCalled();
386
        $client->has('jwks')->willReturn(false)->shouldBeCalled();
387
        $client->has('jwks_uri')->willReturn(false)->shouldBeCalled();
388
        $client->has('client_secret')->willReturn(true)->shouldBeCalled();
389
        $client->get('client_secret')->willReturn('SECRET')->shouldBeCalled();
390
        $client->has('request_object_signing_alg')->willReturn(true)->shouldBeCalled();
391
        $client->get('request_object_signing_alg')->willReturn('RS256')->shouldBeCalled();
392
        $client->has('request_uris')->willReturn(true)->shouldBeCalled();
393
        $client->get('request_uris')->willReturn(['https://www.foo.bar/'])->shouldBeCalled();
394
        $client->getTokenEndpointAuthenticationMethod()->willReturn('client_secret_jwt');
395
396
        $clientRepository = $this->prophesize(ClientRepository::class);
397
        $clientRepository->find(Argument::any())->willReturn($client->reveal())->shouldBeCalled();
398
399
        try {
400
            $this->getAuthorizationRequestLoader($clientRepository->reveal())->load([
401
                'request_uri' => 'https://www.bad.host/eyJhbGciOiJSUzI1NiJ9.eyJjbGllbnRfaWQiOiJDTElFTlRfSUQifQ.R09PRF9TSUdOQVRVUkU',
402
            ]);
403
            static::fail('The expected exception has not been thrown.');
404
        } catch (OAuth2Error $e) {
405
            static::assertEquals(400, $e->getCode());
406
            static::assertEquals('invalid_request_uri', $e->getMessage());
407
            static::assertEquals('The request Uri is not allowed.', $e->getErrorDescription());
408
        }
409
    }
410
411
    /**
412
     * @test
413
     */
414
    public function theRequestUriMustNotContainPathTraversal()
415
    {
416
        $clientRepository = $this->prophesize(ClientRepository::class);
417
418
        try {
419
            $this->getAuthorizationRequestLoader($clientRepository->reveal())->load([
420
                'request_uri' => 'https://www.foo.bar/../eyJhbGciOiJSUzI1NiJ9.eyJjbGllbnRfaWQiOiJDTElFTlRfSUQifQ.R09PRF9TSUdOQVRVUkU',
421
            ]);
422
            static::fail('The expected exception has not been thrown.');
423
        } catch (OAuth2Error $e) {
424
            static::assertEquals(400, $e->getCode());
425
            static::assertEquals('invalid_request_uri', $e->getMessage());
426
            static::assertEquals('The request Uri is not allowed.', $e->getErrorDescription());
427
        }
428
    }
429
430
    private function getAuthorizationRequestLoader(ClientRepository $clientRepository): AuthorizationRequestLoader
431
    {
432
        if (null === $this->authorizationRequestLoader) {
433
            $this->authorizationRequestLoader = new AuthorizationRequestLoader($clientRepository);
434
            $this->authorizationRequestLoader->enableSignedRequestObjectSupport(
435
                $this->getJWSVerifier(),
436
                $this->getClaimCheckerManager()
437
            );
438
            $this->authorizationRequestLoader->enableRequestObjectReferenceSupport(
439
                $this->getHttpClient(),
440
                new Psr17Factory(),
441
                true
442
            );
443
        }
444
445
        return $this->authorizationRequestLoader;
446
    }
447
448
    private function getJWSVerifier(): JWSVerifier
449
    {
450
        $verifier = $this->prophesize(JWSVerifier::class);
451
        $verifier->getSignatureAlgorithmManager()->willReturn(
452
            $this->getSignatureAlgorithmManager()
453
        );
454
        $verifier->verifyWithKeySet(Argument::type(JWS::class), Argument::type(JWKSet::class), 0, null)->will(function (array $args) {
455
            return
456
                'RS256' === ($args[0])->getSignature(0)->getProtectedHeaderParameter('alg') &&
457
                '' !== ($args[0])->getSignature(0)->getSignature();
458
        });
459
460
        return $verifier->reveal();
461
    }
462
463
    private function getSignatureAlgorithmManager(): AlgorithmManager
464
    {
465
        $manager = $this->prophesize(AlgorithmManager::class);
466
        $manager->list()->willReturn(['RS256']);
467
468
        return $manager->reveal();
469
    }
470
471
    private function getClaimCheckerManager(): ClaimCheckerManager
472
    {
473
        $manager = $this->prophesize(ClaimCheckerManager::class);
474
        $manager->check(Argument::any())->will(function (array $args) {
475
            return $args[0];
476
        });
477
478
        return $manager->reveal();
479
    }
480
481
    private function getHttpClient(): Client
482
    {
483
        $client = $this->prophesize(Client::class);
484
        $client->sendRequest(Argument::type(RequestInterface::class))->will(function (array $args) {
485
            /** @var Uri $uri */
486
            $uri = ($args[0])->getUri();
487
            switch ($uri->getPath()) {
488
                case '/eyJhbGciOiJSUzI1NiJ9.eyJjbGllbnRfaWQiOiJDTElFTlRfSUQifQ.R09PRF9TSUdOQVRVUkU':
489
                    $response = new \Nyholm\Psr7\Response();
490
                    $response->getBody()->write('eyJhbGciOiJSUzI1NiJ9.eyJjbGllbnRfaWQiOiJDTElFTlRfSUQifQ.R09PRF9TSUdOQVRVUkU');
491
                    $response->getBody()->rewind();
492
493
                    return $response;
494
            }
495
        });
496
497
        return $client->reveal();
498
    }
499
}
500