2016-11-30 01:45:56 +05:30
|
|
|
<?php
|
2019-09-22 02:47:21 +05:30
|
|
|
declare(strict_types=1);
|
2016-11-30 01:45:56 +05:30
|
|
|
|
2024-12-06 06:04:09 +05:30
|
|
|
namespace common\components\OAuth2\Grants;
|
2017-06-17 23:35:36 +05:30
|
|
|
|
2019-12-09 22:01:54 +05:30
|
|
|
use api\components\Tokens\TokenReader;
|
2024-12-02 15:40:55 +05:30
|
|
|
use Carbon\FactoryImmutable;
|
2024-12-06 06:04:09 +05:30
|
|
|
use common\components\OAuth2\CryptTrait;
|
2019-09-22 05:12:08 +05:30
|
|
|
use common\models\OauthSession;
|
2019-12-09 22:01:54 +05:30
|
|
|
use InvalidArgumentException;
|
2024-12-02 15:40:55 +05:30
|
|
|
use Lcobucci\JWT\Validation\Constraint\LooseValidAt;
|
|
|
|
use Lcobucci\JWT\Validation\Validator;
|
2019-09-22 02:47:21 +05:30
|
|
|
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
|
|
|
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
2019-09-22 05:12:08 +05:30
|
|
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
2019-09-22 02:47:21 +05:30
|
|
|
use League\OAuth2\Server\Grant\RefreshTokenGrant as BaseRefreshTokenGrant;
|
2019-09-22 05:12:08 +05:30
|
|
|
use Psr\Http\Message\ServerRequestInterface;
|
2024-12-02 15:40:55 +05:30
|
|
|
use Throwable;
|
2019-09-22 05:12:08 +05:30
|
|
|
use Yii;
|
2017-06-17 23:35:36 +05:30
|
|
|
|
2024-12-06 06:04:09 +05:30
|
|
|
final class RefreshTokenGrant extends BaseRefreshTokenGrant {
|
2019-12-06 17:07:51 +05:30
|
|
|
use CryptTrait;
|
2016-11-30 01:45:56 +05:30
|
|
|
|
2019-09-22 05:12:08 +05:30
|
|
|
/**
|
2019-09-23 03:23:13 +05:30
|
|
|
* Previously, refresh tokens were stored in Redis.
|
2019-09-22 05:12:08 +05:30
|
|
|
* If received refresh token is matches the legacy token template,
|
|
|
|
* restore the information from the legacy storage.
|
|
|
|
*
|
2024-12-06 06:04:09 +05:30
|
|
|
* @inheritDoc
|
2019-09-22 05:12:08 +05:30
|
|
|
*/
|
2024-12-02 15:40:55 +05:30
|
|
|
protected function validateOldRefreshToken(ServerRequestInterface $request, string $clientId): array {
|
2019-09-22 05:12:08 +05:30
|
|
|
$refreshToken = $this->getRequestParameter('refresh_token', $request);
|
|
|
|
if ($refreshToken !== null && mb_strlen($refreshToken) === 40) {
|
|
|
|
return $this->validateLegacyRefreshToken($refreshToken);
|
|
|
|
}
|
|
|
|
|
2019-12-09 22:01:54 +05:30
|
|
|
return $this->validateAccessToken($refreshToken);
|
2019-09-22 05:12:08 +05:30
|
|
|
}
|
|
|
|
|
2017-07-13 16:14:06 +05:30
|
|
|
/**
|
2019-09-22 02:47:21 +05:30
|
|
|
* Currently we're not rotating refresh tokens.
|
2024-12-02 15:40:55 +05:30
|
|
|
* So we're overriding this method to always return null, which means,
|
2019-09-22 02:47:21 +05:30
|
|
|
* that refresh_token will not be issued.
|
2017-07-13 16:14:06 +05:30
|
|
|
*/
|
2019-09-22 02:47:21 +05:30
|
|
|
protected function issueRefreshToken(AccessTokenEntityInterface $accessToken): ?RefreshTokenEntityInterface {
|
|
|
|
return null;
|
2016-11-30 04:49:14 +05:30
|
|
|
}
|
|
|
|
|
2019-12-04 16:10:12 +05:30
|
|
|
/**
|
2024-12-06 06:04:09 +05:30
|
|
|
* @return array<string, mixed>
|
2019-12-04 16:10:12 +05:30
|
|
|
* @throws OAuthServerException
|
|
|
|
*/
|
2019-09-22 05:12:08 +05:30
|
|
|
private function validateLegacyRefreshToken(string $refreshToken): array {
|
|
|
|
$result = Yii::$app->redis->get("oauth:refresh:tokens:{$refreshToken}");
|
|
|
|
if ($result === null) {
|
|
|
|
throw OAuthServerException::invalidRefreshToken('Token has been revoked');
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
[
|
|
|
|
'access_token_id' => $accessTokenId,
|
|
|
|
'session_id' => $sessionId,
|
2024-12-02 15:40:55 +05:30
|
|
|
] = json_decode((string)$result, true, 512, JSON_THROW_ON_ERROR);
|
|
|
|
} catch (Throwable $e) {
|
2019-09-22 05:12:08 +05:30
|
|
|
throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token', $e);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @var OauthSession|null $relatedSession */
|
|
|
|
$relatedSession = OauthSession::findOne(['legacy_id' => $sessionId]);
|
|
|
|
if ($relatedSession === null) {
|
|
|
|
throw OAuthServerException::invalidRefreshToken('Token has been revoked');
|
|
|
|
}
|
|
|
|
|
|
|
|
return [
|
|
|
|
'client_id' => $relatedSession->client_id,
|
|
|
|
'refresh_token_id' => $refreshToken,
|
|
|
|
'access_token_id' => $accessTokenId,
|
|
|
|
'scopes' => $relatedSession->getScopes(),
|
|
|
|
'user_id' => $relatedSession->account_id,
|
|
|
|
'expire_time' => null,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2019-12-09 22:01:54 +05:30
|
|
|
/**
|
2024-12-06 06:04:09 +05:30
|
|
|
* @return array<string, mixed>
|
2019-12-09 22:01:54 +05:30
|
|
|
* @throws OAuthServerException
|
|
|
|
*/
|
|
|
|
private function validateAccessToken(string $jwt): array {
|
|
|
|
try {
|
|
|
|
$token = Yii::$app->tokens->parse($jwt);
|
|
|
|
} catch (InvalidArgumentException $e) {
|
|
|
|
throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token', $e);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Yii::$app->tokens->verify($token)) {
|
|
|
|
throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token');
|
|
|
|
}
|
|
|
|
|
2024-12-02 15:40:55 +05:30
|
|
|
if (!(new Validator())->validate($token, new LooseValidAt(FactoryImmutable::getDefaultInstance()))) {
|
2019-12-09 22:01:54 +05:30
|
|
|
throw OAuthServerException::invalidRefreshToken('Token has expired');
|
|
|
|
}
|
|
|
|
|
|
|
|
$reader = new TokenReader($token);
|
|
|
|
|
|
|
|
return [
|
|
|
|
'client_id' => $reader->getClientId(),
|
|
|
|
'refresh_token_id' => '', // This value used only to invalidate old token
|
|
|
|
'access_token_id' => '', // This value used only to invalidate old token
|
|
|
|
'scopes' => $reader->getScopes(),
|
|
|
|
'user_id' => $reader->getAccountId(),
|
|
|
|
'expire_time' => null,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2016-11-30 01:45:56 +05:30
|
|
|
}
|