Completed
Push — master ( 79d24d...ad8160 )
by Thomas Mauro
03:36
created

UserInfoService::getUserInfo()   D

Complexity

Conditions 16
Paths 147

Size

Total Lines 82

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 272

Importance

Changes 0
Metric Value
cc 16
nc 147
nop 3
dl 0
loc 82
ccs 0
cts 62
cp 0
crap 272
rs 4.5327
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Facile\OpenIDClient\Service;
6
7
use Facile\OpenIDClient\Client\ClientInterface as OpenIDClient;
8
use Facile\OpenIDClient\Exception\InvalidArgumentException;
9
use Facile\OpenIDClient\Exception\OAuth2Exception;
10
use Facile\OpenIDClient\Exception\RuntimeException;
11
use Facile\OpenIDClient\Token\TokenSetInterface;
12
use Facile\OpenIDClient\Token\TokenVerifierBuilderInterface;
13
use function http_build_query;
14
use function is_array;
15
use function json_decode;
16
use Psr\Http\Client\ClientExceptionInterface;
17
use Psr\Http\Client\ClientInterface;
18
use Psr\Http\Message\RequestFactoryInterface;
19
use function sprintf;
20
21
class UserInfoService
22
{
23
    /** @var ClientInterface */
24
    private $client;
25
26
    /** @var RequestFactoryInterface */
27
    private $requestFactory;
28
29
    /** @var TokenVerifierBuilderInterface */
30
    private $userInfoVerifierBuilder;
31
32
    public function __construct(
33
        TokenVerifierBuilderInterface $userInfoVerifierBuilder,
34
        ClientInterface $client,
35
        RequestFactoryInterface $requestFactory
36
    ) {
37
        $this->userInfoVerifierBuilder = $userInfoVerifierBuilder;
38
        $this->client = $client;
39
        $this->requestFactory = $requestFactory;
40
    }
41
42
    /**
43
     * @param OpenIDClient $client
44
     * @param TokenSetInterface $tokenSet
45
     * @param bool $useBody
46
     *
47
     * @return array<string, mixed>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
48
     */
49
    public function getUserInfo(OpenIDClient $client, TokenSetInterface $tokenSet, bool $useBody = false): array
50
    {
51
        $accessToken = $tokenSet->getAccessToken();
52
53
        if (null === $accessToken) {
54
            throw new RuntimeException('Unable to get an access token from the token set');
55
        }
56
57
        $clientMetadata = $client->getMetadata();
58
        $issuerMetadata = $client->getIssuer()->getMetadata();
59
60
        $mTLS = true === $clientMetadata->get('tls_client_certificate_bound_access_tokens');
61
62
        $endpointUri = $issuerMetadata->getUserinfoEndpoint();
63
64
        if ($mTLS) {
65
            $endpointUri = $issuerMetadata->getMtlsEndpointAliases()['userinfo_endpoint'] ?? $endpointUri;
66
        }
67
68
        if (null === $endpointUri) {
69
            throw new InvalidArgumentException('Invalid issuer userinfo endpoint');
70
        }
71
72
        $expectJwt = null !== $clientMetadata->getUserinfoSignedResponseAlg()
73
            || null !== $clientMetadata->getUserinfoEncryptedResponseAlg()
74
            || null !== $clientMetadata->getUserinfoEncryptedResponseEnc();
75
76
        if ($useBody) {
77
            $request = $this->requestFactory->createRequest('POST', $endpointUri)
78
                ->withHeader('accept', $expectJwt ? 'application/jwt' : 'application/json')
79
                ->withHeader('content-type', 'application/x-www-form-urlencoded');
80
            $request->getBody()->write(http_build_query(['access_token' => $accessToken]));
81
        } else {
82
            $request = $this->requestFactory->createRequest('GET', $endpointUri)
83
                ->withHeader('accept', $expectJwt ? 'application/jwt' : 'application/json')
84
                ->withHeader('authorization', 'Bearer ' . $accessToken);
85
        }
86
87
        $httpClient = $client->getHttpClient() ?? $this->client;
88
89
        try {
90
            $response = $httpClient->sendRequest($request);
91
        } catch (ClientExceptionInterface $e) {
92
            throw new RuntimeException('Unable to get userinfo', 0, $e);
93
        }
94
95
        if (200 !== $response->getStatusCode()) {
96
            throw OAuth2Exception::fromResponse($response);
97
        }
98
99
        if ($expectJwt) {
100
            $payload = $this->userInfoVerifierBuilder->build($client)
101
                ->verify((string) $response->getBody());
102
        } else {
103
            $payload = json_decode((string) $response->getBody(), true);
104
        }
105
106
        if (! is_array($payload)) {
107
            throw new RuntimeException('Unable to parse userinfo claims');
108
        }
109
110
        $idToken = $tokenSet->getIdToken();
111
112
        if (null === $idToken) {
113
            return $payload;
114
        }
115
116
        // check expected sub
117
        $expectedSub = $tokenSet->claims()['sub'] ?? null;
118
119
        if (null === $expectedSub) {
120
            throw new RuntimeException('Unable to get sub claim from id_token');
121
        }
122
123
        if ($expectedSub !== ($payload['sub'] ?? null)) {
124
            throw new RuntimeException(
125
                sprintf('Userinfo sub mismatch, expected %s, got: %s', $expectedSub, $payload['sub'] ?? null)
126
            );
127
        }
128
129
        return $payload;
130
    }
131
}
132