mirror of
				https://github.com/elyby/accounts.git
				synced 2025-05-31 14:11:46 +05:30 
			
		
		
		
	Добавлены тесты для включения/отключения OTP
This commit is contained in:
		@@ -17,16 +17,22 @@ class TwoFactorAuthController extends Controller {
 | 
			
		||||
                'class' => AccessControl::class,
 | 
			
		||||
                'rules' => [
 | 
			
		||||
                    [
 | 
			
		||||
                        'allow' => true,
 | 
			
		||||
                        'class' => ActiveUserRule::class,
 | 
			
		||||
                        'actions' => [
 | 
			
		||||
                            'credentials',
 | 
			
		||||
                        ],
 | 
			
		||||
                    ],
 | 
			
		||||
                ],
 | 
			
		||||
            ],
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function verbs() {
 | 
			
		||||
        return [
 | 
			
		||||
            'credentials' => ['GET'],
 | 
			
		||||
            'activate' => ['POST'],
 | 
			
		||||
            'disable' => ['DELETE'],
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function actionCredentials() {
 | 
			
		||||
        $account = Yii::$app->user->identity;
 | 
			
		||||
        $model = new TwoFactorAuthForm($account);
 | 
			
		||||
@@ -37,6 +43,7 @@ class TwoFactorAuthController extends Controller {
 | 
			
		||||
    public function actionActivate() {
 | 
			
		||||
        $account = Yii::$app->user->identity;
 | 
			
		||||
        $model = new TwoFactorAuthForm($account, ['scenario' => TwoFactorAuthForm::SCENARIO_ACTIVATE]);
 | 
			
		||||
        $model->load(Yii::$app->request->post());
 | 
			
		||||
        if (!$model->activate()) {
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => false,
 | 
			
		||||
@@ -52,6 +59,7 @@ class TwoFactorAuthController extends Controller {
 | 
			
		||||
    public function actionDisable() {
 | 
			
		||||
        $account = Yii::$app->user->identity;
 | 
			
		||||
        $model = new TwoFactorAuthForm($account, ['scenario' => TwoFactorAuthForm::SCENARIO_DISABLE]);
 | 
			
		||||
        $model->load(Yii::$app->request->getBodyParams());
 | 
			
		||||
        if (!$model->disable()) {
 | 
			
		||||
            return [
 | 
			
		||||
                'success' => false,
 | 
			
		||||
 
 | 
			
		||||
@@ -61,7 +61,7 @@ class TwoFactorAuthForm extends ApiForm {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function activate(): bool {
 | 
			
		||||
        if (!$this->validate()) {
 | 
			
		||||
        if ($this->scenario !== self::SCENARIO_ACTIVATE || !$this->validate()) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -75,7 +75,7 @@ class TwoFactorAuthForm extends ApiForm {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function disable(): bool {
 | 
			
		||||
        if (!$this->validate()) {
 | 
			
		||||
        if ($this->scenario !== self::SCENARIO_DISABLE || !$this->validate()) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -54,8 +54,8 @@ final class Error {
 | 
			
		||||
    const SUBJECT_REQUIRED = 'error.subject_required';
 | 
			
		||||
    const MESSAGE_REQUIRED = 'error.message_required';
 | 
			
		||||
 | 
			
		||||
    const OTP_TOKEN_REQUIRED = 'error.otp_token_required';
 | 
			
		||||
    const OTP_TOKEN_INCORRECT = 'error.otp_token_incorrect';
 | 
			
		||||
    const OTP_TOKEN_REQUIRED = 'error.token_required';
 | 
			
		||||
    const OTP_TOKEN_INCORRECT = 'error.token_incorrect';
 | 
			
		||||
    const OTP_ALREADY_ENABLED = 'error.otp_already_enabled';
 | 
			
		||||
    const OTP_NOT_ENABLED = 'error.otp_not_enabled';
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,24 @@ use yii\codeception\BasePage;
 | 
			
		||||
 */
 | 
			
		||||
class TwoFactorAuthRoute extends BasePage {
 | 
			
		||||
 | 
			
		||||
    public $route = '/two-factor-auth';
 | 
			
		||||
 | 
			
		||||
    public function credentials() {
 | 
			
		||||
        $this->route = '/two-factor-auth';
 | 
			
		||||
        $this->actor->sendGET($this->getUrl());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function enable($token = null, $password = null) {
 | 
			
		||||
        $this->actor->sendPOST($this->getUrl(), [
 | 
			
		||||
            'token' => $token,
 | 
			
		||||
            'password' => $password,
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function disable($token = null, $password = null) {
 | 
			
		||||
        $this->actor->sendDELETE($this->getUrl(), [
 | 
			
		||||
            'token' => $token,
 | 
			
		||||
            'password' => $password,
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,61 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace tests\codeception\api\functional;
 | 
			
		||||
 | 
			
		||||
use OTPHP\TOTP;
 | 
			
		||||
use tests\codeception\api\_pages\TwoFactorAuthRoute;
 | 
			
		||||
use tests\codeception\api\FunctionalTester;
 | 
			
		||||
 | 
			
		||||
class TwoFactorAuthDisableCest {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var TwoFactorAuthRoute
 | 
			
		||||
     */
 | 
			
		||||
    private $route;
 | 
			
		||||
 | 
			
		||||
    public function _before(FunctionalTester $I) {
 | 
			
		||||
        $this->route = new TwoFactorAuthRoute($I);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testFails(FunctionalTester $I) {
 | 
			
		||||
        $I->loggedInAsActiveAccount('AccountWithEnabledOtp', 'password_0');
 | 
			
		||||
 | 
			
		||||
        $this->route->disable();
 | 
			
		||||
        $I->canSeeResponseContainsJson([
 | 
			
		||||
            'success' => false,
 | 
			
		||||
            'errors' => [
 | 
			
		||||
                'token' => 'error.token_required',
 | 
			
		||||
                'password' => 'error.password_required',
 | 
			
		||||
            ],
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        $this->route->disable('123456', 'invalid_password');
 | 
			
		||||
        $I->canSeeResponseContainsJson([
 | 
			
		||||
            'success' => false,
 | 
			
		||||
            'errors' => [
 | 
			
		||||
                'token' => 'error.token_incorrect',
 | 
			
		||||
                'password' => 'error.password_incorrect',
 | 
			
		||||
            ],
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        $I->loggedInAsActiveAccount('AccountWithOtpSecret', 'password_0');
 | 
			
		||||
        $this->route->disable('123456', 'invalid_password');
 | 
			
		||||
        $I->canSeeResponseContainsJson([
 | 
			
		||||
            'success' => false,
 | 
			
		||||
            'errors' => [
 | 
			
		||||
                'account' => 'error.otp_not_enabled',
 | 
			
		||||
            ],
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testSuccessEnable(FunctionalTester $I) {
 | 
			
		||||
        $I->loggedInAsActiveAccount('AccountWithEnabledOtp', 'password_0');
 | 
			
		||||
        $totp = new TOTP(null, 'secret-secret-secret');
 | 
			
		||||
        $this->route->disable($totp->now(), 'password_0');
 | 
			
		||||
        $I->canSeeResponseCodeIs(200);
 | 
			
		||||
        $I->canSeeResponseIsJson();
 | 
			
		||||
        $I->canSeeResponseContainsJson([
 | 
			
		||||
            'success' => true,
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										61
									
								
								tests/codeception/api/functional/TwoFactorAuthEnableCest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								tests/codeception/api/functional/TwoFactorAuthEnableCest.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace tests\codeception\api\functional;
 | 
			
		||||
 | 
			
		||||
use OTPHP\TOTP;
 | 
			
		||||
use tests\codeception\api\_pages\TwoFactorAuthRoute;
 | 
			
		||||
use tests\codeception\api\FunctionalTester;
 | 
			
		||||
 | 
			
		||||
class TwoFactorAuthEnableCest {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var TwoFactorAuthRoute
 | 
			
		||||
     */
 | 
			
		||||
    private $route;
 | 
			
		||||
 | 
			
		||||
    public function _before(FunctionalTester $I) {
 | 
			
		||||
        $this->route = new TwoFactorAuthRoute($I);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testFails(FunctionalTester $I) {
 | 
			
		||||
        $I->loggedInAsActiveAccount('AccountWithOtpSecret', 'password_0');
 | 
			
		||||
 | 
			
		||||
        $this->route->enable();
 | 
			
		||||
        $I->canSeeResponseContainsJson([
 | 
			
		||||
            'success' => false,
 | 
			
		||||
            'errors' => [
 | 
			
		||||
                'token' => 'error.token_required',
 | 
			
		||||
                'password' => 'error.password_required',
 | 
			
		||||
            ],
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        $this->route->enable('123456', 'invalid_password');
 | 
			
		||||
        $I->canSeeResponseContainsJson([
 | 
			
		||||
            'success' => false,
 | 
			
		||||
            'errors' => [
 | 
			
		||||
                'token' => 'error.token_incorrect',
 | 
			
		||||
                'password' => 'error.password_incorrect',
 | 
			
		||||
            ],
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        $I->loggedInAsActiveAccount('AccountWithEnabledOtp', 'password_0');
 | 
			
		||||
        $this->route->enable('123456', 'invalid_password');
 | 
			
		||||
        $I->canSeeResponseContainsJson([
 | 
			
		||||
            'success' => false,
 | 
			
		||||
            'errors' => [
 | 
			
		||||
                'account' => 'error.otp_already_enabled',
 | 
			
		||||
            ],
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testSuccessEnable(FunctionalTester $I) {
 | 
			
		||||
        $I->loggedInAsActiveAccount('AccountWithOtpSecret', 'password_0');
 | 
			
		||||
        $totp = new TOTP(null, 'some otp secret value');
 | 
			
		||||
        $this->route->enable($totp->now(), 'password_0');
 | 
			
		||||
        $I->canSeeResponseCodeIs(200);
 | 
			
		||||
        $I->canSeeResponseIsJson();
 | 
			
		||||
        $I->canSeeResponseContainsJson([
 | 
			
		||||
            'success' => true,
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -2,7 +2,9 @@
 | 
			
		||||
namespace tests\codeception\api\unit\models\profile;
 | 
			
		||||
 | 
			
		||||
use api\models\profile\TwoFactorAuthForm;
 | 
			
		||||
use common\helpers\Error as E;
 | 
			
		||||
use common\models\Account;
 | 
			
		||||
use OTPHP\TOTP;
 | 
			
		||||
use tests\codeception\api\unit\TestCase;
 | 
			
		||||
 | 
			
		||||
class TwoFactorAuthFormTest extends TestCase {
 | 
			
		||||
@@ -64,4 +66,100 @@ class TwoFactorAuthFormTest extends TestCase {
 | 
			
		||||
        $this->assertEquals('some valid totp secret value', $result['secret']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testActivate() {
 | 
			
		||||
        /** @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());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -146,4 +146,34 @@ return [
 | 
			
		||||
        'created_at' => 1474404139,
 | 
			
		||||
        'updated_at' => 1474404149,
 | 
			
		||||
    ],
 | 
			
		||||
    'account-with-otp-secret' => [
 | 
			
		||||
        'id' => 12,
 | 
			
		||||
        'uuid' => '9e9dcd11-2322-46dc-a992-e822a422726e',
 | 
			
		||||
        'username' => 'AccountWithOtpSecret',
 | 
			
		||||
        'email' => 'sava-galkin@mail.ru',
 | 
			
		||||
        'password_hash' => '$2y$13$2rYkap5T6jG8z/mMK8a3Ou6aZxJcmAaTha6FEuujvHEmybSHRzW5e', # password_0
 | 
			
		||||
        'password_hash_strategy' => \common\models\Account::PASS_HASH_STRATEGY_YII2,
 | 
			
		||||
        'lang' => 'ru',
 | 
			
		||||
        'status' => \common\models\Account::STATUS_ACTIVE,
 | 
			
		||||
        'rules_agreement_version' => \common\LATEST_RULES_VERSION,
 | 
			
		||||
        'otp_secret' => 'some otp secret value',
 | 
			
		||||
        'is_otp_enabled' => false,
 | 
			
		||||
        'created_at' => 1485124615,
 | 
			
		||||
        'updated_at' => 1485124615,
 | 
			
		||||
    ],
 | 
			
		||||
    'account-with-enabled-otp' => [
 | 
			
		||||
        'id' => 13,
 | 
			
		||||
        'uuid' => '15d0afa7-a2bb-44d3-9f31-964cbccc6043',
 | 
			
		||||
        'username' => 'AccountWithEnabledOtp',
 | 
			
		||||
        'email' => 'otp@gmail.com',
 | 
			
		||||
        'password_hash' => '$2y$13$2rYkap5T6jG8z/mMK8a3Ou6aZxJcmAaTha6FEuujvHEmybSHRzW5e', # password_0
 | 
			
		||||
        'password_hash_strategy' => \common\models\Account::PASS_HASH_STRATEGY_YII2,
 | 
			
		||||
        'lang' => 'ru',
 | 
			
		||||
        'status' => \common\models\Account::STATUS_ACTIVE,
 | 
			
		||||
        'rules_agreement_version' => \common\LATEST_RULES_VERSION,
 | 
			
		||||
        'otp_secret' => 'secret-secret-secret',
 | 
			
		||||
        'is_otp_enabled' => true,
 | 
			
		||||
        'created_at' => 1485124685,
 | 
			
		||||
        'updated_at' => 1485124685,
 | 
			
		||||
    ],
 | 
			
		||||
];
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user