Passed
Push — master ( f18c04...fb67eb )
by Thomas Mauro
02:46
created

UserinfoService::getUserInfo()   C

Complexity

Conditions 14
Paths 75

Size

Total Lines 72
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 210

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 14
eloc 40
c 3
b 0
f 0
nc 75
nop 2
dl 0
loc 72
ccs 0
cts 53
cp 0
crap 210
rs 6.2666

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