diff --git a/api/modules/authserver/controllers/AuthenticationController.php b/api/modules/authserver/controllers/AuthenticationController.php index adb7d57..017549b 100644 --- a/api/modules/authserver/controllers/AuthenticationController.php +++ b/api/modules/authserver/controllers/AuthenticationController.php @@ -30,14 +30,14 @@ class AuthenticationController extends Controller { return $model->authenticate()->getResponseData(true); } - public function refreshAction() { + public function actionRefresh() { $model = new models\RefreshTokenForm(); $model->loadByPost(); return $model->refresh()->getResponseData(false); } - public function validateAction() { + public function actionValidate() { $model = new models\ValidateForm(); $model->loadByPost(); $model->validateToken(); @@ -45,7 +45,7 @@ class AuthenticationController extends Controller { // которое обработает ErrorHandler } - public function signoutAction() { + public function actionSignout() { $model = new models\SignoutForm(); $model->loadByPost(); $model->signout(); @@ -53,7 +53,7 @@ class AuthenticationController extends Controller { // которое обработает ErrorHandler } - public function invalidateAction() { + public function actionInvalidate() { $model = new models\InvalidateForm(); $model->loadByPost(); $model->invalidateToken(); diff --git a/api/modules/authserver/models/Form.php b/api/modules/authserver/models/Form.php index 0ac01b9..c749538 100644 --- a/api/modules/authserver/models/Form.php +++ b/api/modules/authserver/models/Form.php @@ -6,6 +6,10 @@ use yii\base\Model; abstract class Form extends Model { + public function formName() { + return ''; + } + public function loadByGet() { return $this->load(Yii::$app->request->get()); } diff --git a/api/modules/authserver/models/RefreshTokenForm.php b/api/modules/authserver/models/RefreshTokenForm.php index 7258b80..33a7ad2 100644 --- a/api/modules/authserver/models/RefreshTokenForm.php +++ b/api/modules/authserver/models/RefreshTokenForm.php @@ -3,18 +3,17 @@ namespace api\modules\authserver\models; use api\modules\authserver\exceptions\ForbiddenOperationException; use api\modules\authserver\validators\RequiredValidator; +use common\models\Account; use common\models\MinecraftAccessKey; class RefreshTokenForm extends Form { public $accessToken; public $clientToken; - public $selectedProfile; - public $requestUser; public function rules() { return [ - [['accessToken', 'clientToken', 'selectedProfile', 'requestUser'], RequiredValidator::class], + [['accessToken', 'clientToken'], RequiredValidator::class], ]; } @@ -34,6 +33,10 @@ class RefreshTokenForm extends Form { throw new ForbiddenOperationException('Invalid token.'); } + if ($accessToken->account->status === Account::STATUS_BANNED) { + throw new ForbiddenOperationException('This account has been suspended.'); + } + $accessToken->refreshPrimaryKeyValue(); $accessToken->update(); diff --git a/api/modules/authserver/models/SignoutForm.php b/api/modules/authserver/models/SignoutForm.php index 64355e5..86bc725 100644 --- a/api/modules/authserver/models/SignoutForm.php +++ b/api/modules/authserver/models/SignoutForm.php @@ -4,6 +4,7 @@ namespace api\modules\authserver\models; use api\models\authentication\LoginForm; use api\modules\authserver\exceptions\ForbiddenOperationException; use api\modules\authserver\validators\RequiredValidator; +use common\helpers\Error as E; use common\models\MinecraftAccessKey; use Yii; @@ -25,6 +26,12 @@ class SignoutForm extends Form { $loginForm->login = $this->username; $loginForm->password = $this->password; if (!$loginForm->validate()) { + $errors = $loginForm->getFirstErrors(); + if (isset($errors['login']) && $errors['login'] === E::ACCOUNT_BANNED) { + // Считаем, что заблокированный может безболезненно выйти + return true; + } + // На старом сервере авторизации использовалось поле nickname, а не username, так что сохраняем эту логику $attribute = $loginForm->getLoginAttribute(); if ($attribute === 'username') { diff --git a/tests/codeception/api/_pages/AuthserverRoute.php b/tests/codeception/api/_pages/AuthserverRoute.php new file mode 100644 index 0000000..0f5fc67 --- /dev/null +++ b/tests/codeception/api/_pages/AuthserverRoute.php @@ -0,0 +1,36 @@ +route = ['authserver/authentication/authenticate']; + $this->actor->sendPOST($this->getUrl(), $params); + } + + public function refresh($params) { + $this->route = ['authserver/authentication/refresh']; + $this->actor->sendPOST($this->getUrl(), $params); + } + + public function validate($params) { + $this->route = ['authserver/authentication/validate']; + $this->actor->sendPOST($this->getUrl(), $params); + } + + public function invalidate($params) { + $this->route = ['authserver/authentication/invalidate']; + $this->actor->sendPOST($this->getUrl(), $params); + } + + public function signout($params) { + $this->route = ['authserver/authentication/signout']; + $this->actor->sendPOST($this->getUrl(), $params); + } + +} diff --git a/tests/codeception/api/_pages/ContactPage.php b/tests/codeception/api/_pages/ContactPage.php deleted file mode 100644 index 1527bbe..0000000 --- a/tests/codeception/api/_pages/ContactPage.php +++ /dev/null @@ -1,26 +0,0 @@ - $value) { - $inputType = $field === 'body' ? 'textarea' : 'input'; - $this->actor->fillField($inputType . '[name="ContactForm[' . $field . ']"]', $value); - } - $this->actor->click('contact-button'); - } - -} diff --git a/tests/codeception/api/functional/_steps/AuthserverSteps.php b/tests/codeception/api/functional/_steps/AuthserverSteps.php new file mode 100644 index 0000000..e065d36 --- /dev/null +++ b/tests/codeception/api/functional/_steps/AuthserverSteps.php @@ -0,0 +1,24 @@ +toString(); + $route->authenticate([ + 'username' => 'admin', + 'password' => 'password_0', + 'clientToken' => $clientToken, + ]); + + $accessToken = $this->grabDataFromResponseByJsonPath('$.accessToken')[0]; + + return [$accessToken, $clientToken]; + } + +} diff --git a/tests/codeception/api/functional/authserver/AuthorizationCest.php b/tests/codeception/api/functional/authserver/AuthorizationCest.php new file mode 100644 index 0000000..b46f120 --- /dev/null +++ b/tests/codeception/api/functional/authserver/AuthorizationCest.php @@ -0,0 +1,96 @@ +route = new AuthserverRoute($I); + } + + public function byName(FunctionalTester $I) { + $I->wantTo('authenticate by username and password'); + $this->route->authenticate([ + 'username' => 'admin', + 'password' => 'password_0', + 'clientToken' => Uuid::uuid4()->toString(), + ]); + + $this->testSuccessResponse($I); + } + + public function byEmail(FunctionalTester $I) { + $I->wantTo('authenticate by email and password'); + $this->route->authenticate([ + 'username' => 'admin@ely.by', + 'password' => 'password_0', + 'clientToken' => Uuid::uuid4()->toString(), + ]); + + $this->testSuccessResponse($I); + } + + public function wrongArguments(FunctionalTester $I) { + $I->wantTo('get error on wrong amount of arguments'); + $this->route->authenticate([ + 'key' => 'value', + ]); + $I->canSeeResponseCodeIs(400); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'error' => 'IllegalArgumentException', + 'errorMessage' => 'credentials can not be null.', + ]); + } + + public function wrongNicknameAndPassword(FunctionalTester $I) { + $I->wantTo('authenticate by username and password with wrong data'); + $this->route->authenticate([ + 'username' => 'nonexistent_user', + 'password' => 'nonexistent_password', + 'clientToken' => Uuid::uuid4()->toString(), + ]); + $I->canSeeResponseCodeIs(401); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'error' => 'ForbiddenOperationException', + 'errorMessage' => 'Invalid credentials. Invalid nickname or password.', + ]); + } + + public function bannedAccount(FunctionalTester $I) { + $I->wantTo('authenticate in suspended account'); + $this->route->authenticate([ + 'username' => 'Banned', + 'password' => 'password_0', + 'clientToken' => Uuid::uuid4()->toString(), + ]); + $I->canSeeResponseCodeIs(401); + $I->canSeeResponseContainsJson([ + 'error' => 'ForbiddenOperationException', + 'errorMessage' => 'This account has been suspended.', + ]); + } + + private function testSuccessResponse(FunctionalTester $I) { + $I->seeResponseCodeIs(200); + $I->seeResponseIsJson(); + $I->canSeeResponseJsonMatchesJsonPath('$.accessToken'); + $I->canSeeResponseJsonMatchesJsonPath('$.clientToken'); + $I->canSeeResponseJsonMatchesJsonPath('$.availableProfiles[0].id'); + $I->canSeeResponseJsonMatchesJsonPath('$.availableProfiles[0].name'); + $I->canSeeResponseJsonMatchesJsonPath('$.availableProfiles[0].legacy'); + $I->canSeeResponseJsonMatchesJsonPath('$.selectedProfile.id'); + $I->canSeeResponseJsonMatchesJsonPath('$.selectedProfile.name'); + $I->canSeeResponseJsonMatchesJsonPath('$.selectedProfile.legacy'); + } + +} diff --git a/tests/codeception/api/functional/authserver/InvalidateCest.php b/tests/codeception/api/functional/authserver/InvalidateCest.php new file mode 100644 index 0000000..7cab088 --- /dev/null +++ b/tests/codeception/api/functional/authserver/InvalidateCest.php @@ -0,0 +1,53 @@ +route = new AuthserverRoute($I); + } + + public function invalidate(AuthserverSteps $I) { + $I->wantTo('invalidate my token'); + list($accessToken, $clientToken) = $I->amAuthenticated(); + $this->route->invalidate([ + 'accessToken' => $accessToken, + 'clientToken' => $clientToken, + ]); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseEquals(''); + } + + public function wrongArguments(AuthserverSteps $I) { + $I->wantTo('get error on wrong amount of arguments'); + $this->route->invalidate([ + 'key' => 'value', + ]); + $I->canSeeResponseCodeIs(400); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'error' => 'IllegalArgumentException', + 'errorMessage' => 'credentials can not be null.', + ]); + } + + public function wrongAccessTokenOrClientToken(AuthserverSteps $I) { + $I->wantTo('invalidate by wrong client and access token'); + $this->route->invalidate([ + 'accessToken' => Uuid::uuid4()->toString(), + 'clientToken' => Uuid::uuid4()->toString(), + ]); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseEquals(''); + } + +} diff --git a/tests/codeception/api/functional/authserver/RefreshCest.php b/tests/codeception/api/functional/authserver/RefreshCest.php new file mode 100644 index 0000000..e26ccf7 --- /dev/null +++ b/tests/codeception/api/functional/authserver/RefreshCest.php @@ -0,0 +1,77 @@ +route = new AuthserverRoute($I); + } + + public function refresh(AuthserverSteps $I) { + $I->wantTo('refresh my accessToken'); + list($accessToken, $clientToken) = $I->amAuthenticated(); + $this->route->refresh([ + 'accessToken' => $accessToken, + 'clientToken' => $clientToken, + ]); + + $I->seeResponseCodeIs(200); + $I->seeResponseIsJson(); + $I->canSeeResponseJsonMatchesJsonPath('$.accessToken'); + $I->canSeeResponseJsonMatchesJsonPath('$.clientToken'); + $I->canSeeResponseJsonMatchesJsonPath('$.selectedProfile.id'); + $I->canSeeResponseJsonMatchesJsonPath('$.selectedProfile.name'); + $I->canSeeResponseJsonMatchesJsonPath('$.selectedProfile.legacy'); + $I->cantSeeResponseJsonMatchesJsonPath('$.availableProfiles'); + } + + public function wrongArguments(AuthserverSteps $I) { + $I->wantTo('get error on wrong amount of arguments'); + $this->route->refresh([ + 'key' => 'value', + ]); + $I->canSeeResponseCodeIs(400); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'error' => 'IllegalArgumentException', + 'errorMessage' => 'credentials can not be null.', + ]); + } + + public function wrongAccessToken(AuthserverSteps $I) { + $I->wantTo('get error on wrong access or client tokens'); + $this->route->refresh([ + 'accessToken' => Uuid::uuid4()->toString(), + 'clientToken' => Uuid::uuid4()->toString(), + ]); + $I->canSeeResponseCodeIs(401); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'error' => 'ForbiddenOperationException', + 'errorMessage' => 'Invalid token.', + ]); + } + + public function refreshTokenFromBannedUser(AuthserverSteps $I) { + $I->wantTo('refresh token from suspended account'); + $this->route->refresh([ + 'accessToken' => '918ecb41-616c-40ee-a7d2-0b0ef0d0d732', + 'clientToken' => '6042634a-a1e2-4aed-866c-c661fe4e63e2', + ]); + $I->canSeeResponseCodeIs(401); + $I->canSeeResponseContainsJson([ + 'error' => 'ForbiddenOperationException', + 'errorMessage' => 'This account has been suspended.', + ]); + } + +} diff --git a/tests/codeception/api/functional/authserver/SignoutCest.php b/tests/codeception/api/functional/authserver/SignoutCest.php new file mode 100644 index 0000000..3ab6300 --- /dev/null +++ b/tests/codeception/api/functional/authserver/SignoutCest.php @@ -0,0 +1,75 @@ +route = new AuthserverRoute($I); + } + + public function byName(AuthserverSteps $I) { + $I->wantTo('signout by nickname and password'); + $this->route->signout([ + 'username' => 'admin', + 'password' => 'password_0', + ]); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseEquals(''); + } + + public function byEmail(AuthserverSteps $I) { + $I->wantTo('signout by email and password'); + $this->route->signout([ + 'username' => 'admin@ely.by', + 'password' => 'password_0', + ]); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseEquals(''); + } + + public function wrongArguments(AuthserverSteps $I) { + $I->wantTo('get error on wrong amount of arguments'); + $this->route->signout([ + 'key' => 'value', + ]); + $I->canSeeResponseCodeIs(400); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'error' => 'IllegalArgumentException', + 'errorMessage' => 'credentials can not be null.', + ]); + } + + public function wrongNicknameAndPassword(AuthserverSteps $I) { + $I->wantTo('signout by nickname and password with wrong data'); + $this->route->signout([ + 'username' => 'nonexistent_user', + 'password' => 'nonexistent_password', + ]); + $I->canSeeResponseCodeIs(401); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'error' => 'ForbiddenOperationException', + 'errorMessage' => 'Invalid credentials. Invalid nickname or password.', + ]); + } + + public function bannedAccount(AuthserverSteps $I) { + $I->wantTo('signout from banned account'); + $this->route->signout([ + 'username' => 'Banned', + 'password' => 'password_0', + ]); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseEquals(''); + } + +} diff --git a/tests/codeception/api/functional/authserver/ValidateCest.php b/tests/codeception/api/functional/authserver/ValidateCest.php new file mode 100644 index 0000000..1cd5b9b --- /dev/null +++ b/tests/codeception/api/functional/authserver/ValidateCest.php @@ -0,0 +1,69 @@ +route = new AuthserverRoute($I); + } + + public function validate(AuthserverSteps $I) { + $I->wantTo('validate my accessToken'); + list($accessToken) = $I->amAuthenticated(); + $this->route->validate([ + 'accessToken' => $accessToken, + ]); + $I->seeResponseCodeIs(200); + $I->canSeeResponseEquals(''); + } + + public function wrongArguments(AuthserverSteps $I) { + $I->wantTo('get error on wrong amount of arguments'); + $this->route->validate([ + 'key' => 'value', + ]); + $I->canSeeResponseCodeIs(400); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'error' => 'IllegalArgumentException', + 'errorMessage' => 'credentials can not be null.', + ]); + } + + public function wrongAccessToken(AuthserverSteps $I) { + $I->wantTo('get error on wrong accessToken'); + $this->route->validate([ + 'accessToken' => Uuid::uuid4()->toString(), + ]); + $I->canSeeResponseCodeIs(401); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'error' => 'ForbiddenOperationException', + 'errorMessage' => 'Invalid token.', + ]); + } + + public function expiredAccessToken(AuthserverSteps $I) { + $I->wantTo('get error on expired accessToken'); + $this->route->validate([ + // Заведомо истёкший токен из дампа + 'accessToken' => '6042634a-a1e2-4aed-866c-c661fe4e63e2', + ]); + $I->canSeeResponseCodeIs(401); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'error' => 'ForbiddenOperationException', + 'errorMessage' => 'Token expired.', + ]); + } + +} diff --git a/tests/codeception/common/_support/FixtureHelper.php b/tests/codeception/common/_support/FixtureHelper.php index c96fcc4..28ab82d 100644 --- a/tests/codeception/common/_support/FixtureHelper.php +++ b/tests/codeception/common/_support/FixtureHelper.php @@ -6,6 +6,7 @@ use Codeception\TestCase; use tests\codeception\common\fixtures\AccountFixture; use tests\codeception\common\fixtures\AccountSessionFixture; use tests\codeception\common\fixtures\EmailActivationFixture; +use tests\codeception\common\fixtures\MinecraftAccessKeyFixture; use tests\codeception\common\fixtures\OauthClientFixture; use tests\codeception\common\fixtures\OauthSessionFixture; use tests\codeception\common\fixtures\UsernameHistoryFixture; @@ -59,6 +60,7 @@ class FixtureHelper extends Module { 'class' => OauthSessionFixture::class, 'dataFile' => '@tests/codeception/common/fixtures/data/oauth-sessions.php', ], + 'minecraftAccessKeys' => MinecraftAccessKeyFixture::class, ]; } diff --git a/tests/codeception/common/fixtures/data/accounts.php b/tests/codeception/common/fixtures/data/accounts.php index cde12ff..00bf8f9 100644 --- a/tests/codeception/common/fixtures/data/accounts.php +++ b/tests/codeception/common/fixtures/data/accounts.php @@ -120,4 +120,17 @@ return [ 'created_at' => 1470499952, 'updated_at' => 1470499952, ], + 'banned-account' => [ + 'id' => 10, + 'uuid' => 'd2e7360e-50cf-4b9b-baa0-c4440a150795', + 'username' => 'Banned', + 'email' => 'banned@ely.by', + 'password_hash' => '$2y$13$2rYkap5T6jG8z/mMK8a3Ou6aZxJcmAaTha6FEuujvHEmybSHRzW5e', # password_0 + 'password_hash_strategy' => \common\models\Account::PASS_HASH_STRATEGY_YII2, + 'lang' => 'en', + 'status' => \common\models\Account::STATUS_BANNED, + 'rules_agreement_version' => \common\LATEST_RULES_VERSION, + 'created_at' => 1472682343, + 'updated_at' => 1472682343, + ], ]; diff --git a/tests/codeception/common/fixtures/data/minecraft-access-keys.php b/tests/codeception/common/fixtures/data/minecraft-access-keys.php index 4ebb60f..ca1ee26 100644 --- a/tests/codeception/common/fixtures/data/minecraft-access-keys.php +++ b/tests/codeception/common/fixtures/data/minecraft-access-keys.php @@ -1,10 +1,24 @@ [ 'access_token' => 'e7bb6648-2183-4981-9b86-eba5e7f87b42', 'client_token' => '6f380440-0c05-47bd-b7c6-d011f1b5308f', 'account_id' => 1, + 'created_at' => time() - 10, + 'updated_at' => time() - 10, + ], + 'expired-token' => [ + 'access_token' => '6042634a-a1e2-4aed-866c-c661fe4e63e2', + 'client_token' => '47fb164a-2332-42c1-8bad-549e67bb210c', + 'account_id' => 1, 'created_at' => 1472423530, 'updated_at' => 1472423530, ], + 'banned-token' => [ + 'access_token' => '918ecb41-616c-40ee-a7d2-0b0ef0d0d732', + 'client_token' => '6042634a-a1e2-4aed-866c-c661fe4e63e2', + 'account_id' => 10, + 'created_at' => time() - 10, + 'updated_at' => time() - 10, + ], ];