CustomerTokenServiceTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  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\Customer\Api\AccountManagementInterface;
  8. use Magento\Framework\Exception\InputException;
  9. use Magento\Integration\Model\Oauth\Token as TokenModel;
  10. use Magento\TestFramework\Helper\Bootstrap;
  11. use Magento\TestFramework\TestCase\WebapiAbstract;
  12. use Magento\User\Model\User as UserModel;
  13. use Magento\Framework\Webapi\Exception as HTTPExceptionCodes;
  14. use Magento\Integration\Model\ResourceModel\Oauth\Token\CollectionFactory;
  15. use Magento\Integration\Model\Oauth\Token\RequestLog\Config as TokenThrottlerConfig;
  16. use Magento\Integration\Api\CustomerTokenServiceInterface;
  17. /**
  18. * api-functional test for \Magento\Integration\Model\CustomerTokenService.
  19. *
  20. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  21. */
  22. class CustomerTokenServiceTest extends WebapiAbstract
  23. {
  24. const SERVICE_NAME = "integrationCustomerTokenServiceV1";
  25. const SERVICE_VERSION = "V1";
  26. const RESOURCE_PATH_CUSTOMER_TOKEN = "/V1/integration/customer/token";
  27. /**
  28. * @var CustomerTokenServiceInterface
  29. */
  30. private $tokenService;
  31. /**
  32. * @var AccountManagementInterface
  33. */
  34. private $customerAccountManagement;
  35. /**
  36. * @var CollectionFactory
  37. */
  38. private $tokenCollection;
  39. /**
  40. * @var UserModel
  41. */
  42. private $userModel;
  43. /**
  44. * @var int
  45. */
  46. private $attemptsCountToLockAccount;
  47. /**
  48. * Setup CustomerTokenService
  49. */
  50. public function setUp()
  51. {
  52. $this->_markTestAsRestOnly();
  53. $this->tokenService = Bootstrap::getObjectManager()->get(
  54. \Magento\Integration\Model\CustomerTokenService::class
  55. );
  56. $this->customerAccountManagement = Bootstrap::getObjectManager()->get(
  57. \Magento\Customer\Api\AccountManagementInterface::class
  58. );
  59. $tokenCollectionFactory = Bootstrap::getObjectManager()->get(
  60. \Magento\Integration\Model\ResourceModel\Oauth\Token\CollectionFactory::class
  61. );
  62. $this->tokenCollection = $tokenCollectionFactory->create();
  63. $this->userModel = Bootstrap::getObjectManager()->get(\Magento\User\Model\User::class);
  64. /** @var TokenThrottlerConfig $tokenThrottlerConfig */
  65. $tokenThrottlerConfig = Bootstrap::getObjectManager()->get(TokenThrottlerConfig::class);
  66. $this->attemptsCountToLockAccount = $tokenThrottlerConfig->getMaxFailuresCount();
  67. }
  68. /**
  69. * @magentoApiDataFixture Magento/Customer/_files/customer.php
  70. */
  71. public function testCreateCustomerAccessToken()
  72. {
  73. $userName = 'customer@example.com';
  74. $password = 'password';
  75. $serviceInfo = [
  76. 'rest' => [
  77. 'resourcePath' => self::RESOURCE_PATH_CUSTOMER_TOKEN,
  78. 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
  79. ],
  80. ];
  81. $requestData = ['username' => $userName, 'password' => $password];
  82. $accessToken = $this->_webApiCall($serviceInfo, $requestData);
  83. $this->assertToken($accessToken, $userName, $password);
  84. }
  85. /**
  86. * @dataProvider validationDataProvider
  87. */
  88. public function testCreateCustomerAccessTokenEmptyOrNullCredentials($username, $password)
  89. {
  90. $noExceptionOccurred = false;
  91. try {
  92. $serviceInfo = [
  93. 'rest' => [
  94. 'resourcePath' => self::RESOURCE_PATH_CUSTOMER_TOKEN,
  95. 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
  96. ],
  97. ];
  98. $requestData = ['username' => $username, 'password' => $password];
  99. $this->_webApiCall($serviceInfo, $requestData);
  100. $noExceptionOccurred = true;
  101. } catch (\Exception $e) {
  102. $this->assertInputExceptionMessages($e);
  103. }
  104. if ($noExceptionOccurred) {
  105. $this->fail("Exception was expected to be thrown when provided credentials are invalid.");
  106. }
  107. }
  108. public function testCreateCustomerAccessTokenInvalidCustomer()
  109. {
  110. $customerUserName = 'invalid';
  111. $password = 'invalid';
  112. $noExceptionOccurred = false;
  113. try {
  114. $serviceInfo = [
  115. 'rest' => [
  116. 'resourcePath' => self::RESOURCE_PATH_CUSTOMER_TOKEN,
  117. 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
  118. ],
  119. ];
  120. $requestData = ['username' => $customerUserName, 'password' => $password];
  121. $this->_webApiCall($serviceInfo, $requestData);
  122. $noExceptionOccurred = true;
  123. } catch (\Exception $e) {
  124. $this->assertInvalidCredentialsException($e);
  125. }
  126. if ($noExceptionOccurred) {
  127. $this->fail("Exception was expected to be thrown when provided credentials are invalid.");
  128. }
  129. }
  130. /**
  131. * Provider to test input validation
  132. *
  133. * @return array
  134. */
  135. public function validationDataProvider()
  136. {
  137. return [
  138. 'Check for empty credentials' => ['', ''],
  139. 'Check for null credentials' => [null, null]
  140. ];
  141. }
  142. /**
  143. * Assert for presence of Input exception messages
  144. *
  145. * @param \Exception $e
  146. */
  147. private function assertInputExceptionMessages($e)
  148. {
  149. $this->assertEquals(HTTPExceptionCodes::HTTP_BAD_REQUEST, $e->getCode());
  150. $exceptionData = $this->processRestExceptionResult($e);
  151. $expectedExceptionData = [
  152. 'message' => 'One or more input exceptions have occurred.',
  153. 'errors' => [
  154. [
  155. 'message' => '"%fieldName" is required. Enter and try again.',
  156. 'parameters' => [
  157. 'fieldName' => 'username',
  158. ],
  159. ],
  160. [
  161. 'message' => '"%fieldName" is required. Enter and try again.',
  162. 'parameters' => [
  163. 'fieldName' => 'password',
  164. ]
  165. ],
  166. ],
  167. ];
  168. $this->assertEquals($expectedExceptionData, $exceptionData);
  169. }
  170. /**
  171. * @magentoApiDataFixture Magento/Customer/_files/customer.php
  172. */
  173. public function testThrottlingMaxAttempts()
  174. {
  175. $userNameFromFixture = 'customer@example.com';
  176. $passwordFromFixture = 'password';
  177. $serviceInfo = [
  178. 'rest' => [
  179. 'resourcePath' => self::RESOURCE_PATH_CUSTOMER_TOKEN,
  180. 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
  181. ],
  182. ];
  183. $invalidCredentials = [
  184. 'username' => $userNameFromFixture,
  185. 'password' => 'invalid',
  186. ];
  187. $validCredentials = [
  188. 'username' => $userNameFromFixture,
  189. 'password' => $passwordFromFixture,
  190. ];
  191. /* Try to get token using invalid credentials for 5 times (account is locked after 6 attempts) */
  192. $noExceptionOccurred = false;
  193. for ($i = 0; $i < ($this->attemptsCountToLockAccount - 1); $i++) {
  194. try {
  195. $this->_webApiCall($serviceInfo, $invalidCredentials);
  196. $noExceptionOccurred = true;
  197. } catch (\Exception $e) {
  198. }
  199. }
  200. if ($noExceptionOccurred) {
  201. $this->fail(
  202. "Precondition failed: exception should have occurred when token was requested with invalid credentials."
  203. );
  204. }
  205. /** On 6th attempt it still should be possible to get token if valid credentials are specified */
  206. $accessToken = $this->_webApiCall($serviceInfo, $validCredentials);
  207. $this->assertToken($accessToken, $userNameFromFixture, $passwordFromFixture);
  208. }
  209. /**
  210. * @magentoApiDataFixture Magento/Customer/_files/customer.php
  211. */
  212. public function testThrottlingAccountLockout()
  213. {
  214. $userNameFromFixture = 'customer@example.com';
  215. $passwordFromFixture = 'password';
  216. $serviceInfo = [
  217. 'rest' => [
  218. 'resourcePath' => self::RESOURCE_PATH_CUSTOMER_TOKEN,
  219. 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
  220. ],
  221. ];
  222. $invalidCredentials = [
  223. 'username' => $userNameFromFixture,
  224. 'password' => 'invalid',
  225. ];
  226. $validCredentials = [
  227. 'username' => $userNameFromFixture,
  228. 'password' => $passwordFromFixture,
  229. ];
  230. /* Try to get token using invalid credentials for 5 times (account would be locked after 6 attempts) */
  231. $noExceptionOccurred = false;
  232. for ($i = 0; $i < $this->attemptsCountToLockAccount; $i++) {
  233. try {
  234. $this->_webApiCall($serviceInfo, $invalidCredentials);
  235. $noExceptionOccurred = true;
  236. } catch (\Exception $e) {
  237. $this->assertInvalidCredentialsException($e);
  238. }
  239. if ($noExceptionOccurred) {
  240. $this->fail("Exception was expected to be thrown when provided credentials are invalid.");
  241. }
  242. }
  243. $noExceptionOccurred = false;
  244. try {
  245. $this->_webApiCall($serviceInfo, $validCredentials);
  246. $noExceptionOccurred = true;
  247. } catch (\Exception $e) {
  248. $this->assertInvalidCredentialsException($e);
  249. }
  250. if ($noExceptionOccurred) {
  251. $this->fail("Exception was expected to be thrown because account should have been locked at this point.");
  252. }
  253. }
  254. /**
  255. * Make sure that status code and message are correct in case of authentication failure.
  256. *
  257. * @param \Exception $e
  258. */
  259. private function assertInvalidCredentialsException($e)
  260. {
  261. $this->assertEquals(HTTPExceptionCodes::HTTP_UNAUTHORIZED, $e->getCode(), "Response HTTP code is invalid.");
  262. $exceptionData = $this->processRestExceptionResult($e);
  263. $expectedExceptionData = [
  264. 'message' => 'The account sign-in was incorrect or your account is disabled temporarily. '
  265. . 'Please wait and try again later.'
  266. ];
  267. $this->assertEquals($expectedExceptionData, $exceptionData, "Exception message is invalid.");
  268. }
  269. /**
  270. * Make sure provided token is valid and belongs to the specified user.
  271. *
  272. * @param string $accessToken
  273. * @param string $userName
  274. * @param string $password
  275. */
  276. private function assertToken($accessToken, $userName, $password)
  277. {
  278. $customerData = $this->customerAccountManagement->authenticate($userName, $password);
  279. /** @var $this ->tokenCollection \Magento\Integration\Model\ResourceModel\Oauth\Token\Collection */
  280. $this->tokenCollection->addFilterByCustomerId($customerData->getId());
  281. $isTokenCorrect = false;
  282. foreach ($this->tokenCollection->getItems() as $item) {
  283. /** @var $item TokenModel */
  284. if ($item->getToken() == $accessToken) {
  285. $isTokenCorrect = true;
  286. }
  287. }
  288. $this->assertTrue($isTokenCorrect);
  289. }
  290. }