diff --git a/app/code/Magento/GraphQl/Controller/GraphQl.php b/app/code/Magento/GraphQl/Controller/GraphQl.php index c4a0b55de9bf..01d5058427ca 100644 --- a/app/code/Magento/GraphQl/Controller/GraphQl.php +++ b/app/code/Magento/GraphQl/Controller/GraphQl.php @@ -11,6 +11,7 @@ use Magento\Framework\App\Request\Http; use Magento\Framework\App\RequestInterface; use Magento\Framework\App\ResponseInterface; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\GraphQl\Exception\ExceptionFormatter; use Magento\Framework\GraphQl\Query\QueryProcessor; use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; @@ -108,7 +109,20 @@ public function dispatch(RequestInterface $request) : ResponseInterface try { /** @var Http $request */ $this->requestProcessor->processHeaders($request); - $data = $this->jsonSerializer->unserialize($request->getContent()); + if ($request->isPost()) { + $data = $this->jsonSerializer->unserialize($request->getContent()); + } else { + $data = $request->getParams(); + $data['variables'] = isset($data['variables']) ? + $this->jsonSerializer->unserialize($data['variables']) : null; + + // The easiest way to determine mutations without additional parsing + if (strpos(trim($data['query']), 'mutation') === 0) { + throw new LocalizedException( + __('Mutation requests allowed only for POST requests') + ); + } + } $query = isset($data['query']) ? $data['query'] : ''; diff --git a/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/ContentTypeProcessor.php b/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/ContentTypeProcessor.php index 2270f2616e67..69201f93ab4e 100644 --- a/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/ContentTypeProcessor.php +++ b/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/ContentTypeProcessor.php @@ -7,6 +7,7 @@ namespace Magento\GraphQl\Controller\HttpHeaderProcessor; +use Magento\Framework\App\HttpRequestInterface; use Magento\Framework\Exception\LocalizedException; use Magento\GraphQl\Controller\HttpHeaderProcessorInterface; @@ -18,12 +19,14 @@ class ContentTypeProcessor implements HttpHeaderProcessorInterface /** * Handle the mandatory application/json header * - * {@inheritDoc} + * @inheritDoc * @throws LocalizedException */ - public function processHeaderValue(string $headerValue) : void + public function processHeaderValue(string $headerValue, HttpRequestInterface $request) : void { - if (!$headerValue || strpos($headerValue, 'application/json') === false) { + if ($request->isPost() + && (!$headerValue || strpos($headerValue, 'application/json') === false) + ) { throw new LocalizedException( new \Magento\Framework\Phrase('Request content type must be application/json') ); diff --git a/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/StoreProcessor.php b/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/StoreProcessor.php index be359eafdf24..e92ff374eb35 100644 --- a/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/StoreProcessor.php +++ b/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/StoreProcessor.php @@ -7,7 +7,7 @@ namespace Magento\GraphQl\Controller\HttpHeaderProcessor; -use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\App\HttpRequestInterface; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\GraphQl\Controller\HttpHeaderProcessorInterface; use Magento\Store\Model\StoreManagerInterface; @@ -35,10 +35,10 @@ public function __construct(StoreManagerInterface $storeManager) /** * Handle the value of the store and set the scope * - * {@inheritDoc} - * @throws NoSuchEntityException + * @inheritDoc + * @throws GraphQlInputException */ - public function processHeaderValue(string $headerValue) : void + public function processHeaderValue(string $headerValue, HttpRequestInterface $request) : void { if ($headerValue) { $storeCode = ltrim(rtrim($headerValue)); diff --git a/app/code/Magento/GraphQl/Controller/HttpHeaderProcessorInterface.php b/app/code/Magento/GraphQl/Controller/HttpHeaderProcessorInterface.php index a20f88a4ef99..b38b96714eab 100644 --- a/app/code/Magento/GraphQl/Controller/HttpHeaderProcessorInterface.php +++ b/app/code/Magento/GraphQl/Controller/HttpHeaderProcessorInterface.php @@ -7,6 +7,8 @@ namespace Magento\GraphQl\Controller; +use Magento\Framework\App\HttpRequestInterface; + /** * Use this interface to implement a processor for each entry of a header in an HTTP GraphQL request. */ @@ -19,7 +21,8 @@ interface HttpHeaderProcessorInterface * to enforce required headers like "application/json" * * @param string $headerValue + * @param HttpRequestInterface $request * @return void */ - public function processHeaderValue(string $headerValue) : void; + public function processHeaderValue(string $headerValue, HttpRequestInterface $request) : void; } diff --git a/app/code/Magento/GraphQl/Controller/HttpRequestProcessor.php b/app/code/Magento/GraphQl/Controller/HttpRequestProcessor.php index 5e42b81a6198..5d1c970a358a 100644 --- a/app/code/Magento/GraphQl/Controller/HttpRequestProcessor.php +++ b/app/code/Magento/GraphQl/Controller/HttpRequestProcessor.php @@ -36,7 +36,7 @@ public function __construct(array $graphQlHeaders = []) public function processHeaders(Http $request) : void { foreach ($this->headerProcessors as $headerName => $headerClass) { - $headerClass->processHeaderValue((string)$request->getHeader($headerName)); + $headerClass->processHeaderValue((string)$request->getHeader($headerName), $request); } } } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php index 5458b5cfbb73..ab2d094c5e89 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php @@ -78,6 +78,41 @@ public function postQuery(string $query, array $variables = [], string $operatio } } + /** + * Perform HTTP GET request for query + * + * @param string $query + * @param array $variables + * @param string $operationName + * @param array $headers + * @return mixed + * @throws \Exception + */ + public function getQuery(string $query, array $variables = [], string $operationName = '', array $headers = []) + { + $url = $this->getEndpointUrl(); + $requestArray = [ + 'query' => $query, + 'variables' => empty($variables) ? $variables : null, + 'operationName' => empty($operationName) ? $operationName : null + ]; + + $responseBody = $this->curlClient->get($url, $requestArray, $headers); + $responseBodyArray = $this->json->jsonDecode($responseBody); + + if (!is_array($responseBodyArray)) { + throw new \Exception('Unknown GraphQL response body: ' . json_encode($responseBodyArray)); + } + + $this->processErrors($responseBodyArray); + + if (!isset($responseBodyArray['data'])) { + throw new \Exception('Unknown GraphQL response body: ' . json_encode($responseBodyArray)); + } else { + return $responseBodyArray['data']; + } + } + /** * Process errors * diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php index 790581c476da..f55b981a04a9 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php @@ -34,6 +34,7 @@ abstract class GraphQlAbstract extends WebapiAbstract * @param array $variables * @param string $operationName * @param array $headers + * @param string $requestType * @return array|int|string|float|bool GraphQL call results * @throws \Exception */ @@ -41,14 +42,28 @@ public function graphQlQuery( string $query, array $variables = [], string $operationName = '', - array $headers = [] + array $headers = [], + string $requestType = 'POST' ) { - return $this->getGraphQlClient()->postQuery( - $query, - $variables, - $operationName, - $this->composeHeaders($headers) - ); + if ($requestType === 'POST') { + $response = $this->getGraphQlClient()->postQuery( + $query, + $variables, + $operationName, + $this->composeHeaders($headers) + ); + } elseif ($requestType === 'GET') { + $response = $this->getGraphQlClient()->getQuery( + $query, + $variables, + $operationName, + $this->composeHeaders($headers) + ); + } else { + throw new \Exception("Unsupported request type"); + } + + return $response; } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlMutationTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlMutationTest.php index b6e1a61f0357..d60281a6cf41 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlMutationTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlMutationTest.php @@ -34,4 +34,23 @@ public function testMutation() $this->assertArrayHasKey('integer_list', $testItem); $this->assertEquals([4, 5, 6], $testItem['integer_list']); } + + public function testMutationIsNotAllowedViaGetRequest() + { + $id = 3; + + $query = <<graphQlQuery($query, [], '', [], 'GET'); + } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php index d59e255daa10..690844222929 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php @@ -58,4 +58,23 @@ public function testQueryTestModuleExtensionAttribute() $this->assertArrayHasKey('integer_list', $testItem); $this->assertEquals([3, 4, 5], $testItem['integer_list']); } + + public function testQueryViaGetRequestReturnsResults() + { + $id = 1; + + $query = <<graphQlQuery($query, [], '', [], 'GET'); + + $this->assertArrayHasKey('testItem', $response); + } }