AdminTokenServiceTest.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Integration\Model;
  7. use Magento\Framework\Exception\InputException;
  8. use Magento\Integration\Model\Oauth\Token as TokenModel;
  9. use Magento\TestFramework\Helper\Bootstrap;
  10. use Magento\TestFramework\TestCase\WebapiAbstract;
  11. use Magento\User\Model\User as UserModel;
  12. use Magento\Framework\Webapi\Exception as HTTPExceptionCodes;
  13. use Magento\Integration\Model\Oauth\Token\RequestLog\Config as TokenThrottlerConfig;
  14. /**
  15. * api-functional test for \Magento\Integration\Model\AdminTokenService.
  16. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  17. */
  18. class AdminTokenServiceTest extends WebapiAbstract
  19. {
  20. const SERVICE_NAME = "integrationAdminTokenServiceV1";
  21. const SERVICE_VERSION = "V1";
  22. const RESOURCE_PATH_ADMIN_TOKEN = "/V1/integration/admin/token";
  23. /**
  24. * @var \Magento\Integration\Api\AdminTokenServiceInterface
  25. */
  26. private $tokenService;
  27. /**
  28. * @var TokenModel
  29. */
  30. private $tokenModel;
  31. /**
  32. * @var UserModel
  33. */
  34. private $userModel;
  35. /**
  36. * @var int
  37. */
  38. private $attemptsCountToLockAccount;
  39. /**
  40. * Setup AdminTokenService
  41. */
  42. public function setUp()
  43. {
  44. $this->_markTestAsRestOnly();
  45. $this->tokenService = Bootstrap::getObjectManager()->get(\Magento\Integration\Model\AdminTokenService::class);
  46. $this->tokenModel = Bootstrap::getObjectManager()->get(\Magento\Integration\Model\Oauth\Token::class);
  47. $this->userModel = Bootstrap::getObjectManager()->get(\Magento\User\Model\User::class);
  48. /** @var TokenThrottlerConfig $tokenThrottlerConfig */
  49. $tokenThrottlerConfig = Bootstrap::getObjectManager()->get(TokenThrottlerConfig::class);
  50. $this->attemptsCountToLockAccount = $tokenThrottlerConfig->getMaxFailuresCount();
  51. }
  52. /**
  53. * @magentoApiDataFixture Magento/Webapi/_files/webapi_user.php
  54. */
  55. public function testCreateAdminAccessToken()
  56. {
  57. $adminUserNameFromFixture = 'webapi_user';
  58. $serviceInfo = [
  59. 'rest' => [
  60. 'resourcePath' => self::RESOURCE_PATH_ADMIN_TOKEN,
  61. 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
  62. ],
  63. ];
  64. $requestData = [
  65. 'username' => $adminUserNameFromFixture,
  66. 'password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD,
  67. ];
  68. $accessToken = $this->_webApiCall($serviceInfo, $requestData);
  69. $this->assertToken($adminUserNameFromFixture, $accessToken);
  70. }
  71. /**
  72. * Provider to test input validation
  73. *
  74. * @return array
  75. */
  76. public function validationDataProvider()
  77. {
  78. return [
  79. 'Check for empty credentials' => ['', ''],
  80. 'Check for null credentials' => [null, null]
  81. ];
  82. }
  83. /**
  84. * @dataProvider validationDataProvider
  85. */
  86. public function testCreateAdminAccessTokenEmptyOrNullCredentials()
  87. {
  88. $noExceptionOccurred = false;
  89. try {
  90. $serviceInfo = [
  91. 'rest' => [
  92. 'resourcePath' => self::RESOURCE_PATH_ADMIN_TOKEN,
  93. 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
  94. ],
  95. ];
  96. $requestData = ['username' => '', 'password' => ''];
  97. $this->_webApiCall($serviceInfo, $requestData);
  98. $noExceptionOccurred = true;
  99. } catch (\Exception $exception) {
  100. $this->assertInputExceptionMessages($exception);
  101. }
  102. if ($noExceptionOccurred) {
  103. $this->fail("Exception was expected to be thrown when provided credentials are invalid.");
  104. }
  105. }
  106. public function testCreateAdminAccessTokenInvalidCredentials()
  107. {
  108. $customerUserName = 'invalid';
  109. $password = 'invalid';
  110. $noExceptionOccurred = false;
  111. try {
  112. $serviceInfo = [
  113. 'rest' => [
  114. 'resourcePath' => self::RESOURCE_PATH_ADMIN_TOKEN,
  115. 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
  116. ],
  117. ];
  118. $requestData = ['username' => $customerUserName, 'password' => $password];
  119. $this->_webApiCall($serviceInfo, $requestData);
  120. $noExceptionOccurred = true;
  121. } catch (\Exception $exception) {
  122. $this->assertInvalidCredentialsException($exception);
  123. }
  124. if ($noExceptionOccurred) {
  125. $this->fail("Exception was expected to be thrown when provided credentials are invalid.");
  126. }
  127. }
  128. /**
  129. * @magentoApiDataFixture Magento/Webapi/_files/webapi_user.php
  130. */
  131. public function testUseAdminAccessTokenInactiveAdmin()
  132. {
  133. $adminUserNameFromFixture = 'webapi_user';
  134. $serviceInfo = [
  135. 'rest' => [
  136. 'resourcePath' => self::RESOURCE_PATH_ADMIN_TOKEN,
  137. 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
  138. ],
  139. ];
  140. $requestData = [
  141. 'username' => $adminUserNameFromFixture,
  142. 'password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD,
  143. ];
  144. $accessToken = $this->_webApiCall($serviceInfo, $requestData);
  145. $this->assertToken($adminUserNameFromFixture, $accessToken);
  146. $serviceInfo = [
  147. 'rest' => [
  148. 'resourcePath' => '/V1/store/storeConfigs',
  149. 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET,
  150. 'token' => $accessToken
  151. ]
  152. ];
  153. $requestData = [
  154. 'storeCodes' => ['default'],
  155. ];
  156. $storeConfigs = $this->_webApiCall($serviceInfo, $requestData);
  157. $this->assertNotNull($storeConfigs);
  158. $adminUser = $this->userModel->loadByUsername($adminUserNameFromFixture);
  159. $adminUser->setData("is_active", 0);
  160. $adminUser->save();
  161. $serviceInfo = [
  162. 'rest' => [
  163. 'resourcePath' => '/V1/store/storeConfigs',
  164. 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET,
  165. 'token' => $accessToken
  166. ]
  167. ];
  168. $requestData = [
  169. 'storeCodes' => ['default'],
  170. ];
  171. $noExceptionOccurred = false;
  172. try {
  173. $this->_webApiCall($serviceInfo, $requestData);
  174. $noExceptionOccurred = true;
  175. } catch (\Exception $exception) {
  176. $this->assertUnauthorizedAccessException($exception);
  177. }
  178. if ($noExceptionOccurred) {
  179. $this->fail("Exception was expected to be thrown when provided token is expired.");
  180. }
  181. }
  182. /**
  183. * @magentoApiDataFixture Magento/Webapi/_files/webapi_user.php
  184. */
  185. public function testThrottlingMaxAttempts()
  186. {
  187. $adminUserNameFromFixture = 'webapi_user';
  188. $serviceInfo = [
  189. 'rest' => [
  190. 'resourcePath' => self::RESOURCE_PATH_ADMIN_TOKEN,
  191. 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
  192. ],
  193. ];
  194. $invalidCredentials = [
  195. 'username' => $adminUserNameFromFixture,
  196. 'password' => 'invalid',
  197. ];
  198. $validCredentials = [
  199. 'username' => $adminUserNameFromFixture,
  200. 'password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD,
  201. ];
  202. /* Try to get token using invalid credentials for 5 times (account is locked after 6 attempts) */
  203. $noExceptionOccurred = false;
  204. for ($i = 0; $i < ($this->attemptsCountToLockAccount - 1); $i++) {
  205. try {
  206. $this->_webApiCall($serviceInfo, $invalidCredentials);
  207. $noExceptionOccurred = true;
  208. } catch (\Exception $exception) {
  209. }
  210. }
  211. if ($noExceptionOccurred) {
  212. $this->fail(
  213. "Precondition failed: exception should have occurred when token was requested with invalid credentials."
  214. );
  215. }
  216. /** On 6th attempt it still should be possible to get token if valid credentials are specified */
  217. $accessToken = $this->_webApiCall($serviceInfo, $validCredentials);
  218. $this->assertToken($adminUserNameFromFixture, $accessToken);
  219. }
  220. /**
  221. * @magentoApiDataFixture Magento/Webapi/_files/webapi_user.php
  222. */
  223. public function testThrottlingAccountLockout()
  224. {
  225. $adminUserNameFromFixture = 'webapi_user';
  226. $serviceInfo = [
  227. 'rest' => [
  228. 'resourcePath' => self::RESOURCE_PATH_ADMIN_TOKEN,
  229. 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
  230. ],
  231. ];
  232. $invalidCredentials = [
  233. 'username' => $adminUserNameFromFixture,
  234. 'password' => 'invalid',
  235. ];
  236. $validCredentials = [
  237. 'username' => $adminUserNameFromFixture,
  238. 'password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD,
  239. ];
  240. /* Try to get token using invalid credentials for 5 times (account would be locked after 6 attempts) */
  241. $noExceptionOccurred = false;
  242. for ($i = 0; $i < $this->attemptsCountToLockAccount; $i++) {
  243. try {
  244. $this->_webApiCall($serviceInfo, $invalidCredentials);
  245. $noExceptionOccurred = true;
  246. } catch (\Exception $exception) {
  247. $this->assertInvalidCredentialsException($exception);
  248. }
  249. if ($noExceptionOccurred) {
  250. $this->fail("Exception was expected to be thrown when provided credentials are invalid.");
  251. }
  252. }
  253. $noExceptionOccurred = false;
  254. try {
  255. $this->_webApiCall($serviceInfo, $validCredentials);
  256. $noExceptionOccurred = true;
  257. } catch (\Exception $exception) {
  258. $this->assertInvalidCredentialsException($exception);
  259. }
  260. if ($noExceptionOccurred) {
  261. $this->fail("Exception was expected to be thrown because account should have been locked at this point.");
  262. }
  263. }
  264. /**
  265. * Assert for presence of Input exception messages
  266. *
  267. * @param \Exception $exception
  268. */
  269. private function assertInputExceptionMessages($exception)
  270. {
  271. $this->assertEquals(HTTPExceptionCodes::HTTP_BAD_REQUEST, $exception->getCode());
  272. $exceptionData = $this->processRestExceptionResult($exception);
  273. $expectedExceptionData = [
  274. 'message' => 'One or more input exceptions have occurred.',
  275. 'errors' => [
  276. [
  277. 'message' => '"%fieldName" is required. Enter and try again.',
  278. 'parameters' => [
  279. 'fieldName' => 'username',
  280. ],
  281. ],
  282. [
  283. 'message' => '"%fieldName" is required. Enter and try again.',
  284. 'parameters' => [
  285. 'fieldName' => 'password',
  286. ]
  287. ],
  288. ],
  289. ];
  290. $this->assertEquals($expectedExceptionData, $exceptionData);
  291. }
  292. /**
  293. * Make sure that status code and message are correct in case of authentication failure.
  294. *
  295. * @param \Exception $exception
  296. */
  297. private function assertInvalidCredentialsException($exception)
  298. {
  299. $this->assertEquals(
  300. HTTPExceptionCodes::HTTP_UNAUTHORIZED,
  301. $exception->getCode(),
  302. "Response HTTP code is invalid."
  303. );
  304. $exceptionData = $this->processRestExceptionResult($exception);
  305. $expectedExceptionData = [
  306. 'message' => 'The account sign-in was incorrect or your account is disabled temporarily. '
  307. . 'Please wait and try again later.'
  308. ];
  309. $this->assertEquals($expectedExceptionData, $exceptionData, "Exception message is invalid.");
  310. }
  311. /**
  312. * Make sure that status code and message are correct in case of authentication failure.
  313. *
  314. * @param \Exception $exception
  315. */
  316. private function assertUnauthorizedAccessException($exception)
  317. {
  318. $this->assertEquals(
  319. HTTPExceptionCodes::HTTP_UNAUTHORIZED,
  320. $exception->getCode(),
  321. "Response HTTP code is invalid."
  322. );
  323. $exceptionData = $this->processRestExceptionResult($exception);
  324. $expectedExceptionData = [
  325. 'message' => "The consumer isn't authorized to access %resources.",
  326. 'parameters' => [
  327. 'resources' => 'Magento_Backend::store'
  328. ]
  329. ];
  330. $this->assertEquals($expectedExceptionData, $exceptionData, "Exception message is invalid.");
  331. }
  332. /**
  333. * Make sure provided token is valid and belongs to the specified user.
  334. *
  335. * @param string $username
  336. * @param string $accessToken
  337. */
  338. private function assertToken($username, $accessToken)
  339. {
  340. $adminUserId = $this->userModel->loadByUsername($username)->getId();
  341. /** @var $token TokenModel */
  342. $token = $this->tokenModel
  343. ->loadByAdminId($adminUserId)
  344. ->getToken();
  345. $this->assertEquals($accessToken, $token);
  346. }
  347. }