BackendValidatorTest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. declare(strict_types=1);
  7. namespace Magento\Backend\App\Request;
  8. use Magento\Backend\App\AbstractAction;
  9. use Magento\Backend\App\Action\Context;
  10. use Magento\Backend\Model\Auth;
  11. use Magento\Framework\App\ActionInterface;
  12. use Magento\Framework\App\CsrfAwareActionInterface;
  13. use Magento\Framework\App\Request\InvalidRequestException;
  14. use Magento\Framework\App\RequestInterface;
  15. use Magento\Framework\App\ResponseInterface;
  16. use Magento\Framework\Controller\ResultInterface;
  17. use Magento\Framework\Data\Form\FormKey;
  18. use Magento\Framework\Exception\NotFoundException;
  19. use Magento\Framework\Phrase;
  20. use Magento\TestFramework\Request;
  21. use Magento\TestFramework\Response;
  22. use PHPUnit\Framework\TestCase;
  23. use Magento\TestFramework\Helper\Bootstrap;
  24. use Magento\TestFramework\Bootstrap as TestBootstrap;
  25. use Magento\Framework\App\Request\Http as HttpRequest;
  26. use Magento\Framework\App\Response\Http as HttpResponse;
  27. use Zend\Stdlib\Parameters;
  28. use Magento\Backend\Model\UrlInterface as BackendUrl;
  29. use Magento\Framework\App\Response\HttpFactory as HttpResponseFactory;
  30. /**
  31. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  32. */
  33. class BackendValidatorTest extends TestCase
  34. {
  35. private const AWARE_VALIDATION_PARAM = 'test_param';
  36. private const AWARE_LOCATION_VALUE = 'test1';
  37. private const CSRF_AWARE_MESSAGE = 'csrf_aware';
  38. /**
  39. * @var ActionInterface
  40. */
  41. private $mockUnawareAction;
  42. /**
  43. * @var AbstractAction
  44. */
  45. private $mockAwareAction;
  46. /**
  47. * @var BackendValidator
  48. */
  49. private $validator;
  50. /**
  51. * @var Request
  52. */
  53. private $request;
  54. /**
  55. * @var FormKey
  56. */
  57. private $formKey;
  58. /**
  59. * @var BackendUrl
  60. */
  61. private $url;
  62. /**
  63. * @var Auth
  64. */
  65. private $auth;
  66. /**
  67. * @var CsrfAwareActionInterface
  68. */
  69. private $mockCsrfAwareAction;
  70. /**
  71. * @var HttpResponseFactory
  72. */
  73. private $httpResponseFactory;
  74. /**
  75. * @return ActionInterface
  76. */
  77. private function createUnawareAction(): ActionInterface
  78. {
  79. return new class implements ActionInterface {
  80. /**
  81. * @inheritDoc
  82. */
  83. public function execute()
  84. {
  85. throw new NotFoundException(new Phrase('Not implemented'));
  86. }
  87. };
  88. }
  89. /**
  90. * @return AbstractAction
  91. */
  92. private function createAwareAction(): AbstractAction
  93. {
  94. $l = self::AWARE_LOCATION_VALUE;
  95. $p = self::AWARE_VALIDATION_PARAM;
  96. return new class($l, $p) extends AbstractAction{
  97. /**
  98. * @var string
  99. */
  100. private $locationValue;
  101. /**
  102. * @var string
  103. */
  104. private $param;
  105. /**
  106. * @param string $locationValue
  107. * @param string $param
  108. */
  109. public function __construct(
  110. string $locationValue,
  111. string $param
  112. ) {
  113. parent::__construct(
  114. Bootstrap::getObjectManager()->get(Context::class)
  115. );
  116. $this->locationValue= $locationValue;
  117. $this->param = $param;
  118. }
  119. /**
  120. * @inheritDoc
  121. */
  122. public function execute()
  123. {
  124. throw new NotFoundException(new Phrase('Not implemented'));
  125. }
  126. /**
  127. * @inheritDoc
  128. */
  129. public function _processUrlKeys()
  130. {
  131. if ($this->_request->getParam($this->param)) {
  132. return true;
  133. } else {
  134. /** @var Response $response */
  135. $response = $this->_response;
  136. $response->setHeader('Location', $this->locationValue);
  137. return false;
  138. }
  139. }
  140. };
  141. }
  142. /**
  143. * @return CsrfAwareActionInterface
  144. */
  145. private function createCsrfAwareAction(): CsrfAwareActionInterface
  146. {
  147. $r = Bootstrap::getObjectManager()
  148. ->get(ResponseInterface::class);
  149. $m = self::CSRF_AWARE_MESSAGE;
  150. return new class ($r, $m) implements CsrfAwareActionInterface {
  151. /**
  152. * @var ResponseInterface
  153. */
  154. private $response;
  155. /**
  156. * @var string
  157. */
  158. private $message;
  159. /**
  160. * @param ResponseInterface $response
  161. * @param string $message
  162. */
  163. public function __construct(
  164. ResponseInterface $response,
  165. string $message
  166. ) {
  167. $this->response = $response;
  168. $this->message = $message;
  169. }
  170. /**
  171. * @inheritDoc
  172. */
  173. public function execute()
  174. {
  175. return $this->response;
  176. }
  177. /**
  178. * @inheritDoc
  179. */
  180. public function createCsrfValidationException(
  181. RequestInterface $request
  182. ): ?InvalidRequestException {
  183. return new InvalidRequestException(
  184. $this->response,
  185. [new Phrase($this->message)]
  186. );
  187. }
  188. /**
  189. * @inheritDoc
  190. */
  191. public function validateForCsrf(RequestInterface $request): ?bool
  192. {
  193. return false;
  194. }
  195. };
  196. }
  197. /**
  198. * @inheritDoc
  199. */
  200. protected function setUp()
  201. {
  202. $objectManager = Bootstrap::getObjectManager();
  203. $this->request = $objectManager->get(RequestInterface::class);
  204. $this->validator = $objectManager->get(BackendValidator::class);
  205. $this->mockUnawareAction = $this->createUnawareAction();
  206. $this->mockAwareAction = $this->createAwareAction();
  207. $this->formKey = $objectManager->get(FormKey::class);
  208. $this->url = $objectManager->get(BackendUrl::class);
  209. $this->auth = $objectManager->get(Auth::class);
  210. $this->mockCsrfAwareAction = $this->createCsrfAwareAction();
  211. $this->httpResponseFactory = $objectManager->get(
  212. HttpResponseFactory::class
  213. );
  214. }
  215. /**
  216. * @magentoConfigFixture admin/security/use_form_key 1
  217. * @magentoAppArea adminhtml
  218. */
  219. public function testValidateWithValidKey()
  220. {
  221. $this->request->setMethod(HttpRequest::METHOD_GET);
  222. $this->auth->login(
  223. TestBootstrap::ADMIN_NAME,
  224. TestBootstrap::ADMIN_PASSWORD
  225. );
  226. $this->request->setParams([
  227. BackendUrl::SECRET_KEY_PARAM_NAME => $this->url->getSecretKey(),
  228. ]);
  229. $this->validator->validate(
  230. $this->request,
  231. $this->mockUnawareAction
  232. );
  233. }
  234. /**
  235. * @expectedException \Magento\Framework\App\Request\InvalidRequestException
  236. *
  237. * @magentoConfigFixture admin/security/use_form_key 1
  238. * @magentoAppArea adminhtml
  239. */
  240. public function testValidateWithInvalidKey()
  241. {
  242. $invalidKey = $this->url->getSecretKey() .'Invalid';
  243. $this->request->setParams([
  244. BackendUrl::SECRET_KEY_PARAM_NAME => $invalidKey,
  245. ]);
  246. $this->request->setMethod(HttpRequest::METHOD_GET);
  247. $this->auth->login(
  248. TestBootstrap::ADMIN_NAME,
  249. TestBootstrap::ADMIN_PASSWORD
  250. );
  251. $this->validator->validate(
  252. $this->request,
  253. $this->mockUnawareAction
  254. );
  255. }
  256. /**
  257. * @expectedException \Magento\Framework\App\Request\InvalidRequestException
  258. *
  259. * @magentoConfigFixture admin/security/use_form_key 0
  260. * @magentoAppArea adminhtml
  261. */
  262. public function testValidateWithInvalidFormKey()
  263. {
  264. $this->request->setPost(
  265. new Parameters(['form_key' => $this->formKey->getFormKey() .'1'])
  266. );
  267. $this->request->setMethod(HttpRequest::METHOD_POST);
  268. $this->validator->validate(
  269. $this->request,
  270. $this->mockUnawareAction
  271. );
  272. }
  273. /**
  274. * @magentoConfigFixture admin/security/use_form_key 0
  275. * @magentoAppArea adminhtml
  276. */
  277. public function testValidateInvalidWithAwareAction()
  278. {
  279. $this->request->setParams([self::AWARE_VALIDATION_PARAM => '']);
  280. /** @var InvalidRequestException|null $caught */
  281. $caught = null;
  282. try {
  283. $this->validator->validate(
  284. $this->request,
  285. $this->mockAwareAction
  286. );
  287. } catch (InvalidRequestException $exception) {
  288. $caught = $exception;
  289. }
  290. $this->assertNotNull($caught);
  291. /** @var Response $response */
  292. $response = $caught->getReplaceResult();
  293. $this->assertInstanceOf(Response::class, $response);
  294. $this->assertEquals(
  295. self::AWARE_LOCATION_VALUE,
  296. $response->getHeader('Location')->getFieldValue()
  297. );
  298. $this->assertNull($caught->getMessages());
  299. }
  300. /**
  301. * @magentoAppArea adminhtml
  302. */
  303. public function testValidateValidWithAwareAction()
  304. {
  305. $this->request->setParams(
  306. [self::AWARE_VALIDATION_PARAM => '1']
  307. );
  308. $this->validator->validate(
  309. $this->request,
  310. $this->mockAwareAction
  311. );
  312. }
  313. /**
  314. * @magentoConfigFixture admin/security/use_form_key 1
  315. * @magentoAppArea adminhtml
  316. */
  317. public function testValidateWithCsrfAwareAction()
  318. {
  319. //Setting up request that would be valid for default validation.
  320. $this->request->setMethod(HttpRequest::METHOD_GET);
  321. $this->auth->login(
  322. TestBootstrap::ADMIN_NAME,
  323. TestBootstrap::ADMIN_PASSWORD
  324. );
  325. $this->request->setParams([
  326. BackendUrl::SECRET_KEY_PARAM_NAME => $this->url->getSecretKey(),
  327. ]);
  328. /** @var InvalidRequestException|null $caught */
  329. $caught = null;
  330. try {
  331. $this->validator->validate(
  332. $this->request,
  333. $this->mockCsrfAwareAction
  334. );
  335. } catch (InvalidRequestException $exception) {
  336. $caught = $exception;
  337. }
  338. //Checking that custom validation was called and invalidated
  339. //valid request.
  340. $this->assertNotNull($caught);
  341. $this->assertCount(1, $caught->getMessages());
  342. $this->assertEquals(
  343. self::CSRF_AWARE_MESSAGE,
  344. $caught->getMessages()[0]->getText()
  345. );
  346. }
  347. public function testInvalidAjaxRequest()
  348. {
  349. //Setting up AJAX request with invalid secret key.
  350. $this->request->setMethod(HttpRequest::METHOD_GET);
  351. $this->auth->login(
  352. TestBootstrap::ADMIN_NAME,
  353. TestBootstrap::ADMIN_PASSWORD
  354. );
  355. $this->request->setParams([
  356. BackendUrl::SECRET_KEY_PARAM_NAME => 'invalid',
  357. 'isAjax' => '1'
  358. ]);
  359. /** @var InvalidRequestException|null $caught */
  360. $caught = null;
  361. try {
  362. $this->validator->validate(
  363. $this->request,
  364. $this->mockUnawareAction
  365. );
  366. } catch (InvalidRequestException $exception) {
  367. $caught = $exception;
  368. }
  369. $this->assertNotNull($caught);
  370. $this->assertInstanceOf(
  371. ResultInterface::class,
  372. $caught->getReplaceResult()
  373. );
  374. /** @var ResultInterface $result */
  375. $result = $caught->getReplaceResult();
  376. /** @var HttpResponse $response */
  377. $response = $this->httpResponseFactory->create();
  378. $result->renderResult($response);
  379. $this->assertEmpty($response->getBody());
  380. $this->assertEquals(401, $response->getHttpResponseCode());
  381. }
  382. }