mirror of
https://github.com/elyby/accounts.git
synced 2025-05-31 14:11:46 +05:30
Upgrade project to PHP 8.3, add PHPStan, upgrade almost every dependency (#36)
* start updating to PHP 8.3 * taking off! Co-authored-by: ErickSkrauch <erickskrauch@yandex.ru> Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com> * dropped this Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com> * migrate to symfonymailer Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com> * this is so stupid 😭 Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com> * ah, free, at last. Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com> * oh, Gabriel. Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com> * now dawns thy reckoning. Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com> * and thy gore shall GLISTEN before the temples of man. Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com> * creature of steel. Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com> * my gratitude upon thee for my freedom. Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com> * but the crimes thy kind has committed against humanity Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com> * Upgrade PHP-CS-Fixer and do fix the codebase * First review round (maybe I have broken something) * are NOT forgotten. Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com> * Enable parallel PHP-CS-Fixer runner * PHPStan level 1 * PHPStan level 2 * PHPStan level 3 * PHPStan level 4 * PHPStan level 5 * Levels 6 and 7 takes too much effort. Generate a baseline and fix them eventually * Resolve TODO's related to the php-mock * Drastically reduce baseline size with the Rector * More code modernization with help of the Rector * Update GitLab CI --------- Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com> Co-authored-by: ErickSkrauch <erickskrauch@yandex.ru>
This commit is contained in:
@@ -18,7 +18,7 @@ class ErrorHandler extends \yii\web\ErrorHandler {
|
||||
return parent::convertExceptionToArray($exception);
|
||||
}
|
||||
|
||||
public function logException($exception) {
|
||||
public function logException($exception): void {
|
||||
if ($exception instanceof AuthserverException) {
|
||||
Yii::error($exception, AuthserverException::class . ':' . $exception->getName());
|
||||
} elseif ($exception instanceof SessionServerException) {
|
||||
|
||||
@@ -8,12 +8,9 @@ use DateInterval;
|
||||
use League\OAuth2\Server\AuthorizationServer;
|
||||
use yii\base\Component as BaseComponent;
|
||||
|
||||
class Component extends BaseComponent {
|
||||
final class Component extends BaseComponent {
|
||||
|
||||
/**
|
||||
* @var AuthorizationServer
|
||||
*/
|
||||
private $_authServer;
|
||||
private ?AuthorizationServer $_authServer = null;
|
||||
|
||||
public function getAuthServer(): AuthorizationServer {
|
||||
if ($this->_authServer === null) {
|
||||
@@ -39,7 +36,7 @@ class Component extends BaseComponent {
|
||||
new Repositories\EmptyScopeRepository(),
|
||||
new Keys\EmptyKey(),
|
||||
'', // Omit the key because we use our own encryption mechanism
|
||||
new ResponseTypes\BearerTokenResponse()
|
||||
new ResponseTypes\BearerTokenResponse(),
|
||||
);
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$authCodeGrant = new Grants\AuthCodeGrant($authCodesRepo, $refreshTokensRepo, new DateInterval('PT10M'));
|
||||
|
||||
@@ -25,7 +25,7 @@ trait CryptTrait {
|
||||
protected function decrypt($encryptedData): string {
|
||||
try {
|
||||
return Yii::$app->tokens->decryptValue($encryptedData);
|
||||
} catch (SodiumException | RangeException $e) {
|
||||
} catch (SodiumException|RangeException $e) {
|
||||
throw new LogicException($e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ use League\OAuth2\Server\Entities\Traits\EntityTrait;
|
||||
use League\OAuth2\Server\Entities\Traits\TokenEntityTrait;
|
||||
use Yii;
|
||||
|
||||
class AccessTokenEntity implements AccessTokenEntityInterface {
|
||||
final class AccessTokenEntity implements AccessTokenEntityInterface {
|
||||
use EntityTrait;
|
||||
use TokenEntityTrait;
|
||||
|
||||
public function __toString(): string {
|
||||
return (string)Yii::$app->tokensFactory->createForOAuthClient($this);
|
||||
public function toString(): string {
|
||||
return Yii::$app->tokensFactory->createForOAuthClient($this)->toString();
|
||||
}
|
||||
|
||||
public function setPrivateKey(CryptKeyInterface $privateKey): void {
|
||||
|
||||
@@ -12,15 +12,18 @@ class ClientEntity implements ClientEntityInterface {
|
||||
use ClientTrait;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @param non-empty-string $id
|
||||
* @param string|string[] $redirectUri
|
||||
*/
|
||||
private $isTrusted;
|
||||
|
||||
public function __construct(string $id, string $name, $redirectUri, bool $isTrusted) {
|
||||
public function __construct(
|
||||
string $id,
|
||||
string $name,
|
||||
string|array $redirectUri,
|
||||
private readonly bool $isTrusted,
|
||||
) {
|
||||
$this->identifier = $id;
|
||||
$this->name = $name;
|
||||
$this->redirectUri = $redirectUri;
|
||||
$this->isTrusted = $isTrusted;
|
||||
}
|
||||
|
||||
public function isConfidential(): bool {
|
||||
|
||||
@@ -10,7 +10,7 @@ class UserEntity implements UserEntityInterface {
|
||||
use EntityTrait;
|
||||
|
||||
public function __construct(int $id) {
|
||||
$this->identifier = $id;
|
||||
$this->identifier = (string)$id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace api\components\OAuth2\Events;
|
||||
|
||||
use League\Event\AbstractEvent;
|
||||
use League\OAuth2\Server\EventEmitting\AbstractEvent;
|
||||
|
||||
class RequestedRefreshToken extends AbstractEvent {
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@ use api\components\OAuth2\Repositories\PublicScopeRepository;
|
||||
use DateInterval;
|
||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||
use League\OAuth2\Server\Entities\ScopeEntityInterface;
|
||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||
use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException;
|
||||
use League\OAuth2\Server\Grant\AuthCodeGrant as BaseAuthCodeGrant;
|
||||
use League\OAuth2\Server\RequestEvent;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
@@ -22,22 +24,22 @@ class AuthCodeGrant extends BaseAuthCodeGrant {
|
||||
* @param DateInterval $accessTokenTTL
|
||||
* @param ClientEntityInterface $client
|
||||
* @param string|null $userIdentifier
|
||||
* @param \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes
|
||||
* @param ScopeEntityInterface[] $scopes
|
||||
*
|
||||
* @return AccessTokenEntityInterface
|
||||
* @throws \League\OAuth2\Server\Exception\OAuthServerException
|
||||
* @throws \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException
|
||||
* @throws OAuthServerException
|
||||
* @throws UniqueTokenIdentifierConstraintViolationException
|
||||
*/
|
||||
protected function issueAccessToken(
|
||||
DateInterval $accessTokenTTL,
|
||||
ClientEntityInterface $client,
|
||||
$userIdentifier,
|
||||
array $scopes = []
|
||||
?string $userIdentifier,
|
||||
array $scopes = [],
|
||||
): AccessTokenEntityInterface {
|
||||
foreach ($scopes as $i => $scope) {
|
||||
if ($scope->getIdentifier() === PublicScopeRepository::OFFLINE_ACCESS) {
|
||||
unset($scopes[$i]);
|
||||
$this->getEmitter()->emit(new RequestedRefreshToken());
|
||||
$this->getEmitter()->emit(new RequestedRefreshToken('refresh_token_requested'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +49,7 @@ class AuthCodeGrant extends BaseAuthCodeGrant {
|
||||
protected function validateRedirectUri(
|
||||
string $redirectUri,
|
||||
ClientEntityInterface $client,
|
||||
ServerRequestInterface $request
|
||||
ServerRequestInterface $request,
|
||||
): void {
|
||||
$allowedRedirectUris = (array)$client->getRedirectUri();
|
||||
foreach ($allowedRedirectUris as $allowedRedirectUri) {
|
||||
|
||||
@@ -5,15 +5,17 @@ namespace api\components\OAuth2\Grants;
|
||||
|
||||
use api\components\OAuth2\CryptTrait;
|
||||
use api\components\Tokens\TokenReader;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\FactoryImmutable;
|
||||
use common\models\OauthSession;
|
||||
use InvalidArgumentException;
|
||||
use Lcobucci\JWT\ValidationData;
|
||||
use Lcobucci\JWT\Validation\Constraint\LooseValidAt;
|
||||
use Lcobucci\JWT\Validation\Validator;
|
||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||
use League\OAuth2\Server\Grant\RefreshTokenGrant as BaseRefreshTokenGrant;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Throwable;
|
||||
use Yii;
|
||||
|
||||
class RefreshTokenGrant extends BaseRefreshTokenGrant {
|
||||
@@ -30,7 +32,7 @@ class RefreshTokenGrant extends BaseRefreshTokenGrant {
|
||||
* @return array
|
||||
* @throws OAuthServerException
|
||||
*/
|
||||
protected function validateOldRefreshToken(ServerRequestInterface $request, $clientId): array {
|
||||
protected function validateOldRefreshToken(ServerRequestInterface $request, string $clientId): array {
|
||||
$refreshToken = $this->getRequestParameter('refresh_token', $request);
|
||||
if ($refreshToken !== null && mb_strlen($refreshToken) === 40) {
|
||||
return $this->validateLegacyRefreshToken($refreshToken);
|
||||
@@ -41,7 +43,7 @@ class RefreshTokenGrant extends BaseRefreshTokenGrant {
|
||||
|
||||
/**
|
||||
* Currently we're not rotating refresh tokens.
|
||||
* So we overriding this method to always return null, which means,
|
||||
* So we're overriding this method to always return null, which means,
|
||||
* that refresh_token will not be issued.
|
||||
*
|
||||
* @param AccessTokenEntityInterface $accessToken
|
||||
@@ -67,8 +69,8 @@ class RefreshTokenGrant extends BaseRefreshTokenGrant {
|
||||
[
|
||||
'access_token_id' => $accessTokenId,
|
||||
'session_id' => $sessionId,
|
||||
] = json_decode($result, true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (\Exception $e) {
|
||||
] = json_decode((string)$result, true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (Throwable $e) {
|
||||
throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token', $e);
|
||||
}
|
||||
|
||||
@@ -89,8 +91,14 @@ class RefreshTokenGrant extends BaseRefreshTokenGrant {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $jwt
|
||||
* @return array
|
||||
* @return array{
|
||||
* client_id: string,
|
||||
* refresh_token_id?: string,
|
||||
* access_token_id?: string,
|
||||
* scopes: list<string>|null,
|
||||
* user_id: string|null,
|
||||
* expire_time: int|null,
|
||||
* }
|
||||
* @throws OAuthServerException
|
||||
*/
|
||||
private function validateAccessToken(string $jwt): array {
|
||||
@@ -104,7 +112,7 @@ class RefreshTokenGrant extends BaseRefreshTokenGrant {
|
||||
throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token');
|
||||
}
|
||||
|
||||
if (!$token->validate(new ValidationData(Carbon::now()->getTimestamp()))) {
|
||||
if (!(new Validator())->validate($token, new LooseValidAt(FactoryImmutable::getDefaultInstance()))) {
|
||||
throw OAuthServerException::invalidRefreshToken('Token has expired');
|
||||
}
|
||||
|
||||
|
||||
@@ -15,4 +15,8 @@ class EmptyKey implements CryptKeyInterface {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getKeyContents(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,11 +22,11 @@ class AccessTokenRepository implements AccessTokenRepositoryInterface {
|
||||
public function getNewToken(
|
||||
ClientEntityInterface $clientEntity,
|
||||
array $scopes,
|
||||
$userIdentifier = null
|
||||
$userIdentifier = null,
|
||||
): AccessTokenEntityInterface {
|
||||
$accessToken = new AccessTokenEntity();
|
||||
$accessToken->setClient($clientEntity);
|
||||
array_map([$accessToken, 'addScope'], $scopes);
|
||||
array_map($accessToken->addScope(...), $scopes);
|
||||
if ($userIdentifier !== null) {
|
||||
$accessToken->setUserIdentifier($userIdentifier);
|
||||
}
|
||||
|
||||
@@ -21,8 +21,9 @@ class EmptyScopeRepository implements ScopeRepositoryInterface {
|
||||
public function finalizeScopes(
|
||||
array $scopes,
|
||||
$grantType,
|
||||
ClientEntityInterface $client,
|
||||
$userIdentifier = null
|
||||
ClientEntityInterface $clientEntity,
|
||||
$userIdentifier = null,
|
||||
?string $authCodeId = null,
|
||||
): array {
|
||||
return $scopes;
|
||||
}
|
||||
|
||||
@@ -10,11 +10,10 @@ use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||
use League\OAuth2\Server\Entities\ScopeEntityInterface;
|
||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
class InternalScopeRepository implements ScopeRepositoryInterface {
|
||||
|
||||
private const ALLOWED_SCOPES = [
|
||||
private const array ALLOWED_SCOPES = [
|
||||
P::CHANGE_ACCOUNT_USERNAME,
|
||||
P::CHANGE_ACCOUNT_PASSWORD,
|
||||
P::BLOCK_ACCOUNT,
|
||||
@@ -22,7 +21,7 @@ class InternalScopeRepository implements ScopeRepositoryInterface {
|
||||
P::ESCAPE_IDENTITY_VERIFICATION,
|
||||
];
|
||||
|
||||
private const PUBLIC_SCOPES_TO_INTERNAL_PERMISSIONS = [
|
||||
private const array PUBLIC_SCOPES_TO_INTERNAL_PERMISSIONS = [
|
||||
'internal_account_info' => P::OBTAIN_EXTENDED_ACCOUNT_INFO,
|
||||
];
|
||||
|
||||
@@ -35,21 +34,23 @@ class InternalScopeRepository implements ScopeRepositoryInterface {
|
||||
return new ScopeEntity($identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws OAuthServerException
|
||||
*/
|
||||
public function finalizeScopes(
|
||||
array $scopes,
|
||||
$grantType,
|
||||
ClientEntityInterface $client,
|
||||
$userIdentifier = null
|
||||
ClientEntityInterface $clientEntity,
|
||||
$userIdentifier = null,
|
||||
?string $authCodeId = null,
|
||||
): array {
|
||||
/** @var ClientEntity $client */
|
||||
Assert::isInstanceOf($client, ClientEntity::class);
|
||||
|
||||
if (empty($scopes)) {
|
||||
return $scopes;
|
||||
}
|
||||
|
||||
/** @var ClientEntity $clientEntity */
|
||||
// Right now we have no available scopes for the client_credentials grant
|
||||
if (!$client->isTrusted()) {
|
||||
if (!$clientEntity->isTrusted()) {
|
||||
throw OAuthServerException::invalidScope($scopes[0]->getIdentifier());
|
||||
}
|
||||
|
||||
|
||||
@@ -11,18 +11,18 @@ use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
|
||||
|
||||
class PublicScopeRepository implements ScopeRepositoryInterface {
|
||||
|
||||
public const OFFLINE_ACCESS = 'offline_access';
|
||||
public const CHANGE_SKIN = 'change_skin';
|
||||
public const string OFFLINE_ACCESS = 'offline_access';
|
||||
public const string CHANGE_SKIN = 'change_skin';
|
||||
|
||||
private const ACCOUNT_INFO = 'account_info';
|
||||
private const ACCOUNT_EMAIL = 'account_email';
|
||||
private const string ACCOUNT_INFO = 'account_info';
|
||||
private const string ACCOUNT_EMAIL = 'account_email';
|
||||
|
||||
private const PUBLIC_SCOPES_TO_INTERNAL_PERMISSIONS = [
|
||||
private const array PUBLIC_SCOPES_TO_INTERNAL_PERMISSIONS = [
|
||||
self::ACCOUNT_INFO => P::OBTAIN_OWN_ACCOUNT_INFO,
|
||||
self::ACCOUNT_EMAIL => P::OBTAIN_ACCOUNT_EMAIL,
|
||||
];
|
||||
|
||||
private const ALLOWED_SCOPES = [
|
||||
private const array ALLOWED_SCOPES = [
|
||||
P::OBTAIN_OWN_ACCOUNT_INFO,
|
||||
P::OBTAIN_ACCOUNT_EMAIL,
|
||||
P::MINECRAFT_SERVER_SESSION,
|
||||
@@ -43,7 +43,8 @@ class PublicScopeRepository implements ScopeRepositoryInterface {
|
||||
array $scopes,
|
||||
$grantType,
|
||||
ClientEntityInterface $clientEntity,
|
||||
$userIdentifier = null
|
||||
$userIdentifier = null,
|
||||
?string $authCodeId = null,
|
||||
): array {
|
||||
return $scopes;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ class Component extends \yii\base\Component {
|
||||
|
||||
public $secret;
|
||||
|
||||
public function init() {
|
||||
public function init(): void {
|
||||
if ($this->public === null) {
|
||||
throw new InvalidConfigException('Public is required');
|
||||
}
|
||||
|
||||
@@ -12,30 +12,27 @@ use yii\di\Instance;
|
||||
|
||||
class Validator extends \yii\validators\Validator {
|
||||
|
||||
private const SITE_VERIFY_URL = 'https://recaptcha.net/recaptcha/api/siteverify';
|
||||
private const string SITE_VERIFY_URL = 'https://recaptcha.net/recaptcha/api/siteverify';
|
||||
|
||||
private const REPEAT_LIMIT = 3;
|
||||
private const REPEAT_TIMEOUT = 1;
|
||||
private const int REPEAT_LIMIT = 3;
|
||||
private const int REPEAT_TIMEOUT = 1;
|
||||
|
||||
public $skipOnEmpty = false;
|
||||
|
||||
public $message = E::CAPTCHA_INVALID;
|
||||
|
||||
public $requiredMessage = E::CAPTCHA_REQUIRED;
|
||||
public string $requiredMessage = E::CAPTCHA_REQUIRED;
|
||||
|
||||
/**
|
||||
* @var Component|string
|
||||
*/
|
||||
public $component = 'reCaptcha';
|
||||
public Component|string $component = 'reCaptcha';
|
||||
|
||||
private $client;
|
||||
|
||||
public function __construct(ClientInterface $client, array $config = []) {
|
||||
public function __construct(
|
||||
private readonly ClientInterface $client,
|
||||
array $config = [],
|
||||
) {
|
||||
parent::__construct($config);
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
public function init() {
|
||||
public function init(): void {
|
||||
parent::init();
|
||||
$this->component = Instance::ensure($this->component, Component::class);
|
||||
}
|
||||
@@ -43,7 +40,7 @@ class Validator extends \yii\validators\Validator {
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function validateValue($value) {
|
||||
protected function validateValue($value): ?array {
|
||||
if (empty($value)) {
|
||||
return [$this->requiredMessage, []];
|
||||
}
|
||||
@@ -53,7 +50,7 @@ class Validator extends \yii\validators\Validator {
|
||||
$isSuccess = true;
|
||||
try {
|
||||
$response = $this->performRequest($value);
|
||||
} catch (ConnectException | ServerException $e) {
|
||||
} catch (ConnectException|ServerException $e) {
|
||||
if (++$repeats >= self::REPEAT_LIMIT) {
|
||||
throw $e;
|
||||
}
|
||||
@@ -77,9 +74,7 @@ class Validator extends \yii\validators\Validator {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function performRequest(string $value): ResponseInterface {
|
||||
return $this->client->request('POST', self::SITE_VERIFY_URL, [
|
||||
|
||||
@@ -6,22 +6,20 @@ namespace api\components\Tokens\Algorithms;
|
||||
use Lcobucci\JWT\Signer;
|
||||
use Lcobucci\JWT\Signer\Ecdsa\Sha256;
|
||||
use Lcobucci\JWT\Signer\Key;
|
||||
use Lcobucci\JWT\Signer\Key\InMemory;
|
||||
|
||||
final class ES256 implements AlgorithmInterface {
|
||||
|
||||
private string $privateKeyPath;
|
||||
|
||||
private ?string $privateKeyPass;
|
||||
|
||||
private ?Key $privateKey = null;
|
||||
|
||||
private ?Key $publicKey = null;
|
||||
|
||||
private Sha256 $signer;
|
||||
private readonly Sha256 $signer;
|
||||
|
||||
public function __construct(string $privateKeyPath, ?string $privateKeyPass = null) {
|
||||
$this->privateKeyPath = $privateKeyPath;
|
||||
$this->privateKeyPass = $privateKeyPass;
|
||||
public function __construct(
|
||||
private readonly string $privateKeyPath,
|
||||
private readonly ?string $privateKeyPass = null,
|
||||
) {
|
||||
$this->signer = new Sha256();
|
||||
}
|
||||
|
||||
@@ -31,7 +29,7 @@ final class ES256 implements AlgorithmInterface {
|
||||
|
||||
public function getPrivateKey(): Key {
|
||||
if ($this->privateKey === null) {
|
||||
$this->privateKey = new Key($this->privateKeyPath, $this->privateKeyPass);
|
||||
$this->privateKey = InMemory::plainText($this->privateKeyPath, $this->privateKeyPass ?? '');
|
||||
}
|
||||
|
||||
return $this->privateKey;
|
||||
@@ -40,9 +38,9 @@ final class ES256 implements AlgorithmInterface {
|
||||
public function getPublicKey(): Key {
|
||||
if ($this->publicKey === null) {
|
||||
$privateKey = $this->getPrivateKey();
|
||||
$privateKeyOpenSSL = openssl_pkey_get_private($privateKey->getContent(), $privateKey->getPassphrase() ?? '');
|
||||
$privateKeyOpenSSL = openssl_pkey_get_private($privateKey->contents(), $privateKey->passphrase());
|
||||
$publicPem = openssl_pkey_get_details($privateKeyOpenSSL)['key'];
|
||||
$this->publicKey = new Key($publicPem);
|
||||
$this->publicKey = InMemory::plainText($publicPem);
|
||||
}
|
||||
|
||||
return $this->publicKey;
|
||||
|
||||
@@ -6,21 +6,18 @@ namespace api\components\Tokens\Algorithms;
|
||||
use Lcobucci\JWT\Signer;
|
||||
use Lcobucci\JWT\Signer\Hmac\Sha256;
|
||||
use Lcobucci\JWT\Signer\Key;
|
||||
use Lcobucci\JWT\Signer\Key\InMemory;
|
||||
|
||||
class HS256 implements AlgorithmInterface {
|
||||
final class HS256 implements AlgorithmInterface {
|
||||
|
||||
private ?InMemory $loadedKey = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @param non-empty-string $key
|
||||
*/
|
||||
private $key;
|
||||
|
||||
/**
|
||||
* @var Key|null
|
||||
*/
|
||||
private $loadedKey;
|
||||
|
||||
public function __construct(string $key) {
|
||||
$this->key = $key;
|
||||
public function __construct(
|
||||
private readonly string $key,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getSigner(): Signer {
|
||||
@@ -37,7 +34,7 @@ class HS256 implements AlgorithmInterface {
|
||||
|
||||
private function loadKey(): Key {
|
||||
if ($this->loadedKey === null) {
|
||||
$this->loadedKey = new Key($this->key);
|
||||
$this->loadedKey = InMemory::plainText($this->key);
|
||||
}
|
||||
|
||||
return $this->loadedKey;
|
||||
|
||||
@@ -22,11 +22,11 @@ final class AlgorithmsManager {
|
||||
* @param AlgorithmInterface[] $algorithms
|
||||
*/
|
||||
public function __construct(array $algorithms = []) {
|
||||
array_map([$this, 'add'], $algorithms);
|
||||
array_map($this->add(...), $algorithms);
|
||||
}
|
||||
|
||||
public function add(AlgorithmInterface $algorithm): self {
|
||||
$id = $algorithm->getSigner()->getAlgorithmId();
|
||||
$id = $algorithm->getSigner()->algorithmId();
|
||||
Assert::keyNotExists($this->algorithms, $id, 'passed algorithm is already exists');
|
||||
$this->algorithms[$id] = $algorithm;
|
||||
|
||||
|
||||
@@ -5,31 +5,40 @@ namespace api\components\Tokens;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Lcobucci\JWT\Builder;
|
||||
use Lcobucci\JWT\Parser;
|
||||
use InvalidArgumentException;
|
||||
use Lcobucci\JWT\Encoding\ChainedFormatter;
|
||||
use Lcobucci\JWT\Encoding\JoseEncoder;
|
||||
use Lcobucci\JWT\Token;
|
||||
use Lcobucci\JWT\Token\Builder;
|
||||
use Lcobucci\JWT\Token\Parser;
|
||||
use Lcobucci\JWT\Token\RegisteredClaims;
|
||||
use Lcobucci\JWT\UnencryptedToken;
|
||||
use Lcobucci\JWT\Validation\Constraint\SignedWith;
|
||||
use Lcobucci\JWT\Validation\Validator;
|
||||
use ParagonIE\ConstantTime\Base64UrlSafe;
|
||||
use RangeException;
|
||||
use SodiumException;
|
||||
use Webmozart\Assert\Assert;
|
||||
use yii\base\Component as BaseComponent;
|
||||
|
||||
class Component extends BaseComponent {
|
||||
|
||||
private const PREFERRED_ALGORITHM = 'ES256';
|
||||
private const string PREFERRED_ALGORITHM = 'ES256';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $privateKeyPath;
|
||||
public string $privateKeyPath;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
public $privateKeyPass;
|
||||
public ?string $privateKeyPass = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $encryptionKey;
|
||||
public string $encryptionKey;
|
||||
|
||||
private ?AlgorithmsManager $algorithmManager = null;
|
||||
|
||||
@@ -39,19 +48,49 @@ class Component extends BaseComponent {
|
||||
Assert::notEmpty($this->encryptionKey, 'encryptionKey must be set');
|
||||
}
|
||||
|
||||
public function create(array $payloads = [], array $headers = []): Token {
|
||||
/**
|
||||
* @param array{
|
||||
* sub?: string,
|
||||
* jti?: string,
|
||||
* iat?: \DateTimeImmutable,
|
||||
* nbf?: \DateTimeImmutable,
|
||||
* exp?: \DateTimeImmutable,
|
||||
* } $payloads
|
||||
* @param array $headers
|
||||
*
|
||||
* @throws \api\components\Tokens\AlgorithmIsNotDefinedException
|
||||
*/
|
||||
public function create(array $payloads = [], array $headers = []): UnencryptedToken {
|
||||
$now = Carbon::now();
|
||||
$builder = (new Builder())->issuedAt($now->getTimestamp());
|
||||
$builder = (new Builder(new JoseEncoder(), ChainedFormatter::default()))->issuedAt($now->toDateTimeImmutable());
|
||||
if (isset($payloads['sub'])) {
|
||||
$builder = $builder->relatedTo($payloads['sub']);
|
||||
}
|
||||
|
||||
if (isset($payloads['jti'])) {
|
||||
$builder = $builder->identifiedBy($payloads['jti']);
|
||||
}
|
||||
|
||||
if (isset($payloads['iat'])) {
|
||||
$builder = $builder->issuedAt($payloads['iat']);
|
||||
}
|
||||
|
||||
if (isset($payloads['nbf'])) {
|
||||
$builder = $builder->canOnlyBeUsedAfter($payloads['nbf']);
|
||||
}
|
||||
|
||||
if (isset($payloads['exp'])) {
|
||||
$builder->expiresAt($payloads['exp']);
|
||||
$builder = $builder->expiresAt($payloads['exp']);
|
||||
}
|
||||
|
||||
foreach ($payloads as $claim => $value) {
|
||||
$builder->withClaim($claim, $this->prepareValue($value));
|
||||
if (!in_array($claim, RegisteredClaims::ALL, true)) { // Registered claims are handled by the if-chain above
|
||||
$builder = $builder->withClaim($claim, $this->prepareValue($value));
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($headers as $claim => $value) {
|
||||
$builder->withHeader($claim, $this->prepareValue($value));
|
||||
$builder = $builder->withHeader($claim, $this->prepareValue($value));
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
@@ -64,17 +103,17 @@ class Component extends BaseComponent {
|
||||
* @param string $jwt
|
||||
*
|
||||
* @return Token
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function parse(string $jwt): Token {
|
||||
return (new Parser())->parse($jwt);
|
||||
return (new Parser(new JoseEncoder()))->parse($jwt);
|
||||
}
|
||||
|
||||
public function verify(Token $token): bool {
|
||||
try {
|
||||
$algorithm = $this->getAlgorithmManager()->get($token->getHeader('alg'));
|
||||
return $token->verify($algorithm->getSigner(), $algorithm->getPublicKey());
|
||||
} catch (Exception $e) {
|
||||
$algorithm = $this->getAlgorithmManager()->get($token->headers()->get('alg'));
|
||||
return (new Validator())->validate($token, new SignedWith($algorithm->getSigner(), $algorithm->getPublicKey()));
|
||||
} catch (Exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -92,8 +131,8 @@ class Component extends BaseComponent {
|
||||
* @param string $encryptedValue
|
||||
*
|
||||
* @return string
|
||||
* @throws \SodiumException
|
||||
* @throws \RangeException
|
||||
* @throws SodiumException
|
||||
* @throws RangeException
|
||||
*/
|
||||
public function decryptValue(string $encryptedValue): string {
|
||||
$decoded = Base64UrlSafe::decode($encryptedValue);
|
||||
@@ -109,7 +148,7 @@ class Component extends BaseComponent {
|
||||
}
|
||||
|
||||
public function getPublicKey(): string {
|
||||
return $this->getAlgorithmManager()->get(self::PREFERRED_ALGORITHM)->getPublicKey()->getContent();
|
||||
return $this->getAlgorithmManager()->get(self::PREFERRED_ALGORITHM)->getPublicKey()->contents();
|
||||
}
|
||||
|
||||
private function getAlgorithmManager(): AlgorithmsManager {
|
||||
@@ -122,9 +161,9 @@ class Component extends BaseComponent {
|
||||
return $this->algorithmManager;
|
||||
}
|
||||
|
||||
private function prepareValue($value) {
|
||||
private function prepareValue(EncryptedValue|string $value): string {
|
||||
if ($value instanceof EncryptedValue) {
|
||||
return $this->encryptValue($value->getValue());
|
||||
return $this->encryptValue($value->value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
|
||||
@@ -3,19 +3,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace api\components\Tokens;
|
||||
|
||||
class EncryptedValue {
|
||||
final readonly class EncryptedValue {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $value;
|
||||
|
||||
public function __construct(string $value) {
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function getValue(): string {
|
||||
return $this->value;
|
||||
public function __construct(public string $value) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,19 +3,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace api\components\Tokens;
|
||||
|
||||
use Lcobucci\JWT\Token;
|
||||
use Lcobucci\JWT\UnencryptedToken;
|
||||
use Yii;
|
||||
|
||||
final class TokenReader {
|
||||
final readonly class TokenReader {
|
||||
|
||||
private Token $token;
|
||||
|
||||
public function __construct(Token $token) {
|
||||
$this->token = $token;
|
||||
public function __construct(
|
||||
private UnencryptedToken $token,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getAccountId(): ?int {
|
||||
$sub = $this->token->getClaim('sub', false);
|
||||
$sub = $this->token->claims()->get('sub', false);
|
||||
if ($sub === false) {
|
||||
return null;
|
||||
}
|
||||
@@ -24,36 +23,36 @@ final class TokenReader {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int)mb_substr($sub, mb_strlen(TokensFactory::SUB_ACCOUNT_PREFIX));
|
||||
return (int)mb_substr((string)$sub, mb_strlen(TokensFactory::SUB_ACCOUNT_PREFIX));
|
||||
}
|
||||
|
||||
public function getClientId(): ?string {
|
||||
return $this->token->getClaim('client_id', false) ?: null;
|
||||
return $this->token->claims()->get('client_id', false) ?: null;
|
||||
}
|
||||
|
||||
public function getScopes(): ?array {
|
||||
$scopes = $this->token->getClaim('scope', false);
|
||||
$scopes = $this->token->claims()->get('scope', false);
|
||||
if ($scopes !== false) {
|
||||
return explode(' ', $scopes);
|
||||
return explode(' ', (string)$scopes);
|
||||
}
|
||||
|
||||
// Handle legacy tokens, which used "ely-scopes" claim and was delimited with comma
|
||||
$scopes = $this->token->getClaim('ely-scopes', false);
|
||||
$scopes = $this->token->claims()->get('ely-scopes', false);
|
||||
if ($scopes === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return explode(',', $scopes);
|
||||
return explode(',', (string)$scopes);
|
||||
}
|
||||
|
||||
public function getMinecraftClientToken(): ?string {
|
||||
$encodedClientToken = $this->token->getClaim('ely-client-token', false);
|
||||
$encodedClientToken = $this->token->claims()->get('ely-client-token', false);
|
||||
if ($encodedClientToken === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* It really might throw an exception but we have not seen any case of such exception yet
|
||||
* It really might throw an exception, but we have not seen any case of such exception yet
|
||||
* @noinspection PhpUnhandledExceptionInspection
|
||||
*/
|
||||
return Yii::$app->tokens->decryptValue($encodedClientToken);
|
||||
|
||||
@@ -10,6 +10,7 @@ use common\models\Account;
|
||||
use common\models\AccountSession;
|
||||
use DateTime;
|
||||
use Lcobucci\JWT\Token;
|
||||
use Lcobucci\JWT\UnencryptedToken;
|
||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||
use League\OAuth2\Server\Entities\ScopeEntityInterface;
|
||||
use Yii;
|
||||
@@ -17,54 +18,52 @@ use yii\base\Component;
|
||||
|
||||
class TokensFactory extends Component {
|
||||
|
||||
public const SUB_ACCOUNT_PREFIX = 'ely|';
|
||||
public const string SUB_ACCOUNT_PREFIX = 'ely|';
|
||||
|
||||
public function createForWebAccount(Account $account, AccountSession $session = null): Token {
|
||||
public function createForWebAccount(Account $account, AccountSession $session = null): UnencryptedToken {
|
||||
$payloads = [
|
||||
'sub' => $this->buildSub($account->id),
|
||||
'exp' => Carbon::now()->addHour()->getTimestamp(),
|
||||
'exp' => Carbon::now()->addHour()->toDateTimeImmutable(),
|
||||
'scope' => $this->prepareScopes([R::ACCOUNTS_WEB_USER]),
|
||||
];
|
||||
if ($session === null) {
|
||||
// If we don't remember a session, the token should live longer
|
||||
// so that the session doesn't end while working with the account
|
||||
$payloads['exp'] = Carbon::now()->addDays(7)->getTimestamp();
|
||||
$payloads['exp'] = Carbon::now()->addDays(7)->toDateTimeImmutable();
|
||||
} else {
|
||||
$payloads['jti'] = $session->id;
|
||||
$payloads['jti'] = (string)$session->id;
|
||||
}
|
||||
|
||||
return Yii::$app->tokens->create($payloads);
|
||||
}
|
||||
|
||||
public function createForOAuthClient(AccessTokenEntityInterface $accessToken): Token {
|
||||
public function createForOAuthClient(AccessTokenEntityInterface $accessToken): UnencryptedToken {
|
||||
$payloads = [
|
||||
'client_id' => $accessToken->getClient()->getIdentifier(),
|
||||
'scope' => $this->prepareScopes($accessToken->getScopes()),
|
||||
];
|
||||
if ($accessToken->getExpiryDateTime() > new DateTime()) {
|
||||
$payloads['exp'] = $accessToken->getExpiryDateTime()->getTimestamp();
|
||||
$payloads['exp'] = $accessToken->getExpiryDateTime();
|
||||
}
|
||||
|
||||
if ($accessToken->getUserIdentifier() !== null) {
|
||||
$payloads['sub'] = $this->buildSub($accessToken->getUserIdentifier());
|
||||
$payloads['sub'] = $this->buildSub((int)$accessToken->getUserIdentifier());
|
||||
}
|
||||
|
||||
return Yii::$app->tokens->create($payloads);
|
||||
}
|
||||
|
||||
public function createForMinecraftAccount(Account $account, string $clientToken): Token {
|
||||
public function createForMinecraftAccount(Account $account, string $clientToken): UnencryptedToken {
|
||||
return Yii::$app->tokens->create([
|
||||
'scope' => $this->prepareScopes([P::OBTAIN_OWN_ACCOUNT_INFO, P::MINECRAFT_SERVER_SESSION]),
|
||||
'ely-client-token' => new EncryptedValue($clientToken),
|
||||
'sub' => $this->buildSub($account->id),
|
||||
'exp' => Carbon::now()->addDays(2)->getTimestamp(),
|
||||
'exp' => Carbon::now()->addDays(2)->toDateTimeImmutable(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ScopeEntityInterface[]|string[] $scopes
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function prepareScopes(array $scopes): string {
|
||||
return implode(' ', array_map(function($scope): string {
|
||||
|
||||
@@ -18,13 +18,13 @@ use yii\web\User as YiiUserComponent;
|
||||
*/
|
||||
class Component extends YiiUserComponent {
|
||||
|
||||
public const KEEP_MINECRAFT_SESSIONS = 1;
|
||||
public const KEEP_SITE_SESSIONS = 2;
|
||||
public const KEEP_CURRENT_SESSION = 4;
|
||||
public const int KEEP_MINECRAFT_SESSIONS = 1;
|
||||
public const int KEEP_SITE_SESSIONS = 2;
|
||||
public const int KEEP_CURRENT_SESSION = 4;
|
||||
|
||||
public $enableSession = false;
|
||||
|
||||
public $loginUrl = null;
|
||||
public $loginUrl;
|
||||
|
||||
/**
|
||||
* We don't use the standard web authorization mechanism via cookies.
|
||||
@@ -57,12 +57,13 @@ class Component extends YiiUserComponent {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sessionId = $identity->getToken()->getClaim('jti', false);
|
||||
if ($sessionId === false) {
|
||||
/** @var int|null $sessionId */
|
||||
$sessionId = $identity->getToken()->claims()->get('jti');
|
||||
if ($sessionId === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return AccountSession::findOne($sessionId);
|
||||
return AccountSession::findOne(['id' => (int)$sessionId]);
|
||||
}
|
||||
|
||||
public function terminateSessions(Account $account, int $mode = 0): void {
|
||||
|
||||
@@ -5,57 +5,54 @@ namespace api\components\User;
|
||||
|
||||
use api\components\Tokens\TokenReader;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\FactoryImmutable;
|
||||
use common\models\Account;
|
||||
use common\models\OauthClient;
|
||||
use common\models\OauthSession;
|
||||
use DateTimeImmutable;
|
||||
use Exception;
|
||||
use Lcobucci\JWT\Token;
|
||||
use Lcobucci\JWT\ValidationData;
|
||||
use Lcobucci\JWT\UnencryptedToken;
|
||||
use Lcobucci\JWT\Validation\Constraint\LooseValidAt;
|
||||
use Lcobucci\JWT\Validation\Validator;
|
||||
use Yii;
|
||||
use yii\base\NotSupportedException;
|
||||
use yii\web\UnauthorizedHttpException;
|
||||
|
||||
class JwtIdentity implements IdentityInterface {
|
||||
|
||||
/**
|
||||
* @var Token
|
||||
*/
|
||||
private $token;
|
||||
private ?TokenReader $reader = null;
|
||||
|
||||
/**
|
||||
* @var TokenReader|null
|
||||
*/
|
||||
private $reader;
|
||||
|
||||
private function __construct(Token $token) {
|
||||
$this->token = $token;
|
||||
private function __construct(
|
||||
private readonly UnencryptedToken $token,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function findIdentityByAccessToken($rawToken, $type = null): IdentityInterface {
|
||||
public static function findIdentityByAccessToken($token, $type = null): IdentityInterface {
|
||||
try {
|
||||
$token = Yii::$app->tokens->parse($rawToken);
|
||||
$parsedToken = Yii::$app->tokens->parse($token);
|
||||
} catch (Exception $e) {
|
||||
Yii::error($e);
|
||||
throw new UnauthorizedHttpException('Incorrect token');
|
||||
}
|
||||
|
||||
if (!Yii::$app->tokens->verify($token)) {
|
||||
if (!Yii::$app->tokens->verify($parsedToken)) {
|
||||
throw new UnauthorizedHttpException('Incorrect token');
|
||||
}
|
||||
|
||||
$now = Carbon::now();
|
||||
if ($token->isExpired($now)) {
|
||||
if ($parsedToken->isExpired($now)) {
|
||||
throw new UnauthorizedHttpException('Token expired');
|
||||
}
|
||||
|
||||
if (!$token->validate(new ValidationData($now->getTimestamp()))) {
|
||||
if (!(new Validator())->validate($parsedToken, new LooseValidAt(FactoryImmutable::getDefaultInstance()))) {
|
||||
throw new UnauthorizedHttpException('Incorrect token');
|
||||
}
|
||||
|
||||
$tokenReader = new TokenReader($token);
|
||||
$tokenReader = new TokenReader($parsedToken);
|
||||
$accountId = $tokenReader->getAccountId();
|
||||
if ($accountId !== null) {
|
||||
$iat = $token->getClaim('iat');
|
||||
/** @var DateTimeImmutable $iat */
|
||||
$iat = $parsedToken->claims()->get('iat');
|
||||
if ($tokenReader->getMinecraftClientToken() !== null
|
||||
&& self::isRevoked($accountId, OauthClient::UNAUTHORIZED_MINECRAFT_GAME_LAUNCHER, $iat)
|
||||
) {
|
||||
@@ -69,10 +66,10 @@ class JwtIdentity implements IdentityInterface {
|
||||
}
|
||||
}
|
||||
|
||||
return new self($token);
|
||||
return new self($parsedToken);
|
||||
}
|
||||
|
||||
public function getToken(): Token {
|
||||
public function getToken(): UnencryptedToken {
|
||||
return $this->token;
|
||||
}
|
||||
|
||||
@@ -85,10 +82,10 @@ class JwtIdentity implements IdentityInterface {
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
return (string)$this->token;
|
||||
return $this->token->toString();
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
/** @codeCoverageIgnoreStart */
|
||||
public function getAuthKey() {
|
||||
throw new NotSupportedException('This method used for cookie auth, except we using Bearer auth');
|
||||
}
|
||||
@@ -97,17 +94,19 @@ class JwtIdentity implements IdentityInterface {
|
||||
throw new NotSupportedException('This method used for cookie auth, except we using Bearer auth');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotSupportedException
|
||||
*/
|
||||
public static function findIdentity($id) {
|
||||
throw new NotSupportedException('This method used for cookie auth, except we using Bearer auth');
|
||||
}
|
||||
|
||||
private static function isRevoked(int $accountId, string $clientId, int $iat): bool {
|
||||
private static function isRevoked(int $accountId, string $clientId, DateTimeImmutable $iat): bool {
|
||||
$session = OauthSession::findOne(['account_id' => $accountId, 'client_id' => $clientId]);
|
||||
return $session !== null && $session->revoked_at !== null && $session->revoked_at > $iat;
|
||||
return $session !== null && $session->revoked_at !== null && $session->revoked_at > $iat->getTimestamp();
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
/** @codeCoverageIgnoreEnd */
|
||||
private function getReader(): TokenReader {
|
||||
if ($this->reader === null) {
|
||||
$this->reader = new TokenReader($this->token);
|
||||
|
||||
@@ -10,33 +10,21 @@ use Yii;
|
||||
use yii\base\NotSupportedException;
|
||||
use yii\web\UnauthorizedHttpException;
|
||||
|
||||
class LegacyOAuth2Identity implements IdentityInterface {
|
||||
readonly class LegacyOAuth2Identity implements IdentityInterface {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @param string[] $scopes
|
||||
*/
|
||||
private $accessToken;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sessionId;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $scopes;
|
||||
|
||||
private function __construct(string $accessToken, int $sessionId, array $scopes) {
|
||||
$this->accessToken = $accessToken;
|
||||
$this->sessionId = $sessionId;
|
||||
$this->scopes = $scopes;
|
||||
private function __construct(
|
||||
private string $accessToken,
|
||||
private int $sessionId,
|
||||
private array $scopes,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws UnauthorizedHttpException
|
||||
* @return IdentityInterface
|
||||
*/
|
||||
public static function findIdentityByAccessToken($token, $type = null): IdentityInterface {
|
||||
$tokenParams = self::findRecordOnLegacyStorage($token);
|
||||
@@ -48,16 +36,11 @@ class LegacyOAuth2Identity implements IdentityInterface {
|
||||
throw new UnauthorizedHttpException('Token expired');
|
||||
}
|
||||
|
||||
return new static($token, $tokenParams['session_id'], $tokenParams['scopes']);
|
||||
return new self($token, $tokenParams['session_id'], $tokenParams['scopes']);
|
||||
}
|
||||
|
||||
public function getAccount(): ?Account {
|
||||
$session = $this->getSession();
|
||||
if ($session === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $session->account;
|
||||
return $this->getSession()?->account;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,7 +54,7 @@ class LegacyOAuth2Identity implements IdentityInterface {
|
||||
return $this->accessToken;
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
/** @codeCoverageIgnoreStart */
|
||||
public function getAuthKey() {
|
||||
throw new NotSupportedException('This method used for cookie auth, except we using Bearer auth');
|
||||
}
|
||||
@@ -84,8 +67,7 @@ class LegacyOAuth2Identity implements IdentityInterface {
|
||||
throw new NotSupportedException('This method used for cookie auth, except we using Bearer auth');
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
/** @codeCoverageIgnoreEnd */
|
||||
private static function findRecordOnLegacyStorage(string $accessToken): ?array {
|
||||
$record = Yii::$app->redis->get("oauth:access:tokens:{$accessToken}");
|
||||
if ($record === null) {
|
||||
@@ -93,8 +75,8 @@ class LegacyOAuth2Identity implements IdentityInterface {
|
||||
}
|
||||
|
||||
try {
|
||||
$data = json_decode($record, true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (Exception $e) {
|
||||
$data = json_decode((string)$record, true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (Exception) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user