UserInfoService::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 3
dl 0
loc 8
ccs 0
cts 4
cp 0
crap 2
rs 10
c 0
b 0
f 0
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
/**
22
 * @psalm-import-type TokenSetClaimsType from TokenSetInterface
23
 */
24
final class UserInfoService
25
{
26
    /** @var ClientInterface */
27
    private $client;
28
29
    /** @var RequestFactoryInterface */
30
    private $requestFactory;
31
32
    /** @var TokenVerifierBuilderInterface */
33
    private $userInfoVerifierBuilder;
34
35
    public function __construct(
36
        TokenVerifierBuilderInterface $userInfoVerifierBuilder,
37
        ClientInterface $client,
38
        RequestFactoryInterface $requestFactory
39
    ) {
40
        $this->userInfoVerifierBuilder = $userInfoVerifierBuilder;
41
        $this->client = $client;
42
        $this->requestFactory = $requestFactory;
43
    }
44
45
    /**
46
     * @param OpenIDClient $client
47
     * @param TokenSetInterface $tokenSet
48
     * @param bool $useBody
49
     *
50
     * @return array<string, mixed>
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
            /** @var TokenSetClaimsType $payload */
104
            $payload = $this->userInfoVerifierBuilder->build($client)
105
                ->verify((string) $response->getBody());
106
        } else {
107
            /** @var false|TokenSetClaimsType $payload */
108
            $payload = json_decode((string) $response->getBody(), true);
109
        }
110
111
        if (! is_array($payload)) {
0 ignored issues
show
introduced by
The condition is_array($payload) is always false.
Loading history...
112
            throw new RuntimeException('Unable to parse userinfo claims');
113
        }
114
115
        $idToken = $tokenSet->getIdToken();
116
117
        if (null === $idToken) {
118
            return $payload;
119
        }
120
121
        // check expected sub
122
        /** @var string|null $expectedSub */
123
        $expectedSub = $tokenSet->claims()['sub'] ?? null;
124
125
        if (null === $expectedSub) {
126
            throw new RuntimeException('Unable to get sub claim from id_token');
127
        }
128
129
        if ($expectedSub !== ($payload['sub'] ?? null)) {
130
            throw new RuntimeException(
131
                sprintf('Userinfo sub mismatch, expected %s, got: %s', $expectedSub, $payload['sub'] ?? '')
132
            );
133
        }
134
135
        return $payload;
136
    }
137
}
138