Добавлены тесты для включения/отключения OTP

This commit is contained in:
ErickSkrauch 2017-01-23 02:07:29 +03:00
parent be4c7908b2
commit 6aab2592b4
8 changed files with 281 additions and 8 deletions

View File

@ -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,

View File

@ -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;
}

View File

@ -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';

View File

@ -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,
]);
}
}

View 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 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,
]);
}
}

View 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,
]);
}
}

View File

@ -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());
}
}

View File

@ -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,
],
];