diff --git a/api/components/OAuth2/RequestTypes/AuthorizationRequestProxy.php b/api/components/OAuth2/RequestTypes/AuthorizationRequestProxy.php deleted file mode 100644 index 0d07bc1..0000000 --- a/api/components/OAuth2/RequestTypes/AuthorizationRequestProxy.php +++ /dev/null @@ -1,97 +0,0 @@ -authorizationRequest = $authorizationRequest; - } - - public function getOriginalAuthorizationRequest(): AuthorizationRequest { - return $this->authorizationRequest; - } - - public function getGrantTypeId(): string { - return $this->authorizationRequest->getGrantTypeId(); - } - - public function setGrantTypeId($grantTypeId): void { - $this->authorizationRequest->setGrantTypeId($grantTypeId); - } - - public function getClient(): ClientEntityInterface { - return $this->authorizationRequest->getClient(); - } - - public function setClient(ClientEntityInterface $client): void { - $this->authorizationRequest->setClient($client); - } - - public function getUser(): UserEntityInterface { - return $this->authorizationRequest->getUser(); - } - - public function setUser(UserEntityInterface $user): void { - $this->authorizationRequest->setUser($user); - } - - public function getScopes(): array { - return $this->authorizationRequest->getScopes(); - } - - public function setScopes(array $scopes): void { - $this->authorizationRequest->setScopes($scopes); - } - - public function isAuthorizationApproved(): bool { - return $this->authorizationRequest->isAuthorizationApproved(); - } - - public function setAuthorizationApproved($authorizationApproved): void { - $this->authorizationRequest->setAuthorizationApproved($authorizationApproved); - } - - public function getRedirectUri(): ?string { - return $this->authorizationRequest->getRedirectUri(); - } - - public function setRedirectUri($redirectUri): void { - $this->authorizationRequest->setRedirectUri($redirectUri); - } - - public function getState(): ?string { - return $this->authorizationRequest->getState(); - } - - public function setState($state): void { - $this->authorizationRequest->setState($state); - } - - public function getCodeChallenge(): string { - return $this->authorizationRequest->getCodeChallenge(); - } - - public function setCodeChallenge($codeChallenge): void { - $this->authorizationRequest->setCodeChallenge($codeChallenge); - } - - public function getCodeChallengeMethod(): string { - return $this->authorizationRequest->getCodeChallengeMethod(); - } - - public function setCodeChallengeMethod($codeChallengeMethod): void { - $this->authorizationRequest->setCodeChallengeMethod($codeChallengeMethod); - } - -} diff --git a/api/components/OAuth2/Traits/ValidateScopesTrait.php b/api/components/OAuth2/Traits/ValidateScopesTrait.php deleted file mode 100644 index c94f65b..0000000 --- a/api/components/OAuth2/Traits/ValidateScopesTrait.php +++ /dev/null @@ -1,12 +0,0 @@ - getenv('JWT_ENCRYPTION_KEY'), ], 'tokensFactory' => [ - 'class' => api\components\Tokens\TokensFactory::class, + 'class' => api\components\Tokens\TokensFactory::class, ], 'log' => [ 'traceLevel' => YII_DEBUG ? 3 : 0, diff --git a/api/modules/authserver/models/RefreshTokenForm.php b/api/modules/authserver/models/RefreshTokenForm.php index 92eb6a5..d719c8d 100644 --- a/api/modules/authserver/models/RefreshTokenForm.php +++ b/api/modules/authserver/models/RefreshTokenForm.php @@ -37,14 +37,16 @@ class RefreshTokenForm extends ApiForm { */ public function refresh(): AuthenticateData { $this->validate(); - + $account = null; if (mb_strlen($this->accessToken) === 36) { /** @var MinecraftAccessKey $token */ $token = MinecraftAccessKey::findOne([ 'access_token' => $this->accessToken, 'client_token' => $this->clientToken, ]); - $account = $token->account; + if ($token !== null) { + $account = $token->account; + } } else { $token = Yii::$app->tokens->parse($this->accessToken); @@ -59,7 +61,11 @@ class RefreshTokenForm extends ApiForm { $account = Account::findOne(['id' => $accountId]); } - if ($account === null || $account->status === Account::STATUS_BANNED) { + if ($account === null) { + throw new ForbiddenOperationException('Invalid token.'); + } + + if ($account->status === Account::STATUS_BANNED) { throw new ForbiddenOperationException('This account has been suspended.'); } diff --git a/api/tests/functional/authserver/RefreshCest.php b/api/tests/functional/authserver/RefreshCest.php index 5245732..1a92c16 100644 --- a/api/tests/functional/authserver/RefreshCest.php +++ b/api/tests/functional/authserver/RefreshCest.php @@ -28,6 +28,31 @@ class RefreshCest { $this->assertSuccessResponse($I); } + public function refreshWithInvalidClientToken(AuthserverSteps $I) { + $I->wantTo('refresh accessToken with not matched client token'); + [$accessToken] = $I->amAuthenticated(); + $I->sendPOST('/api/authserver/authentication/refresh', [ + 'accessToken' => $accessToken, + 'clientToken' => Uuid::uuid4()->toString(), + ]); + $I->canSeeResponseContainsJson([ + 'error' => 'ForbiddenOperationException', + 'errorMessage' => 'Invalid token.', + ]); + } + + public function refreshLegacyAccessTokenWithInvalidClientToken(AuthserverSteps $I) { + $I->wantTo('refresh legacy accessToken with not matched client token'); + $I->sendPOST('/api/authserver/authentication/refresh', [ + 'accessToken' => 'e7bb6648-2183-4981-9b86-eba5e7f87b42', + 'clientToken' => Uuid::uuid4()->toString(), + ]); + $I->canSeeResponseContainsJson([ + 'error' => 'ForbiddenOperationException', + 'errorMessage' => 'Invalid token.', + ]); + } + /** * @example {"accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJpYXQiOjE1NzU0Nzk1NTMsImV4cCI6MTU3NTY1MjM1MywiZWx5LXNjb3BlcyI6Im1pbmVjcmFmdF9zZXJ2ZXJfc2Vzc2lvbiIsImVseS1jbGllbnQtdG9rZW4iOiJkZWY1MDIwMDE2ZTEzMTBmMzM2YzVjYWQzZDdiMTJmYjcyNmVhYzdlYjgyOGUzMzg1MzBhMmFmODdkZTJhMjRiMTVmNzAxNWQ1MjU1MjhiNGZiMjgzMTgxOTA2ODhlMWE4Njk5MjAwMzBlMTQyZmQ5ZWM5ODBlZDkzMWI1Mzc2MzgyMTliMjVjMjI1MjQyYzdmMjgzMjE0NjcyNDg3ZDQ4MTYxYjMwMGU1MGIzYWJlMTYwYjVkMmE4ZWMyMzMwMGJhMGNlMTg3MzYyYTgyMjJiYjQ4OTU0MzM4MDJiNTBlZDBhYzFhMWUwZDk3NDgxNDciLCJzdWIiOiJlbHl8MSJ9.PuM-8rzj4qtD9l0lUANSIWC8yjJe8ifarOYsAjc3r4iYFt0P6za-gzJEPncDC80oCXsYVlJHtrEypcsB9wJFSg", "clientToken": "d1b1162c-3d73-4b35-b64f-7bf68bd0e853"} * @example {"accessToken": "6042634a-a1e2-4aed-866c-c661fe4e63e2", "clientToken": "47fb164a-2332-42c1-8bad-549e67bb210c"} diff --git a/api/tests/unit/modules/authserver/models/AuthenticationFormTest.php b/api/tests/unit/modules/authserver/models/AuthenticationFormTest.php index d6ebf7e..01c352f 100644 --- a/api/tests/unit/modules/authserver/models/AuthenticationFormTest.php +++ b/api/tests/unit/modules/authserver/models/AuthenticationFormTest.php @@ -3,139 +3,61 @@ declare(strict_types=1); namespace codeception\api\unit\modules\authserver\models; -use api\models\authentication\LoginForm; use api\modules\authserver\exceptions\ForbiddenOperationException; -use api\modules\authserver\models\AuthenticateData; use api\modules\authserver\models\AuthenticationForm; use api\tests\unit\TestCase; -use common\models\Account; -use common\models\MinecraftAccessKey; -use common\tests\_support\ProtectedCaller; use common\tests\fixtures\AccountFixture; -use common\tests\fixtures\MinecraftAccessKeyFixture; use Ramsey\Uuid\Uuid; class AuthenticationFormTest extends TestCase { - use ProtectedCaller; public function _fixtures(): array { return [ 'accounts' => AccountFixture::class, - 'minecraftAccessKeys' => MinecraftAccessKeyFixture::class, ]; } - public function testAuthenticateByWrongNicknamePass() { + public function testAuthenticateByValidCredentials() { + $authForm = new AuthenticationForm(); + $authForm->username = 'admin'; + $authForm->password = 'password_0'; + $authForm->clientToken = Uuid::uuid4()->toString(); + $result = $authForm->authenticate()->getResponseData(); + $this->assertRegExp('/^[\w=-]+\.[\w=-]+\.[\w=-]+$/', $result['accessToken']); + $this->assertSame($authForm->clientToken, $result['clientToken']); + $this->assertSame('df936908-b2e1-544d-96f8-2977ec213022', $result['selectedProfile']['id']); + $this->assertSame('Admin', $result['selectedProfile']['name']); + $this->assertFalse($result['selectedProfile']['legacy']); + } + + /** + * @dataProvider getInvalidCredentialsCases + */ + public function testAuthenticateByWrongNicknamePass(string $expectedFieldError, string $login, string $password) { $this->expectException(ForbiddenOperationException::class); - $this->expectExceptionMessage('Invalid credentials. Invalid nickname or password.'); - - $authForm = $this->createAuthForm(); - - $authForm->username = 'wrong-username'; - $authForm->password = 'wrong-password'; - $authForm->clientToken = Uuid::uuid4(); + $this->expectExceptionMessage("Invalid credentials. Invalid {$expectedFieldError} or password."); + $authForm = new AuthenticationForm(); + $authForm->username = $login; + $authForm->password = $password; + $authForm->clientToken = Uuid::uuid4()->toString(); $authForm->authenticate(); } - public function testAuthenticateByWrongEmailPass() { - $this->expectException(ForbiddenOperationException::class); - $this->expectExceptionMessage('Invalid credentials. Invalid email or password.'); - - $authForm = $this->createAuthForm(); - - $authForm->username = 'wrong-email@ely.by'; - $authForm->password = 'wrong-password'; - $authForm->clientToken = Uuid::uuid4(); - - $authForm->authenticate(); + public function getInvalidCredentialsCases() { + yield ['nickname', 'wrong-username', 'wrong-password']; + yield ['email', 'wrong-email@ely.by', 'wrong-password']; } public function testAuthenticateByValidCredentialsIntoBlockedAccount() { $this->expectException(ForbiddenOperationException::class); $this->expectExceptionMessage('This account has been suspended.'); - $authForm = $this->createAuthForm(Account::STATUS_BANNED); - - $authForm->username = 'dummy'; + $authForm = new AuthenticationForm(); + $authForm->username = 'Banned'; $authForm->password = 'password_0'; - $authForm->clientToken = Uuid::uuid4(); - + $authForm->clientToken = Uuid::uuid4()->toString(); $authForm->authenticate(); } - public function testAuthenticateByValidCredentials() { - $authForm = $this->createAuthForm(); - - $minecraftAccessKey = new MinecraftAccessKey(); - $minecraftAccessKey->access_token = Uuid::uuid4(); - $authForm->expects($this->once()) - ->method('createMinecraftAccessToken') - ->willReturn($minecraftAccessKey); - - $authForm->username = 'dummy'; - $authForm->password = 'password_0'; - $authForm->clientToken = Uuid::uuid4(); - - $result = $authForm->authenticate(); - $this->assertInstanceOf(AuthenticateData::class, $result); - $this->assertSame($minecraftAccessKey->access_token, $result->getToken()->access_token); - } - - public function testCreateMinecraftAccessToken() { - $authForm = new AuthenticationForm(); - $authForm->clientToken = Uuid::uuid4(); - /** @var Account $account */ - $account = $this->tester->grabFixture('accounts', 'admin'); - /** @var MinecraftAccessKey $result */ - $result = $this->callProtected($authForm, 'createMinecraftAccessToken', $account); - $this->assertInstanceOf(MinecraftAccessKey::class, $result); - $this->assertSame($account->id, $result->account_id); - $this->assertSame($authForm->clientToken, $result->client_token); - $this->assertInstanceOf(MinecraftAccessKey::class, MinecraftAccessKey::findOne($result->access_token)); - } - - public function testCreateMinecraftAccessTokenWithExistsClientId() { - $authForm = new AuthenticationForm(); - $minecraftFixture = $this->tester->grabFixture('minecraftAccessKeys', 'admin-token'); - $authForm->clientToken = $minecraftFixture['client_token']; - /** @var Account $account */ - $account = $this->tester->grabFixture('accounts', 'admin'); - /** @var MinecraftAccessKey $result */ - $result = $this->callProtected($authForm, 'createMinecraftAccessToken', $account); - $this->assertInstanceOf(MinecraftAccessKey::class, $result); - $this->assertSame($account->id, $result->account_id); - $this->assertSame($authForm->clientToken, $result->client_token); - $this->assertNull(MinecraftAccessKey::findOne($minecraftFixture['access_token'])); - $this->assertInstanceOf(MinecraftAccessKey::class, MinecraftAccessKey::findOne($result->access_token)); - } - - private function createAuthForm($status = Account::STATUS_ACTIVE) { - /** @var LoginForm|\PHPUnit\Framework\MockObject\MockObject $loginForm */ - $loginForm = $this->getMockBuilder(LoginForm::class) - ->setMethods(['getAccount']) - ->getMock(); - - $account = new Account(); - $account->username = 'dummy'; - $account->email = 'dummy@ely.by'; - $account->status = $status; - $account->setPassword('password_0'); - - $loginForm - ->method('getAccount') - ->willReturn($account); - - /** @var AuthenticationForm|\PHPUnit\Framework\MockObject\MockObject $authForm */ - $authForm = $this->getMockBuilder(AuthenticationForm::class) - ->setMethods(['createLoginForm', 'createMinecraftAccessToken']) - ->getMock(); - - $authForm - ->method('createLoginForm') - ->willReturn($loginForm); - - return $authForm; - } - } diff --git a/common/tests/_support/ProtectedCaller.php b/common/tests/_support/ProtectedCaller.php index a5e69a9..08a6a59 100644 --- a/common/tests/_support/ProtectedCaller.php +++ b/common/tests/_support/ProtectedCaller.php @@ -1,13 +1,18 @@ getMethod($function); + $method = $class->getMethod($methodName); $method->setAccessible(true); return $method->invokeArgs($object, $args);