Объединены сущности для авторизации посредством JWT токенов и токенов, выданных через oAuth2.

Все действия, связанные с аккаунтами, теперь вызываются через url `/api/v1/accounts/<id>/<action>`.
Добавлена вменяемая система разграничения прав на основе RBAC.
Теперь oAuth2 токены генерируются как случайная строка в 40 символов длинной, а не UUID.
Исправлен баг с неправильным временем жизни токена в ответе успешного запроса аутентификации.
Теперь все unit тесты можно успешно прогнать без наличия интернета.
This commit is contained in:
ErickSkrauch
2017-09-19 20:06:16 +03:00
parent 928b3aa7fc
commit dd2c4bc413
173 changed files with 2719 additions and 2748 deletions

View File

@@ -8,13 +8,13 @@ use yii\codeception\BasePage;
*/
class AccountsRoute extends BasePage {
public function current() {
$this->route = ['accounts/current'];
public function get(int $accountId) {
$this->route = "/v1/accounts/{$accountId}";
$this->actor->sendGET($this->getUrl());
}
public function changePassword($currentPassword = null, $newPassword = null, $newRePassword = null) {
$this->route = ['accounts/change-password'];
public function changePassword(int $accountId, $currentPassword = null, $newPassword = null, $newRePassword = null) {
$this->route = "/v1/accounts/{$accountId}/password";
$this->actor->sendPOST($this->getUrl(), [
'password' => $currentPassword,
'newPassword' => $newPassword,
@@ -22,46 +22,56 @@ class AccountsRoute extends BasePage {
]);
}
public function changeUsername($currentPassword = null, $newUsername = null) {
$this->route = ['accounts/change-username'];
public function changeUsername(int $accountId, $currentPassword = null, $newUsername = null) {
$this->route = "/v1/accounts/{$accountId}/username";
$this->actor->sendPOST($this->getUrl(), [
'password' => $currentPassword,
'username' => $newUsername,
]);
}
public function changeEmailInitialize($password = '') {
$this->route = ['accounts/change-email-initialize'];
public function changeEmailInitialize(int $accountId, $password = '') {
$this->route = "/v1/accounts/{$accountId}/email-verification";
$this->actor->sendPOST($this->getUrl(), [
'password' => $password,
]);
}
public function changeEmailSubmitNewEmail($key = null, $email = null) {
$this->route = ['accounts/change-email-submit-new-email'];
public function changeEmailSubmitNewEmail(int $accountId, $key = null, $email = null) {
$this->route = "/v1/accounts/{$accountId}/new-email-verification";
$this->actor->sendPOST($this->getUrl(), [
'key' => $key,
'email' => $email,
]);
}
public function changeEmailConfirmNewEmail($key = null) {
$this->route = ['accounts/change-email-confirm-new-email'];
public function changeEmail(int $accountId, $key = null) {
$this->route = "/v1/accounts/{$accountId}/email";
$this->actor->sendPOST($this->getUrl(), [
'key' => $key,
]);
}
public function changeLang($lang = null) {
$this->route = ['accounts/change-lang'];
public function changeLanguage(int $accountId, $lang = null) {
$this->route = "/v1/accounts/{$accountId}/language";
$this->actor->sendPOST($this->getUrl(), [
'lang' => $lang,
]);
}
public function acceptRules() {
$this->route = ['accounts/accept-rules'];
public function acceptRules(int $accountId) {
$this->route = "/v1/accounts/{$accountId}/rules";
$this->actor->sendPOST($this->getUrl());
}
public function ban(int $accountId) {
$this->route = "/v1/accounts/{$accountId}/ban";
$this->actor->sendPOST($this->getUrl());
}
public function pardon($accountId) {
$this->route = "/v1/accounts/{$accountId}/ban";
$this->actor->sendDELETE($this->getUrl());
}
}

View File

@@ -8,16 +8,6 @@ use yii\codeception\BasePage;
*/
class InternalRoute extends BasePage {
public function ban($accountId) {
$this->route = '/internal/accounts/' . $accountId . '/ban';
$this->actor->sendPOST($this->getUrl());
}
public function pardon($accountId) {
$this->route = '/internal/accounts/' . $accountId . '/ban';
$this->actor->sendDELETE($this->getUrl());
}
public function info(string $param, string $value) {
$this->route = '/internal/accounts/info';
$this->actor->sendGET($this->getUrl(), [$param => $value]);

View File

@@ -8,24 +8,29 @@ use yii\codeception\BasePage;
*/
class TwoFactorAuthRoute extends BasePage {
public $route = '/two-factor-auth';
public function credentials() {
public function credentials(int $accountId) {
$this->setRoute($accountId);
$this->actor->sendGET($this->getUrl());
}
public function enable($totp = null, $password = null) {
public function enable(int $accountId, $totp = null, $password = null) {
$this->setRoute($accountId);
$this->actor->sendPOST($this->getUrl(), [
'totp' => $totp,
'password' => $password,
]);
}
public function disable($totp = null, $password = null) {
public function disable(int $accountId, $totp = null, $password = null) {
$this->setRoute($accountId);
$this->actor->sendDELETE($this->getUrl(), [
'totp' => $totp,
'password' => $password,
]);
}
private function setRoute(int $accountId) {
$this->route = "/v1/accounts/{$accountId}/two-factor-auth";
}
}

View File

@@ -1,8 +1,8 @@
<?php
namespace tests\codeception\api;
use api\models\AccountIdentity;
use Codeception\Actor;
use common\models\Account;
use InvalidArgumentException;
use Yii;
@@ -25,14 +25,16 @@ class FunctionalTester extends Actor {
use _generated\FunctionalTesterActions;
public function amAuthenticated(string $asUsername = 'admin') {
/** @var AccountIdentity $account */
$account = AccountIdentity::findOne(['username' => $asUsername]);
/** @var Account $account */
$account = Account::findOne(['username' => $asUsername]);
if ($account === null) {
throw new InvalidArgumentException("Cannot find account for username \"$asUsername\"");
}
$result = Yii::$app->user->login($account);
$result = Yii::$app->user->createJwtAuthenticationToken($account, false);
$this->amBearerAuthenticated($result->getJwt());
return $account->id;
}
public function notLoggedIn() {

View File

@@ -5,6 +5,7 @@ modules:
- Yii2
- tests\codeception\common\_support\FixtureHelper
- tests\codeception\common\_support\amqp\Helper
- tests\codeception\common\_support\Mockery
- Redis
- Asserts
- REST:

View File

@@ -1,24 +1,24 @@
<?php
namespace tests\codeception\api\functional\internal;
namespace tests\codeception\api\functional;
use common\models\OauthScope as S;
use tests\codeception\api\_pages\InternalRoute;
use common\rbac\Permissions as P;
use tests\codeception\api\_pages\AccountsRoute;
use tests\codeception\api\functional\_steps\OauthSteps;
use tests\codeception\api\FunctionalTester;
class BanCest {
class AccountBanCest {
/**
* @var InternalRoute
* @var AccountsRoute
*/
private $route;
public function _before(FunctionalTester $I) {
$this->route = new InternalRoute($I);
$this->route = new AccountsRoute($I);
}
public function testBanAccount(OauthSteps $I) {
$accessToken = $I->getAccessTokenByClientCredentialsGrant([S::ACCOUNT_BLOCK]);
$accessToken = $I->getAccessTokenByClientCredentialsGrant([P::BLOCK_ACCOUNT]);
$I->amBearerAuthenticated($accessToken);
$this->route->ban(1);
@@ -30,7 +30,7 @@ class BanCest {
}
public function testBanBannedAccount(OauthSteps $I) {
$accessToken = $I->getAccessTokenByClientCredentialsGrant([S::ACCOUNT_BLOCK]);
$accessToken = $I->getAccessTokenByClientCredentialsGrant([P::BLOCK_ACCOUNT]);
$I->amBearerAuthenticated($accessToken);
$this->route->ban(10);

View File

@@ -1,24 +1,24 @@
<?php
namespace tests\codeception\api\functional\internal;
namespace tests\codeception\api\functional;
use common\models\OauthScope as S;
use tests\codeception\api\_pages\InternalRoute;
use common\rbac\Permissions as P;
use tests\codeception\api\_pages\AccountsRoute;
use tests\codeception\api\functional\_steps\OauthSteps;
use tests\codeception\api\FunctionalTester;
class PardonCest {
class AccountPardonCest {
/**
* @var InternalRoute
* @var AccountsRoute
*/
private $route;
public function _before(FunctionalTester $I) {
$this->route = new InternalRoute($I);
$this->route = new AccountsRoute($I);
}
public function testPardonAccount(OauthSteps $I) {
$accessToken = $I->getAccessTokenByClientCredentialsGrant([S::ACCOUNT_BLOCK]);
$accessToken = $I->getAccessTokenByClientCredentialsGrant([P::BLOCK_ACCOUNT]);
$I->amBearerAuthenticated($accessToken);
$this->route->pardon(10);
@@ -30,7 +30,7 @@ class PardonCest {
}
public function testPardonNotBannedAccount(OauthSteps $I) {
$accessToken = $I->getAccessTokenByClientCredentialsGrant([S::ACCOUNT_BLOCK]);
$accessToken = $I->getAccessTokenByClientCredentialsGrant([P::BLOCK_ACCOUNT]);
$I->amBearerAuthenticated($accessToken);
$this->route->pardon(1);

View File

@@ -17,7 +17,7 @@ class AccountsAcceptRulesCest {
public function testCurrent(FunctionalTester $I) {
$I->amAuthenticated('Veleyaba');
$this->route->acceptRules();
$this->route->acceptRules(9);
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([

View File

@@ -19,7 +19,7 @@ class AccountsChangeEmailConfirmNewEmailCest {
$I->wantTo('change my email and get changed value');
$I->amAuthenticated('CrafterGameplays');
$this->route->changeEmailConfirmNewEmail('H28HBDCHHAG2HGHGHS');
$this->route->changeEmail(8, 'H28HBDCHHAG2HGHGHS');
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([

View File

@@ -17,9 +17,9 @@ class AccountsChangeEmailInitializeCest {
public function testChangeEmailInitialize(FunctionalTester $I) {
$I->wantTo('send current email confirmation');
$I->amAuthenticated();
$id = $I->amAuthenticated();
$this->route->changeEmailInitialize('password_0');
$this->route->changeEmailInitialize($id, 'password_0');
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([
@@ -29,9 +29,9 @@ class AccountsChangeEmailInitializeCest {
public function testChangeEmailInitializeFrequencyError(FunctionalTester $I) {
$I->wantTo('see change email request frequency error');
$I->amAuthenticated('ILLIMUNATI');
$id = $I->amAuthenticated('ILLIMUNATI');
$this->route->changeEmailInitialize('password_0');
$this->route->changeEmailInitialize($id, 'password_0');
$I->canSeeResponseContainsJson([
'success' => false,
'errors' => [

View File

@@ -1,9 +1,10 @@
<?php
namespace tests\codeception\api\functional;
use Codeception\Specify;
use tests\codeception\api\_pages\AccountsRoute;
use tests\codeception\api\FunctionalTester;
use tests\codeception\common\helpers\Mock;
use yii\validators\EmailValidator;
class AccountsChangeEmailSubmitNewEmailCest {
@@ -17,10 +18,12 @@ class AccountsChangeEmailSubmitNewEmailCest {
}
public function testSubmitNewEmail(FunctionalTester $I) {
$I->wantTo('submit new email');
$I->amAuthenticated('ILLIMUNATI');
Mock::func(EmailValidator::class, 'checkdnsrr')->andReturnTrue();
$this->route->changeEmailSubmitNewEmail('H27HBDCHHAG2HGHGHS', 'my-new-email@ely.by');
$I->wantTo('submit new email');
$id = $I->amAuthenticated('ILLIMUNATI');
$this->route->changeEmailSubmitNewEmail($id, 'H27HBDCHHAG2HGHGHS', 'my-new-email@ely.by');
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([

View File

@@ -1,7 +1,6 @@
<?php
namespace tests\codeception\api\functional;
use Codeception\Specify;
use tests\codeception\api\_pages\AccountsRoute;
use tests\codeception\api\FunctionalTester;
@@ -18,9 +17,9 @@ class AccountsChangeLangCest {
public function testSubmitNewEmail(FunctionalTester $I) {
$I->wantTo('change my account language');
$I->amAuthenticated();
$id = $I->amAuthenticated();
$this->route->changeLang('ru');
$this->route->changeLanguage($id, 'ru');
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([

View File

@@ -1,7 +1,6 @@
<?php
namespace tests\codeception\api\functional;
use Codeception\Specify;
use common\models\Account;
use tests\codeception\api\_pages\AccountsRoute;
use tests\codeception\api\_pages\AuthenticationRoute;
@@ -27,9 +26,9 @@ class AccountsChangePasswordCest {
public function testChangePassword(FunctionalTester $I) {
$I->wantTo('change my password');
$I->amAuthenticated();
$id = $I->amAuthenticated();
$this->route->changePassword('password_0', 'new-password', 'new-password');
$this->route->changePassword($id, 'password_0', 'new-password', 'new-password');
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([

View File

@@ -1,7 +1,6 @@
<?php
namespace tests\codeception\api\functional;
use Codeception\Specify;
use common\models\Account;
use tests\codeception\api\_pages\AccountsRoute;
use tests\codeception\api\FunctionalTester;
@@ -26,9 +25,9 @@ class AccountsChangeUsernameCest {
public function testChangeUsername(FunctionalTester $I) {
$I->wantTo('change my nickname');
$I->amAuthenticated();
$id = $I->amAuthenticated();
$this->route->changeUsername('password_0', 'bruce_wayne');
$this->route->changeUsername($id, 'password_0', 'bruce_wayne');
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([
@@ -38,9 +37,9 @@ class AccountsChangeUsernameCest {
public function testChangeUsernameNotAvailable(FunctionalTester $I) {
$I->wantTo('see, that nickname "in use" is not available');
$I->amAuthenticated();
$id = $I->amAuthenticated();
$this->route->changeUsername('password_0', 'Jon');
$this->route->changeUsername($id, 'password_0', 'Jon');
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([

View File

@@ -1,55 +0,0 @@
<?php
namespace tests\codeception\api\functional;
use tests\codeception\api\_pages\AccountsRoute;
use tests\codeception\api\FunctionalTester;
class AccountsCurrentCest {
/**
* @var AccountsRoute
*/
private $route;
public function _before(FunctionalTester $I) {
$this->route = new AccountsRoute($I);
}
public function testCurrent(FunctionalTester $I) {
$I->amAuthenticated();
$this->route->current();
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([
'id' => 1,
'username' => 'Admin',
'email' => 'admin@ely.by',
'lang' => 'en',
'isActive' => true,
'hasMojangUsernameCollision' => false,
'shouldAcceptRules' => false,
'isOtpEnabled' => false,
]);
$I->canSeeResponseJsonMatchesJsonPath('$.passwordChangedAt');
}
public function testExpiredCurrent(FunctionalTester $I) {
// Устанавливаем заведомо истёкший токен
$I->amBearerAuthenticated(
'eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJodHRwOlwvXC9sb2NhbGhvc3QiLCJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3QiLCJpYXQiO' .
'jE0NjQ2Mjc1NDUsImV4cCI6MTQ2NDYzMTE0NSwianRpIjoxfQ.9c1mm0BK-cuW1qh15F12s2Fh37IN43YeeZeU4DFtlrE'
);
$this->route->current();
$I->canSeeResponseCodeIs(401);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([
'name' => 'Unauthorized',
'message' => 'Token expired',
'code' => 0,
'status' => 401,
]);
}
}

View File

@@ -0,0 +1,93 @@
<?php
namespace tests\codeception\api\functional;
use tests\codeception\api\_pages\AccountsRoute;
use tests\codeception\api\FunctionalTester;
class AccountsGetCest {
/**
* @var AccountsRoute
*/
private $route;
public function _before(FunctionalTester $I) {
$this->route = new AccountsRoute($I);
}
public function testGetInfo(FunctionalTester $I) {
$accountId = $I->amAuthenticated();
$this->route->get($accountId);
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([
'id' => 1,
'uuid' => 'df936908-b2e1-544d-96f8-2977ec213022',
'username' => 'Admin',
'isOtpEnabled' => false,
'email' => 'admin@ely.by',
'lang' => 'en',
'isActive' => true,
'hasMojangUsernameCollision' => false,
'shouldAcceptRules' => false,
'elyProfileLink' => 'http://ely.by/u1',
]);
$I->canSeeResponseJsonMatchesJsonPath('$.passwordChangedAt');
}
public function testGetWithNotAcceptedLatestRules(FunctionalTester $I) {
$accountId = $I->amAuthenticated('Veleyaba');
$this->route->get($accountId);
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([
'id' => 9,
'uuid' => '410462d3-8e71-47cc-bac6-64f77f88cf80',
'username' => 'Veleyaba',
'email' => 'veleyaba@gmail.com',
'isOtpEnabled' => false,
'lang' => 'en',
'isActive' => true,
'hasMojangUsernameCollision' => false,
'shouldAcceptRules' => true,
'elyProfileLink' => 'http://ely.by/u9',
]);
$I->canSeeResponseJsonMatchesJsonPath('$.passwordChangedAt');
}
public function testGetInfoWithExpiredToken(FunctionalTester $I) {
// Устанавливаем заведомо истёкший токен
$I->amBearerAuthenticated(
// TODO: обновить токен
'eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJodHRwOlwvXC9sb2NhbGhvc3QiLCJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3QiLCJpYXQiO' .
'jE0NjQ2Mjc1NDUsImV4cCI6MTQ2NDYzMTE0NSwianRpIjoxfQ.9c1mm0BK-cuW1qh15F12s2Fh37IN43YeeZeU4DFtlrE'
);
$this->route->get(1);
$I->canSeeResponseCodeIs(401);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([
'name' => 'Unauthorized',
'message' => 'Token expired',
'code' => 0,
'status' => 401,
]);
}
public function testGetInfoNotCurrentAccount(FunctionalTester $I) {
$I->amAuthenticated();
$this->route->get(10);
$I->canSeeResponseCodeIs(403);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([
'name' => 'Forbidden',
'message' => 'You are not allowed to perform this action.',
'code' => 0,
'status' => 403,
]);
}
}

View File

@@ -1,7 +1,6 @@
<?php
namespace codeception\api\functional;
use common\models\OauthScope as S;
use tests\codeception\api\_pages\IdentityInfoRoute;
use tests\codeception\api\functional\_steps\OauthSteps;
use tests\codeception\api\FunctionalTester;
@@ -30,7 +29,7 @@ class IdentityInfoCest {
}
public function testGetInfo(OauthSteps $I) {
$accessToken = $I->getAccessToken([S::ACCOUNT_INFO]);
$accessToken = $I->getAccessToken(['account_info']);
$I->amBearerAuthenticated($accessToken);
$this->route->info();
$I->canSeeResponseCodeIs(200);
@@ -47,7 +46,7 @@ class IdentityInfoCest {
}
public function testGetInfoWithEmail(OauthSteps $I) {
$accessToken = $I->getAccessToken([S::ACCOUNT_INFO, S::ACCOUNT_EMAIL]);
$accessToken = $I->getAccessToken(['account_info', 'account_email']);
$I->amBearerAuthenticated($accessToken);
$this->route->info();
$I->canSeeResponseCodeIs(200);

View File

@@ -21,7 +21,7 @@ class RecoverPasswordCest {
$jwt = $I->grabDataFromResponseByJsonPath('$.access_token')[0];
$I->amBearerAuthenticated($jwt);
$accountRoute = new AccountsRoute($I);
$accountRoute->current();
$accountRoute->get(5);
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();
$I->notLoggedIn();

View File

@@ -1,7 +1,6 @@
<?php
namespace tests\codeception\api\functional;
use Codeception\Specify;
use tests\codeception\api\_pages\SignupRoute;
use tests\codeception\api\FunctionalTester;

View File

@@ -16,8 +16,8 @@ class TwoFactorAuthCredentialsCest {
}
public function testGetCredentials(FunctionalTester $I) {
$I->amAuthenticated();
$this->route->credentials();
$accountId = $I->amAuthenticated();
$this->route->credentials($accountId);
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();
$I->canSeeResponseJsonMatchesJsonPath('$.secret');

View File

@@ -17,9 +17,9 @@ class TwoFactorAuthDisableCest {
}
public function testFails(FunctionalTester $I) {
$I->amAuthenticated('AccountWithEnabledOtp');
$accountId = $I->amAuthenticated('AccountWithEnabledOtp');
$this->route->disable();
$this->route->disable($accountId);
$I->canSeeResponseContainsJson([
'success' => false,
'errors' => [
@@ -28,7 +28,7 @@ class TwoFactorAuthDisableCest {
],
]);
$this->route->disable('123456', 'invalid_password');
$this->route->disable($accountId, '123456', 'invalid_password');
$I->canSeeResponseContainsJson([
'success' => false,
'errors' => [
@@ -37,8 +37,8 @@ class TwoFactorAuthDisableCest {
],
]);
$I->amAuthenticated('AccountWithOtpSecret');
$this->route->disable('123456', 'invalid_password');
$accountId = $I->amAuthenticated('AccountWithOtpSecret');
$this->route->disable($accountId, '123456', 'invalid_password');
$I->canSeeResponseContainsJson([
'success' => false,
'errors' => [
@@ -48,9 +48,9 @@ class TwoFactorAuthDisableCest {
}
public function testSuccessEnable(FunctionalTester $I) {
$I->amAuthenticated('AccountWithEnabledOtp');
$accountId = $I->amAuthenticated('AccountWithEnabledOtp');
$totp = TOTP::create('BBBB');
$this->route->disable($totp->now(), 'password_0');
$this->route->disable($accountId, $totp->now(), 'password_0');
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([

View File

@@ -17,9 +17,9 @@ class TwoFactorAuthEnableCest {
}
public function testFails(FunctionalTester $I) {
$I->amAuthenticated('AccountWithOtpSecret');
$accountId = $I->amAuthenticated('AccountWithOtpSecret');
$this->route->enable();
$this->route->enable($accountId);
$I->canSeeResponseContainsJson([
'success' => false,
'errors' => [
@@ -28,7 +28,7 @@ class TwoFactorAuthEnableCest {
],
]);
$this->route->enable('123456', 'invalid_password');
$this->route->enable($accountId, '123456', 'invalid_password');
$I->canSeeResponseContainsJson([
'success' => false,
'errors' => [
@@ -37,8 +37,8 @@ class TwoFactorAuthEnableCest {
],
]);
$I->amAuthenticated('AccountWithEnabledOtp');
$this->route->enable('123456', 'invalid_password');
$accountId = $I->amAuthenticated('AccountWithEnabledOtp');
$this->route->enable($accountId, '123456', 'invalid_password');
$I->canSeeResponseContainsJson([
'success' => false,
'errors' => [
@@ -48,9 +48,9 @@ class TwoFactorAuthEnableCest {
}
public function testSuccessEnable(FunctionalTester $I) {
$I->amAuthenticated('AccountWithOtpSecret');
$accountId = $I->amAuthenticated('AccountWithOtpSecret');
$totp = TOTP::create('AAAA');
$this->route->enable($totp->now(), 'password_0');
$this->route->enable($accountId, $totp->now(), 'password_0');
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([

View File

@@ -1,14 +1,13 @@
<?php
namespace tests\codeception\api\functional\_steps;
use common\models\OauthScope as S;
use api\components\OAuth2\Storage\ScopeStorage as S;
use tests\codeception\api\_pages\OauthRoute;
use tests\codeception\api\FunctionalTester;
class OauthSteps extends FunctionalTester {
public function getAuthCode(array $permissions = []) {
// TODO: по идее можно напрямую сделать запись в базу, что ускорит процесс тестирования
$this->amAuthenticated();
$route = new OauthRoute($this);
$route->complete([
@@ -32,7 +31,6 @@ class OauthSteps extends FunctionalTester {
}
public function getRefreshToken(array $permissions = []) {
// TODO: по идее можно напрямую сделать запись в базу, что ускорит процесс тестирования
$authCode = $this->getAuthCode(array_merge([S::OFFLINE_ACCESS], $permissions));
$response = $this->issueToken($authCode);

View File

@@ -1,15 +1,16 @@
<?php
namespace tests\codeception\api\functional\_steps;
use common\models\OauthScope as S;
use common\rbac\Permissions as P;
use Faker\Provider\Uuid;
use tests\codeception\api\_pages\SessionServerRoute;
use tests\codeception\api\FunctionalTester;
class SessionServerSteps extends \tests\codeception\api\FunctionalTester {
class SessionServerSteps extends FunctionalTester {
public function amJoined($byLegacy = false) {
$oauthSteps = new OauthSteps($this->scenario);
$accessToken = $oauthSteps->getAccessToken([S::MINECRAFT_SERVER_SESSION]);
$accessToken = $oauthSteps->getAccessToken([P::MINECRAFT_SERVER_SESSION]);
$route = new SessionServerRoute($this);
$serverId = Uuid::uuid();
$username = 'Admin';

View File

@@ -1,7 +1,6 @@
<?php
namespace tests\codeception\api\functional\internal;
use common\models\OauthScope as S;
use tests\codeception\api\_pages\InternalRoute;
use tests\codeception\api\functional\_steps\OauthSteps;
use tests\codeception\api\FunctionalTester;
@@ -18,7 +17,7 @@ class InfoCest {
}
public function testGetInfoById(OauthSteps $I) {
$accessToken = $I->getAccessTokenByClientCredentialsGrant([S::INTERNAL_ACCOUNT_INFO]);
$accessToken = $I->getAccessTokenByClientCredentialsGrant(['internal_account_info']);
$I->amBearerAuthenticated($accessToken);
$this->route->info('id', 1);
@@ -26,7 +25,7 @@ class InfoCest {
}
public function testGetInfoByUuid(OauthSteps $I) {
$accessToken = $I->getAccessTokenByClientCredentialsGrant([S::INTERNAL_ACCOUNT_INFO]);
$accessToken = $I->getAccessTokenByClientCredentialsGrant(['internal_account_info']);
$I->amBearerAuthenticated($accessToken);
$this->route->info('uuid', 'df936908-b2e1-544d-96f8-2977ec213022');
@@ -34,7 +33,7 @@ class InfoCest {
}
public function testGetInfoByUsername(OauthSteps $I) {
$accessToken = $I->getAccessTokenByClientCredentialsGrant([S::INTERNAL_ACCOUNT_INFO]);
$accessToken = $I->getAccessTokenByClientCredentialsGrant(['internal_account_info']);
$I->amBearerAuthenticated($accessToken);
$this->route->info('username', 'admin');
@@ -42,7 +41,7 @@ class InfoCest {
}
public function testInvalidParams(OauthSteps $I) {
$accessToken = $I->getAccessTokenByClientCredentialsGrant([S::INTERNAL_ACCOUNT_INFO]);
$accessToken = $I->getAccessTokenByClientCredentialsGrant(['internal_account_info']);
$I->amBearerAuthenticated($accessToken);
$this->route->info('', '');
@@ -50,7 +49,7 @@ class InfoCest {
}
public function testAccountNotFound(OauthSteps $I) {
$accessToken = $I->getAccessTokenByClientCredentialsGrant([S::INTERNAL_ACCOUNT_INFO]);
$accessToken = $I->getAccessTokenByClientCredentialsGrant(['internal_account_info']);
$I->amBearerAuthenticated($accessToken);
$this->route->info('username', 'this-user-not-exists');

View File

@@ -1,7 +1,6 @@
<?php
namespace tests\codeception\api\oauth;
use common\models\OauthScope as S;
use tests\codeception\api\_pages\OauthRoute;
use tests\codeception\api\functional\_steps\OauthSteps;
use tests\codeception\api\FunctionalTester;
@@ -72,7 +71,7 @@ class AccessTokenCest {
}
public function testIssueTokenWithRefreshToken(OauthSteps $I) {
$authCode = $I->getAuthCode([S::OFFLINE_ACCESS]);
$authCode = $I->getAuthCode(['offline_access']);
$this->route->issueToken($this->buildParams(
$authCode,
'ely',

View File

@@ -1,7 +1,7 @@
<?php
namespace tests\codeception\api\oauth;
use common\models\OauthScope as S;
use common\rbac\Permissions as P;
use tests\codeception\api\_pages\OauthRoute;
use tests\codeception\api\FunctionalTester;
@@ -24,7 +24,7 @@ class AuthCodeCest {
'ely',
'http://ely.by',
'code',
[S::MINECRAFT_SERVER_SESSION],
[P::MINECRAFT_SERVER_SESSION],
'test-state'
));
$I->canSeeResponseCodeIs(200);
@@ -101,7 +101,7 @@ class AuthCodeCest {
'ely',
'http://ely.by',
'code',
[S::MINECRAFT_SERVER_SESSION]
[P::MINECRAFT_SERVER_SESSION]
));
$I->canSeeResponseCodeIs(401);
$I->canSeeResponseContainsJson([
@@ -119,7 +119,7 @@ class AuthCodeCest {
'ely',
'http://ely.by',
'code',
[S::MINECRAFT_SERVER_SESSION]
[P::MINECRAFT_SERVER_SESSION]
), ['accept' => true]);
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseContainsJson([
@@ -146,7 +146,7 @@ class AuthCodeCest {
'ely',
'http://ely.by',
'code',
[S::MINECRAFT_SERVER_SESSION]
[P::MINECRAFT_SERVER_SESSION]
));
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseContainsJson([
@@ -162,13 +162,13 @@ class AuthCodeCest {
'ely',
'http://ely.by',
'code',
[S::MINECRAFT_SERVER_SESSION]
[P::MINECRAFT_SERVER_SESSION]
), ['accept' => true]);
$this->route->complete($this->buildQueryParams(
'ely',
'http://ely.by',
'code',
[S::MINECRAFT_SERVER_SESSION, S::ACCOUNT_INFO]
[P::MINECRAFT_SERVER_SESSION, 'account_info']
));
$I->canSeeResponseCodeIs(401);
$I->canSeeResponseContainsJson([
@@ -186,7 +186,7 @@ class AuthCodeCest {
'ely',
'http://ely.by',
'code',
[S::MINECRAFT_SERVER_SESSION]
[P::MINECRAFT_SERVER_SESSION]
), ['accept' => false]);
$I->canSeeResponseCodeIs(401);
$I->canSeeResponseContainsJson([
@@ -270,7 +270,7 @@ class AuthCodeCest {
$I->wantTo('check behavior on some invalid scopes');
$this->route->$action($this->buildQueryParams('ely', 'http://ely.by', 'code', [
S::MINECRAFT_SERVER_SESSION,
P::MINECRAFT_SERVER_SESSION,
'some_wrong_scope',
]));
$I->canSeeResponseCodeIs(400);
@@ -285,15 +285,15 @@ class AuthCodeCest {
$I->wantTo('check behavior on request internal scope');
$this->route->$action($this->buildQueryParams('ely', 'http://ely.by', 'code', [
S::MINECRAFT_SERVER_SESSION,
S::ACCOUNT_BLOCK,
P::MINECRAFT_SERVER_SESSION,
P::BLOCK_ACCOUNT,
]));
$I->canSeeResponseCodeIs(400);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([
'success' => false,
'error' => 'invalid_scope',
'parameter' => S::ACCOUNT_BLOCK,
'parameter' => P::BLOCK_ACCOUNT,
'statusCode' => 400,
]);
$I->canSeeResponseJsonMatchesJsonPath('$.redirectUri');

View File

@@ -1,7 +1,6 @@
<?php
namespace tests\codeception\api\oauth;
use common\models\OauthScope as S;
use tests\codeception\api\_pages\OauthRoute;
use tests\codeception\api\functional\_steps\OauthSteps;
use tests\codeception\api\FunctionalTester;
@@ -79,7 +78,7 @@ class ClientCredentialsCest {
$this->route->issueToken($this->buildParams(
'ely',
'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM',
[S::ACCOUNT_BLOCK]
['account_block']
));
$I->canSeeResponseCodeIs(400);
$I->canSeeResponseIsJson();
@@ -90,7 +89,7 @@ class ClientCredentialsCest {
$this->route->issueToken($this->buildParams(
'trusted-client',
'tXBbyvMcyaOgHMOAXBpN2EC7uFoJAaL9',
[S::ACCOUNT_BLOCK]
['account_block']
));
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();

View File

@@ -1,7 +1,8 @@
<?php
namespace tests\codeception\api\oauth;
use common\models\OauthScope as S;
use api\components\OAuth2\Storage\ScopeStorage as S;
use common\rbac\Permissions as P;
use tests\codeception\api\_pages\OauthRoute;
use tests\codeception\api\functional\_steps\OauthSteps;
use tests\codeception\api\FunctionalTester;
@@ -40,23 +41,23 @@ class RefreshTokenCest {
}
public function testRefreshTokenWithSameScopes(OauthSteps $I) {
$refreshToken = $I->getRefreshToken([S::MINECRAFT_SERVER_SESSION]);
$refreshToken = $I->getRefreshToken([P::MINECRAFT_SERVER_SESSION]);
$this->route->issueToken($this->buildParams(
$refreshToken,
'ely',
'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM',
[S::MINECRAFT_SERVER_SESSION, S::OFFLINE_ACCESS]
[P::MINECRAFT_SERVER_SESSION, S::OFFLINE_ACCESS]
));
$this->canSeeRefreshTokenSuccess($I);
}
public function testRefreshTokenTwice(OauthSteps $I) {
$refreshToken = $I->getRefreshToken([S::MINECRAFT_SERVER_SESSION]);
$refreshToken = $I->getRefreshToken([P::MINECRAFT_SERVER_SESSION]);
$this->route->issueToken($this->buildParams(
$refreshToken,
'ely',
'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM',
[S::MINECRAFT_SERVER_SESSION, S::OFFLINE_ACCESS]
[P::MINECRAFT_SERVER_SESSION, S::OFFLINE_ACCESS]
));
$this->canSeeRefreshTokenSuccess($I);
@@ -64,18 +65,18 @@ class RefreshTokenCest {
$refreshToken,
'ely',
'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM',
[S::MINECRAFT_SERVER_SESSION, S::OFFLINE_ACCESS]
[P::MINECRAFT_SERVER_SESSION, S::OFFLINE_ACCESS]
));
$this->canSeeRefreshTokenSuccess($I);
}
public function testRefreshTokenWithNewScopes(OauthSteps $I) {
$refreshToken = $I->getRefreshToken([S::MINECRAFT_SERVER_SESSION]);
$refreshToken = $I->getRefreshToken([P::MINECRAFT_SERVER_SESSION]);
$this->route->issueToken($this->buildParams(
$refreshToken,
'ely',
'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM',
[S::MINECRAFT_SERVER_SESSION, S::OFFLINE_ACCESS, S::ACCOUNT_EMAIL]
[P::MINECRAFT_SERVER_SESSION, S::OFFLINE_ACCESS, 'account_email']
));
$I->canSeeResponseCodeIs(400);
$I->canSeeResponseIsJson();

View File

@@ -1,7 +1,7 @@
<?php
namespace tests\codeception\api\functional\sessionserver;
use common\models\OauthScope as S;
use common\rbac\Permissions as P;
use Faker\Provider\Uuid;
use tests\codeception\api\_pages\SessionServerRoute;
use tests\codeception\api\functional\_steps\AuthserverSteps;
@@ -43,7 +43,7 @@ class JoinCest {
public function joinByOauth2Token(OauthSteps $I) {
$I->wantTo('join to server, using modern oAuth2 generated token');
$accessToken = $I->getAccessToken([S::MINECRAFT_SERVER_SESSION]);
$accessToken = $I->getAccessToken([P::MINECRAFT_SERVER_SESSION]);
$this->route->join([
'accessToken' => $accessToken,
'selectedProfile' => 'df936908-b2e1-544d-96f8-2977ec213022',
@@ -54,7 +54,7 @@ class JoinCest {
public function joinByModernOauth2TokenWithoutPermission(OauthSteps $I) {
$I->wantTo('join to server, using moder oAuth2 generated token, but without minecraft auth permission');
$accessToken = $I->getAccessToken([S::ACCOUNT_INFO, S::ACCOUNT_EMAIL]);
$accessToken = $I->getAccessToken(['account_info', 'account_email']);
$this->route->join([
'accessToken' => $accessToken,
'selectedProfile' => 'df936908-b2e1-544d-96f8-2977ec213022',

View File

@@ -1,7 +1,7 @@
<?php
namespace tests\codeception\api\functional\sessionserver;
use common\models\OauthScope as S;
use common\rbac\Permissions as P;
use Faker\Provider\Uuid;
use tests\codeception\api\_pages\SessionServerRoute;
use tests\codeception\api\functional\_steps\AuthserverSteps;
@@ -43,7 +43,7 @@ class JoinLegacyCest {
public function joinByOauth2Token(OauthSteps $I) {
$I->wantTo('join to server using modern oAuth2 generated token with new launcher session format');
$accessToken = $I->getAccessToken([S::MINECRAFT_SERVER_SESSION]);
$accessToken = $I->getAccessToken([P::MINECRAFT_SERVER_SESSION]);
$this->route->joinLegacy([
'sessionId' => 'token:' . $accessToken . ':' . 'df936908-b2e1-544d-96f8-2977ec213022',
'user' => 'Admin',
@@ -74,7 +74,7 @@ class JoinLegacyCest {
public function joinWithAccessTokenWithoutMinecraftPermission(OauthSteps $I) {
$I->wantTo('join to some server with wrong accessToken');
$accessToken = $I->getAccessToken([S::ACCOUNT_INFO]);
$accessToken = $I->getAccessToken(['account_info']);
$this->route->joinLegacy([
'sessionId' => 'token:' . $accessToken . ':' . 'df936908-b2e1-544d-96f8-2977ec213022',
'user' => 'Admin',

View File

@@ -2,14 +2,12 @@
namespace codeception\api\unit\components\User;
use api\components\User\Component;
use api\components\User\LoginResult;
use api\components\User\RenewResult;
use api\models\AccountIdentity;
use Codeception\Specify;
use api\components\User\Identity;
use api\components\User\AuthenticationResult;
use common\models\Account;
use common\models\AccountSession;
use Emarref\Jwt\Algorithm\AlgorithmInterface;
use Emarref\Jwt\Claim\ClaimInterface;
use Emarref\Jwt\Claim\Expiration;
use Emarref\Jwt\Claim;
use Emarref\Jwt\Jwt;
use Emarref\Jwt\Token;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\_support\ProtectedCaller;
@@ -20,7 +18,6 @@ use Yii;
use yii\web\Request;
class ComponentTest extends TestCase {
use Specify;
use ProtectedCaller;
/**
@@ -30,7 +27,7 @@ class ComponentTest extends TestCase {
public function _before() {
parent::_before();
$this->component = new Component($this->getComponentArguments());
$this->component = new Component($this->getComponentConfig());
}
public function _fixtures() {
@@ -41,183 +38,140 @@ class ComponentTest extends TestCase {
];
}
public function testLogin() {
public function testCreateJwtAuthenticationToken() {
$this->mockRequest();
$this->specify('success get LoginResult object without session value', function() {
$account = new AccountIdentity(['id' => 1]);
$result = $this->component->login($account, false);
expect($result)->isInstanceOf(LoginResult::class);
expect($result->getSession())->null();
expect($result->getIdentity())->equals($account);
$jwt = $result->getJwt();
expect(is_string($jwt))->true();
$token = $this->component->parseToken($jwt);
$claim = $token->getPayload()->findClaimByName(Expiration::NAME);
// Токен выписывается на 7 дней, но мы проверим хотя бы на 2 суток
expect($claim->getValue())->greaterThan(time() + 60 * 60 * 24 * 2);
});
$this->specify('success get LoginResult object with session value if rememberMe is true', function() {
/** @var AccountIdentity $account */
$account = AccountIdentity::findOne($this->tester->grabFixture('accounts', 'admin')['id']);
$result = $this->component->login($account, true);
expect($result)->isInstanceOf(LoginResult::class);
expect($result->getSession())->isInstanceOf(AccountSession::class);
expect($result->getIdentity())->equals($account);
expect($result->getSession()->refresh())->true();
$jwt = $result->getJwt();
expect(is_string($jwt))->true();
$token = $this->component->parseToken($jwt);
$claim = $token->getPayload()->findClaimByName(Expiration::NAME);
// Токен выписывается на 1 час, т.к. в дальнейшем он будет рефрешиться
expect($claim->getValue())->lessOrEquals(time() + 3600);
});
$account = new Account(['id' => 1]);
$result = $this->component->createJwtAuthenticationToken($account, false);
$this->assertInstanceOf(AuthenticationResult::class, $result);
$this->assertNull($result->getSession());
$this->assertEquals($account, $result->getAccount());
$payloads = (new Jwt())->deserialize($result->getJwt())->getPayload();
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals(time(), $payloads->findClaimByName(Claim\IssuedAt::NAME)->getValue(), '', 3);
/** @noinspection SummerTimeUnsafeTimeManipulationInspection */
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals(time() + 60 * 60 * 24 * 7, $payloads->findClaimByName('exp')->getValue(), '', 3);
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals('ely|1', $payloads->findClaimByName('sub')->getValue());
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals('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->assertEquals($account, $result->getAccount());
/** @noinspection NullPointerExceptionInspection */
$this->assertTrue($result->getSession()->refresh());
$payloads = (new Jwt())->deserialize($result->getJwt())->getPayload();
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals(time(), $payloads->findClaimByName(Claim\IssuedAt::NAME)->getValue(), '', 3);
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals(time() + 3600, $payloads->findClaimByName('exp')->getValue(), '', 3);
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals('ely|1', $payloads->findClaimByName('sub')->getValue());
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals('accounts_web_user', $payloads->findClaimByName('ely-scopes')->getValue());
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals($result->getSession()->id, $payloads->findClaimByName('jti')->getValue());
}
public function testRenew() {
$this->specify('success get RenewResult object', function() {
$userIP = '192.168.0.1';
$this->mockRequest($userIP);
/** @var AccountSession $session */
$session = AccountSession::findOne($this->tester->grabFixture('sessions', 'admin')['id']);
$callTime = time();
$result = $this->component->renew($session);
expect($result)->isInstanceOf(RenewResult::class);
expect(is_string($result->getJwt()))->true();
expect($result->getIdentity()->getId())->equals($session->account_id);
$session->refresh();
expect($session->last_refreshed_at)->greaterOrEquals($callTime);
expect($session->getReadableIp())->equals($userIP);
});
public function testRenewJwtAuthenticationToken() {
$userIP = '192.168.0.1';
$this->mockRequest($userIP);
/** @var AccountSession $session */
$session = $this->tester->grabFixture('sessions', 'admin');
$result = $this->component->renewJwtAuthenticationToken($session);
$this->assertInstanceOf(AuthenticationResult::class, $result);
$this->assertEquals($session, $result->getSession());
$this->assertEquals($session->account_id, $result->getAccount()->id);
$session->refresh(); // reload data from db
$this->assertEquals(time(), $session->last_refreshed_at, '', 3);
$this->assertEquals($userIP, $session->getReadableIp());
$payloads = (new Jwt())->deserialize($result->getJwt())->getPayload();
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals(time(), $payloads->findClaimByName(Claim\IssuedAt::NAME)->getValue(), '', 3);
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals(time() + 3600, $payloads->findClaimByName('exp')->getValue(), '', 3);
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals('ely|1', $payloads->findClaimByName('sub')->getValue());
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals('accounts_web_user', $payloads->findClaimByName('ely-scopes')->getValue());
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals($session->id, $payloads->findClaimByName('jti')->getValue(), 'session has not changed');
}
public function testParseToken() {
$this->mockRequest();
$this->specify('success get RenewResult object', function() {
$identity = new AccountIdentity(['id' => 1]);
$token = $this->callProtected($this->component, 'createToken', $identity);
$jwt = $this->callProtected($this->component, 'serializeToken', $token);
expect($this->component->parseToken($jwt))->isInstanceOf(Token::class);
});
$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');
}
public function testGetActiveSession() {
$this->specify('get used account session', function() {
/** @var AccountIdentity $identity */
$identity = AccountIdentity::findOne($this->tester->grabFixture('accounts', 'admin')['id']);
$result = $this->component->login($identity, true);
$this->component->logout();
$account = $this->tester->grabFixture('accounts', 'admin');
$result = $this->component->createJwtAuthenticationToken($account, true);
$this->component->logout();
/** @var Component|\PHPUnit_Framework_MockObject_MockObject $component */
$component = $this->getMockBuilder(Component::class)
->setMethods(['getIsGuest'])
->setConstructorArgs([$this->getComponentArguments()])
->getMock();
/** @var Component|\PHPUnit_Framework_MockObject_MockObject $component */
$component = $this->getMockBuilder(Component::class)
->setMethods(['getIsGuest'])
->setConstructorArgs([$this->getComponentConfig()])
->getMock();
$component
->expects($this->any())
->method('getIsGuest')
->willReturn(false);
$component
->expects($this->any())
->method('getIsGuest')
->willReturn(false);
$this->mockAuthorizationHeader($result->getJwt());
$this->mockAuthorizationHeader($result->getJwt());
$session = $component->getActiveSession();
expect($session)->isInstanceOf(AccountSession::class);
expect($session->id)->equals($result->getSession()->id);
});
$session = $component->getActiveSession();
$this->assertInstanceOf(AccountSession::class, $session);
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals($session->id, $result->getSession()->id);
}
public function testTerminateSessions() {
/** @var AccountSession $session */
$session = AccountSession::findOne($this->tester->grabFixture('sessions', 'admin2')['id']);
/** @var Component|\PHPUnit_Framework_MockObject_MockObject $component */
$component = $this->getMockBuilder(Component::class)
->setMethods(['getActiveSession'])
->setConstructorArgs([$this->getComponentArguments()])
->getMock();
/** @var Component|\Mockery\MockInterface $component */
$component = mock(Component::class . '[getActiveSession]', [$this->getComponentConfig()])->shouldDeferMissing();
$component->shouldReceive('getActiveSession')->times(1)->andReturn($session);
$component
->expects($this->exactly(1))
->method('getActiveSession')
->willReturn($session);
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$component->createJwtAuthenticationToken($account, true);
/** @var AccountIdentity $identity */
$identity = AccountIdentity::findOne($this->tester->grabFixture('accounts', 'admin')['id']);
$component->login($identity, true);
$component->terminateSessions($account, Component::KEEP_MINECRAFT_SESSIONS | Component::KEEP_SITE_SESSIONS);
$this->assertNotEmpty($account->getMinecraftAccessKeys()->all());
$this->assertNotEmpty($account->getSessions()->all());
$component->terminateSessions(0);
$this->assertNotEmpty($identity->getMinecraftAccessKeys()->all());
$this->assertNotEmpty($identity->getSessions()->all());
$component->terminateSessions($account, Component::KEEP_SITE_SESSIONS);
$this->assertEmpty($account->getMinecraftAccessKeys()->all());
$this->assertNotEmpty($account->getSessions()->all());
$component->terminateSessions(Component::TERMINATE_MINECRAFT_SESSIONS);
$this->assertEmpty($identity->getMinecraftAccessKeys()->all());
$this->assertNotEmpty($identity->getSessions()->all());
$component->terminateSessions(Component::TERMINATE_SITE_SESSIONS | Component::DO_NOT_TERMINATE_CURRENT_SESSION);
$sessions = $identity->getSessions()->all();
$component->terminateSessions($account, Component::KEEP_CURRENT_SESSION);
$sessions = $account->getSessions()->all();
$this->assertEquals(1, count($sessions));
$this->assertTrue($sessions[0]->id === $session->id);
$component->terminateSessions(Component::TERMINATE_ALL);
$this->assertEmpty($identity->getSessions()->all());
$this->assertEmpty($identity->getMinecraftAccessKeys()->all());
$component->terminateSessions($account);
$this->assertEmpty($account->getSessions()->all());
$this->assertEmpty($account->getMinecraftAccessKeys()->all());
}
public function testSerializeToken() {
$this->specify('get string, contained jwt token', function() {
$token = new Token();
expect($this->callProtected($this->component, 'serializeToken', $token))
->regExp('/^[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+\/=]*$/');
});
}
public function testCreateToken() {
$this->specify('create token', function() {
expect($this->callProtected($this->component, 'createToken', new AccountIdentity(['id' => 1])))
->isInstanceOf(Token::class);
});
}
public function testGetAlgorithm() {
$this->specify('get expected hash algorithm object', function() {
expect($this->component->getAlgorithm())->isInstanceOf(AlgorithmInterface::class);
});
}
public function testGetClaims() {
$this->specify('get expected array of claims', function() {
$claims = $this->callProtected($this->component, 'getClaims', new AccountIdentity(['id' => 1]));
expect(is_array($claims))->true();
expect('all array items should have valid type', array_filter($claims, function($claim) {
return !$claim instanceof ClaimInterface;
}))->isEmpty();
});
}
/**
* @param string $userIP
* @return \PHPUnit_Framework_MockObject_MockObject
*/
private function mockRequest($userIP = '127.0.0.1') {
$request = $this->getMockBuilder(Request::class)
->setMethods(['getHostInfo', 'getUserIP'])
->getMock();
$request
->expects($this->any())
->method('getHostInfo')
->will($this->returnValue('http://localhost'));
$request
->expects($this->any())
->method('getUserIP')
->will($this->returnValue($userIP));
/** @var Request|\Mockery\MockInterface $request */
$request = mock(Request::class . '[getHostInfo,getUserIP]')->shouldDeferMissing();
$request->shouldReceive('getHostInfo')->andReturn('http://localhost');
$request->shouldReceive('getUserIP')->andReturn($userIP);
Yii::$app->set('request', $request);
return $request;
}
/**
@@ -231,9 +185,9 @@ class ComponentTest extends TestCase {
Yii::$app->request->headers->set('Authorization', $bearerToken);
}
private function getComponentArguments() {
private function getComponentConfig() {
return [
'identityClass' => AccountIdentity::class,
'identityClass' => Identity::class,
'enableSession' => false,
'loginUrl' => null,
'secret' => 'secret',

View File

@@ -0,0 +1,63 @@
<?php
namespace tests\codeception\api\unit\components\User;
use api\components\User\AuthenticationResult;
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;
use tests\codeception\api\unit\TestCase;
class JwtAuthenticationResultTest extends TestCase {
public function testGetAccount() {
$account = new Account();
$account->id = 123;
$model = new AuthenticationResult($account, '', null);
$this->assertEquals($account, $model->getAccount());
}
public function testGetJwt() {
$model = new AuthenticationResult(new Account(), 'mocked jwt', null);
$this->assertEquals('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->assertEquals($session, $model->getSession());
}
public function testGetAsResponse() {
$jwtToken = $this->createJwtToken(time() + 3600);
$model = new AuthenticationResult(new Account(), $jwtToken, null);
$result = $model->getAsResponse();
$this->assertEquals($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->assertEquals($jwtToken, $result['access_token']);
$this->assertEquals('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')));
}
}

View File

@@ -1,66 +0,0 @@
<?php
namespace tests\codeception\api\unit\filters;
use api\filters\ActiveUserRule;
use api\models\AccountIdentity;
use Codeception\Specify;
use common\models\Account;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\_support\ProtectedCaller;
use yii\base\Action;
use const common\LATEST_RULES_VERSION;
class ActiveUserRuleTest extends TestCase {
use Specify;
use ProtectedCaller;
public function testMatchCustom() {
$account = new AccountIdentity();
$this->specify('get false if user not finished registration', function() use (&$account) {
$account->status = Account::STATUS_REGISTERED;
$filter = $this->getFilterMock($account);
expect($this->callProtected($filter, 'matchCustom', new Action(null, null)))->false();
});
$this->specify('get false if user has banned status', function() use (&$account) {
$account->status = Account::STATUS_BANNED;
$filter = $this->getFilterMock($account);
expect($this->callProtected($filter, 'matchCustom', new Action(null, null)))->false();
});
$this->specify('get false if user have old EULA agreement', function() use (&$account) {
$account->status = Account::STATUS_ACTIVE;
$account->rules_agreement_version = null;
$filter = $this->getFilterMock($account);
expect($this->callProtected($filter, 'matchCustom', new Action(null, null)))->false();
});
$this->specify('get true if user fully active', function() use (&$account) {
$account->status = Account::STATUS_ACTIVE;
$account->rules_agreement_version = LATEST_RULES_VERSION;
$filter = $this->getFilterMock($account);
expect($this->callProtected($filter, 'matchCustom', new Action(null, null)))->true();
});
}
/**
* @param AccountIdentity $returnIdentity
* @return ActiveUserRule|\PHPUnit_Framework_MockObject_MockObject
*/
private function getFilterMock(AccountIdentity $returnIdentity) {
/** @var ActiveUserRule|\PHPUnit_Framework_MockObject_MockObject $filter */
$filter = $this
->getMockBuilder(ActiveUserRule::class)
->setMethods(['getIdentity'])
->getMock();
$filter
->expects($this->any())
->method('getIdentity')
->will($this->returnValue($returnIdentity));
return $filter;
}
}

View File

@@ -1,35 +1,34 @@
<?php
namespace codeception\api\unit\models;
use api\models\AccountIdentity;
use api\components\User\IdentityInterface;
use api\components\User\Jwt;
use api\components\User\JwtIdentity;
use Codeception\Specify;
use Emarref\Jwt\Claim;
use Emarref\Jwt\Encryption\Factory as EncryptionFactory;
use Emarref\Jwt\Jwt;
use Emarref\Jwt\Token;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\_support\ProtectedCaller;
use tests\codeception\common\fixtures\AccountFixture;
use Yii;
use yii\web\IdentityInterface;
/**
* @property AccountIdentity $accounts
*/
class AccountIdentityTest extends TestCase {
class JwtIdentityTest extends TestCase {
use Specify;
use ProtectedCaller;
public function _fixtures() {
public function _fixtures(): array {
return [
'accounts' => AccountFixture::class,
];
}
public function testFindIdentityByAccessToken() {
$identity = AccountIdentity::findIdentityByAccessToken($this->generateToken());
$token = $this->generateToken();
$identity = JwtIdentity::findIdentityByAccessToken($token);
$this->assertInstanceOf(IdentityInterface::class, $identity);
$this->assertEquals($this->tester->grabFixture('accounts', 'admin')['id'], $identity->getId());
$this->assertEquals($token, $identity->getId());
$this->assertEquals($this->tester->grabFixture('accounts', 'admin')['id'], $identity->getAccount()->id);
}
/**
@@ -42,10 +41,10 @@ class AccountIdentityTest extends TestCase {
$token->addClaim(new Claim\Issuer('http://localhost'));
$token->addClaim(new Claim\IssuedAt(1464593193));
$token->addClaim(new Claim\Expiration(1464596793));
$token->addClaim(new Claim\JwtId($this->tester->grabFixture('accounts', 'admin')['id']));
$token->addClaim(new Claim\Subject('ely|' . $this->tester->grabFixture('accounts', 'admin')['id']));
$expiredToken = (new Jwt())->serialize($token, EncryptionFactory::create(Yii::$app->user->getAlgorithm()));
AccountIdentity::findIdentityByAccessToken($expiredToken);
JwtIdentity::findIdentityByAccessToken($expiredToken);
}
/**
@@ -53,15 +52,14 @@ class AccountIdentityTest extends TestCase {
* @expectedExceptionMessage Incorrect token
*/
public function testFindIdentityByAccessTokenWithEmptyToken() {
AccountIdentity::findIdentityByAccessToken('');
JwtIdentity::findIdentityByAccessToken('');
}
protected function generateToken() {
/** @var \api\components\User\Component $component */
$component = Yii::$app->user;
/** @var AccountIdentity $account */
$account = AccountIdentity::findOne($this->tester->grabFixture('accounts', 'admin')['id']);
/** @var \common\models\Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$token = $this->callProtected($component, 'createToken', $account);
return $this->callProtected($component, 'serializeToken', $token);

View File

@@ -1,7 +1,7 @@
<?php
namespace tests\codeception\api\models\authentication;
use api\components\User\LoginResult;
use api\components\User\AuthenticationResult;
use api\models\authentication\ConfirmEmailForm;
use common\models\Account;
use common\models\AccountSession;
@@ -21,7 +21,7 @@ class ConfirmEmailFormTest extends TestCase {
$fixture = $this->tester->grabFixture('emailActivations', 'freshRegistrationConfirmation');
$model = $this->createModel($fixture['key']);
$result = $model->confirm();
$this->assertInstanceOf(LoginResult::class, $result);
$this->assertInstanceOf(AuthenticationResult::class, $result);
$this->assertInstanceOf(AccountSession::class, $result->getSession(), 'session was generated');
$activationExists = EmailActivation::find()->andWhere(['key' => $fixture['key']])->exists();
$this->assertFalse($activationExists, 'email activation key is not exist');

View File

@@ -1,8 +1,7 @@
<?php
namespace tests\codeception\api\models\authentication;
use api\components\User\LoginResult;
use api\models\AccountIdentity;
use api\components\User\AuthenticationResult;
use api\models\authentication\LoginForm;
use Codeception\Specify;
use common\models\Account;
@@ -45,7 +44,7 @@ class LoginFormTest extends TestCase {
$this->specify('no errors if login exists', function () {
$model = $this->createModel([
'login' => 'mr-test',
'account' => new AccountIdentity(),
'account' => new Account(),
]);
$model->validateLogin('login');
$this->assertEmpty($model->getErrors('login'));
@@ -56,7 +55,7 @@ class LoginFormTest extends TestCase {
$this->specify('error.password_incorrect if password invalid', function () {
$model = $this->createModel([
'password' => '87654321',
'account' => new AccountIdentity(['password' => '12345678']),
'account' => new Account(['password' => '12345678']),
]);
$model->validatePassword('password');
$this->assertEquals(['error.password_incorrect'], $model->getErrors('password'));
@@ -65,7 +64,7 @@ class LoginFormTest extends TestCase {
$this->specify('no errors if password valid', function () {
$model = $this->createModel([
'password' => '12345678',
'account' => new AccountIdentity(['password' => '12345678']),
'account' => new Account(['password' => '12345678']),
]);
$model->validatePassword('password');
$this->assertEmpty($model->getErrors('password'));
@@ -73,7 +72,7 @@ class LoginFormTest extends TestCase {
}
public function testValidateTotp() {
$account = new AccountIdentity(['password' => '12345678']);
$account = new Account(['password' => '12345678']);
$account->password = '12345678';
$account->is_otp_enabled = true;
$account->otp_secret = 'AAAA';
@@ -103,7 +102,7 @@ class LoginFormTest extends TestCase {
public function testValidateActivity() {
$this->specify('error.account_not_activated if account in not activated state', function () {
$model = $this->createModel([
'account' => new AccountIdentity(['status' => Account::STATUS_REGISTERED]),
'account' => new Account(['status' => Account::STATUS_REGISTERED]),
]);
$model->validateActivity('login');
$this->assertEquals(['error.account_not_activated'], $model->getErrors('login'));
@@ -111,7 +110,7 @@ class LoginFormTest extends TestCase {
$this->specify('error.account_banned if account has banned status', function () {
$model = $this->createModel([
'account' => new AccountIdentity(['status' => Account::STATUS_BANNED]),
'account' => new Account(['status' => Account::STATUS_BANNED]),
]);
$model->validateActivity('login');
$this->assertEquals(['error.account_banned'], $model->getErrors('login'));
@@ -119,7 +118,7 @@ class LoginFormTest extends TestCase {
$this->specify('no errors if account active', function () {
$model = $this->createModel([
'account' => new AccountIdentity(['status' => Account::STATUS_ACTIVE]),
'account' => new Account(['status' => Account::STATUS_ACTIVE]),
]);
$model->validateActivity('login');
$this->assertEmpty($model->getErrors('login'));
@@ -130,13 +129,13 @@ class LoginFormTest extends TestCase {
$model = $this->createModel([
'login' => 'erickskrauch',
'password' => '12345678',
'account' => new AccountIdentity([
'account' => new Account([
'username' => 'erickskrauch',
'password' => '12345678',
'status' => Account::STATUS_ACTIVE,
]),
]);
$this->assertInstanceOf(LoginResult::class, $model->login(), 'model should login user');
$this->assertInstanceOf(AuthenticationResult::class, $model->login(), 'model should login user');
$this->assertEmpty($model->getErrors(), 'error message should not be set');
}
@@ -145,7 +144,7 @@ class LoginFormTest extends TestCase {
'login' => $this->tester->grabFixture('accounts', 'user-with-old-password-type')['username'],
'password' => '12345678',
]);
$this->assertInstanceOf(LoginResult::class, $model->login());
$this->assertInstanceOf(AuthenticationResult::class, $model->login());
$this->assertEmpty($model->getErrors());
$this->assertEquals(
Account::PASS_HASH_STRATEGY_YII2,
@@ -166,7 +165,7 @@ class LoginFormTest extends TestCase {
$this->_account = $value;
}
public function getAccount() {
public function getAccount(): ?Account {
return $this->_account;
}
};

View File

@@ -2,7 +2,7 @@
namespace tests\codeception\api\models\authentication;
use api\components\User\Component;
use api\models\AccountIdentity;
use api\components\User\Identity;
use api\models\authentication\LogoutForm;
use Codeception\Specify;
use common\models\AccountSession;
@@ -59,7 +59,7 @@ class LogoutFormTest extends TestCase {
private function getComponentArgs() {
return [
'identityClass' => AccountIdentity::class,
'identityClass' => Identity::class,
'enableSession' => false,
'loginUrl' => null,
'secret' => 'secret',

View File

@@ -1,7 +1,7 @@
<?php
namespace tests\codeception\api\models\authentication;
use api\components\User\LoginResult;
use api\components\User\AuthenticationResult;
use api\models\authentication\RecoverPasswordForm;
use Codeception\Specify;
use common\models\Account;
@@ -26,7 +26,7 @@ class RecoverPasswordFormTest extends TestCase {
'newRePassword' => '12345678',
]);
$result = $model->recoverPassword();
$this->assertInstanceOf(LoginResult::class, $result);
$this->assertInstanceOf(AuthenticationResult::class, $result);
$this->assertNull($result->getSession(), 'session was not generated');
$this->assertFalse(EmailActivation::find()->andWhere(['key' => $fixture['key']])->exists());
/** @var Account $account */

View File

@@ -1,7 +1,7 @@
<?php
namespace codeception\api\unit\models\authentication;
use api\components\User\RenewResult;
use api\components\User\AuthenticationResult;
use api\models\authentication\RefreshTokenForm;
use Codeception\Specify;
use common\models\AccountSession;
@@ -26,7 +26,7 @@ class RefreshTokenFormTest extends TestCase {
}
};
$model->validateRefreshToken();
expect($model->getErrors('refresh_token'))->equals(['error.refresh_token_not_exist']);
$this->assertEquals(['error.refresh_token_not_exist'], $model->getErrors('refresh_token'));
});
$this->specify('no errors if token exists', function() {
@@ -37,14 +37,14 @@ class RefreshTokenFormTest extends TestCase {
}
};
$model->validateRefreshToken();
expect($model->getErrors('refresh_token'))->isEmpty();
$this->assertEmpty($model->getErrors('refresh_token'));
});
}
public function testRenew() {
$model = new RefreshTokenForm();
$model->refresh_token = $this->tester->grabFixture('sessions', 'admin')['refresh_token'];
$this->assertInstanceOf(RenewResult::class, $model->renew());
$this->assertInstanceOf(AuthenticationResult::class, $model->renew());
}
}

View File

@@ -8,12 +8,13 @@ use common\models\Account;
use common\models\EmailActivation;
use common\models\UsernameHistory;
use GuzzleHttp\ClientInterface;
use ReflectionClass;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\EmailActivationFixture;
use tests\codeception\common\fixtures\UsernameHistoryFixture;
use tests\codeception\common\helpers\Mock;
use Yii;
use yii\validators\EmailValidator;
use yii\web\Request;
use const common\LATEST_RULES_VERSION;
@@ -59,6 +60,7 @@ class RegistrationFormTest extends TestCase {
}
public function testSignup() {
Mock::func(EmailValidator::class, 'checkdnsrr')->andReturnTrue();
$model = new RegistrationForm([
'username' => 'some_username',
'email' => 'some_email@example.com',
@@ -75,6 +77,7 @@ class RegistrationFormTest extends TestCase {
}
public function testSignupWithDefaultLanguage() {
Mock::func(EmailValidator::class, 'checkdnsrr')->andReturnTrue();
$model = new RegistrationForm([
'username' => 'some_username',
'email' => 'some_email@example.com',

View File

@@ -1,26 +0,0 @@
<?php
namespace codeception\api\unit\models\profile;
use api\models\profile\AcceptRulesForm;
use common\models\Account;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use const common\LATEST_RULES_VERSION;
class AcceptRulesFormTest extends TestCase {
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
];
}
public function testAgreeWithLatestRules() {
/** @var Account $account */
$account = Account::findOne($this->tester->grabFixture('accounts', 'account-with-old-rules-version'));
$model = new AcceptRulesForm($account);
$this->assertTrue($model->agreeWithLatestRules());
$this->assertEquals(LATEST_RULES_VERSION, $account->rules_agreement_version);
}
}

View File

@@ -1,26 +0,0 @@
<?php
namespace codeception\api\unit\models\profile;
use api\models\profile\ChangeLanguageForm;
use common\models\Account;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
class ChangeLanguageFormTest extends TestCase {
public function _fixtures() {
return [
'accounts' => AccountFixture::class
];
}
public function testApplyLanguage() {
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$model = new ChangeLanguageForm($account);
$model->lang = 'ru';
$this->assertTrue($model->applyLanguage());
$this->assertEquals('ru', $account->lang);
}
}

View File

@@ -1,135 +0,0 @@
<?php
namespace tests\codeception\api\models\profile;
use api\components\User\Component;
use api\models\AccountIdentity;
use api\models\profile\ChangePasswordForm;
use Codeception\Specify;
use common\models\Account;
use common\models\AccountSession;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\AccountSessionFixture;
use Yii;
class ChangePasswordFormTest extends TestCase {
use Specify;
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
'accountSessions' => AccountSessionFixture::class,
];
}
public function testValidatePasswordAndRePasswordMatch() {
$this->specify('error.rePassword_does_not_match expected if passwords not match', function() {
$account = new Account();
$account->setPassword('12345678');
$model = new ChangePasswordForm($account, [
'password' => '12345678',
'newPassword' => 'my-new-password',
'newRePassword' => 'another-password',
]);
$model->validatePasswordAndRePasswordMatch('newRePassword');
expect($model->getErrors('newRePassword'))->equals(['error.rePassword_does_not_match']);
});
$this->specify('no errors expected if passwords are valid', function() {
$account = new Account();
$account->setPassword('12345678');
$model = new ChangePasswordForm($account, [
'password' => '12345678',
'newPassword' => 'my-new-password',
'newRePassword' => 'my-new-password',
]);
$model->validatePasswordAndRePasswordMatch('newRePassword');
expect($model->getErrors('newRePassword'))->isEmpty();
});
$this->specify('error.rePassword_does_not_match expected even if there are errors on other attributes', function() {
// this is very important, because password change flow may be combined of two steps
// therefore we need to validate password sameness before we will validate current account password
$account = new Account();
$account->setPassword('12345678');
$model = new ChangePasswordForm($account, [
'newPassword' => 'my-new-password',
'newRePassword' => 'another-password',
]);
$model->validate();
expect($model->getErrors('newRePassword'))->equals(['error.rePassword_does_not_match']);
});
}
public function testChangePassword() {
$this->specify('successfully change password with modern hash strategy', function() {
/** @var Account $account */
$account = Account::findOne($this->tester->grabFixture('accounts', 'admin')['id']);
$model = new ChangePasswordForm($account, [
'password' => 'password_0',
'newPassword' => 'my-new-password',
'newRePassword' => 'my-new-password',
]);
$callTime = time();
expect('form should return true', $model->changePassword())->true();
expect('new password should be successfully stored into account', $account->validatePassword('my-new-password'))->true();
expect('password change time updated', $account->password_changed_at)->greaterOrEquals($callTime);
});
$this->specify('successfully change password with legacy hash strategy', function() {
/** @var Account $account */
$account = Account::findOne($this->tester->grabFixture('accounts', 'user-with-old-password-type')['id']);
$model = new ChangePasswordForm($account, [
'password' => '12345678',
'newPassword' => 'my-new-password',
'newRePassword' => 'my-new-password',
]);
$callTime = time();
expect($model->changePassword())->true();
expect($account->validatePassword('my-new-password'))->true();
expect($account->password_changed_at)->greaterOrEquals($callTime);
expect($account->password_hash_strategy)->equals(Account::PASS_HASH_STRATEGY_YII2);
});
}
public function testChangePasswordWithLogout() {
/** @var Component|\PHPUnit_Framework_MockObject_MockObject $component */
$component = $this->getMockBuilder(Component::class)
->setMethods(['getActiveSession', 'terminateSessions'])
->setConstructorArgs([[
'identityClass' => AccountIdentity::class,
'enableSession' => false,
'loginUrl' => null,
'secret' => 'secret',
]])
->getMock();
/** @var AccountSession $session */
$session = AccountSession::findOne($this->tester->grabFixture('accountSessions', 'admin2')['id']);
$component
->expects($this->any())
->method('getActiveSession')
->will($this->returnValue($session));
$component
->expects($this->once())
->method('terminateSessions');
Yii::$app->set('user', $component);
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$model = new ChangePasswordForm($account, [
'password' => 'password_0',
'newPassword' => 'my-new-password',
'newRePassword' => 'my-new-password',
'logoutAll' => true,
]);
$this->assertTrue($model->changePassword());
}
}

View File

@@ -1,208 +0,0 @@
<?php
namespace tests\codeception\api\unit\models\profile;
use api\components\User\Component;
use api\models\AccountIdentity;
use api\models\profile\TwoFactorAuthForm;
use common\helpers\Error as E;
use common\models\Account;
use OTPHP\TOTP;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\_support\ProtectedCaller;
use Yii;
class TwoFactorAuthFormTest extends TestCase {
use ProtectedCaller;
public function testGetCredentials() {
/** @var Account|\PHPUnit_Framework_MockObject_MockObject $account */
$account = $this->getMockBuilder(Account::class)
->setMethods(['save'])
->getMock();
$account->expects($this->once())
->method('save')
->willReturn(true);
$account->email = 'mock@email.com';
$account->otp_secret = null;
/** @var TwoFactorAuthForm|\PHPUnit_Framework_MockObject_MockObject $model */
$model = $this->getMockBuilder(TwoFactorAuthForm::class)
->setConstructorArgs([$account])
->setMethods(['drawQrCode'])
->getMock();
$model->expects($this->once())
->method('drawQrCode')
->willReturn('<_/>');
$result = $model->getCredentials();
$this->assertTrue(is_array($result));
$this->assertArrayHasKey('qr', $result);
$this->assertArrayHasKey('uri', $result);
$this->assertArrayHasKey('secret', $result);
$this->assertNotNull($account->otp_secret);
$this->assertEquals($account->otp_secret, $result['secret']);
$this->assertEquals('data:image/svg+xml,<_/>', $result['qr']);
/** @var Account|\PHPUnit_Framework_MockObject_MockObject $account */
$account = $this->getMockBuilder(Account::class)
->setMethods(['save'])
->getMock();
$account->expects($this->never())
->method('save');
$account->email = 'mock@email.com';
$account->otp_secret = 'some valid totp secret value';
/** @var TwoFactorAuthForm|\PHPUnit_Framework_MockObject_MockObject $model */
$model = $this->getMockBuilder(TwoFactorAuthForm::class)
->setConstructorArgs([$account])
->setMethods(['drawQrCode'])
->getMock();
$model->expects($this->once())
->method('drawQrCode')
->willReturn('this is qr code, trust me');
$result = $model->getCredentials();
$this->assertEquals('some valid totp secret value', $result['secret']);
}
public function testActivate() {
/** @var Component|\PHPUnit_Framework_MockObject_MockObject $component */
$component = $this->getMockBuilder(Component::class)
->setMethods(['terminateSessions'])
->setConstructorArgs([[
'identityClass' => AccountIdentity::class,
'enableSession' => false,
'loginUrl' => null,
'secret' => 'secret',
]])
->getMock();
$component
->expects($this->once())
->method('terminateSessions');
Yii::$app->set('user', $component);
/** @var Account|\PHPUnit_Framework_MockObject_MockObject $account */
$account = $this->getMockBuilder(Account::class)
->setMethods(['save'])
->getMock();
$account->expects($this->once())
->method('save')
->willReturn(true);
$account->is_otp_enabled = false;
$account->otp_secret = 'mock secret';
/** @var TwoFactorAuthForm|\PHPUnit_Framework_MockObject_MockObject $model */
$model = $this->getMockBuilder(TwoFactorAuthForm::class)
->setMethods(['validate'])
->setConstructorArgs([$account, ['scenario' => TwoFactorAuthForm::SCENARIO_ACTIVATE]])
->getMock();
$model->expects($this->once())
->method('validate')
->willReturn(true);
$this->assertTrue($model->activate());
$this->assertTrue($account->is_otp_enabled);
}
public function testDisable() {
/** @var Account|\PHPUnit_Framework_MockObject_MockObject $account */
$account = $this->getMockBuilder(Account::class)
->setMethods(['save'])
->getMock();
$account->expects($this->once())
->method('save')
->willReturn(true);
$account->is_otp_enabled = true;
$account->otp_secret = 'mock secret';
/** @var TwoFactorAuthForm|\PHPUnit_Framework_MockObject_MockObject $model */
$model = $this->getMockBuilder(TwoFactorAuthForm::class)
->setMethods(['validate'])
->setConstructorArgs([$account, ['scenario' => TwoFactorAuthForm::SCENARIO_DISABLE]])
->getMock();
$model->expects($this->once())
->method('validate')
->willReturn(true);
$this->assertTrue($model->disable());
$this->assertNull($account->otp_secret);
$this->assertFalse($account->is_otp_enabled);
}
public function testValidateOtpDisabled() {
$account = new Account();
$account->is_otp_enabled = true;
$model = new TwoFactorAuthForm($account);
$model->validateOtpDisabled('account');
$this->assertEquals([E::OTP_ALREADY_ENABLED], $model->getErrors('account'));
$account = new Account();
$account->is_otp_enabled = false;
$model = new TwoFactorAuthForm($account);
$model->validateOtpDisabled('account');
$this->assertEmpty($model->getErrors('account'));
}
public function testValidateOtpEnabled() {
$account = new Account();
$account->is_otp_enabled = false;
$model = new TwoFactorAuthForm($account);
$model->validateOtpEnabled('account');
$this->assertEquals([E::OTP_NOT_ENABLED], $model->getErrors('account'));
$account = new Account();
$account->is_otp_enabled = true;
$model = new TwoFactorAuthForm($account);
$model->validateOtpEnabled('account');
$this->assertEmpty($model->getErrors('account'));
}
public function testGetTotp() {
$account = new Account();
$account->otp_secret = 'mock secret';
$account->email = 'check@this.email';
$model = new TwoFactorAuthForm($account);
$totp = $model->getTotp();
$this->assertInstanceOf(TOTP::class, $totp);
$this->assertEquals('check@this.email', $totp->getLabel());
$this->assertEquals('mock secret', $totp->getSecret());
$this->assertEquals('Ely.by', $totp->getIssuer());
}
public function testSetOtpSecret() {
/** @var Account|\PHPUnit_Framework_MockObject_MockObject $account */
$account = $this->getMockBuilder(Account::class)
->setMethods(['save'])
->getMock();
$account->expects($this->exactly(2))
->method('save')
->willReturn(true);
$model = new TwoFactorAuthForm($account);
$this->callProtected($model, 'setOtpSecret');
$this->assertEquals(24, strlen($model->getAccount()->otp_secret));
$this->assertSame(strtoupper($model->getAccount()->otp_secret), $model->getAccount()->otp_secret);
$model = new TwoFactorAuthForm($account);
$this->callProtected($model, 'setOtpSecret', 25);
$this->assertEquals(25, strlen($model->getAccount()->otp_secret));
$this->assertSame(strtoupper($model->getAccount()->otp_secret), $model->getAccount()->otp_secret);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace tests\codeception\api\unit\modules\accounts\models;
use api\modules\accounts\models\AcceptRulesForm;
use common\models\Account;
use tests\codeception\api\unit\TestCase;
use const common\LATEST_RULES_VERSION;
class AcceptRulesFormTest extends TestCase {
public function testAgreeWithLatestRules() {
/** @var Account|\Mockery\MockInterface $account */
$account = mock(Account::class . '[save]');
$account->shouldReceive('save')->andReturn(true);
$account->rules_agreement_version = LATEST_RULES_VERSION - 1;
$model = new AcceptRulesForm($account);
$this->assertTrue($model->performAction());
$this->assertEquals(LATEST_RULES_VERSION, $account->rules_agreement_version);
}
}

View File

@@ -1,14 +1,14 @@
<?php
namespace codeception\api\unit\models\profile\ChangeEmail;
namespace tests\codeception\api\unit\modules\accounts\models;
use api\models\profile\ChangeEmail\ConfirmNewEmailForm;
use api\modules\accounts\models\ChangeEmailForm;
use common\models\Account;
use common\models\EmailActivation;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\EmailActivationFixture;
class ConfirmNewEmailFormTest extends TestCase {
class ChangeEmailFormTest extends TestCase {
public function _fixtures() {
return [
@@ -21,14 +21,15 @@ class ConfirmNewEmailFormTest extends TestCase {
/** @var Account $account */
$account = Account::findOne($this->getAccountId());
$newEmailConfirmationFixture = $this->tester->grabFixture('emailActivations', 'newEmailConfirmation');
$model = new ConfirmNewEmailForm($account, [
$model = new ChangeEmailForm($account, [
'key' => $newEmailConfirmationFixture['key'],
]);
$this->assertTrue($model->changeEmail());
$this->assertTrue($model->performAction());
$this->assertNull(EmailActivation::findOne([
'account_id' => $account->id,
'type' => EmailActivation::TYPE_NEW_EMAIL_CONFIRMATION,
]));
/** @noinspection UnserializeExploitsInspection */
$data = unserialize($newEmailConfirmationFixture['_data']);
$this->assertEquals($data['newEmail'], $account->email);
$this->tester->canSeeAmqpMessageIsCreated('events');
@@ -37,7 +38,7 @@ class ConfirmNewEmailFormTest extends TestCase {
public function testCreateTask() {
/** @var Account $account */
$account = Account::findOne($this->getAccountId());
$model = new ConfirmNewEmailForm($account);
$model = new ChangeEmailForm($account);
$model->createTask(1, 'test1@ely.by', 'test@ely.by');
$message = $this->tester->grabLastSentAmqpMessage('events');
$body = json_decode($message->getBody(), true);

View File

@@ -0,0 +1,21 @@
<?php
namespace tests\codeception\api\unit\modules\accounts\models;
use api\modules\accounts\models\ChangeLanguageForm;
use common\models\Account;
use tests\codeception\api\unit\TestCase;
class ChangeLanguageFormTest extends TestCase {
public function testApplyLanguage() {
/** @var Account|\Mockery\MockInterface $account */
$account = mock(Account::class . '[save]');
$account->shouldReceive('save')->andReturn(true);
$model = new ChangeLanguageForm($account);
$model->lang = 'ru';
$this->assertTrue($model->performAction());
$this->assertEquals('ru', $account->lang);
}
}

View File

@@ -0,0 +1,139 @@
<?php
namespace tests\codeception\api\unit\modules\accounts\models;
use api\components\User\Component;
use api\components\User\Identity;
use api\modules\accounts\models\ChangePasswordForm;
use common\components\UserPass;
use common\helpers\Error as E;
use common\models\Account;
use tests\codeception\api\unit\TestCase;
use Yii;
use yii\db\Transaction;
class ChangePasswordFormTest extends TestCase {
public function testValidatePasswordAndRePasswordMatch() {
$account = new Account();
$account->setPassword('12345678');
$model = new ChangePasswordForm($account, [
'password' => '12345678',
'newPassword' => 'my-new-password',
'newRePassword' => 'another-password',
]);
$model->validatePasswordAndRePasswordMatch('newRePassword');
$this->assertEquals(
[E::NEW_RE_PASSWORD_DOES_NOT_MATCH],
$model->getErrors('newRePassword'),
'error.rePassword_does_not_match expected if passwords not match'
);
$account = new Account();
$account->setPassword('12345678');
$model = new ChangePasswordForm($account, [
'password' => '12345678',
'newPassword' => 'my-new-password',
'newRePassword' => 'my-new-password',
]);
$model->validatePasswordAndRePasswordMatch('newRePassword');
$this->assertEmpty($model->getErrors('newRePassword'), 'no errors expected if passwords are valid');
// this is very important, because password change flow may be combined of two steps
// therefore we need to validate password sameness before we will validate current account password
$account = new Account();
$account->setPassword('12345678');
$model = new ChangePasswordForm($account, [
'newPassword' => 'my-new-password',
'newRePassword' => 'another-password',
]);
$model->validate();
$this->assertEquals(
[E::NEW_RE_PASSWORD_DOES_NOT_MATCH],
$model->getErrors('newRePassword'),
'error.rePassword_does_not_match expected even if there are errors on other attributes'
);
$this->assertEmpty($model->getErrors('password'));
}
public function testPerformAction() {
$component = mock(Component::class . '[terminateSessions]', [[
'identityClass' => Identity::class,
'enableSession' => false,
'loginUrl' => null,
'secret' => 'secret',
]]);
$component->shouldNotReceive('terminateSessions');
Yii::$app->set('user', $component);
$transaction = mock(Transaction::class . '[commit]');
$transaction->shouldReceive('commit');
$connection = mock(Yii::$app->db);
$connection->shouldReceive('beginTransaction')->andReturn($transaction);
Yii::$app->set('db', $connection);
/** @var Account|\Mockery\MockInterface $account */
$account = mock(Account::class . '[save]');
$account->shouldReceive('save')->andReturn(true);
$account->setPassword('password_0');
$model = new ChangePasswordForm($account, [
'password' => 'password_0',
'newPassword' => 'my-new-password',
'newRePassword' => 'my-new-password',
]);
$callTime = time();
$this->assertTrue($model->performAction(), 'successfully change password with modern hash strategy');
$this->assertTrue($account->validatePassword('my-new-password'), 'new password should be successfully stored into account');
$this->assertGreaterThanOrEqual($callTime, $account->password_changed_at, 'password change time updated');
/** @var Account|\Mockery\MockInterface $account */
$account = mock(Account::class . '[save]');
$account->shouldReceive('save')->andReturn(true);
$account->email = 'mock@ely.by';
$account->password_hash_strategy = Account::PASS_HASH_STRATEGY_OLD_ELY;
$account->password_hash = UserPass::make($account->email, '12345678');
$model = new ChangePasswordForm($account, [
'password' => '12345678',
'newPassword' => 'my-new-password',
'newRePassword' => 'my-new-password',
]);
$callTime = time();
$this->assertTrue($model->performAction(), 'successfully change password with legacy hash strategy');
$this->assertTrue($account->validatePassword('my-new-password'));
$this->assertGreaterThanOrEqual($callTime, $account->password_changed_at);
$this->assertEquals(Account::PASS_HASH_STRATEGY_YII2, $account->password_hash_strategy);
}
public function testPerformActionWithLogout() {
/** @var Account|\Mockery\MockInterface $account */
$account = mock(Account::class . '[save]');
$account->shouldReceive('save')->andReturn(true);
$account->setPassword('password_0');
/** @var Component|\Mockery\MockInterface $component */
$component = mock(Component::class . '[terminateSessions]', [[
'identityClass' => Identity::class,
'enableSession' => false,
'loginUrl' => null,
'secret' => 'secret',
]]);
$component->shouldReceive('terminateSessions')->once()->withArgs([$account, Component::KEEP_CURRENT_SESSION]);
Yii::$app->set('user', $component);
$model = new ChangePasswordForm($account, [
'password' => 'password_0',
'newPassword' => 'my-new-password',
'newRePassword' => 'my-new-password',
'logoutAll' => true,
]);
$this->assertTrue($model->performAction());
}
}

View File

@@ -1,18 +1,14 @@
<?php
namespace tests\codeception\api\models\profile;
namespace tests\codeception\api\unit\modules\accounts\models;
use api\models\AccountIdentity;
use api\models\profile\ChangeUsernameForm;
use Codeception\Specify;
use api\modules\accounts\models\ChangeUsernameForm;
use common\models\Account;
use common\models\UsernameHistory;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\UsernameHistoryFixture;
use Yii;
class ChangeUsernameFormTest extends TestCase {
use Specify;
public function _fixtures() {
return [
@@ -21,23 +17,18 @@ class ChangeUsernameFormTest extends TestCase {
];
}
public function setUp() {
parent::setUp();
Yii::$app->user->setIdentity($this->getAccount());
}
public function testChange() {
public function testPerformAction() {
$model = new ChangeUsernameForm($this->getAccount(), [
'password' => 'password_0',
'username' => 'my_new_nickname',
]);
$this->assertTrue($model->change());
$this->assertTrue($model->performAction());
$this->assertEquals('my_new_nickname', Account::findOne($this->getAccountId())->username);
$this->assertInstanceOf(UsernameHistory::class, UsernameHistory::findOne(['username' => 'my_new_nickname']));
$this->tester->canSeeAmqpMessageIsCreated('events');
}
public function testChangeWithoutChange() {
public function testPerformActionWithTheSameUsername() {
$account = $this->getAccount();
$username = $account->username;
$model = new ChangeUsernameForm($account, [
@@ -45,7 +36,7 @@ class ChangeUsernameFormTest extends TestCase {
'username' => $username,
]);
$callTime = time();
$this->assertTrue($model->change());
$this->assertTrue($model->performAction());
$this->assertNull(UsernameHistory::findOne([
'AND',
'username' => $username,
@@ -54,13 +45,13 @@ class ChangeUsernameFormTest extends TestCase {
$this->tester->cantSeeAmqpMessageIsCreated('events');
}
public function testChangeCase() {
public function testPerformActionWithChangeCase() {
$newUsername = mb_strtoupper($this->tester->grabFixture('accounts', 'admin')['username']);
$model = new ChangeUsernameForm($this->getAccount(), [
'password' => 'password_0',
'username' => $newUsername,
]);
$this->assertTrue($model->change());
$this->assertTrue($model->performAction());
$this->assertEquals($newUsername, Account::findOne($this->getAccountId())->username);
$this->assertInstanceOf(
UsernameHistory::class,
@@ -80,12 +71,12 @@ class ChangeUsernameFormTest extends TestCase {
$this->assertEquals('test', $body['oldUsername']);
}
private function getAccount(): AccountIdentity {
return AccountIdentity::findOne($this->getAccountId());
private function getAccount(): Account {
return $this->tester->grabFixture('accounts', 'admin');
}
private function getAccountId() {
return $this->tester->grabFixture('accounts', 'admin')->id;
return $this->getAccount()->id;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace tests\codeception\api\unit\modules\accounts\models;
use api\modules\accounts\models\DisableTwoFactorAuthForm;
use common\helpers\Error as E;
use common\models\Account;
use tests\codeception\api\unit\TestCase;
class DisableTwoFactorAuthFormTest extends TestCase {
public function testPerformAction() {
/** @var Account|\Mockery\MockInterface $account */
$account = mock(Account::class)->makePartial();
$account->shouldReceive('save')->once()->andReturn(true);
$account->is_otp_enabled = true;
$account->otp_secret = 'mock secret';
/** @var DisableTwoFactorAuthForm|\Mockery\MockInterface $model */
$model = mock(DisableTwoFactorAuthForm::class . '[validate]', [$account]);
$model->shouldReceive('validate')->once()->andReturn(true);
$this->assertTrue($model->performAction());
$this->assertNull($account->otp_secret);
$this->assertFalse($account->is_otp_enabled);
}
public function testValidateOtpEnabled() {
$account = new Account();
$account->is_otp_enabled = false;
$model = new DisableTwoFactorAuthForm($account);
$model->validateOtpEnabled('account');
$this->assertEquals([E::OTP_NOT_ENABLED], $model->getErrors('account'));
$account = new Account();
$account->is_otp_enabled = true;
$model = new DisableTwoFactorAuthForm($account);
$model->validateOtpEnabled('account');
$this->assertEmpty($model->getErrors('account'));
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace tests\codeception\api\unit\modules\accounts\models;
use api\components\User\Component;
use api\components\User\Identity;
use api\modules\accounts\models\EnableTwoFactorAuthForm;
use common\helpers\Error as E;
use common\models\Account;
use tests\codeception\api\unit\TestCase;
use Yii;
class EnableTwoFactorAuthFormTest extends TestCase {
public function testPerformAction() {
/** @var Account|\Mockery\MockInterface $account */
$account = mock(Account::class . '[save]');
$account->shouldReceive('save')->andReturn(true);
$account->is_otp_enabled = false;
$account->otp_secret = 'mock secret';
/** @var Component|\Mockery\MockInterface $component */
$component = mock(Component::class . '[terminateSessions]', [[
'identityClass' => Identity::class,
'enableSession' => false,
'loginUrl' => null,
'secret' => 'secret',
]]);
$component->shouldReceive('terminateSessions')->withArgs([$account, Component::KEEP_CURRENT_SESSION]);
Yii::$app->set('user', $component);
/** @var EnableTwoFactorAuthForm|\Mockery\MockInterface $model */
$model = mock(EnableTwoFactorAuthForm::class . '[validate]', [$account]);
$model->shouldReceive('validate')->andReturn(true);
$this->assertTrue($model->performAction());
$this->assertTrue($account->is_otp_enabled);
}
public function testValidateOtpDisabled() {
$account = new Account();
$account->is_otp_enabled = true;
$model = new EnableTwoFactorAuthForm($account);
$model->validateOtpDisabled('account');
$this->assertEquals([E::OTP_ALREADY_ENABLED], $model->getErrors('account'));
$account = new Account();
$account->is_otp_enabled = false;
$model = new EnableTwoFactorAuthForm($account);
$model->validateOtpDisabled('account');
$this->assertEmpty($model->getErrors('account'));
}
}

View File

@@ -1,7 +1,7 @@
<?php
namespace codeception\api\unit\models\profile\ChangeEmail;
namespace tests\codeception\api\unit\modules\accounts\models;
use api\models\profile\ChangeEmail\InitStateForm;
use api\modules\accounts\models\SendEmailVerificationForm;
use common\models\Account;
use common\models\confirmations\CurrentEmailConfirmation;
use common\models\EmailActivation;
@@ -9,7 +9,7 @@ use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\EmailActivationFixture;
class InitStateFormTest extends TestCase {
class SendEmailVerificationFormTest extends TestCase {
public function _fixtures() {
return [
@@ -21,7 +21,7 @@ class InitStateFormTest extends TestCase {
public function testCreateCode() {
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$model = new InitStateForm($account);
$model = new SendEmailVerificationForm($account);
$activationModel = $model->createCode();
$this->assertInstanceOf(CurrentEmailConfirmation::class, $activationModel);
$this->assertEquals($account->id, $activationModel->account_id);
@@ -31,10 +31,10 @@ class InitStateFormTest extends TestCase {
public function testSendCurrentEmailConfirmation() {
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$model = new InitStateForm($account, [
$model = new SendEmailVerificationForm($account, [
'password' => 'password_0',
]);
$this->assertTrue($model->sendCurrentEmailConfirmation());
$this->assertTrue($model->performAction());
$this->assertTrue(EmailActivation::find()->andWhere([
'account_id' => $account->id,
'type' => EmailActivation::TYPE_CURRENT_EMAIL_CONFIRMATION,

View File

@@ -1,15 +1,17 @@
<?php
namespace codeception\api\unit\models\profile\ChangeEmail;
namespace tests\codeception\api\unit\modules\accounts\models;
use api\models\profile\ChangeEmail\NewEmailForm;
use api\modules\accounts\models\SendNewEmailVerificationForm;
use common\models\Account;
use common\models\confirmations\NewEmailConfirmation;
use common\models\EmailActivation;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\EmailActivationFixture;
use tests\codeception\common\helpers\Mock;
use yii\validators\EmailValidator;
class NewEmailFormTest extends TestCase {
class SendNewEmailVerificationFormTest extends TestCase {
public function _fixtures() {
return [
@@ -21,7 +23,7 @@ class NewEmailFormTest extends TestCase {
public function testCreateCode() {
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$model = new NewEmailForm($account);
$model = new SendNewEmailVerificationForm($account);
$model->email = 'my-new-email@ely.by';
$activationModel = $model->createCode();
$this->assertInstanceOf(NewEmailConfirmation::class, $activationModel);
@@ -33,13 +35,14 @@ class NewEmailFormTest extends TestCase {
public function testSendNewEmailConfirmation() {
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'account-with-change-email-init-state');
/** @var NewEmailForm $model */
/** @var SendNewEmailVerificationForm $model */
$key = $this->tester->grabFixture('emailActivations', 'currentChangeEmailConfirmation')['key'];
$model = new NewEmailForm($account, [
$model = new SendNewEmailVerificationForm($account, [
'key' => $key,
'email' => 'my-new-email@ely.by',
]);
$this->assertTrue($model->sendNewEmailConfirmation());
Mock::func(EmailValidator::class, 'checkdnsrr')->andReturn(true);
$this->assertTrue($model->performAction());
$this->assertNull(EmailActivation::findOne($key));
$this->assertNotNull(EmailActivation::findOne([
'account_id' => $account->id,

View File

@@ -0,0 +1,47 @@
<?php
namespace tests\codeception\api\unit\modules\accounts\models;
use api\modules\accounts\models\TwoFactorAuthInfo;
use common\models\Account;
use tests\codeception\api\unit\TestCase;
class TwoFactorAuthInfoTest extends TestCase {
public function testGetCredentials() {
/** @var Account|\Mockery\MockInterface $account */
$account = mock(Account::class . '[save]');
$account->shouldReceive('save')->andReturn(true);
$account->email = 'mock@email.com';
$account->otp_secret = null;
$model = new TwoFactorAuthInfo($account);
$result = $model->getCredentials();
$this->assertTrue(is_array($result));
$this->assertArrayHasKey('qr', $result);
$this->assertArrayHasKey('uri', $result);
$this->assertArrayHasKey('secret', $result);
$this->assertSame($account->otp_secret, $result['secret']);
$this->assertSame(strtoupper($account->otp_secret), $account->otp_secret);
$this->assertStringStartsWith('data:image/svg+xml,<?xml', $result['qr']);
$previous = libxml_use_internal_errors(true);
simplexml_load_string(base64_decode($result['qr']));
libxml_use_internal_errors($previous);
$this->assertEmpty(libxml_get_errors());
/** @var Account|\Mockery\MockInterface $account */
$account = mock(Account::class . '[save]');
$account->shouldReceive('save')->andReturn(true);
$account->email = 'mock@email.com';
$account->otp_secret = 'AAAA';
$model = new TwoFactorAuthInfo($account);
$result = $model->getCredentials();
$this->assertEquals('AAAA', $result['secret']);
}
}

View File

@@ -1,7 +1,6 @@
<?php
namespace codeception\api\unit\modules\authserver\models;
use api\models\AccountIdentity;
use api\models\authentication\LoginForm;
use api\modules\authserver\models\AuthenticateData;
use api\modules\authserver\models\AuthenticationForm;
@@ -117,7 +116,7 @@ class AuthenticationFormTest extends TestCase {
->setMethods(['getAccount'])
->getMock();
$account = new AccountIdentity();
$account = new Account();
$account->username = 'dummy';
$account->email = 'dummy@ely.by';
$account->status = $status;

View File

@@ -2,7 +2,7 @@
namespace tests\codeception\api\unit\modules\internal\models;
use api\modules\internal\helpers\Error as E;
use api\modules\internal\models\BanForm;
use api\modules\accounts\models\BanAccountForm;
use common\models\Account;
use tests\codeception\api\unit\TestCase;
@@ -11,13 +11,13 @@ class BanFormTest extends TestCase {
public function testValidateAccountActivity() {
$account = new Account();
$account->status = Account::STATUS_ACTIVE;
$form = new BanForm($account);
$form = new BanAccountForm($account);
$form->validateAccountActivity();
$this->assertEmpty($form->getErrors('account'));
$account = new Account();
$account->status = Account::STATUS_BANNED;
$form = new BanForm($account);
$form = new BanAccountForm($account);
$form->validateAccountActivity();
$this->assertEquals([E::ACCOUNT_ALREADY_BANNED], $form->getErrors('account'));
}
@@ -32,8 +32,8 @@ class BanFormTest extends TestCase {
->method('save')
->willReturn(true);
$model = new BanForm($account);
$this->assertTrue($model->ban());
$model = new BanAccountForm($account);
$this->assertTrue($model->performAction());
$this->assertEquals(Account::STATUS_BANNED, $account->status);
$this->tester->canSeeAmqpMessageIsCreated('events');
}
@@ -42,14 +42,14 @@ class BanFormTest extends TestCase {
$account = new Account();
$account->id = 3;
$model = new BanForm($account);
$model = new BanAccountForm($account);
$model->createTask();
$message = json_decode($this->tester->grabLastSentAmqpMessage('events')->body, true);
$this->assertSame(3, $message['accountId']);
$this->assertSame(-1, $message['duration']);
$this->assertSame('', $message['message']);
$model = new BanForm($account);
$model = new BanAccountForm($account);
$model->duration = 123;
$model->message = 'test';
$model->createTask();

View File

@@ -2,7 +2,7 @@
namespace tests\codeception\api\unit\modules\internal\models;
use api\modules\internal\helpers\Error as E;
use api\modules\internal\models\PardonForm;
use api\modules\accounts\models\PardonAccountForm;
use common\models\Account;
use tests\codeception\api\unit\TestCase;
@@ -11,13 +11,13 @@ class PardonFormTest extends TestCase {
public function testValidateAccountBanned() {
$account = new Account();
$account->status = Account::STATUS_BANNED;
$form = new PardonForm($account);
$form = new PardonAccountForm($account);
$form->validateAccountBanned();
$this->assertEmpty($form->getErrors('account'));
$account = new Account();
$account->status = Account::STATUS_ACTIVE;
$form = new PardonForm($account);
$form = new PardonAccountForm($account);
$form->validateAccountBanned();
$this->assertEquals([E::ACCOUNT_NOT_BANNED], $form->getErrors('account'));
}
@@ -33,8 +33,8 @@ class PardonFormTest extends TestCase {
->willReturn(true);
$account->status = Account::STATUS_BANNED;
$model = new PardonForm($account);
$this->assertTrue($model->pardon());
$model = new PardonAccountForm($account);
$this->assertTrue($model->performAction());
$this->assertEquals(Account::STATUS_ACTIVE, $account->status);
$this->tester->canSeeAmqpMessageIsCreated('events');
}
@@ -43,7 +43,7 @@ class PardonFormTest extends TestCase {
$account = new Account();
$account->id = 3;
$model = new PardonForm($account);
$model = new PardonAccountForm($account);
$model->createTask();
$message = json_decode($this->tester->grabLastSentAmqpMessage('events')->body, true);
$this->assertSame(3, $message['accountId']);

View File

@@ -1,7 +1,6 @@
<?php
namespace tests\codeception\api\traits;
use api\models\AccountIdentity;
use api\traits\AccountFinder;
use Codeception\Specify;
use common\models\Account;
@@ -18,50 +17,27 @@ class AccountFinderTest extends TestCase {
}
public function testGetAccount() {
$this->specify('founded account for passed login data', function() {
$model = new AccountFinderTestTestClass();
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$model->login = $account->email;
$account = $model->getAccount();
expect($account)->isInstanceOf(Account::class);
expect($account->id)->equals($account->id);
});
$model = new AccountFinderTestTestClass();
/** @var Account $account */
$accountFixture = $this->tester->grabFixture('accounts', 'admin');
$model->login = $accountFixture->email;
$account = $model->getAccount();
$this->assertInstanceOf(Account::class, $account);
$this->assertSame($accountFixture->id, $account->id, 'founded account for passed login data');
$this->specify('founded account for passed login data with changed account model class name', function() {
/** @var AccountFinderTestTestClass $model */
$model = new class extends AccountFinderTestTestClass {
protected function getAccountClassName() {
return AccountIdentity::class;
}
};
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$model->login = $account->email;
$account = $model->getAccount();
expect($account)->isInstanceOf(AccountIdentity::class);
expect($account->id)->equals($account->id);
});
$this->specify('null, if account not founded', function() {
$model = new AccountFinderTestTestClass();
$model->login = 'unexpected';
expect($account = $model->getAccount())->null();
});
$model = new AccountFinderTestTestClass();
$model->login = 'unexpected';
$this->assertNull($account = $model->getAccount(), 'null, if account can\'t be found');
}
public function testGetLoginAttribute() {
$this->specify('if login look like email value, then \'email\'', function() {
$model = new AccountFinderTestTestClass();
$model->login = 'erickskrauch@ely.by';
expect($model->getLoginAttribute())->equals('email');
});
$model = new AccountFinderTestTestClass();
$model->login = 'erickskrauch@ely.by';
$this->assertEquals('email', $model->getLoginAttribute(), 'if login look like email value, then \'email\'');
$this->specify('username in any other case', function() {
$model = new AccountFinderTestTestClass();
$model->login = 'erickskrauch';
expect($model->getLoginAttribute())->equals('username');
});
$model = new AccountFinderTestTestClass();
$model->login = 'erickskrauch';
$this->assertEquals('username', $model->getLoginAttribute(), 'username in any other case');
}
}
@@ -71,7 +47,7 @@ class AccountFinderTestTestClass {
public $login;
public function getLogin() {
public function getLogin(): string {
return $this->login;
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace tests\codeception\common\helpers;
use phpmock\mockery\PHPMockery;
use ReflectionClass;
class Mock {
/**
* @param string $className
* @param string $function
*
* @return \Mockery\Expectation
*/
public static function func(string $className, string $function) {
return PHPMockery::mock(self::getClassNamespace($className), $function);
}
private static function getClassNamespace(string $className): string {
return (new ReflectionClass($className))->getNamespaceName();
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace tests\codeception\common\unit\rbac\rules;
use api\components\User\Component;
use api\components\User\IdentityInterface;
use common\models\Account;
use common\rbac\rules\AccountOwner;
use tests\codeception\common\unit\TestCase;
use Yii;
use yii\rbac\Item;
use const common\LATEST_RULES_VERSION;
class AccountOwnerTest extends TestCase {
public function testExecute() {
$rule = new AccountOwner();
$item = new Item();
$account = new Account();
$account->id = 1;
$account->status = Account::STATUS_ACTIVE;
$account->rules_agreement_version = LATEST_RULES_VERSION;
$identity = mock(IdentityInterface::class);
$identity->shouldReceive('getAccount')->andReturn($account);
$component = mock(Component::class . '[findIdentityByAccessToken]', [['secret' => 'secret']]);
$component->shouldDeferMissing();
$component->shouldReceive('findIdentityByAccessToken')->withArgs(['token'])->andReturn($identity);
Yii::$app->set('user', $component);
$this->assertFalse($rule->execute('token', $item, ['accountId' => 2]));
$this->assertFalse($rule->execute('token', $item, ['accountId' => '2']));
$this->assertTrue($rule->execute('token', $item, ['accountId' => 1]));
$this->assertTrue($rule->execute('token', $item, ['accountId' => '1']));
$account->rules_agreement_version = null;
$this->assertFalse($rule->execute('token', $item, ['accountId' => 1]));
$this->assertTrue($rule->execute('token', $item, ['accountId' => 1, 'optionalRules' => true]));
$account->rules_agreement_version = LATEST_RULES_VERSION;
$account->status = Account::STATUS_BANNED;
$this->assertFalse($rule->execute('token', $item, ['accountId' => 1]));
$this->assertFalse($rule->execute('token', $item, ['accountId' => 1, 'optionalRules' => true]));
}
/**
* @expectedException \yii\base\InvalidParamException
*/
public function testExecuteWithException() {
(new AccountOwner())->execute('', new Item(), []);
}
}

View File

@@ -3,8 +3,10 @@ namespace codeception\common\unit\validators;
use common\validators\EmailValidator;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\helpers\Mock;
use tests\codeception\common\unit\TestCase;
use yii\base\Model;
use yii\validators\EmailValidator as YiiEmailValidator;
class EmailValidatorTest extends TestCase {
@@ -29,6 +31,7 @@ class EmailValidatorTest extends TestCase {
}
public function testValidateAttributeLength() {
Mock::func(YiiEmailValidator::class, 'checkdnsrr')->andReturnTrue();
$model = $this->createModel(
'emailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemail' .
'emailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemail' .
@@ -44,6 +47,8 @@ class EmailValidatorTest extends TestCase {
}
public function testValidateAttributeEmail() {
Mock::func(YiiEmailValidator::class, 'checkdnsrr')->times(3)->andReturnValues([false, false, true]);
$model = $this->createModel('non-email');
$this->validator->validateAttribute($model, 'field');
$this->assertEquals(['error.email_invalid'], $model->getErrors('field'));
@@ -58,6 +63,8 @@ class EmailValidatorTest extends TestCase {
}
public function testValidateAttributeTempmail() {
Mock::func(YiiEmailValidator::class, 'checkdnsrr')->times(2)->andReturnTrue();
$model = $this->createModel('ibrpycwyjdnt@dropmail.me');
$this->validator->validateAttribute($model, 'field');
$this->assertEquals(['error.email_is_tempmail'], $model->getErrors('field'));
@@ -68,6 +75,8 @@ class EmailValidatorTest extends TestCase {
}
public function testValidateAttributeUnique() {
Mock::func(YiiEmailValidator::class, 'checkdnsrr')->times(3)->andReturnTrue();
$this->tester->haveFixtures([
'accounts' => AccountFixture::class,
]);

View File

@@ -11,6 +11,7 @@ services:
- testredis
volumes:
- ./..:/var/www/html
- ./.bash_history:/root/.bash_history
environment:
YII_DEBUG: "true"
YII_ENV: "test"