Failed Conditions
Push — ng ( 6dd952...0976bc )
by Florent
16:09
created

TokenEndpoint::issueAccessToken()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 9.0856
c 0
b 0
f 0
cc 1
eloc 18
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2018 Spomky-Labs
9
 *
10
 * This software may be modified and distributed under the terms
11
 * of the MIT license.  See the LICENSE file for details.
12
 */
13
14
namespace OAuth2Framework\Component\TokenEndpoint;
15
16
use Http\Message\ResponseFactory;
17
use OAuth2Framework\Component\Core\AccessToken\AccessTokenIdGenerator;
18
use Psr\Http\Server\RequestHandlerInterface;
19
use Psr\Http\Server\MiddlewareInterface;
20
use OAuth2Framework\Component\Core\AccessToken\AccessToken;
21
use OAuth2Framework\Component\Core\AccessToken\AccessTokenRepository;
22
use OAuth2Framework\Component\Core\Client\Client;
23
use OAuth2Framework\Component\Core\Client\ClientId;
24
use OAuth2Framework\Component\Core\Client\ClientRepository;
25
use OAuth2Framework\Component\Core\ResourceOwner\ResourceOwnerId;
26
use OAuth2Framework\Component\Core\ResourceOwner\ResourceOwner;
27
use OAuth2Framework\Component\Core\UserAccount\UserAccountId;
28
use OAuth2Framework\Component\Core\UserAccount\UserAccountRepository;
29
use OAuth2Framework\Component\Core\Exception\OAuth2Exception;
30
use OAuth2Framework\Component\TokenEndpoint\Extension\TokenEndpointExtensionManager;
31
use OAuth2Framework\Component\TokenType\TokenType;
32
use Psr\Http\Message\ResponseInterface;
33
use Psr\Http\Message\ServerRequestInterface;
34
35
class TokenEndpoint implements MiddlewareInterface
36
{
37
    /**
38
     * @var TokenEndpointExtensionManager
39
     */
40
    private $tokenEndpointExtensionManager;
41
42
    /**
43
     * @var ClientRepository
44
     */
45
    private $clientRepository;
46
47
    /**
48
     * @var UserAccountRepository
49
     */
50
    private $userAccountRepository;
51
52
    /**
53
     * @var ResponseFactory
54
     */
55
    private $responseFactory;
56
57
    /**
58
     * @var AccessTokenIdGenerator
59
     */
60
    private $accessTokenIdGenerator;
61
62
    /**
63
     * @var AccessTokenRepository
64
     */
65
    private $accessTokenRepository;
66
67
    /**
68
     * @var int
69
     */
70
    private $accessTokenLifetime;
71
72
    /**
73
     * TokenEndpoint constructor.
74
     *
75
     * @param ClientRepository              $clientRepository
76
     * @param UserAccountRepository         $userAccountRepository
77
     * @param TokenEndpointExtensionManager $tokenEndpointExtensionManager
78
     * @param ResponseFactory               $responseFactory
79
     * @param AccessTokenRepository         $accessTokenRepository
80
     * @param AccessTokenIdGenerator        $accessTokenIdGenerator
81
     * @param int                           $accessLifetime
82
     */
83
    public function __construct(ClientRepository $clientRepository, UserAccountRepository $userAccountRepository, TokenEndpointExtensionManager $tokenEndpointExtensionManager, ResponseFactory $responseFactory, AccessTokenRepository $accessTokenRepository, AccessTokenIdGenerator $accessTokenIdGenerator, int $accessLifetime)
84
    {
85
        $this->clientRepository = $clientRepository;
86
        $this->userAccountRepository = $userAccountRepository;
87
        $this->tokenEndpointExtensionManager = $tokenEndpointExtensionManager;
88
        $this->responseFactory = $responseFactory;
89
        $this->accessTokenIdGenerator = $accessTokenIdGenerator;
90
        $this->accessTokenRepository = $accessTokenRepository;
91
        $this->accessTokenLifetime = $accessLifetime;
92
    }
93
94
    /**
95
     * {@inheritdoc}
96
     */
97
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
98
    {
99
        // We prepare the Grant Type Data.
100
        // The client may be null (authenticated by other means).
101
        $grantTypeData = GrantTypeData::create($request->getAttribute('client'));
102
103
        // We retrieve the Grant Type.
104
        // This middleware must be behind the GrantTypeMiddleware
105
        $grantType = $request->getAttribute('grant_type');
106
        if (!$grantType instanceof GrantType) {
107
            throw new OAuth2Exception(500, OAuth2Exception::ERROR_INTERNAL, null);
108
        }
109
110
        // We check that the request has all parameters needed for the selected grant type
111
        $grantType->checkRequest($request);
112
113
        // The grant type prepare the token response
114
        // The grant type data should be updated accordingly
115
        $grantTypeData = $grantType->prepareResponse($request, $grantTypeData);
116
117
        // At this stage, the client should be authenticated
118
        // If not, we stop the authorization grant
119
        if (null === $grantTypeData->getClient() || $grantTypeData->getClient()->isDeleted()) {
120
            throw new OAuth2Exception(401, OAuth2Exception::ERROR_INVALID_CLIENT, 'Client authentication failed.');
121
        }
122
123
        // We check the client is allowed to use the selected grant type
124
        if (!$grantTypeData->getClient()->isGrantTypeAllowed($grantType->name())) {
125
            throw new OAuth2Exception(400, OAuth2Exception::ERROR_UNAUTHORIZED_CLIENT, sprintf('The grant type "%s" is unauthorized for this client.', $grantType->name()));
126
        }
127
128
        // We populate the token type parameters
129
        $grantTypeData = $this->updateWithTokenTypeParameters($request, $grantTypeData);
130
131
        // We call for extensions prior to the Access Token issuance
132
        $grantTypeData = $this->tokenEndpointExtensionManager->handleBeforeAccessTokenIssuance($request, $grantTypeData, $grantType);
133
134
        // We grant the client
135
        $grantTypeData = $grantType->grant($request, $grantTypeData);
136
137
        // Everything is fine so we can issue the access token
138
        $accessToken = $this->issueAccessToken($grantTypeData);
139
        $resourceOwner = $this->getResourceOwner($grantTypeData->getResourceOwnerId());
0 ignored issues
show
Bug introduced by
It seems like $grantTypeData->getResourceOwnerId() can be null; however, getResourceOwner() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
140
141
        // We call for extensions after to the Access Token issuance
142
        $data = $this->tokenEndpointExtensionManager->handleAfterAccessTokenIssuance($grantTypeData->getClient(), $resourceOwner, $accessToken);
143
144
        return $this->createResponse($data);
145
    }
146
147
    /**
148
     * @param array $data
149
     *
150
     * @return ResponseInterface
151
     */
152
    private function createResponse(array $data): ResponseInterface
153
    {
154
        $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Cache-Control' => 'no-cache, no-store, max-age=0, must-revalidate, private', 'Pragma' => 'no-cache'];
155
        $response = $this->responseFactory->createResponse(200, null, $headers);
156
        $response->getBody()->write(json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
157
158
        return $response;
159
    }
160
161
    /**
162
     * @param GrantTypeData $grantTypeData
163
     *
164
     * @return AccessToken
165
     */
166
    private function issueAccessToken(GrantTypeData $grantTypeData): AccessToken
167
    {
168
        $accessTokenId = $this->accessTokenIdGenerator->create(
169
            $grantTypeData->getResourceOwnerId(),
0 ignored issues
show
Bug introduced by
It seems like $grantTypeData->getResourceOwnerId() can be null; however, create() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
170
            $grantTypeData->getClient()->getPublicId(),
171
            $grantTypeData->getParameters(),
172
            $grantTypeData->getMetadatas(),
173
            null
174
        );
175
        $accessToken = AccessToken::createEmpty();
176
        $accessToken = $accessToken->create(
177
            $accessTokenId,
178
            $grantTypeData->getResourceOwnerId(),
179
            $grantTypeData->getClient()->getPublicId(),
180
            $grantTypeData->getParameters(),
181
            $grantTypeData->getMetadatas(),
182
            new \DateTimeImmutable(sprintf('now +%d seconds', $this->accessTokenLifetime)),
183
            null
184
        );
185
        $this->accessTokenRepository->save($accessToken);
186
187
        return $accessToken;
188
    }
189
190
    /**
191
     * @param ResourceOwnerId $resourceOwnerId
192
     *
193
     * @throws OAuth2Exception
194
     *
195
     * @return ResourceOwner
196
     */
197
    private function getResourceOwner(ResourceOwnerId $resourceOwnerId): ResourceOwner
198
    {
199
        $resourceOwner = $this->clientRepository->find(ClientId::create($resourceOwnerId->getValue()));
200
        if (null === $resourceOwner) {
201
            $resourceOwner = $this->userAccountRepository->find(UserAccountId::create($resourceOwnerId->getValue()));
202
        }
203
204
        if (null === $resourceOwner) {
205
            throw new OAuth2Exception(400, OAuth2Exception::ERROR_INVALID_REQUEST, 'Unable to find the associated resource owner.');
206
        }
207
208
        return $resourceOwner;
209
    }
210
211
    /**
212
     * @param ServerRequestInterface $request
213
     * @param GrantTypeData          $grantTypeData
214
     *
215
     * @return GrantTypeData
216
     */
217
    private function updateWithTokenTypeParameters(ServerRequestInterface $request, GrantTypeData $grantTypeData): GrantTypeData
218
    {
219
        /** @var TokenType $tokenType */
220
        $tokenType = $request->getAttribute('token_type');
221
222
        $info = $tokenType->getAdditionalInformation();
223
        $info['token_type'] = $tokenType->name();
224
        foreach ($info as $k => $v) {
225
            $grantTypeData = $grantTypeData->withParameter($k, $v);
226
        }
227
228
        return $grantTypeData;
229
    }
230
231
    /**
232
     * @param Client $client
233
     * @param string $grant_type
234
     *
235
     * @return bool
236
     */
237
    private function isGrantTypeAllowedForTheClient(Client $client, string $grant_type): bool
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
238
    {
239
        $grant_types = $client->has('grant_types') ? $client->get('grant_types') : [];
240
        if (!is_array($grant_types)) {
241
            throw new \InvalidArgumentException('The metadata "grant_types" must be an array.');
242
        }
243
244
        return in_array($grant_type, $grant_types);
245
    }
246
}
247