From 9c658f5bd956bb973261293deb40c6b42c960186 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Mon, 29 Aug 2016 02:17:45 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=D1=8B=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20AuthorizationForm=20=D0=B8=20=D0=BA=D0=B0=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D0=BC=D0=BD=D0=BE=D0=B3=D0=BE=20RequiredValidator=20?= =?UTF-8?q?=D0=92=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8C=20Authserver=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=85?= =?UTF-8?q?=D0=B5=D0=BB=D0=BF=D0=B5=D1=80=D1=8B=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BB=D0=BE=D0=B3=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B0=20=D0=B2=20Minecr?= =?UTF-8?q?aftAccessKey=20=D0=A3=D1=81=D0=BA=D0=BE=D1=80=D0=B5=D0=BD=D0=BE?= =?UTF-8?q?=20=D1=82=D0=B5=D1=81=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B2=D1=81=D0=B5=D0=B3=D0=BE=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/models/authentication/LoginForm.php | 1 - api/modules/authserver/Module.php | 9 ++ .../authserver/models/AuthenticationForm.php | 40 +++-- common/models/MinecraftAccessKey.php | 2 +- .../models/AuthenticationFormTest.php | 147 ++++++++++++++++++ .../validators/RequiredValidatorTest.php | 24 +++ .../fixtures/MinecraftAccessKeyFixture.php | 17 ++ .../fixtures/data/minecraft-access-keys.php | 10 ++ tests/codeception/config/config.php | 4 + 9 files changed, 241 insertions(+), 13 deletions(-) create mode 100644 tests/codeception/api/unit/modules/authserver/models/AuthenticationFormTest.php create mode 100644 tests/codeception/api/unit/modules/authserver/validators/RequiredValidatorTest.php create mode 100644 tests/codeception/common/fixtures/MinecraftAccessKeyFixture.php create mode 100644 tests/codeception/common/fixtures/data/minecraft-access-keys.php diff --git a/api/models/authentication/LoginForm.php b/api/models/authentication/LoginForm.php index c860d02..d56e7b6 100644 --- a/api/models/authentication/LoginForm.php +++ b/api/models/authentication/LoginForm.php @@ -52,7 +52,6 @@ class LoginForm extends ApiForm { } public function validateActivity($attribute) { - // TODO: проверить, не заблокирован ли аккаунт if (!$this->hasErrors()) { $account = $this->getAccount(); if ($account->status === Account::STATUS_BANNED) { diff --git a/api/modules/authserver/Module.php b/api/modules/authserver/Module.php index 747a616..2e1e041 100644 --- a/api/modules/authserver/Module.php +++ b/api/modules/authserver/Module.php @@ -1,6 +1,7 @@ validate(); - Yii::info("Trying to authenticate user by login = '{$this->username}'.", 'legacy-authentication'); + Authserver::info("Trying to authenticate user by login = '{$this->username}'."); - $loginForm = new LoginForm(); + $loginForm = $this->createLoginForm(); $loginForm->login = $this->username; $loginForm->password = $this->password; if (!$loginForm->validate()) { $errors = $loginForm->getFirstErrors(); if (isset($errors['login'])) { - Yii::error("Cannot find user by login = '{$this->username}", 'legacy-authentication'); + if ($errors['login'] === E::ACCOUNT_BANNED) { + Authserver::error("User with login = '{$this->username}' is banned"); + throw new ForbiddenOperationException('This account has been suspended.'); + } else { + Authserver::error("Cannot find user by login = '{$this->username}'"); + } } elseif (isset($errors['password'])) { - Yii::error("User with login = '{$this->username}' passed wrong password.", 'legacy-authentication'); + Authserver::error("User with login = '{$this->username}' passed wrong password."); } // На старом сервере авторизации использовалось поле nickname, а не username, так что сохраняем эту логику @@ -45,16 +52,27 @@ class AuthenticationForm extends Form { $attribute = 'nickname'; } - // TODO: если аккаунт заблокирован, то возвращалось сообщение return "This account has been suspended." // TODO: эта логика дублируется с логикой в SignoutForm throw new ForbiddenOperationException("Invalid credentials. Invalid {$attribute} or password."); } $account = $loginForm->getAccount(); + $accessTokenModel = $this->createMinecraftAccessToken($account); + $dataModel = new AuthenticateData($accessTokenModel); + Authserver::info("User with id = {$account->id}, username = '{$account->username}' and email = '{$account->email}' successfully logged in."); + + return $dataModel; + } + + protected function createMinecraftAccessToken(Account $account) : MinecraftAccessKey { /** @var MinecraftAccessKey|null $accessTokenModel */ - $accessTokenModel = MinecraftAccessKey::findOne(['client_token' => $this->clientToken]); + $accessTokenModel = MinecraftAccessKey::findOne([ + 'client_token' => $this->clientToken, + 'account_id' => $account->id, + ]); + if ($accessTokenModel === null) { $accessTokenModel = new MinecraftAccessKey(); $accessTokenModel->client_token = $this->clientToken; @@ -64,11 +82,11 @@ class AuthenticationForm extends Form { $accessTokenModel->refreshPrimaryKeyValue(); } - $dataModel = new AuthenticateData($accessTokenModel); + return $accessTokenModel; + } - Yii::info("User with id = {$account->id}, username = '{$account->username}' and email = '{$account->email}' successfully logged in.", 'legacy-authentication'); - - return $dataModel; + protected function createLoginForm() : LoginForm { + return new LoginForm(); } } diff --git a/common/models/MinecraftAccessKey.php b/common/models/MinecraftAccessKey.php index ea9f59c..5df1712 100644 --- a/common/models/MinecraftAccessKey.php +++ b/common/models/MinecraftAccessKey.php @@ -54,7 +54,7 @@ class MinecraftAccessKey extends ActiveRecord { } public function isActual() : bool { - return $this->timestamp + self::LIFETIME >= time(); + return $this->updated_at + self::LIFETIME >= time(); } } diff --git a/tests/codeception/api/unit/modules/authserver/models/AuthenticationFormTest.php b/tests/codeception/api/unit/modules/authserver/models/AuthenticationFormTest.php new file mode 100644 index 0000000..39b9cfa --- /dev/null +++ b/tests/codeception/api/unit/modules/authserver/models/AuthenticationFormTest.php @@ -0,0 +1,147 @@ + AccountFixture::class, + 'minecraftAccessKeys' => MinecraftAccessKeyFixture::class, + ]; + } + + /** + * @expectedException \api\modules\authserver\exceptions\ForbiddenOperationException + * @expectedExceptionMessage Invalid credentials. Invalid nickname or password. + */ + public function testAuthenticateByWrongNicknamePass() { + $authForm = $this->createAuthForm(); + + $authForm->username = 'wrong-username'; + $authForm->password = 'wrong-password'; + $authForm->clientToken = Uuid::uuid4(); + + $authForm->authenticate(); + } + + /** + * @expectedException \api\modules\authserver\exceptions\ForbiddenOperationException + * @expectedExceptionMessage Invalid credentials. Invalid email or password. + */ + public function testAuthenticateByWrongEmailPass() { + $authForm = $this->createAuthForm(); + + $authForm->username = 'wrong-email@ely.by'; + $authForm->password = 'wrong-password'; + $authForm->clientToken = Uuid::uuid4(); + + $authForm->authenticate(); + } + + /** + * @expectedException \api\modules\authserver\exceptions\ForbiddenOperationException + * @expectedExceptionMessage This account has been suspended. + */ + public function testAuthenticateByValidCredentialsIntoBlockedAccount() { + $authForm = $this->createAuthForm(Account::STATUS_BANNED); + + $authForm->username = 'dummy'; + $authForm->password = 'password_0'; + $authForm->clientToken = Uuid::uuid4(); + + $authForm->authenticate(); + } + + public function testAuthenticateByValidCredentials() { + $authForm = $this->createAuthForm(); + + $minecraftAccessKey = new MinecraftAccessKey(); + $minecraftAccessKey->access_token = Uuid::uuid4(); + $authForm->expects($this->once()) + ->method('createMinecraftAccessToken') + ->will($this->returnValue($minecraftAccessKey)); + + $authForm->username = 'dummy'; + $authForm->password = 'password_0'; + $authForm->clientToken = Uuid::uuid4(); + + $result = $authForm->authenticate(); + $this->assertInstanceOf(AuthenticateData::class, $result); + $this->assertEquals($minecraftAccessKey->access_token, $result->getMinecraftAccessKey()->access_token); + } + + public function testCreateMinecraftAccessToken() { + $authForm = new AuthenticationForm(); + $fixturesCount = count($this->minecraftAccessKeys->data); + $authForm->clientToken = Uuid::uuid4(); + /** @var Account $account */ + $account = $this->accounts->getModel('admin'); + /** @var MinecraftAccessKey $result */ + $result = $this->callProtected($authForm, 'createMinecraftAccessToken', $account); + $this->assertInstanceOf(MinecraftAccessKey::class, $result); + $this->assertEquals($account->id, $result->account_id); + $this->assertEquals($authForm->clientToken, $result->client_token); + $this->assertEquals($fixturesCount + 1, MinecraftAccessKey::find()->count()); + } + + public function testCreateMinecraftAccessTokenWithExistsClientId() { + $authForm = new AuthenticationForm(); + $fixturesCount = count($this->minecraftAccessKeys->data); + $authForm->clientToken = $this->minecraftAccessKeys[0]['client_token']; + /** @var Account $account */ + $account = $this->accounts->getModel('admin'); + /** @var MinecraftAccessKey $result */ + $result = $this->callProtected($authForm, 'createMinecraftAccessToken', $account); + $this->assertInstanceOf(MinecraftAccessKey::class, $result); + $this->assertEquals($account->id, $result->account_id); + $this->assertEquals($authForm->clientToken, $result->client_token); + $this->assertEquals($fixturesCount, MinecraftAccessKey::find()->count()); + } + + private function createAuthForm($status = Account::STATUS_ACTIVE) { + /** @var LoginForm|\PHPUnit_Framework_MockObject_MockObject $loginForm */ + $loginForm = $this->getMockBuilder(LoginForm::class) + ->setMethods(['getAccount']) + ->getMock(); + + $account = new AccountIdentity(); + $account->username = 'dummy'; + $account->email = 'dummy@ely.by'; + $account->status = $status; + $account->setPassword('password_0'); + + $loginForm->expects($this->any()) + ->method('getAccount') + ->will($this->returnValue($account)); + + /** @var AuthenticationForm|\PHPUnit_Framework_MockObject_MockObject $authForm */ + $authForm = $this->getMockBuilder(AuthenticationForm::class) + ->setMethods(['createLoginForm', 'createMinecraftAccessToken']) + ->getMock(); + + $authForm->expects($this->any()) + ->method('createLoginForm') + ->will($this->returnValue($loginForm)); + + return $authForm; + } + + +} diff --git a/tests/codeception/api/unit/modules/authserver/validators/RequiredValidatorTest.php b/tests/codeception/api/unit/modules/authserver/validators/RequiredValidatorTest.php new file mode 100644 index 0000000..8e46c8c --- /dev/null +++ b/tests/codeception/api/unit/modules/authserver/validators/RequiredValidatorTest.php @@ -0,0 +1,24 @@ +assertNull($this->callProtected($validator, 'validateValue', 'dummy')); + } + + /** + * @expectedException \api\modules\authserver\exceptions\IllegalArgumentException + */ + public function testValidateValueEmpty() { + $validator = new RequiredValidator(); + $this->assertNull($this->callProtected($validator, 'validateValue', '')); + } + +} diff --git a/tests/codeception/common/fixtures/MinecraftAccessKeyFixture.php b/tests/codeception/common/fixtures/MinecraftAccessKeyFixture.php new file mode 100644 index 0000000..9fc395f --- /dev/null +++ b/tests/codeception/common/fixtures/MinecraftAccessKeyFixture.php @@ -0,0 +1,17 @@ + 'e7bb6648-2183-4981-9b86-eba5e7f87b42', + 'client_token' => '6f380440-0c05-47bd-b7c6-d011f1b5308f', + 'account_id' => 1, + 'created_at' => 1472423530, + 'updated_at' => 1472423530, + ], +]; diff --git a/tests/codeception/config/config.php b/tests/codeception/config/config.php index c9038e1..395f425 100644 --- a/tests/codeception/config/config.php +++ b/tests/codeception/config/config.php @@ -30,5 +30,9 @@ return [ 'password' => 'tester-password', 'vhost' => '/account.ely.by/tests', ], + 'security' => [ + // Для тестов нам не сильно важна безопасность, а вот время прохождения тестов значительно сокращается + 'passwordHashCost' => 4, + ], ], ];