diff --git a/api/modules/authserver/models/AuthenticateData.php b/api/modules/authserver/models/AuthenticateData.php index 6ecacde..8706c67 100644 --- a/api/modules/authserver/models/AuthenticateData.php +++ b/api/modules/authserver/models/AuthenticateData.php @@ -4,39 +4,33 @@ declare(strict_types=1); namespace api\modules\authserver\models; use common\models\Account; -use Lcobucci\JWT\Token; -class AuthenticateData { +final class AuthenticateData { - /** - * @var Account - */ - private $account; + private Account $account; - /** - * @var Token - */ - private $accessToken; + private string $accessToken; - /** - * @var string - */ - private $clientToken; + private string $clientToken; - public function __construct(Account $account, string $accessToken, string $clientToken) { + private bool $requestUser; + + public function __construct(Account $account, string $accessToken, string $clientToken, bool $requestUser) { $this->account = $account; $this->accessToken = $accessToken; $this->clientToken = $clientToken; + $this->requestUser = $requestUser; } public function getResponseData(bool $includeAvailableProfiles = false): array { + $uuid = str_replace('-', '', $this->account->uuid); $result = [ 'accessToken' => $this->accessToken, 'clientToken' => $this->clientToken, 'selectedProfile' => [ - 'id' => str_replace('-', '', $this->account->uuid), + // Might contain a lot more fields, but even Mojang returns only those: + 'id' => $uuid, 'name' => $this->account->username, - 'legacy' => false, ], ]; @@ -46,6 +40,20 @@ class AuthenticateData { $result['availableProfiles'] = $availableProfiles; } + if ($this->requestUser) { + // There are a lot of fields, but even Mojang returns only those: + $result['user'] = [ + 'id' => $uuid, + 'username' => $this->account->username, + 'properties' => [ + [ + 'name' => 'preferredLanguage', + 'value' => $this->account->lang, + ], + ], + ]; + } + return $result; } diff --git a/api/modules/authserver/models/AuthenticationForm.php b/api/modules/authserver/models/AuthenticationForm.php index 7257ecd..e17056e 100644 --- a/api/modules/authserver/models/AuthenticationForm.php +++ b/api/modules/authserver/models/AuthenticationForm.php @@ -34,10 +34,16 @@ class AuthenticationForm extends ApiForm { */ public $clientToken; + /** + * @var string|bool + */ + public $requestUser; + public function rules(): array { return [ [['username', 'password', 'clientToken'], RequiredValidator::class], [['clientToken'], ClientTokenValidator::class], + [['requestUser'], 'boolean'], ]; } @@ -85,7 +91,7 @@ class AuthenticationForm extends ApiForm { /** @var Account $account */ $account = $loginForm->getAccount(); $token = Yii::$app->tokensFactory->createForMinecraftAccount($account, $this->clientToken); - $dataModel = new AuthenticateData($account, (string)$token, $this->clientToken); + $dataModel = new AuthenticateData($account, (string)$token, $this->clientToken, (bool)$this->requestUser); /** @var OauthSession|null $minecraftOauthSession */ $minecraftOauthSession = $account->getOauthSessions() ->andWhere(['client_id' => OauthClient::UNAUTHORIZED_MINECRAFT_GAME_LAUNCHER]) diff --git a/api/modules/authserver/models/RefreshTokenForm.php b/api/modules/authserver/models/RefreshTokenForm.php index cf632fa..281f0bb 100644 --- a/api/modules/authserver/models/RefreshTokenForm.php +++ b/api/modules/authserver/models/RefreshTokenForm.php @@ -28,10 +28,16 @@ class RefreshTokenForm extends ApiForm { */ public $clientToken; + /** + * @var string|bool + */ + public $requestUser; + public function rules(): array { return [ [['accessToken', 'clientToken'], RequiredValidator::class], [['accessToken'], AccessTokenValidator::class, 'verifyExpiration' => false], + [['requestUser'], 'boolean'], ]; } @@ -83,7 +89,7 @@ class RefreshTokenForm extends ApiForm { $minecraftOauthSession->last_used_at = time(); Assert::true($minecraftOauthSession->save()); - return new AuthenticateData($account, (string)$token, $this->clientToken); + return new AuthenticateData($account, (string)$token, $this->clientToken, (bool)$this->requestUser); } } diff --git a/api/tests/functional/authserver/AuthorizationCest.php b/api/tests/functional/authserver/AuthorizationCest.php index 110e6bf..31a44fe 100644 --- a/api/tests/functional/authserver/AuthorizationCest.php +++ b/api/tests/functional/authserver/AuthorizationCest.php @@ -10,33 +10,70 @@ use Ramsey\Uuid\Uuid; class AuthorizationCest { /** + * # Matrix: + * # * login: username/email + * # * requestUser: true/false + * # * json: true/false + * + * JSON: false * @example {"login": "admin", "password": "password_0"} + * @example {"login": "admin", "password": "password_0", "requestUser": true} * @example {"login": "admin@ely.by", "password": "password_0"} + * @example {"login": "admin@ely.by", "password": "password_0", "requestUser": true} + * + * JSON: true + * @example {"json": true, "login": "admin", "password": "password_0"} + * @example {"json": true, "login": "admin", "password": "password_0", "requestUser": true} + * @example {"json": true, "login": "admin@ely.by", "password": "password_0"} + * @example {"json": true, "login": "admin@ely.by", "password": "password_0", "requestUser": true} */ - public function byFormParamsPostRequest(FunctionalTester $I, Example $example) { - $I->wantTo('authenticate by username and password'); - $I->sendPOST('/api/authserver/authentication/authenticate', [ - 'username' => $example['login'], - 'password' => $example['password'], + public function authenticate(FunctionalTester $I, Example $case) { + $params = [ + 'username' => $case['login'], + 'password' => $case['password'], 'clientToken' => Uuid::uuid4()->toString(), + ]; + if ($case['requestUser'] ?? false) { + $params['requestUser'] = true; + } + + if ($case['json'] ?? false) { + $params = json_encode($params); + } + + $I->sendPOST('/api/authserver/authentication/authenticate', $params); + + $I->canSeeResponseJsonMatchesJsonPath('$.accessToken'); + $I->canSeeResponseJsonMatchesJsonPath('$.clientToken'); + $I->canSeeResponseContainsJson([ + 'selectedProfile' => [ + 'id' => 'df936908b2e1544d96f82977ec213022', + 'name' => 'Admin', + ], + 'availableProfiles' => [ + [ + 'id' => 'df936908b2e1544d96f82977ec213022', + 'name' => 'Admin', + ], + ], ]); - $this->testSuccessResponse($I); - } - - /** - * @example {"login": "admin", "password": "password_0"} - * @example {"login": "admin@ely.by", "password": "password_0"} - */ - public function byJsonPostRequest(FunctionalTester $I, Example $example) { - $I->wantTo('authenticate by username and password sent via post body'); - $I->sendPOST('/api/authserver/authentication/authenticate', json_encode([ - 'username' => $example['login'], - 'password' => $example['password'], - 'clientToken' => Uuid::uuid4()->toString(), - ])); - - $this->testSuccessResponse($I); + if ($case['requestUser'] ?? false) { + $I->canSeeResponseContainsJson([ + 'user' => [ + 'id' => 'df936908b2e1544d96f82977ec213022', + 'username' => 'Admin', + 'properties' => [ + [ + 'name' => 'preferredLanguage', + 'value' => 'en', + ], + ], + ], + ]); + } else { + $I->cantSeeResponseJsonMatchesJsonPath('$.user'); + } } public function byEmailWithEnabledTwoFactorAuth(FunctionalTester $I) { @@ -125,17 +162,4 @@ class AuthorizationCest { ]); } - 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/api/tests/functional/authserver/RefreshCest.php b/api/tests/functional/authserver/RefreshCest.php index fb9323c..a9a7ded 100644 --- a/api/tests/functional/authserver/RefreshCest.php +++ b/api/tests/functional/authserver/RefreshCest.php @@ -9,23 +9,33 @@ use Ramsey\Uuid\Uuid; class RefreshCest { - public function refresh(AuthserverSteps $I) { + /** + * @example [true] + * @example [false] + */ + public function refresh(AuthserverSteps $I, Example $case) { $I->wantTo('refresh accessToken'); [$accessToken, $clientToken] = $I->amAuthenticated(); $I->sendPOST('/api/authserver/authentication/refresh', [ 'accessToken' => $accessToken, 'clientToken' => $clientToken, + 'requestUser' => $case[0], ]); - $this->assertSuccessResponse($I); + $this->assertSuccessResponse($I, $case[0]); } - public function refreshLegacyAccessToken(AuthserverSteps $I) { + /** + * @example [true] + * @example [false] + */ + public function refreshLegacyAccessToken(AuthserverSteps $I, Example $case) { $I->wantTo('refresh legacy accessToken'); $I->sendPOST('/api/authserver/authentication/refresh', [ 'accessToken' => 'e7bb6648-2183-4981-9b86-eba5e7f87b42', 'clientToken' => '6f380440-0c05-47bd-b7c6-d011f1b5308f', + 'requestUser' => $case[0], ]); - $this->assertSuccessResponse($I); + $this->assertSuccessResponse($I, $case[0]); } public function refreshWithInvalidClientToken(AuthserverSteps $I) { @@ -63,7 +73,7 @@ class RefreshCest { 'accessToken' => $example['accessToken'], 'clientToken' => $example['clientToken'], ]); - $this->assertSuccessResponse($I); + $this->assertSuccessResponse($I, false); } public function wrongArguments(AuthserverSteps $I) { @@ -119,15 +129,36 @@ class RefreshCest { ]); } - private function assertSuccessResponse(AuthserverSteps $I) { + private function assertSuccessResponse(AuthserverSteps $I, bool $requestUser) { $I->seeResponseCodeIs(200); $I->seeResponseIsJson(); $I->canSeeResponseJsonMatchesJsonPath('$.accessToken'); $I->canSeeResponseJsonMatchesJsonPath('$.clientToken'); + $I->canSeeResponseContainsJson([ + 'selectedProfile' => [ + 'id' => 'df936908b2e1544d96f82977ec213022', + 'name' => 'Admin', + ], + ]); $I->canSeeResponseJsonMatchesJsonPath('$.selectedProfile.id'); $I->canSeeResponseJsonMatchesJsonPath('$.selectedProfile.name'); - $I->canSeeResponseJsonMatchesJsonPath('$.selectedProfile.legacy'); $I->cantSeeResponseJsonMatchesJsonPath('$.availableProfiles'); + if ($requestUser) { + $I->canSeeResponseContainsJson([ + 'user' => [ + 'id' => 'df936908b2e1544d96f82977ec213022', + 'username' => 'Admin', + 'properties' => [ + [ + 'name' => 'preferredLanguage', + 'value' => 'en', + ], + ], + ], + ]); + } else { + $I->cantSeeResponseJsonMatchesJsonPath('$.user'); + } } } diff --git a/api/tests/unit/modules/authserver/models/AuthenticationFormTest.php b/api/tests/unit/modules/authserver/models/AuthenticationFormTest.php index 75fbedb..ec4317a 100644 --- a/api/tests/unit/modules/authserver/models/AuthenticationFormTest.php +++ b/api/tests/unit/modules/authserver/models/AuthenticationFormTest.php @@ -31,11 +31,24 @@ class AuthenticationFormTest extends TestCase { $this->assertSame($authForm->clientToken, $result['clientToken']); $this->assertSame('df936908b2e1544d96f82977ec213022', $result['selectedProfile']['id']); $this->assertSame('Admin', $result['selectedProfile']['name']); - $this->assertFalse($result['selectedProfile']['legacy']); $this->assertTrue(OauthSession::find()->andWhere([ 'account_id' => 1, 'client_id' => OauthClient::UNAUTHORIZED_MINECRAFT_GAME_LAUNCHER, ])->exists()); + $this->assertArrayNotHasKey('user', $result); + + $authForm->requestUser = true; + $result = $authForm->authenticate()->getResponseData(); + $this->assertSame([ + 'id' => 'df936908b2e1544d96f82977ec213022', + 'username' => 'Admin', + 'properties' => [ + [ + 'name' => 'preferredLanguage', + 'value' => 'en', + ], + ], + ], $result['user']); } /**