Issues (1)

src/IdTokenResponse.php (1 issue)

1
<?php
2
/**
3
 * @author Steve Rhoades <[email protected]>
4
 * @license http://opensource.org/licenses/MIT MIT
5
 */
6
namespace OpenIDConnectServer;
7
8
use Lcobucci\JWT\Signer\Key\InMemory;
9
use Lcobucci\JWT\Signer\Key\LocalFileReference;
10
use OpenIDConnectServer\Repositories\IdentityProviderInterface;
11
use OpenIDConnectServer\Entities\ClaimSetInterface;
12
use League\OAuth2\Server\Entities\UserEntityInterface;
13
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
14
use League\OAuth2\Server\Entities\ScopeEntityInterface;
15
use League\OAuth2\Server\ResponseTypes\BearerTokenResponse;
16
use Lcobucci\JWT\Signer\Rsa\Sha256;
17
use Lcobucci\JWT\Encoding\ChainedFormatter;
18
use Lcobucci\JWT\Token\Builder;
19
use Lcobucci\JWT\Encoding\JoseEncoder;
20
21
class IdTokenResponse extends BearerTokenResponse
22
{
23
    /**
24
     * @var IdentityProviderInterface
25
     */
26
    protected $identityProvider;
27
28
    /**
29 5
     * @var ClaimExtractor
30
     */
31
    protected $claimExtractor;
32
33 5
    /**
34 5
     * @var string|null
35 5
     */
36
    protected $keyIdentifier;
37 2
    
38
    public function __construct(
39 2
        IdentityProviderInterface $identityProvider,
40
        ClaimExtractor $claimExtractor,
41
        ?string $keyIdentifier = null
42
    ) {
43 2
        $this->identityProvider = $identityProvider;
44
        $this->claimExtractor   = $claimExtractor;
45
        $this->keyIdentifier   = $keyIdentifier;
46
    }
47 2
48 2
    protected function getBuilder(AccessTokenEntityInterface $accessToken, UserEntityInterface $userEntity)
49 2
    {
50 2
        $claimsFormatter = ChainedFormatter::withUnixTimestampDates();
51
        $builder = new Builder(new JoseEncoder(), $claimsFormatter);
52
53
        // Since version 8.0 league/oauth2-server returns \DateTimeImmutable
54 2
        $expiresAt = $accessToken->getExpiryDateTime();
55 2
        if ($expiresAt instanceof \DateTime) {
0 ignored issues
show
$expiresAt is never a sub-type of DateTime.
Loading history...
56 2
            $expiresAt = \DateTimeImmutable::createFromMutable($expiresAt);
57 2
        }
58 2
59
        // Add required id_token claims
60 2
        return $builder
61
            ->permittedFor($accessToken->getClient()->getIdentifier())
62
            ->issuedBy('https://' . $_SERVER['HTTP_HOST'])
63
            ->issuedAt(new \DateTimeImmutable())
64
            ->expiresAt($expiresAt)
65
            ->relatedTo($userEntity->getIdentifier());
66
    }
67 5
68
    /**
69 5
     * @param AccessTokenEntityInterface $accessToken
70 1
     * @return array
71
     */
72
    protected function getExtraParams(AccessTokenEntityInterface $accessToken): array
73
    {
74 4
        if (false === $this->isOpenIDRequest($accessToken->getScopes())) {
75
            return [];
76 4
        }
77 1
78 3
        /** @var UserEntityInterface $userEntity */
79 1
        $userEntity = $this->identityProvider->getUserEntityByIdentifier($accessToken->getUserIdentifier());
80
81
        if (false === is_a($userEntity, UserEntityInterface::class)) {
82
            throw new \RuntimeException('UserEntity must implement UserEntityInterface');
83 2
        } else if (false === is_a($userEntity, ClaimSetInterface::class)) {
84
            throw new \RuntimeException('UserEntity must implement ClaimSetInterface');
85
        }
86 2
87
        // Add required id_token claims
88 2
        $builder = $this->getBuilder($accessToken, $userEntity);
89 1
90 2
        // Need a claim factory here to reduce the number of claims by provided scope.
91
        $claims = $this->claimExtractor->extract($accessToken->getScopes(), $userEntity->getClaims());
92 2
93 2
        foreach ($claims as $claimName => $claimValue) {
94 2
            $builder = $builder->withClaim($claimName, $claimValue);
95 2
        }
96
97
        if ($this->keyIdentifier !== null) {
98 2
            $builder = $builder->withHeader('kid', $this->keyIdentifier);
99 2
        }
100
101
        if (
102
            method_exists($this->privateKey, 'getKeyContents')
103
            && !empty($this->privateKey->getKeyContents())
104
        ) {
105
            $key = InMemory::plainText($this->privateKey->getKeyContents(), (string)$this->privateKey->getPassPhrase());
106 5
        } else {
107
            $key = LocalFileReference::file($this->privateKey->getKeyPath(), (string)$this->privateKey->getPassPhrase());
108
        }
109 5
110
        $token = $builder->getToken(new Sha256(), $key);
111 5
112 5
        return [
113 4
            'id_token' => $token->toString()
114 4
        ];
115
    }
116 5
117
    /**
118 5
     * @param ScopeEntityInterface[] $scopes
119
     * @return bool
120
     */
121
    private function isOpenIDRequest($scopes)
122
    {
123
        // Verify scope and make sure openid exists.
124
        $valid  = false;
125
126
        foreach ($scopes as $scope) {
127
            if ($scope->getIdentifier() === 'openid') {
128
                $valid = true;
129
                break;
130
            }
131
        }
132
133
        return $valid;
134
    }
135
136
}
137