_markTestAsRestOnly(); $this->tokenService = Bootstrap::getObjectManager()->get(\Magento\Integration\Model\AdminTokenService::class); $this->tokenModel = Bootstrap::getObjectManager()->get(\Magento\Integration\Model\Oauth\Token::class); $this->userModel = Bootstrap::getObjectManager()->get(\Magento\User\Model\User::class); /** @var TokenThrottlerConfig $tokenThrottlerConfig */ $tokenThrottlerConfig = Bootstrap::getObjectManager()->get(TokenThrottlerConfig::class); $this->attemptsCountToLockAccount = $tokenThrottlerConfig->getMaxFailuresCount(); } /** * @magentoApiDataFixture Magento/Webapi/_files/webapi_user.php */ public function testCreateAdminAccessToken() { $adminUserNameFromFixture = 'webapi_user'; $serviceInfo = [ 'rest' => [ 'resourcePath' => self::RESOURCE_PATH_ADMIN_TOKEN, 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, ], ]; $requestData = [ 'username' => $adminUserNameFromFixture, 'password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD, ]; $accessToken = $this->_webApiCall($serviceInfo, $requestData); $this->assertToken($adminUserNameFromFixture, $accessToken); } /** * Provider to test input validation * * @return array */ public function validationDataProvider() { return [ 'Check for empty credentials' => ['', ''], 'Check for null credentials' => [null, null] ]; } /** * @dataProvider validationDataProvider */ public function testCreateAdminAccessTokenEmptyOrNullCredentials() { $noExceptionOccurred = false; try { $serviceInfo = [ 'rest' => [ 'resourcePath' => self::RESOURCE_PATH_ADMIN_TOKEN, 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, ], ]; $requestData = ['username' => '', 'password' => '']; $this->_webApiCall($serviceInfo, $requestData); $noExceptionOccurred = true; } catch (\Exception $exception) { $this->assertInputExceptionMessages($exception); } if ($noExceptionOccurred) { $this->fail("Exception was expected to be thrown when provided credentials are invalid."); } } public function testCreateAdminAccessTokenInvalidCredentials() { $customerUserName = 'invalid'; $password = 'invalid'; $noExceptionOccurred = false; try { $serviceInfo = [ 'rest' => [ 'resourcePath' => self::RESOURCE_PATH_ADMIN_TOKEN, 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, ], ]; $requestData = ['username' => $customerUserName, 'password' => $password]; $this->_webApiCall($serviceInfo, $requestData); $noExceptionOccurred = true; } catch (\Exception $exception) { $this->assertInvalidCredentialsException($exception); } if ($noExceptionOccurred) { $this->fail("Exception was expected to be thrown when provided credentials are invalid."); } } /** * @magentoApiDataFixture Magento/Webapi/_files/webapi_user.php */ public function testUseAdminAccessTokenInactiveAdmin() { $adminUserNameFromFixture = 'webapi_user'; $serviceInfo = [ 'rest' => [ 'resourcePath' => self::RESOURCE_PATH_ADMIN_TOKEN, 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, ], ]; $requestData = [ 'username' => $adminUserNameFromFixture, 'password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD, ]; $accessToken = $this->_webApiCall($serviceInfo, $requestData); $this->assertToken($adminUserNameFromFixture, $accessToken); $serviceInfo = [ 'rest' => [ 'resourcePath' => '/V1/store/storeConfigs', 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, 'token' => $accessToken ] ]; $requestData = [ 'storeCodes' => ['default'], ]; $storeConfigs = $this->_webApiCall($serviceInfo, $requestData); $this->assertNotNull($storeConfigs); $adminUser = $this->userModel->loadByUsername($adminUserNameFromFixture); $adminUser->setData("is_active", 0); $adminUser->save(); $serviceInfo = [ 'rest' => [ 'resourcePath' => '/V1/store/storeConfigs', 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, 'token' => $accessToken ] ]; $requestData = [ 'storeCodes' => ['default'], ]; $noExceptionOccurred = false; try { $this->_webApiCall($serviceInfo, $requestData); $noExceptionOccurred = true; } catch (\Exception $exception) { $this->assertUnauthorizedAccessException($exception); } if ($noExceptionOccurred) { $this->fail("Exception was expected to be thrown when provided token is expired."); } } /** * @magentoApiDataFixture Magento/Webapi/_files/webapi_user.php */ public function testThrottlingMaxAttempts() { $adminUserNameFromFixture = 'webapi_user'; $serviceInfo = [ 'rest' => [ 'resourcePath' => self::RESOURCE_PATH_ADMIN_TOKEN, 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, ], ]; $invalidCredentials = [ 'username' => $adminUserNameFromFixture, 'password' => 'invalid', ]; $validCredentials = [ 'username' => $adminUserNameFromFixture, 'password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD, ]; /* Try to get token using invalid credentials for 5 times (account is locked after 6 attempts) */ $noExceptionOccurred = false; for ($i = 0; $i < ($this->attemptsCountToLockAccount - 1); $i++) { try { $this->_webApiCall($serviceInfo, $invalidCredentials); $noExceptionOccurred = true; } catch (\Exception $exception) { } } if ($noExceptionOccurred) { $this->fail( "Precondition failed: exception should have occurred when token was requested with invalid credentials." ); } /** On 6th attempt it still should be possible to get token if valid credentials are specified */ $accessToken = $this->_webApiCall($serviceInfo, $validCredentials); $this->assertToken($adminUserNameFromFixture, $accessToken); } /** * @magentoApiDataFixture Magento/Webapi/_files/webapi_user.php */ public function testThrottlingAccountLockout() { $adminUserNameFromFixture = 'webapi_user'; $serviceInfo = [ 'rest' => [ 'resourcePath' => self::RESOURCE_PATH_ADMIN_TOKEN, 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, ], ]; $invalidCredentials = [ 'username' => $adminUserNameFromFixture, 'password' => 'invalid', ]; $validCredentials = [ 'username' => $adminUserNameFromFixture, 'password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD, ]; /* Try to get token using invalid credentials for 5 times (account would be locked after 6 attempts) */ $noExceptionOccurred = false; for ($i = 0; $i < $this->attemptsCountToLockAccount; $i++) { try { $this->_webApiCall($serviceInfo, $invalidCredentials); $noExceptionOccurred = true; } catch (\Exception $exception) { $this->assertInvalidCredentialsException($exception); } if ($noExceptionOccurred) { $this->fail("Exception was expected to be thrown when provided credentials are invalid."); } } $noExceptionOccurred = false; try { $this->_webApiCall($serviceInfo, $validCredentials); $noExceptionOccurred = true; } catch (\Exception $exception) { $this->assertInvalidCredentialsException($exception); } if ($noExceptionOccurred) { $this->fail("Exception was expected to be thrown because account should have been locked at this point."); } } /** * Assert for presence of Input exception messages * * @param \Exception $exception */ private function assertInputExceptionMessages($exception) { $this->assertEquals(HTTPExceptionCodes::HTTP_BAD_REQUEST, $exception->getCode()); $exceptionData = $this->processRestExceptionResult($exception); $expectedExceptionData = [ 'message' => 'One or more input exceptions have occurred.', 'errors' => [ [ 'message' => '"%fieldName" is required. Enter and try again.', 'parameters' => [ 'fieldName' => 'username', ], ], [ 'message' => '"%fieldName" is required. Enter and try again.', 'parameters' => [ 'fieldName' => 'password', ] ], ], ]; $this->assertEquals($expectedExceptionData, $exceptionData); } /** * Make sure that status code and message are correct in case of authentication failure. * * @param \Exception $exception */ private function assertInvalidCredentialsException($exception) { $this->assertEquals( HTTPExceptionCodes::HTTP_UNAUTHORIZED, $exception->getCode(), "Response HTTP code is invalid." ); $exceptionData = $this->processRestExceptionResult($exception); $expectedExceptionData = [ 'message' => 'The account sign-in was incorrect or your account is disabled temporarily. ' . 'Please wait and try again later.' ]; $this->assertEquals($expectedExceptionData, $exceptionData, "Exception message is invalid."); } /** * Make sure that status code and message are correct in case of authentication failure. * * @param \Exception $exception */ private function assertUnauthorizedAccessException($exception) { $this->assertEquals( HTTPExceptionCodes::HTTP_UNAUTHORIZED, $exception->getCode(), "Response HTTP code is invalid." ); $exceptionData = $this->processRestExceptionResult($exception); $expectedExceptionData = [ 'message' => "The consumer isn't authorized to access %resources.", 'parameters' => [ 'resources' => 'Magento_Backend::store' ] ]; $this->assertEquals($expectedExceptionData, $exceptionData, "Exception message is invalid."); } /** * Make sure provided token is valid and belongs to the specified user. * * @param string $username * @param string $accessToken */ private function assertToken($username, $accessToken) { $adminUserId = $this->userModel->loadByUsername($username)->getId(); /** @var $token TokenModel */ $token = $this->tokenModel ->loadByAdminId($adminUserId) ->getToken(); $this->assertEquals($accessToken, $token); } }