diff --git a/vendor/magento/module-customer-graph-ql/Controller/HttpRequestValidator/AuthorizationRequestValidator.php b/vendor/magento/module-customer-graph-ql/Controller/HttpRequestValidator/AuthorizationRequestValidator.php
new file mode 100644
index 00000000000..8131419a182
--- /dev/null
+++ b/vendor/magento/module-customer-graph-ql/Controller/HttpRequestValidator/AuthorizationRequestValidator.php
@@ -0,0 +1,60 @@
+getHeader(self::AUTH);
+ if (!$authorizationHeaderValue) {
+ return;
+ }
+
+ $headerPieces = explode(' ', $authorizationHeaderValue);
+ if (count($headerPieces) !== 2 || strtolower($headerPieces[0]) !== self::BEARER) {
+ return;
+ }
+
+ try {
+ $this->tokenValidator->validate($this->tokenReader->read($headerPieces[1]));
+ } catch (UserTokenException | AuthorizationException $exception) {
+ throw new GraphQlAuthenticationException(__($exception->getMessage()));
+ }
+ }
+}
diff --git a/vendor/magento/module-customer-graph-ql/Model/GetGuestOrdersByEmail.php b/vendor/magento/module-customer-graph-ql/Model/GetGuestOrdersByEmail.php
new file mode 100644
index 00000000000..b06ff42b7d2
--- /dev/null
+++ b/vendor/magento/module-customer-graph-ql/Model/GetGuestOrdersByEmail.php
@@ -0,0 +1,54 @@
+searchCriteriaBuilder->addFilter(
+ 'customer_email',
+ $email,
+ 'eq'
+ )->addFilter(
+ 'customer_is_guest',
+ 1,
+ 'eq'
+ );
+ return $this->orderRepository->getList($this->searchCriteriaBuilder->create());
+ }
+}
diff --git a/vendor/magento/module-customer-graph-ql/Plugin/Model/MergeGuestOrder.php b/vendor/magento/module-customer-graph-ql/Plugin/Model/MergeGuestOrder.php
new file mode 100644
index 00000000000..b86c133a5da
--- /dev/null
+++ b/vendor/magento/module-customer-graph-ql/Plugin/Model/MergeGuestOrder.php
@@ -0,0 +1,51 @@
+getGuestOrdersByEmail->execute($customer->getEmail());
+ foreach ($searchResult->getItems() as $order) {
+ $this->customerAssignment->execute($order, $customer);
+ }
+ return $customer;
+ }
+}
diff --git a/vendor/magento/module-customer-graph-ql/etc/graphql/di.xml b/vendor/magento/module-customer-graph-ql/etc/graphql/di.xml
index 305e9cd12d6..77bfbb3fe28 100644
--- a/vendor/magento/module-customer-graph-ql/etc/graphql/di.xml
+++ b/vendor/magento/module-customer-graph-ql/etc/graphql/di.xml
@@ -209,4 +209,15 @@
+
+
+
+ - Magento\CustomerGraphQl\Controller\HttpRequestValidator\AuthorizationRequestValidator
+
+
+
+
+
+
diff --git a/vendor/magento/module-graph-ql/Controller/GraphQl.php b/vendor/magento/module-graph-ql/Controller/GraphQl.php
index f20956407c2..8d478233354 100644
--- a/vendor/magento/module-graph-ql/Controller/GraphQl.php
+++ b/vendor/magento/module-graph-ql/Controller/GraphQl.php
@@ -9,6 +9,10 @@ declare(strict_types=1);
namespace Magento\GraphQl\Controller;
+use Exception;
+use GraphQL\Error\FormattedError;
+use GraphQL\Error\SyntaxError;
+use GraphQL\Language\Source;
use Magento\Framework\App\Area;
use Magento\Framework\App\AreaList;
use Magento\Framework\App\FrontControllerInterface;
@@ -19,6 +23,10 @@ use Magento\Framework\App\Response\Http as HttpResponse;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\GraphQl\Exception\ExceptionFormatter;
+use Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException;
+use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
+use Magento\Framework\GraphQl\Exception\GraphQlInputException;
+use Magento\Framework\GraphQl\Exception\InvalidRequestInterface;
use Magento\Framework\GraphQl\Query\Fields as QueryFields;
use Magento\Framework\GraphQl\Query\QueryParser;
use Magento\Framework\GraphQl\Query\QueryProcessor;
@@ -39,6 +47,8 @@ use Magento\GraphQl\Model\Query\Logger\LoggerPool;
*/
class GraphQl implements FrontControllerInterface
{
+ private const METHOD_OPTIONS = 'OPTIONS';
+
/**
* @var \Magento\Framework\Webapi\Response
* @deprecated 100.3.2
@@ -180,35 +190,38 @@ class GraphQl implements FrontControllerInterface
public function dispatch(RequestInterface $request): ResponseInterface
{
$this->areaList->getArea(Area::AREA_GRAPHQL)->load(Area::PART_TRANSLATE);
-
- $statusCode = 200;
$jsonResult = $this->jsonFactory->create();
- $data = $this->getDataFromRequest($request);
- $result = [];
-
+ $data = [];
+ $result = null;
$schema = null;
+
try {
+ $data = $this->getDataFromRequest($request);
+ $query = $data['query'] ?? '';
+
/** @var Http $request */
$this->requestProcessor->validateRequest($request);
- $query = $data['query'] ?? '';
- $parsedQuery = $this->queryParser->parse($query);
- $data['parsedQuery'] = $parsedQuery;
-
- // We must extract queried field names to avoid instantiation of unnecessary fields in webonyx schema
- // Temporal coupling is required for performance optimization
- $this->queryFields->setQuery($parsedQuery, $data['variables'] ?? null);
- $schema = $this->schemaGenerator->generate();
-
- $result = $this->queryProcessor->process(
- $schema,
- $parsedQuery,
- $this->contextFactory->create(),
- $data['variables'] ?? []
- );
- } catch (\Exception $error) {
- $result['errors'] = isset($result['errors']) ? $result['errors'] : [];
- $result['errors'][] = $this->graphQlError->create($error);
- $statusCode = ExceptionFormatter::HTTP_GRAPH_QL_SCHEMA_ERROR_STATUS;
+ $statusCode = $request->getMethod() === self::METHOD_OPTIONS ? 204 : 200;
+
+ if ($request->isGet() || $request->isPost()) {
+ $parsedQuery = $this->queryParser->parse($query);
+ $data['parsedQuery'] = $parsedQuery;
+
+ // We must extract queried field names to avoid instantiation of unnecessary fields in webonyx schema
+ // Temporal coupling is required for performance optimization
+ $this->queryFields->setQuery($parsedQuery, $data['variables'] ?? null);
+ $schema = $this->schemaGenerator->generate();
+
+ $result = $this->queryProcessor->process(
+ $schema,
+ $parsedQuery,
+ $this->contextFactory->create(),
+ $data['variables'] ?? []
+ );
+ $statusCode = $this->getHttpResponseCode($result);
+ }
+ } catch (Exception $error) {
+ [$result, $statusCode] = $this->handleGraphQlException($error);
}
$jsonResult->setHttpResponseCode($statusCode);
@@ -224,25 +237,76 @@ class GraphQl implements FrontControllerInterface
return $this->httpResponse;
}
+ /**
+ * Handle GraphQL Exceptions
+ *
+ * @param Exception $e
+ * @return array
+ */
+ private function handleGraphQlException(Exception $e): array
+ {
+ [$error, $statusCode] = match (true) {
+ $e instanceof InvalidRequestInterface => [FormattedError::createFromException($e), $e->getStatusCode()],
+ $e instanceof SyntaxError => [FormattedError::createFromException($e), 400],
+ $e instanceof GraphQlAuthenticationException => [$this->graphQlError->create($e), 401],
+ $e instanceof GraphQlAuthorizationException => [$this->graphQlError->create($e), 403],
+ $e instanceof GraphQlInputException => [FormattedError::createFromException($e), 200],
+ default => [$this->graphQlError->create($e), ExceptionFormatter::HTTP_GRAPH_QL_SCHEMA_ERROR_STATUS],
+ };
+
+ return [['errors' => [$error]], $statusCode];
+ }
+
+ /**
+ * Retrieve http response code based on the error categories
+ *
+ * @param array $result
+ * @return int
+ */
+ private function getHttpResponseCode(array $result): int
+ {
+ foreach ($result['errors'] ?? [] as $error) {
+ if (isset($error['extensions']['category'])) {
+ return match ($error['extensions']['category']) {
+ GraphQlAuthenticationException::EXCEPTION_CATEGORY => 401,
+ GraphQlAuthorizationException::EXCEPTION_CATEGORY => 403,
+ default => 200,
+ };
+ }
+ }
+
+ return 200;
+ }
+
/**
* Get data from request body or query string
*
* @param RequestInterface $request
* @return array
+ * @throws SyntaxError
*/
private function getDataFromRequest(RequestInterface $request): array
{
+ $data = [];
/** @var Http $request */
- if ($request->isPost()) {
- $data = $this->jsonSerializer->unserialize($request->getContent());
+ if ($request->isPost() && $request->getContent()) {
+ $content = $request->getContent();
+ try {
+ $data = $this->jsonSerializer->unserialize($content);
+ } catch (\InvalidArgumentException) {
+ $source = new Source($content);
+ throw new SyntaxError($source, 0, 'Unable to parse the request.');
+ }
} elseif ($request->isGet()) {
$data = $request->getParams();
- $data['variables'] = isset($data['variables']) ?
- $this->jsonSerializer->unserialize($data['variables']) : null;
- $data['variables'] = is_array($data['variables']) ?
- $data['variables'] : null;
- } else {
- return [];
+ try {
+ $data['variables'] = !empty($data['variables']) && is_string($data['variables'])
+ ? $this->jsonSerializer->unserialize($data['variables'])
+ : null;
+ } catch (\InvalidArgumentException) {
+ $source = new Source($data['variables']);
+ throw new SyntaxError($source, 0, 'Unable to parse the variables.');
+ }
}
return $data;
diff --git a/vendor/magento/module-graph-ql/Controller/HttpRequestValidator/ContentTypeValidator.php b/vendor/magento/module-graph-ql/Controller/HttpRequestValidator/ContentTypeValidator.php
index 555048aac67..a8e6a8478f4 100644
--- a/vendor/magento/module-graph-ql/Controller/HttpRequestValidator/ContentTypeValidator.php
+++ b/vendor/magento/module-graph-ql/Controller/HttpRequestValidator/ContentTypeValidator.php
@@ -1,14 +1,15 @@
isPost()
&& strpos($headerValue, $requiredHeaderValue) === false
) {
- throw new GraphQlInputException(
- new \Magento\Framework\Phrase('Request content type must be application/json')
+ throw new UnsupportedMediaTypeException(
+ new Phrase('Request content type must be application/json')
);
}
}
diff --git a/vendor/magento/module-graph-ql/Controller/HttpRequestValidator/HttpVerbValidator.php b/vendor/magento/module-graph-ql/Controller/HttpRequestValidator/HttpVerbValidator.php
index 56351c7711c..ff4cb247175 100644
--- a/vendor/magento/module-graph-ql/Controller/HttpRequestValidator/HttpVerbValidator.php
+++ b/vendor/magento/module-graph-ql/Controller/HttpRequestValidator/HttpVerbValidator.php
@@ -1,7 +1,7 @@
orderRepository->get($orderId);
+ if ($order->getCustomerIsGuest() && $order->getCustomerEmail()) {
+ try {
+ $websiteID = $this->storeManager->getStore()->getWebsiteId();
+ $customer = $this->customerRepository->get($order->getCustomerEmail(), $websiteID);
+ if ($customer->getId()) {
+ $this->customerAssignment->execute($order, $customer);
+ }
+ // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock
+ } catch (NoSuchEntityException $e) {
+ // Do not remove this handle as it used to check that customer
+ // with this email not registered in the system
+ }
+ }
+ }
+ return $orderId;
+ }
+}
diff --git a/vendor/magento/module-quote-graph-ql/etc/graphql/di.xml b/vendor/magento/module-quote-graph-ql/etc/graphql/di.xml
index 83942f1daeb..6ded0467ecd 100644
--- a/vendor/magento/module-quote-graph-ql/etc/graphql/di.xml
+++ b/vendor/magento/module-quote-graph-ql/etc/graphql/di.xml
@@ -73,4 +73,8 @@
+
+
+
diff --git a/vendor/magento/theme-frontend-blank/i18n/en_US.csv b/vendor/magento/theme-frontend-blank/i18n/en_US.csv
index c947c603d5e..c9f6ad01ad5 100644
--- a/vendor/magento/theme-frontend-blank/i18n/en_US.csv
+++ b/vendor/magento/theme-frontend-blank/i18n/en_US.csv
@@ -441,3 +441,4 @@ Test,Test
test,test
Two,Two
"Invalid data type","Invalid data type"
+"Unknown type ""%1"".","Unknown type ""%1""."
diff --git a/vendor/magento/theme-frontend-luma/i18n/en_US.csv b/vendor/magento/theme-frontend-luma/i18n/en_US.csv
index 0bde3949f25..e476d0575a7 100644
--- a/vendor/magento/theme-frontend-luma/i18n/en_US.csv
+++ b/vendor/magento/theme-frontend-luma/i18n/en_US.csv
@@ -491,3 +491,4 @@ Test,Test
test,test
Two,Two
"Invalid data type","Invalid data type"
+"Unknown type ""%1"".","Unknown type ""%1""."
diff --git a/vendor/magento/framework/GraphQl/Exception/InvalidRequestInterface.php b/vendor/magento/framework/GraphQl/Exception/InvalidRequestInterface.php
new file mode 100644
index 00000000000..4a6d16f32ee
--- /dev/null
+++ b/vendor/magento/framework/GraphQl/Exception/InvalidRequestInterface.php
@@ -0,0 +1,23 @@
+isSafe;
+ }
+}
diff --git a/vendor/magento/framework/GraphQl/Exception/UnsupportedMediaTypeException.php b/vendor/magento/framework/GraphQl/Exception/UnsupportedMediaTypeException.php
new file mode 100644
index 00000000000..6be650a3eb0
--- /dev/null
+++ b/vendor/magento/framework/GraphQl/Exception/UnsupportedMediaTypeException.php
@@ -0,0 +1,46 @@
+isSafe;
+ }
+}
diff --git a/vendor/magento/framework/GraphQl/Schema/SchemaGenerator.php b/vendor/magento/framework/GraphQl/Schema/SchemaGenerator.php
index 250b80defa6..560137d7ed6 100644
--- a/vendor/magento/framework/GraphQl/Schema/SchemaGenerator.php
+++ b/vendor/magento/framework/GraphQl/Schema/SchemaGenerator.php
@@ -8,6 +8,7 @@ declare(strict_types=1);
namespace Magento\Framework\GraphQl\Schema;
use Magento\Framework\GraphQl\ConfigInterface;
+use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Schema;
use Magento\Framework\GraphQl\Schema\Type\TypeRegistry;
use Magento\Framework\GraphQl\SchemaFactory;
@@ -57,7 +58,11 @@ class SchemaGenerator implements SchemaGeneratorInterface
'query' => $this->typeRegistry->get('Query'),
'mutation' => $this->typeRegistry->get('Mutation'),
'typeLoader' => function ($name) {
- return $this->typeRegistry->get($name);
+ try {
+ return $this->typeRegistry->get($name);
+ } catch (GraphQlInputException) {
+ return null;
+ }
},
'types' => function () {
$typesImplementors = [];
diff --git a/vendor/magento/framework/GraphQl/Schema/Type/TypeRegistry.php b/vendor/magento/framework/GraphQl/Schema/Type/TypeRegistry.php
index 414e1eebe65..77548d3ec3f 100644
--- a/vendor/magento/framework/GraphQl/Schema/Type/TypeRegistry.php
+++ b/vendor/magento/framework/GraphQl/Schema/Type/TypeRegistry.php
@@ -7,6 +7,7 @@ declare(strict_types=1);
namespace Magento\Framework\GraphQl\Schema\Type;
+use LogicException;
use Magento\Framework\GraphQl\ConfigInterface;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Schema\TypeInterface;
@@ -66,7 +67,13 @@ class TypeRegistry implements ResetAfterRequestInterface
public function get(string $typeName): TypeInterface
{
if (!isset($this->types[$typeName])) {
- $configElement = $this->config->getConfigElement($typeName);
+ try {
+ $configElement = $this->config->getConfigElement($typeName);
+ } catch (LogicException) {
+ throw new GraphQlInputException(
+ new Phrase('Unknown type "%1".', [$typeName])
+ );
+ }
$configElementClass = get_class($configElement);
if (!isset($this->configToTypeMap[$configElementClass])) {