mirror of
https://github.com/elyby/accounts.git
synced 2025-05-31 14:11:46 +05:30
Replace emarref/jwt with lcobucci/jwt
Refactor all JWT-related components Replace RS256 with ES256 as a preferred JWT algorithm
This commit is contained in:
92
api/tests/unit/components/Tokens/ComponentTest.php
Normal file
92
api/tests/unit/components/Tokens/ComponentTest.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace api\tests\unit\components\Tokens;
|
||||
|
||||
use api\tests\unit\TestCase;
|
||||
use InvalidArgumentException;
|
||||
use Lcobucci\JWT\Parser;
|
||||
use Lcobucci\JWT\Token;
|
||||
use Yii;
|
||||
|
||||
class ComponentTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @var \api\components\Tokens\Component
|
||||
*/
|
||||
private $component;
|
||||
|
||||
protected function _setUp() {
|
||||
parent::_setUp();
|
||||
$this->component = Yii::$app->tokens;
|
||||
}
|
||||
|
||||
public function testCreate() {
|
||||
// Run without any arguments
|
||||
$token = $this->component->create();
|
||||
$this->assertSame('ES256', $token->getHeader('alg'));
|
||||
$this->assertEmpty(array_diff(array_keys($token->getClaims()), ['iat', 'exp']));
|
||||
$this->assertEqualsWithDelta(time(), $token->getClaim('iat'), 1);
|
||||
$this->assertEqualsWithDelta(time() + 3600, $token->getClaim('exp'), 2);
|
||||
|
||||
// Pass custom payloads
|
||||
$token = $this->component->create(['find' => 'me']);
|
||||
$this->assertArrayHasKey('find', $token->getClaims());
|
||||
$this->assertSame('me', $token->getClaim('find'));
|
||||
|
||||
// Pass custom headers
|
||||
$token = $this->component->create([], ['find' => 'me']);
|
||||
$this->assertArrayHasKey('find', $token->getHeaders());
|
||||
$this->assertSame('me', $token->getHeader('find'));
|
||||
}
|
||||
|
||||
public function testParse() {
|
||||
// Valid token signed with HS256
|
||||
$token = $this->component->parse('eyJhbGciOiJIUzI1NiJ9.eyJlbHktc2NvcGVzIjoiYWNjb3VudHNfd2ViX3VzZXIiLCJpYXQiOjE1NjQ1Mjc0NzYsImV4cCI6MTU2NDUzMTA3Niwic3ViIjoiZWx5fDEiLCJqdGkiOjMwNjk1OTJ9.ixapBbhaUCejbcPTnFi5nqk75XKd1_lQJd1ZPgGTLEc');
|
||||
$this->assertValidParsedToken($token, 'HS256');
|
||||
|
||||
// Valid token signed with ES256
|
||||
$token = $this->component->parse('eyJhbGciOiJFUzI1NiJ9.eyJlbHktc2NvcGVzIjoiYWNjb3VudHNfd2ViX3VzZXIiLCJpYXQiOjE1NjQ1Mjc0NzYsImV4cCI6MTU2NDUzMTA3Niwic3ViIjoiZWx5fDEiLCJqdGkiOjMwNjk1OTJ9.M8Kam9bv0BXui3k7Posq_vc0I95Kb_Tw7L2vPdEPlwsHqh1VJHoWtlQc32_SlsotttL7j6RYbffBkRFX2wDGFQ');
|
||||
$this->assertValidParsedToken($token, 'ES256');
|
||||
|
||||
// Valid token signed with ES256, but the signature is invalid
|
||||
$token = $this->component->parse('eyJhbGciOiJFUzI1NiJ9.eyJlbHktc2NvcGVzIjoiYWNjb3VudHNfd2ViX3VzZXIiLCJpYXQiOjE1NjQ1Mjc0NzYsImV4cCI6MTU2NDUzMTA3Niwic3ViIjoiZWx5fDEiLCJqdGkiOjMwNjk1OTJ9.xxx');
|
||||
$this->assertValidParsedToken($token, 'ES256');
|
||||
|
||||
// Completely invalid token
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->component->parse('How do you tame a horse in Minecraft?');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getVerifyCases
|
||||
*/
|
||||
public function testVerify(Token $token, bool $shouldBeValid) {
|
||||
$this->assertSame($shouldBeValid, $this->component->verify($token));
|
||||
}
|
||||
|
||||
public function getVerifyCases() {
|
||||
yield 'HS256' => [
|
||||
(new Parser())->parse('eyJhbGciOiJIUzI1NiJ9.eyJlbHktc2NvcGVzIjoiYWNjb3VudHNfd2ViX3VzZXIiLCJpYXQiOjE1NjQ1Mjc0NzYsImV4cCI6MTU2NDUzMTA3Niwic3ViIjoiZWx5fDEiLCJqdGkiOjMwNjk1OTJ9.ixapBbhaUCejbcPTnFi5nqk75XKd1_lQJd1ZPgGTLEc'),
|
||||
true,
|
||||
];
|
||||
yield 'ES256' => [
|
||||
(new Parser())->parse('eyJhbGciOiJFUzI1NiJ9.eyJlbHktc2NvcGVzIjoiYWNjb3VudHNfd2ViX3VzZXIiLCJpYXQiOjE1NjQ1Mjc0NzYsImV4cCI6MTU2NDUzMTA3Niwic3ViIjoiZWx5fDEiLCJqdGkiOjMwNjk1OTJ9.M8Kam9bv0BXui3k7Posq_vc0I95Kb_Tw7L2vPdEPlwsHqh1VJHoWtlQc32_SlsotttL7j6RYbffBkRFX2wDGFQ'),
|
||||
true,
|
||||
];
|
||||
yield 'ES256 with an invalid signature' => [
|
||||
(new Parser())->parse('eyJhbGciOiJFUzI1NiJ9.eyJlbHktc2NvcGVzIjoiYWNjb3VudHNfd2ViX3VzZXIiLCJpYXQiOjE1NjQ1Mjc0NzYsImV4cCI6MTU2NDUzMTA3Niwic3ViIjoiZWx5fDEiLCJqdGkiOjMwNjk1OTJ9.xxx'),
|
||||
false,
|
||||
];
|
||||
}
|
||||
|
||||
private function assertValidParsedToken(Token $token, string $expectedAlg) {
|
||||
$this->assertSame($expectedAlg, $token->getHeader('alg'));
|
||||
$this->assertSame(1564527476, $token->getClaim('iat'));
|
||||
$this->assertSame(1564531076, $token->getClaim('exp'));
|
||||
$this->assertSame('ely|1', $token->getClaim('sub'));
|
||||
$this->assertSame(3069592, $token->getClaim('jti'));
|
||||
$this->assertSame('accounts_web_user', $token->getClaim('ely-scopes'));
|
||||
}
|
||||
|
||||
}
|
35
api/tests/unit/components/Tokens/TokensFactoryTest.php
Normal file
35
api/tests/unit/components/Tokens/TokensFactoryTest.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace api\tests\unit\components\Tokens;
|
||||
|
||||
use api\components\Tokens\TokensFactory;
|
||||
use api\tests\unit\TestCase;
|
||||
use common\models\Account;
|
||||
use common\models\AccountSession;
|
||||
|
||||
class TokensFactoryTest extends TestCase {
|
||||
|
||||
public function testCreateForAccount() {
|
||||
$account = new Account();
|
||||
$account->id = 1;
|
||||
|
||||
$token = TokensFactory::createForAccount($account);
|
||||
$this->assertEqualsWithDelta(time(), $token->getClaim('iat'), 1);
|
||||
$this->assertEqualsWithDelta(time() + 60 * 60 * 24 * 7, $token->getClaim('exp'), 2);
|
||||
$this->assertSame('ely|1', $token->getClaim('sub'));
|
||||
$this->assertSame('accounts_web_user', $token->getClaim('ely-scopes'));
|
||||
$this->assertArrayNotHasKey('jti', $token->getClaims());
|
||||
|
||||
$session = new AccountSession();
|
||||
$session->id = 2;
|
||||
|
||||
$token = TokensFactory::createForAccount($account, $session);
|
||||
$this->assertEqualsWithDelta(time(), $token->getClaim('iat'), 1);
|
||||
$this->assertEqualsWithDelta(time() + 3600, $token->getClaim('exp'), 2);
|
||||
$this->assertSame('ely|1', $token->getClaim('sub'));
|
||||
$this->assertSame('accounts_web_user', $token->getClaim('ely-scopes'));
|
||||
$this->assertSame(2, $token->getClaim('jti'));
|
||||
}
|
||||
|
||||
}
|
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
namespace codeception\api\unit\components\User;
|
||||
|
||||
use api\components\User\Component;
|
||||
use api\components\User\Identity;
|
||||
use api\components\User\IdentityFactory;
|
||||
use api\tests\unit\TestCase;
|
||||
use common\models\Account;
|
||||
use common\models\AccountSession;
|
||||
@@ -36,29 +36,7 @@ class ComponentTest extends TestCase {
|
||||
];
|
||||
}
|
||||
|
||||
public function testCreateJwtAuthenticationToken() {
|
||||
$this->mockRequest();
|
||||
|
||||
// Token without session
|
||||
$account = new Account(['id' => 1]);
|
||||
$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);
|
||||
$this->assertSame('ely|1', $payloads->findClaimByName('sub')->getValue());
|
||||
$this->assertSame('accounts_web_user', $payloads->findClaimByName('ely-scopes')->getValue());
|
||||
$this->assertNull($payloads->findClaimByName('jti'));
|
||||
|
||||
$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);
|
||||
$this->assertSame('ely|1', $payloads->findClaimByName('sub')->getValue());
|
||||
$this->assertSame('accounts_web_user', $payloads->findClaimByName('ely-scopes')->getValue());
|
||||
$this->assertSame(2, $payloads->findClaimByName('jti')->getValue());
|
||||
}
|
||||
|
||||
// TODO: move test to refresh token form
|
||||
public function testRenewJwtAuthenticationToken() {
|
||||
$userIP = '192.168.0.1';
|
||||
$this->mockRequest($userIP);
|
||||
@@ -83,14 +61,6 @@ class ComponentTest extends TestCase {
|
||||
$this->assertSame($session->id, $payloads->findClaimByName('jti')->getValue(), 'session has not changed');
|
||||
}
|
||||
|
||||
public function testParseToken() {
|
||||
$this->mockRequest();
|
||||
$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');
|
||||
@@ -172,7 +142,7 @@ class ComponentTest extends TestCase {
|
||||
|
||||
private function getComponentConfig() {
|
||||
return [
|
||||
'identityClass' => Identity::class,
|
||||
'identityClass' => IdentityFactory::class,
|
||||
'enableSession' => false,
|
||||
'loginUrl' => null,
|
||||
'secret' => 'secret',
|
||||
|
@@ -1,63 +0,0 @@
|
||||
<?php
|
||||
namespace api\tests\unit\components\User;
|
||||
|
||||
use api\components\User\AuthenticationResult;
|
||||
use api\tests\unit\TestCase;
|
||||
use common\models\Account;
|
||||
use common\models\AccountSession;
|
||||
use Emarref\Jwt\Algorithm\Hs256;
|
||||
use Emarref\Jwt\Claim\Expiration;
|
||||
use Emarref\Jwt\Encryption\Factory as EncryptionFactory;
|
||||
use Emarref\Jwt\Jwt;
|
||||
use Emarref\Jwt\Token;
|
||||
|
||||
class JwtAuthenticationResultTest extends TestCase {
|
||||
|
||||
public function testGetAccount() {
|
||||
$account = new Account();
|
||||
$account->id = 123;
|
||||
$model = new AuthenticationResult($account, '', null);
|
||||
$this->assertSame($account, $model->getAccount());
|
||||
}
|
||||
|
||||
public function testGetJwt() {
|
||||
$model = new AuthenticationResult(new Account(), 'mocked jwt', null);
|
||||
$this->assertSame('mocked jwt', $model->getJwt());
|
||||
}
|
||||
|
||||
public function testGetSession() {
|
||||
$model = new AuthenticationResult(new Account(), '', null);
|
||||
$this->assertNull($model->getSession());
|
||||
|
||||
$session = new AccountSession();
|
||||
$session->id = 321;
|
||||
$model = new AuthenticationResult(new Account(), '', $session);
|
||||
$this->assertSame($session, $model->getSession());
|
||||
}
|
||||
|
||||
public function testGetAsResponse() {
|
||||
$jwtToken = $this->createJwtToken(time() + 3600);
|
||||
$model = new AuthenticationResult(new Account(), $jwtToken, null);
|
||||
$result = $model->getAsResponse();
|
||||
$this->assertSame($jwtToken, $result['access_token']);
|
||||
$this->assertSame(3600, $result['expires_in']);
|
||||
|
||||
/** @noinspection SummerTimeUnsafeTimeManipulationInspection */
|
||||
$jwtToken = $this->createJwtToken(time() + 86400);
|
||||
$session = new AccountSession();
|
||||
$session->refresh_token = 'refresh token';
|
||||
$model = new AuthenticationResult(new Account(), $jwtToken, $session);
|
||||
$result = $model->getAsResponse();
|
||||
$this->assertSame($jwtToken, $result['access_token']);
|
||||
$this->assertSame('refresh token', $result['refresh_token']);
|
||||
$this->assertSame(86400, $result['expires_in']);
|
||||
}
|
||||
|
||||
private function createJwtToken(int $expires): string {
|
||||
$token = new Token();
|
||||
$token->addClaim(new Expiration($expires));
|
||||
|
||||
return (new Jwt())->serialize($token, EncryptionFactory::create(new Hs256('123')));
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user