mirror of
https://github.com/elyby/accounts.git
synced 2024-11-10 07:22:00 +05:30
Cleanup User Component, update tests
This commit is contained in:
parent
445c234360
commit
4c2a9cc172
@ -1,4 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace api\components\User;
|
||||
|
||||
use api\exceptions\ThisShouldNotHappenException;
|
||||
@ -11,6 +13,7 @@ use Emarref\Jwt\Algorithm\AlgorithmInterface;
|
||||
use Emarref\Jwt\Algorithm\Hs256;
|
||||
use Emarref\Jwt\Algorithm\Rs256;
|
||||
use Emarref\Jwt\Claim;
|
||||
use Emarref\Jwt\Encryption\Asymmetric as AsymmetricEncryption;
|
||||
use Emarref\Jwt\Encryption\EncryptionInterface;
|
||||
use Emarref\Jwt\Encryption\Factory as EncryptionFactory;
|
||||
use Emarref\Jwt\Exception\VerificationException;
|
||||
@ -18,6 +21,7 @@ use Emarref\Jwt\HeaderParameter\Custom;
|
||||
use Emarref\Jwt\Token;
|
||||
use Emarref\Jwt\Verification\Context as VerificationContext;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use Webmozart\Assert\Assert;
|
||||
use Yii;
|
||||
use yii\base\InvalidConfigException;
|
||||
@ -39,6 +43,8 @@ class Component extends YiiUserComponent {
|
||||
|
||||
public const JWT_SUBJECT_PREFIX = 'ely|';
|
||||
|
||||
private const LATEST_JWT_VERSION = 1;
|
||||
|
||||
public $enableSession = false;
|
||||
|
||||
public $loginUrl = null;
|
||||
@ -71,26 +77,6 @@ class Component extends YiiUserComponent {
|
||||
Assert::notEmpty($this->privateKeyPath, 'private key path must be specified');
|
||||
}
|
||||
|
||||
public function getPublicKey() {
|
||||
if (empty($this->publicKey)) {
|
||||
if (!($this->publicKey = file_get_contents($this->publicKeyPath))) {
|
||||
throw new InvalidConfigException('invalid public key path');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->publicKey;
|
||||
}
|
||||
|
||||
public function getPrivateKey() {
|
||||
if (empty($this->privateKey)) {
|
||||
if (!($this->privateKey = file_get_contents($this->privateKeyPath))) {
|
||||
throw new InvalidConfigException('invalid private key path');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->privateKey;
|
||||
}
|
||||
|
||||
public function findIdentityByAccessToken($accessToken): ?IdentityInterface {
|
||||
if (empty($accessToken)) {
|
||||
return null;
|
||||
@ -109,29 +95,17 @@ class Component extends YiiUserComponent {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function createJwtAuthenticationToken(Account $account, bool $rememberMe): AuthenticationResult {
|
||||
$ip = Yii::$app->request->userIP;
|
||||
public function createJwtAuthenticationToken(Account $account, AccountSession $session = null): Token {
|
||||
$token = $this->createToken($account);
|
||||
if ($rememberMe) {
|
||||
$session = new AccountSession();
|
||||
$session->account_id = $account->id;
|
||||
$session->setIp($ip);
|
||||
$session->generateRefreshToken();
|
||||
if (!$session->save()) {
|
||||
throw new ThisShouldNotHappenException('Cannot save account session model');
|
||||
}
|
||||
|
||||
if ($session !== null) {
|
||||
$token->addClaim(new Claim\JwtId($session->id));
|
||||
} else {
|
||||
$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
|
||||
$token->addClaim(new Claim\Expiration((new DateTime())->add(new DateInterval($this->sessionTimeout))));
|
||||
}
|
||||
|
||||
$jwt = $this->serializeToken($token);
|
||||
|
||||
return new AuthenticationResult($account, $jwt, $session);
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function renewJwtAuthenticationToken(AccountSession $session): AuthenticationResult {
|
||||
@ -155,6 +129,13 @@ class Component extends YiiUserComponent {
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function serializeToken(Token $token): string {
|
||||
$encryption = $this->getEncryptionForVersion(self::LATEST_JWT_VERSION);
|
||||
$this->prepareEncryptionForEncoding($encryption);
|
||||
|
||||
return (new Jwt())->serialize($token, $encryption);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $jwtString
|
||||
* @return Token
|
||||
@ -170,9 +151,10 @@ class Component extends YiiUserComponent {
|
||||
throw new VerificationException('Incorrect token encoding', 0, $e);
|
||||
}
|
||||
|
||||
$version = $notVerifiedToken->getHeader()->findParameterByName('v');
|
||||
$version = $version ? $version->getValue() : null;
|
||||
$encryption = $this->getEncryption($version);
|
||||
$versionHeader = $notVerifiedToken->getHeader()->findParameterByName('v');
|
||||
$version = $versionHeader ? $versionHeader->getValue() : 0;
|
||||
$encryption = $this->getEncryptionForVersion($version);
|
||||
$this->prepareEncryptionForDecoding($encryption);
|
||||
|
||||
$context = new VerificationContext($encryption);
|
||||
$context->setSubject(self::JWT_SUBJECT_PREFIX);
|
||||
@ -240,28 +222,27 @@ class Component extends YiiUserComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public function getAlgorithm(): AlgorithmInterface {
|
||||
return new Rs256();
|
||||
}
|
||||
|
||||
public function getEncryption(?int $version): EncryptionInterface {
|
||||
$algorithm = $version ? new Rs256() : new Hs256($this->secret);
|
||||
$encryption = EncryptionFactory::create($algorithm);
|
||||
|
||||
if ($version) {
|
||||
$encryption->setPublicKey($this->getPublicKey())->setPrivateKey($this->getPrivateKey());
|
||||
private function getPublicKey() {
|
||||
if (empty($this->publicKey)) {
|
||||
if (!($this->publicKey = file_get_contents($this->publicKeyPath))) {
|
||||
throw new InvalidConfigException('invalid public key path');
|
||||
}
|
||||
}
|
||||
|
||||
return $encryption;
|
||||
return $this->publicKey;
|
||||
}
|
||||
|
||||
protected function serializeToken(Token $token): string {
|
||||
$encryption = $this->getEncryption(1);
|
||||
private function getPrivateKey() {
|
||||
if (empty($this->privateKey)) {
|
||||
if (!($this->privateKey = file_get_contents($this->privateKeyPath))) {
|
||||
throw new InvalidConfigException('invalid private key path');
|
||||
}
|
||||
}
|
||||
|
||||
return (new Jwt())->serialize($token, $encryption);
|
||||
return $this->privateKey;
|
||||
}
|
||||
|
||||
protected function createToken(Account $account): Token {
|
||||
private function createToken(Account $account): Token {
|
||||
$token = new Token();
|
||||
$token->addHeader(new Custom('v', 1));
|
||||
foreach ($this->getClaims($account) as $claim) {
|
||||
@ -275,7 +256,7 @@ class Component extends YiiUserComponent {
|
||||
* @param Account $account
|
||||
* @return Claim\AbstractClaim[]
|
||||
*/
|
||||
protected function getClaims(Account $account): array {
|
||||
private function getClaims(Account $account): array {
|
||||
$currentTime = new DateTime();
|
||||
|
||||
return [
|
||||
@ -286,7 +267,22 @@ class Component extends YiiUserComponent {
|
||||
];
|
||||
}
|
||||
|
||||
private function getBearerToken() {
|
||||
private function getEncryptionForVersion(int $version): EncryptionInterface {
|
||||
return EncryptionFactory::create($this->getAlgorithm($version ?? 0));
|
||||
}
|
||||
|
||||
private function getAlgorithm(int $version): AlgorithmInterface {
|
||||
switch ($version) {
|
||||
case 0:
|
||||
return new Hs256($this->secret);
|
||||
case 1:
|
||||
return new Rs256();
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Unsupported token version');
|
||||
}
|
||||
|
||||
private function getBearerToken(): ?string {
|
||||
$authHeader = Yii::$app->request->getHeaders()->get('Authorization');
|
||||
if ($authHeader === null || !preg_match('/^Bearer\s+(.*?)$/', $authHeader, $matches)) {
|
||||
return null;
|
||||
@ -295,4 +291,16 @@ class Component extends YiiUserComponent {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
private function prepareEncryptionForEncoding(EncryptionInterface $encryption): void {
|
||||
if ($encryption instanceof AsymmetricEncryption) {
|
||||
$encryption->setPrivateKey($this->getPrivateKey());
|
||||
}
|
||||
}
|
||||
|
||||
private function prepareEncryptionForDecoding(EncryptionInterface $encryption) {
|
||||
if ($encryption instanceof AsymmetricEncryption) {
|
||||
$encryption->setPublicKey($this->getPublicKey());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,12 +4,14 @@ declare(strict_types=1);
|
||||
namespace api\models\authentication;
|
||||
|
||||
use api\aop\annotations\CollectModelMetrics;
|
||||
use api\components\User\AuthenticationResult;
|
||||
use api\models\base\ApiForm;
|
||||
use api\validators\EmailActivationKeyValidator;
|
||||
use common\models\Account;
|
||||
use common\models\AccountSession;
|
||||
use common\models\EmailActivation;
|
||||
use Webmozart\Assert\Assert;
|
||||
use Yii;
|
||||
use yii\base\ErrorException;
|
||||
|
||||
class ConfirmEmailForm extends ApiForm {
|
||||
|
||||
@ -23,8 +25,8 @@ class ConfirmEmailForm extends ApiForm {
|
||||
|
||||
/**
|
||||
* @CollectModelMetrics(prefix="signup.confirmEmail")
|
||||
* @return \api\components\User\AuthenticationResult|bool
|
||||
* @throws ErrorException
|
||||
* @return AuthenticationResult|bool
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function confirm() {
|
||||
if (!$this->validate()) {
|
||||
@ -37,17 +39,22 @@ class ConfirmEmailForm extends ApiForm {
|
||||
$confirmModel = $this->key;
|
||||
$account = $confirmModel->account;
|
||||
$account->status = Account::STATUS_ACTIVE;
|
||||
if (!$confirmModel->delete()) {
|
||||
throw new ErrorException('Unable remove activation key.');
|
||||
}
|
||||
Assert::notSame($confirmModel->delete(), false, 'Unable remove activation key.');
|
||||
|
||||
if (!$account->save()) {
|
||||
throw new ErrorException('Unable activate user account.');
|
||||
}
|
||||
Assert::true($account->save(), 'Unable activate user account.');
|
||||
|
||||
$session = new AccountSession();
|
||||
$session->account_id = $account->id;
|
||||
$session->setIp(Yii::$app->request->userIP);
|
||||
$session->generateRefreshToken();
|
||||
Assert::true($session->save(), 'Cannot save account session model');
|
||||
|
||||
$token = Yii::$app->user->createJwtAuthenticationToken($account, $session);
|
||||
$jwt = Yii::$app->user->serializeToken($token);
|
||||
|
||||
$transaction->commit();
|
||||
|
||||
return Yii::$app->user->createJwtAuthenticationToken($account, true);
|
||||
return new AuthenticationResult($account, $jwt, $session);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,12 +1,17 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace api\models\authentication;
|
||||
|
||||
use api\aop\annotations\CollectModelMetrics;
|
||||
use api\components\User\AuthenticationResult;
|
||||
use api\models\base\ApiForm;
|
||||
use api\traits\AccountFinder;
|
||||
use api\validators\TotpValidator;
|
||||
use common\helpers\Error as E;
|
||||
use common\models\Account;
|
||||
use common\models\AccountSession;
|
||||
use Webmozart\Assert\Assert;
|
||||
use Yii;
|
||||
|
||||
class LoginForm extends ApiForm {
|
||||
@ -41,15 +46,13 @@ class LoginForm extends ApiForm {
|
||||
];
|
||||
}
|
||||
|
||||
public function validateLogin($attribute) {
|
||||
if (!$this->hasErrors()) {
|
||||
if ($this->getAccount() === null) {
|
||||
$this->addError($attribute, E::LOGIN_NOT_EXIST);
|
||||
}
|
||||
public function validateLogin(string $attribute): void {
|
||||
if (!$this->hasErrors() && $this->getAccount() === null) {
|
||||
$this->addError($attribute, E::LOGIN_NOT_EXIST);
|
||||
}
|
||||
}
|
||||
|
||||
public function validatePassword($attribute) {
|
||||
public function validatePassword(string $attribute): void {
|
||||
if (!$this->hasErrors()) {
|
||||
$account = $this->getAccount();
|
||||
if ($account === null || !$account->validatePassword($this->password)) {
|
||||
@ -58,11 +61,12 @@ class LoginForm extends ApiForm {
|
||||
}
|
||||
}
|
||||
|
||||
public function validateTotp($attribute) {
|
||||
public function validateTotp(string $attribute): void {
|
||||
if ($this->hasErrors()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Account $account */
|
||||
$account = $this->getAccount();
|
||||
if (!$account->is_otp_enabled) {
|
||||
return;
|
||||
@ -73,8 +77,9 @@ class LoginForm extends ApiForm {
|
||||
$validator->validateAttribute($this, $attribute);
|
||||
}
|
||||
|
||||
public function validateActivity($attribute) {
|
||||
public function validateActivity(string $attribute): void {
|
||||
if (!$this->hasErrors()) {
|
||||
/** @var Account $account */
|
||||
$account = $this->getAccount();
|
||||
if ($account->status === Account::STATUS_BANNED) {
|
||||
$this->addError($attribute, E::ACCOUNT_BANNED);
|
||||
@ -92,20 +97,37 @@ class LoginForm extends ApiForm {
|
||||
|
||||
/**
|
||||
* @CollectModelMetrics(prefix="authentication.login")
|
||||
* @return \api\components\User\AuthenticationResult|bool
|
||||
* @return AuthenticationResult|bool
|
||||
*/
|
||||
public function login() {
|
||||
if (!$this->validate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$transaction = Yii::$app->db->beginTransaction();
|
||||
|
||||
/** @var Account $account */
|
||||
$account = $this->getAccount();
|
||||
if ($account->password_hash_strategy !== Account::PASS_HASH_STRATEGY_YII2) {
|
||||
$account->setPassword($this->password);
|
||||
$account->save();
|
||||
Assert::true($account->save(), 'Unable to upgrade user\'s password');
|
||||
}
|
||||
|
||||
return Yii::$app->user->createJwtAuthenticationToken($account, $this->rememberMe);
|
||||
$session = null;
|
||||
if ($this->rememberMe) {
|
||||
$session = new AccountSession();
|
||||
$session->account_id = $account->id;
|
||||
$session->setIp(Yii::$app->request->userIP);
|
||||
$session->generateRefreshToken();
|
||||
Assert::true($session->save(), 'Cannot save account session model');
|
||||
}
|
||||
|
||||
$token = Yii::$app->user->createJwtAuthenticationToken($account, $session);
|
||||
$jwt = Yii::$app->user->serializeToken($token);
|
||||
|
||||
$transaction->commit();
|
||||
|
||||
return new AuthenticationResult($account, $jwt, $session);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace api\models\authentication;
|
||||
|
||||
use api\aop\annotations\CollectModelMetrics;
|
||||
@ -7,8 +9,8 @@ use api\validators\EmailActivationKeyValidator;
|
||||
use common\helpers\Error as E;
|
||||
use common\models\EmailActivation;
|
||||
use common\validators\PasswordValidator;
|
||||
use Webmozart\Assert\Assert;
|
||||
use Yii;
|
||||
use yii\base\ErrorException;
|
||||
|
||||
class RecoverPasswordForm extends ApiForm {
|
||||
|
||||
@ -18,7 +20,7 @@ class RecoverPasswordForm extends ApiForm {
|
||||
|
||||
public $newRePassword;
|
||||
|
||||
public function rules() {
|
||||
public function rules(): array {
|
||||
return [
|
||||
['key', EmailActivationKeyValidator::class, 'type' => EmailActivation::TYPE_FORGOT_PASSWORD_KEY],
|
||||
['newPassword', 'required', 'message' => E::NEW_PASSWORD_REQUIRED],
|
||||
@ -28,18 +30,16 @@ class RecoverPasswordForm extends ApiForm {
|
||||
];
|
||||
}
|
||||
|
||||
public function validatePasswordAndRePasswordMatch($attribute) {
|
||||
if (!$this->hasErrors()) {
|
||||
if ($this->newPassword !== $this->newRePassword) {
|
||||
$this->addError($attribute, E::NEW_RE_PASSWORD_DOES_NOT_MATCH);
|
||||
}
|
||||
public function validatePasswordAndRePasswordMatch(string $attribute): void {
|
||||
if (!$this->hasErrors() && $this->newPassword !== $this->newRePassword) {
|
||||
$this->addError($attribute, E::NEW_RE_PASSWORD_DOES_NOT_MATCH);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @CollectModelMetrics(prefix="authentication.recoverPassword")
|
||||
* @return \api\components\User\AuthenticationResult|bool
|
||||
* @throws ErrorException
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function recoverPassword() {
|
||||
if (!$this->validate()) {
|
||||
@ -52,17 +52,16 @@ class RecoverPasswordForm extends ApiForm {
|
||||
$confirmModel = $this->key;
|
||||
$account = $confirmModel->account;
|
||||
$account->password = $this->newPassword;
|
||||
if (!$confirmModel->delete()) {
|
||||
throw new ErrorException('Unable remove activation key.');
|
||||
}
|
||||
Assert::notSame($confirmModel->delete(), false, 'Unable remove activation key.');
|
||||
|
||||
if (!$account->save(false)) {
|
||||
throw new ErrorException('Unable activate user account.');
|
||||
}
|
||||
Assert::true($account->save(), 'Unable activate user account.');
|
||||
|
||||
$token = Yii::$app->user->createJwtAuthenticationToken($account);
|
||||
$jwt = Yii::$app->user->serializeToken($token);
|
||||
|
||||
$transaction->commit();
|
||||
|
||||
return Yii::$app->user->createJwtAuthenticationToken($account, false);
|
||||
return new \api\components\User\AuthenticationResult($account, $jwt, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,8 +19,9 @@ class FunctionalTester extends Actor {
|
||||
throw new InvalidArgumentException("Cannot find account for username \"{$asUsername}\"");
|
||||
}
|
||||
|
||||
$result = Yii::$app->user->createJwtAuthenticationToken($account, false);
|
||||
$this->amBearerAuthenticated($result->getJwt());
|
||||
$token = Yii::$app->user->createJwtAuthenticationToken($account);
|
||||
$jwt = Yii::$app->user->serializeToken($token);
|
||||
$this->amBearerAuthenticated($jwt);
|
||||
|
||||
return $account->id;
|
||||
}
|
||||
|
@ -1,24 +1,22 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace codeception\api\unit\components\User;
|
||||
|
||||
use api\components\User\AuthenticationResult;
|
||||
use api\components\User\Component;
|
||||
use api\components\User\Identity;
|
||||
use api\tests\unit\TestCase;
|
||||
use common\models\Account;
|
||||
use common\models\AccountSession;
|
||||
use common\tests\_support\ProtectedCaller;
|
||||
use common\tests\fixtures\AccountFixture;
|
||||
use common\tests\fixtures\AccountSessionFixture;
|
||||
use common\tests\fixtures\MinecraftAccessKeyFixture;
|
||||
use Emarref\Jwt\Claim;
|
||||
use Emarref\Jwt\Jwt;
|
||||
use Emarref\Jwt\Token;
|
||||
use Yii;
|
||||
use yii\web\Request;
|
||||
|
||||
class ComponentTest extends TestCase {
|
||||
use ProtectedCaller;
|
||||
|
||||
/**
|
||||
* @var Component|\PHPUnit\Framework\MockObject\MockObject
|
||||
@ -41,42 +39,24 @@ class ComponentTest extends TestCase {
|
||||
public function testCreateJwtAuthenticationToken() {
|
||||
$this->mockRequest();
|
||||
|
||||
// Token without session
|
||||
$account = new Account(['id' => 1]);
|
||||
$result = $this->component->createJwtAuthenticationToken($account, false);
|
||||
$this->assertInstanceOf(AuthenticationResult::class, $result);
|
||||
$this->assertNull($result->getSession());
|
||||
$this->assertSame($account, $result->getAccount());
|
||||
$payloads = (new Jwt())->deserialize($result->getJwt())->getPayload();
|
||||
/** @noinspection NullPointerExceptionInspection */
|
||||
$this->assertEqualsWithDelta(time(), $payloads->findClaimByName(Claim\IssuedAt::NAME)->getValue(), 3);
|
||||
/** @noinspection SummerTimeUnsafeTimeManipulationInspection */
|
||||
/** @noinspection NullPointerExceptionInspection */
|
||||
$token = $this->component->createJwtAuthenticationToken($account);
|
||||
$payloads = $token->getPayload();
|
||||
$this->assertEqualsWithDelta(time(), $payloads->findClaimByName('iat')->getValue(), 3);
|
||||
$this->assertEqualsWithDelta(time() + 60 * 60 * 24 * 7, $payloads->findClaimByName('exp')->getValue(), 3);
|
||||
/** @noinspection NullPointerExceptionInspection */
|
||||
$this->assertSame('ely|1', $payloads->findClaimByName('sub')->getValue());
|
||||
/** @noinspection NullPointerExceptionInspection */
|
||||
$this->assertSame('accounts_web_user', $payloads->findClaimByName('ely-scopes')->getValue());
|
||||
$this->assertNull($payloads->findClaimByName('jti'));
|
||||
|
||||
/** @var Account $account */
|
||||
$account = $this->tester->grabFixture('accounts', 'admin');
|
||||
$result = $this->component->createJwtAuthenticationToken($account, true);
|
||||
$this->assertInstanceOf(AuthenticationResult::class, $result);
|
||||
$this->assertInstanceOf(AccountSession::class, $result->getSession());
|
||||
$this->assertSame($account, $result->getAccount());
|
||||
/** @noinspection NullPointerExceptionInspection */
|
||||
$this->assertTrue($result->getSession()->refresh());
|
||||
$payloads = (new Jwt())->deserialize($result->getJwt())->getPayload();
|
||||
/** @noinspection NullPointerExceptionInspection */
|
||||
$this->assertEqualsWithDelta(time(), $payloads->findClaimByName(Claim\IssuedAt::NAME)->getValue(), 3);
|
||||
/** @noinspection NullPointerExceptionInspection */
|
||||
$session = new AccountSession(['id' => 2]);
|
||||
$token = $this->component->createJwtAuthenticationToken($account, $session);
|
||||
$payloads = $token->getPayload();
|
||||
$this->assertEqualsWithDelta(time(), $payloads->findClaimByName('iat')->getValue(), 3);
|
||||
$this->assertEqualsWithDelta(time() + 3600, $payloads->findClaimByName('exp')->getValue(), 3);
|
||||
/** @noinspection NullPointerExceptionInspection */
|
||||
$this->assertSame('ely|1', $payloads->findClaimByName('sub')->getValue());
|
||||
/** @noinspection NullPointerExceptionInspection */
|
||||
$this->assertSame('accounts_web_user', $payloads->findClaimByName('ely-scopes')->getValue());
|
||||
/** @noinspection NullPointerExceptionInspection */
|
||||
$this->assertSame($result->getSession()->id, $payloads->findClaimByName('jti')->getValue());
|
||||
$this->assertSame(2, $payloads->findClaimByName('jti')->getValue());
|
||||
}
|
||||
|
||||
public function testRenewJwtAuthenticationToken() {
|
||||
@ -105,15 +85,19 @@ class ComponentTest extends TestCase {
|
||||
|
||||
public function testParseToken() {
|
||||
$this->mockRequest();
|
||||
$token = $this->callProtected($this->component, 'createToken', new Account(['id' => 1]));
|
||||
$jwt = $this->callProtected($this->component, 'serializeToken', $token);
|
||||
$this->assertInstanceOf(Token::class, $this->component->parseToken($jwt), 'success get RenewResult object');
|
||||
$account = new Account(['id' => 1]);
|
||||
$token = $this->component->createJwtAuthenticationToken($account);
|
||||
$jwt = $this->component->serializeToken($token);
|
||||
$this->component->parseToken($jwt);
|
||||
}
|
||||
|
||||
public function testGetActiveSession() {
|
||||
/** @var Account $account */
|
||||
$account = $this->tester->grabFixture('accounts', 'admin');
|
||||
$result = $this->component->createJwtAuthenticationToken($account, true);
|
||||
$this->component->logout();
|
||||
/** @var AccountSession $session */
|
||||
$session = $this->tester->grabFixture('sessions', 'admin');
|
||||
$token = $this->component->createJwtAuthenticationToken($account, $session);
|
||||
$jwt = $this->component->serializeToken($token);
|
||||
|
||||
/** @var Component|\PHPUnit\Framework\MockObject\MockObject $component */
|
||||
$component = $this->getMockBuilder(Component::class)
|
||||
@ -125,39 +109,42 @@ class ComponentTest extends TestCase {
|
||||
->method('getIsGuest')
|
||||
->willReturn(false);
|
||||
|
||||
$this->mockAuthorizationHeader($result->getJwt());
|
||||
$this->mockAuthorizationHeader($jwt);
|
||||
|
||||
$session = $component->getActiveSession();
|
||||
$this->assertInstanceOf(AccountSession::class, $session);
|
||||
/** @noinspection NullPointerExceptionInspection */
|
||||
$this->assertSame($session->id, $result->getSession()->id);
|
||||
$foundSession = $component->getActiveSession();
|
||||
$this->assertInstanceOf(AccountSession::class, $foundSession);
|
||||
$this->assertSame($session->id, $foundSession->id);
|
||||
}
|
||||
|
||||
public function testTerminateSessions() {
|
||||
/** @var AccountSession $session */
|
||||
$session = AccountSession::findOne($this->tester->grabFixture('sessions', 'admin2')['id']);
|
||||
$session = $this->tester->grabFixture('sessions', 'admin2');
|
||||
|
||||
/** @var Component|\Mockery\MockInterface $component */
|
||||
$component = mock(Component::class . '[getActiveSession]', [$this->getComponentConfig()])->shouldDeferMissing();
|
||||
$component = mock(Component::class . '[getActiveSession]', [$this->getComponentConfig()])->makePartial();
|
||||
$component->shouldReceive('getActiveSession')->times(1)->andReturn($session);
|
||||
|
||||
/** @var Account $account */
|
||||
$account = $this->tester->grabFixture('accounts', 'admin');
|
||||
$component->createJwtAuthenticationToken($account, true);
|
||||
$component->createJwtAuthenticationToken($account);
|
||||
|
||||
// Dry run: no sessions should be removed
|
||||
$component->terminateSessions($account, Component::KEEP_MINECRAFT_SESSIONS | Component::KEEP_SITE_SESSIONS);
|
||||
$this->assertNotEmpty($account->getMinecraftAccessKeys()->all());
|
||||
$this->assertNotEmpty($account->getSessions()->all());
|
||||
|
||||
// All Minecraft sessions should be removed. Web sessions should be kept
|
||||
$component->terminateSessions($account, Component::KEEP_SITE_SESSIONS);
|
||||
$this->assertEmpty($account->getMinecraftAccessKeys()->all());
|
||||
$this->assertNotEmpty($account->getSessions()->all());
|
||||
|
||||
// All sessions should be removed except the current one
|
||||
$component->terminateSessions($account, Component::KEEP_CURRENT_SESSION);
|
||||
$sessions = $account->getSessions()->all();
|
||||
$this->assertCount(1, $sessions);
|
||||
$this->assertSame($session->id, $sessions[0]->id);
|
||||
|
||||
// With no arguments each and every session should be removed
|
||||
$component->terminateSessions($account);
|
||||
$this->assertEmpty($account->getSessions()->all());
|
||||
$this->assertEmpty($account->getMinecraftAccessKeys()->all());
|
||||
@ -165,7 +152,7 @@ class ComponentTest extends TestCase {
|
||||
|
||||
private function mockRequest($userIP = '127.0.0.1') {
|
||||
/** @var Request|\Mockery\MockInterface $request */
|
||||
$request = mock(Request::class . '[getHostInfo,getUserIP]')->shouldDeferMissing();
|
||||
$request = mock(Request::class . '[getHostInfo,getUserIP]')->makePartial();
|
||||
$request->shouldReceive('getHostInfo')->andReturn('http://localhost');
|
||||
$request->shouldReceive('getUserIP')->andReturn($userIP);
|
||||
|
||||
|
@ -1,19 +1,15 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace codeception\api\unit\models;
|
||||
|
||||
use api\components\User\Jwt;
|
||||
use api\components\User\JwtIdentity;
|
||||
use api\tests\unit\TestCase;
|
||||
use common\tests\_support\ProtectedCaller;
|
||||
use common\tests\fixtures\AccountFixture;
|
||||
use Emarref\Jwt\Claim;
|
||||
use Emarref\Jwt\Encryption\Factory as EncryptionFactory;
|
||||
use Emarref\Jwt\HeaderParameter\Custom;
|
||||
use Emarref\Jwt\Token;
|
||||
use Emarref\Jwt\Claim\Expiration as ExpirationClaim;
|
||||
use Yii;
|
||||
|
||||
class JwtIdentityTest extends TestCase {
|
||||
use ProtectedCaller;
|
||||
|
||||
public function _fixtures(): array {
|
||||
return [
|
||||
@ -33,13 +29,7 @@ class JwtIdentityTest extends TestCase {
|
||||
* @expectedExceptionMessage Token expired
|
||||
*/
|
||||
public function testFindIdentityByAccessTokenWithExpiredToken() {
|
||||
$token = new Token();
|
||||
$token->addHeader(new Custom('v', 1));
|
||||
$token->addClaim(new Claim\IssuedAt(1464593193));
|
||||
$token->addClaim(new Claim\Expiration(1464596793));
|
||||
$token->addClaim(new Claim\Subject('ely|' . $this->tester->grabFixture('accounts', 'admin')['id']));
|
||||
$expiredToken = (new Jwt())->serialize($token, EncryptionFactory::create(Yii::$app->user->getAlgorithm())->setPrivateKey(Yii::$app->user->privateKey));
|
||||
|
||||
$expiredToken = $this->generateToken(time() - 3600);
|
||||
JwtIdentity::findIdentityByAccessToken($expiredToken);
|
||||
}
|
||||
|
||||
@ -51,14 +41,17 @@ class JwtIdentityTest extends TestCase {
|
||||
JwtIdentity::findIdentityByAccessToken('');
|
||||
}
|
||||
|
||||
protected function generateToken() {
|
||||
private function generateToken(int $expiresAt = null): string {
|
||||
/** @var \api\components\User\Component $component */
|
||||
$component = Yii::$app->user;
|
||||
/** @var \common\models\Account $account */
|
||||
$account = $this->tester->grabFixture('accounts', 'admin');
|
||||
$token = $this->callProtected($component, 'createToken', $account);
|
||||
$token = $component->createJwtAuthenticationToken($account);
|
||||
if ($expiresAt !== null) {
|
||||
$token->addClaim(new ExpirationClaim($expiresAt));
|
||||
}
|
||||
|
||||
return $this->callProtected($component, 'serializeToken', $token);
|
||||
return $component->serializeToken($token);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user