Skip to content

Commit 59fb3f9

Browse files
authored
Merge pull request #9676 from magento-lynx/lynx-2.4.8
2 parents b2b07d3 + f2378e3 commit 59fb3f9

File tree

13 files changed

+717
-88
lines changed

13 files changed

+717
-88
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CustomerGraphQl\Controller\HttpRequestValidator;
9+
10+
use Magento\Framework\App\HttpRequestInterface;
11+
use Magento\Framework\Exception\AuthorizationException;
12+
use Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException;
13+
use Magento\GraphQl\Controller\HttpRequestValidatorInterface;
14+
use Magento\Integration\Api\Exception\UserTokenException;
15+
use Magento\Integration\Api\UserTokenReaderInterface;
16+
use Magento\Integration\Api\UserTokenValidatorInterface;
17+
18+
class AuthorizationRequestValidator implements HttpRequestValidatorInterface
19+
{
20+
private const AUTH = 'Authorization';
21+
private const BEARER = 'bearer';
22+
23+
/**
24+
* AuthorizationRequestValidator Constructor
25+
*
26+
* @param UserTokenReaderInterface $tokenReader
27+
* @param UserTokenValidatorInterface $tokenValidator
28+
*/
29+
public function __construct(
30+
private readonly UserTokenReaderInterface $tokenReader,
31+
private readonly UserTokenValidatorInterface $tokenValidator
32+
) {
33+
}
34+
35+
/**
36+
* Validate the authorization header bearer token if it is set
37+
*
38+
* @param HttpRequestInterface $request
39+
* @return void
40+
* @throws GraphQlAuthenticationException
41+
*/
42+
public function validate(HttpRequestInterface $request): void
43+
{
44+
$authorizationHeaderValue = $request->getHeader(self::AUTH);
45+
if (!$authorizationHeaderValue) {
46+
return;
47+
}
48+
49+
$headerPieces = explode(' ', $authorizationHeaderValue);
50+
if (count($headerPieces) !== 2 || strtolower($headerPieces[0]) !== self::BEARER) {
51+
return;
52+
}
53+
54+
try {
55+
$this->tokenValidator->validate($this->tokenReader->read($headerPieces[1]));
56+
} catch (UserTokenException | AuthorizationException $exception) {
57+
throw new GraphQlAuthenticationException(__($exception->getMessage()));
58+
}
59+
}
60+
}

app/code/Magento/CustomerGraphQl/etc/graphql/di.xml

+7
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,11 @@
214214
<plugin name="merge_order_after_customer_signup"
215215
type="Magento\CustomerGraphQl\Plugin\Model\MergeGuestOrder" />
216216
</type>
217+
<type name="Magento\GraphQl\Controller\HttpRequestProcessor">
218+
<arguments>
219+
<argument name="requestValidators" xsi:type="array">
220+
<item name="authorizationValidator" xsi:type="object">Magento\CustomerGraphQl\Controller\HttpRequestValidator\AuthorizationRequestValidator</item>
221+
</argument>
222+
</arguments>
223+
</type>
217224
</config>

app/code/Magento/GraphQl/Controller/GraphQl.php

+61-20
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Magento\GraphQl\Controller;
99

10+
use Exception;
1011
use GraphQL\Error\FormattedError;
1112
use GraphQL\Error\SyntaxError;
1213
use Magento\Framework\App\Area;
@@ -19,6 +20,8 @@
1920
use Magento\Framework\App\ResponseInterface;
2021
use Magento\Framework\Controller\Result\JsonFactory;
2122
use Magento\Framework\GraphQl\Exception\ExceptionFormatter;
23+
use Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException;
24+
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
2225
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
2326
use Magento\Framework\GraphQl\Query\Fields as QueryFields;
2427
use Magento\Framework\GraphQl\Query\QueryParser;
@@ -30,6 +33,7 @@
3033
use Magento\GraphQl\Helper\Query\Logger\LogData;
3134
use Magento\GraphQl\Model\Query\ContextFactoryInterface;
3235
use Magento\GraphQl\Model\Query\Logger\LoggerPool;
36+
use Throwable;
3337

3438
/**
3539
* Front controller for web API GraphQL area.
@@ -40,6 +44,8 @@
4044
*/
4145
class GraphQl implements FrontControllerInterface
4246
{
47+
private const METHOD_OPTIONS = 'OPTIONS';
48+
4349
/**
4450
* @var \Magento\Framework\Webapi\Response
4551
* @deprecated 100.3.2
@@ -181,20 +187,19 @@ public function __construct(
181187
public function dispatch(RequestInterface $request): ResponseInterface
182188
{
183189
$this->areaList->getArea(Area::AREA_GRAPHQL)->load(Area::PART_TRANSLATE);
184-
185-
$statusCode = 200;
186190
$jsonResult = $this->jsonFactory->create();
187191
$data = [];
188192
$result = null;
189193
$schema = null;
190-
$query = '';
191194

192195
try {
193196
$data = $this->getDataFromRequest($request);
194197
$query = $data['query'] ?? '';
195198

196199
/** @var Http $request */
197200
$this->requestProcessor->validateRequest($request);
201+
$statusCode = $request->getMethod() === self::METHOD_OPTIONS ? 204 : 200;
202+
198203
if ($request->isGet() || $request->isPost()) {
199204
$parsedQuery = $this->queryParser->parse($query);
200205
$data['parsedQuery'] = $parsedQuery;
@@ -210,17 +215,10 @@ public function dispatch(RequestInterface $request): ResponseInterface
210215
$this->contextFactory->create(),
211216
$data['variables'] ?? []
212217
);
218+
$statusCode = $this->getHttpResponseCode($result);
213219
}
214-
} catch (SyntaxError|GraphQlInputException $error) {
215-
$result = [
216-
'errors' => [FormattedError::createFromException($error)],
217-
];
218-
$statusCode = 400;
219-
} catch (\Exception $error) {
220-
$result = [
221-
'errors' => [$this->graphQlError->create($error)],
222-
];
223-
$statusCode = ExceptionFormatter::HTTP_GRAPH_QL_SCHEMA_ERROR_STATUS;
220+
} catch (Exception $error) {
221+
[$result, $statusCode] = $this->handleGraphQlException($error);
224222
}
225223

226224
$jsonResult->setHttpResponseCode($statusCode);
@@ -230,14 +228,59 @@ public function dispatch(RequestInterface $request): ResponseInterface
230228
$jsonResult->renderResult($this->httpResponse);
231229

232230
// log information about the query, unless it is an introspection query
233-
if (strpos($query, 'IntrospectionQuery') === false) {
231+
if (!isset($data['query']) || strpos($data['query'], 'IntrospectionQuery') === false) {
234232
$queryInformation = $this->logDataHelper->getLogData($request, $data, $schema, $this->httpResponse);
235233
$this->loggerPool->execute($queryInformation);
236234
}
237235

238236
return $this->httpResponse;
239237
}
240238

239+
/**
240+
* Handle GraphQL Exceptions
241+
*
242+
* @param Exception $error
243+
* @return array
244+
* @throws Throwable
245+
*/
246+
private function handleGraphQlException(Exception $error): array
247+
{
248+
if ($error instanceof SyntaxError || $error instanceof GraphQlInputException) {
249+
return [['errors' => [FormattedError::createFromException($error)]], 400];
250+
}
251+
if ($error instanceof GraphQlAuthenticationException) {
252+
return [['errors' => [$this->graphQlError->create($error)]], 401];
253+
}
254+
if ($error instanceof GraphQlAuthorizationException) {
255+
return [['errors' => [$this->graphQlError->create($error)]], 403];
256+
}
257+
return [
258+
['errors' => [$this->graphQlError->create($error)]],
259+
ExceptionFormatter::HTTP_GRAPH_QL_SCHEMA_ERROR_STATUS
260+
];
261+
}
262+
263+
/**
264+
* Retrieve http response code based on the error categories
265+
*
266+
* @param array $result
267+
* @return int
268+
*/
269+
private function getHttpResponseCode(array $result): int
270+
{
271+
foreach ($result['errors'] ?? [] as $error) {
272+
if (isset($error['extensions']['category'])) {
273+
return match ($error['extensions']['category']) {
274+
GraphQlAuthenticationException::EXCEPTION_CATEGORY => 401,
275+
GraphQlAuthorizationException::EXCEPTION_CATEGORY => 403,
276+
default => 200,
277+
};
278+
}
279+
}
280+
281+
return 200;
282+
}
283+
241284
/**
242285
* Get data from request body or query string
243286
*
@@ -247,18 +290,16 @@ public function dispatch(RequestInterface $request): ResponseInterface
247290
*/
248291
private function getDataFromRequest(RequestInterface $request): array
249292
{
293+
$data = [];
250294
try {
251295
/** @var Http $request */
252296
if ($request->isPost()) {
253297
$data = $request->getContent() ? $this->jsonSerializer->unserialize($request->getContent()) : [];
254298
} elseif ($request->isGet()) {
255299
$data = $request->getParams();
256-
$data['variables'] = isset($data['variables']) ?
257-
$this->jsonSerializer->unserialize($data['variables']) : null;
258-
$data['variables'] = is_array($data['variables']) ?
259-
$data['variables'] : null;
260-
} else {
261-
$data = [];
300+
$data['variables'] = !empty($data['variables']) && is_string($data['variables'])
301+
? $this->jsonSerializer->unserialize($data['variables'])
302+
: null;
262303
}
263304
} catch (\InvalidArgumentException $e) {
264305
throw new GraphQlInputException(__('Unable to parse the request.'), $e);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\QuoteGraphQl\Plugin\Model;
9+
10+
use Magento\Payment\Model\MethodList;
11+
use Magento\Quote\Api\Data\CartInterface;
12+
13+
class RestrictPaymentMethods
14+
{
15+
/**
16+
* Show only the "free" payment method if the order total is 0
17+
*
18+
* @param MethodList $subject
19+
* @param array $result
20+
* @param CartInterface|null $quote
21+
* @return array
22+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
23+
*/
24+
public function afterGetAvailableMethods(
25+
MethodList $subject,
26+
array $result,
27+
?CartInterface $quote = null
28+
): array {
29+
if (!$quote || $quote->getGrandTotal() != 0) {
30+
return $result;
31+
}
32+
33+
return array_filter($result, fn ($method) => $method->getCode() === 'free') ?: [];
34+
}
35+
}

app/code/Magento/QuoteGraphQl/composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"magento/module-catalog-inventory": "*",
2121
"magento/module-eav-graph-ql": "*",
2222
"magento/module-downloadable": "*",
23-
"magento/module-catalog-graph-ql": "*"
23+
"magento/module-catalog-graph-ql": "*",
24+
"magento/module-payment": "*"
2425
},
2526
"suggest": {
2627
"magento/module-graph-ql-cache": "*",

app/code/Magento/QuoteGraphQl/etc/graphql/di.xml

+3
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,7 @@
9696
<plugin name="billing_address_save_same_as_shipping"
9797
type="Magento\QuoteGraphQl\Plugin\CustomerManagementPlugin"/>
9898
</type>
99+
<type name="Magento\Payment\Model\MethodList">
100+
<plugin name="restrict_payment_methods_graphql" type="Magento\QuoteGraphQl\Plugin\Model\RestrictPaymentMethods"/>
101+
</type>
99102
</config>

0 commit comments

Comments
 (0)