UserinfoService::getUserInfo()   D
last analyzed

Complexity

Conditions 17
Paths 147

Size

Total Lines 81
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 306

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 17
eloc 47
c 3
b 0
f 0
nc 147
nop 3
dl 0
loc 81
ccs 0
cts 61
cp 0
crap 306
rs 4.825

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 TMV\OpenIdClient\Service;
6
7
use Http\Discovery\Psr17FactoryDiscovery;
8
use Http\Discovery\Psr18ClientDiscovery;
9
use function http_build_query;
10
use function is_array;
11
use function json_decode;
12
use Psr\Http\Client\ClientExceptionInterface;
13
use Psr\Http\Client\ClientInterface;
14
use Psr\Http\Message\RequestFactoryInterface;
15
use function sprintf;
16
use TMV\OpenIdClient\Client\ClientInterface as OpenIDClient;
17
use TMV\OpenIdClient\Exception\InvalidArgumentException;
18
use TMV\OpenIdClient\Exception\OAuth2Exception;
19
use TMV\OpenIdClient\Exception\RuntimeException;
20
use TMV\OpenIdClient\Token\IdTokenVerifier;
21
use TMV\OpenIdClient\Token\IdTokenVerifierInterface;
22
use TMV\OpenIdClient\Token\TokenDecrypter;
23
use TMV\OpenIdClient\Token\TokenDecrypterInterface;
24
use TMV\OpenIdClient\Token\TokenSetInterface;
25
26
class UserinfoService
27
{
28
    /** @var ClientInterface */
29
    private $client;
30
31
    /** @var RequestFactoryInterface */
32
    private $requestFactory;
33
34
    /** @var IdTokenVerifierInterface */
35
    private $idTokenVerifier;
36
37
    /** @var TokenDecrypterInterface */
38
    private $idTokenDecrypter;
39
40
    public function __construct(
41
        ?ClientInterface $client = null,
42
        ?IdTokenVerifierInterface $idTokenVerifier = null,
43
        ?TokenDecrypterInterface $idTokenDecrypter = null,
44
        ?RequestFactoryInterface $requestFactory = null
45
    ) {
46
        $this->client = $client ?: Psr18ClientDiscovery::find();
47
        $this->idTokenVerifier = $idTokenVerifier ?: new IdTokenVerifier();
48
        $this->idTokenDecrypter = $idTokenDecrypter ?: new TokenDecrypter();
49
        $this->requestFactory = $requestFactory ?: Psr17FactoryDiscovery::findRequestFactory();
50
    }
51
52
    public function getUserInfo(OpenIDClient $client, TokenSetInterface $tokenSet, bool $useBody = false): array
53
    {
54
        $accessToken = $tokenSet->getAccessToken();
55
56
        if (null === $accessToken) {
57
            throw new RuntimeException('Unable to get an access token from the token set');
58
        }
59
60
        $clientMetadata = $client->getMetadata();
61
        $issuerMetadata = $client->getIssuer()->getMetadata();
62
63
        $mTLS = true === $clientMetadata->get('tls_client_certificate_bound_access_tokens');
64
65
        $endpointUri = $issuerMetadata->getUserinfoEndpoint();
66
67
        if ($mTLS) {
68
            $endpointUri = $issuerMetadata->getMtlsEndpointAliases()['userinfo_endpoint'] ?? $endpointUri;
69
        }
70
71
        if (null === $endpointUri) {
72
            throw new InvalidArgumentException('Invalid issuer userinfo endpoint');
73
        }
74
75
        $expectJwt = null !== $clientMetadata->getUserinfoSignedResponseAlg()
76
            || null !== $clientMetadata->getUserinfoEncryptedResponseAlg()
77
            || null !== $clientMetadata->getUserinfoEncryptedResponseEnc();
78
79
        if ($useBody) {
80
            $request = $this->requestFactory->createRequest('POST', $endpointUri)
81
                ->withHeader('accept', $expectJwt ? 'application/jwt' : 'application/json')
82
                ->withHeader('content-type', 'application/x-www-form-urlencoded');
83
            $request->getBody()->write(http_build_query(['access_token' => $accessToken]));
84
        } else {
85
            $request = $this->requestFactory->createRequest('GET', $endpointUri)
86
                ->withHeader('accept', $expectJwt ? 'application/jwt' : 'application/json')
87
                ->withHeader('authorization', 'Bearer ' . $accessToken);
88
        }
89
90
        $httpClient = $client->getHttpClient() ?: $this->client;
91
92
        try {
93
            $response = $httpClient->sendRequest($request);
94
        } catch (ClientExceptionInterface $e) {
95
            throw new RuntimeException('Unable to get userinfo', 0, $e);
96
        }
97
98
        if (200 !== $response->getStatusCode()) {
99
            throw OAuth2Exception::fromResponse($response);
100
        }
101
102
        if ($expectJwt) {
103
            $token = $this->idTokenDecrypter->decryptToken($client, (string) $response->getBody(), 'userinfo');
104
            $payload = $this->idTokenVerifier->validateUserinfoToken($client, $token);
105
        } else {
106
            $payload = json_decode((string) $response->getBody(), true);
107
        }
108
109
        if (! is_array($payload)) {
110
            throw new RuntimeException('Unable to parse userinfo claims');
111
        }
112
113
        $idToken = $tokenSet->getIdToken();
114
115
        if (null === $idToken) {
116
            return $payload;
117
        }
118
119
        // check expected sub
120
        $expectedSub = $tokenSet->claims()['sub'] ?? null;
121
122
        if (! $expectedSub) {
123
            throw new RuntimeException('Unable to get sub claim from id_token');
124
        }
125
126
        if ($expectedSub !== ($payload['sub'] ?? null)) {
127
            throw new RuntimeException(
128
                sprintf('Userinfo sub mismatch, expected %s, got: %s', $expectedSub, $payload['sub'] ?? null)
129
            );
130
        }
131
132
        return $payload;
133
    }
134
}
135