]> BookStack Code Mirror - bookstack/blob - app/Access/Oidc/OidcUserinfoResponse.php
OIDC: Added extra userinfo content-type normalisation and test
[bookstack] / app / Access / Oidc / OidcUserinfoResponse.php
1 <?php
2
3 namespace BookStack\Access\Oidc;
4
5 use Psr\Http\Message\ResponseInterface;
6
7 class OidcUserinfoResponse implements ProvidesClaims
8 {
9     protected array $claims = [];
10     protected ?OidcJwtWithClaims $jwt = null;
11
12     public function __construct(ResponseInterface $response, string $issuer, array $keys)
13     {
14         $contentTypeHeaderValue = $response->getHeader('Content-Type')[0] ?? '';
15         $contentType = strtolower(trim(explode(';', $contentTypeHeaderValue, 2)[0]));
16
17         if ($contentType === 'application/json') {
18             $this->claims = json_decode($response->getBody()->getContents(), true);
19         }
20
21         if ($contentType === 'application/jwt') {
22             $this->jwt = new OidcJwtWithClaims($response->getBody()->getContents(), $issuer, $keys);
23             $this->claims = $this->jwt->getAllClaims();
24         }
25     }
26
27     /**
28      * @throws OidcInvalidTokenException
29      */
30     public function validate(string $idTokenSub, string $clientId): bool
31     {
32         if (!is_null($this->jwt)) {
33             $this->jwt->validateCommonTokenDetails($clientId);
34         }
35
36         $sub = $this->getClaim('sub');
37
38         // Spec: v1.0 5.3.2: The sub (subject) Claim MUST always be returned in the UserInfo Response.
39         if (!is_string($sub) || empty($sub)) {
40             throw new OidcInvalidTokenException("No valid subject value found in userinfo data");
41         }
42
43         // Spec: v1.0 5.3.2: The sub Claim in the UserInfo Response MUST be verified to exactly match the sub Claim in the ID Token;
44         // if they do not match, the UserInfo Response values MUST NOT be used.
45         if ($idTokenSub !== $sub) {
46             throw new OidcInvalidTokenException("Subject value provided in the userinfo endpoint does not match the provided ID token value");
47         }
48
49         // Spec v1.0 5.3.4 Defines the following:
50         // Verify that the OP that responded was the intended OP through a TLS server certificate check, per RFC 6125 [RFC6125].
51           // This is effectively done as part of the HTTP request we're making through CURLOPT_SSL_VERIFYHOST on the request.
52         // If the Client has provided a userinfo_encrypted_response_alg parameter during Registration, decrypt the UserInfo Response using the keys specified during Registration.
53           // We don't currently support JWT encryption for OIDC
54         // If the response was signed, the Client SHOULD validate the signature according to JWS [JWS].
55           // This is done as part of the validateCommonClaims above.
56
57         return true;
58     }
59
60     public function getClaim(string $claim): mixed
61     {
62         return $this->claims[$claim] ?? null;
63     }
64
65     public function getAllClaims(): array
66     {
67         return $this->claims;
68     }
69 }