Skip to content

Commit 67e9930

Browse files
authored
Merge branch '2.4-develop' into automation
2 parents c1221b9 + 73d7bd4 commit 67e9930

File tree

95 files changed

+3561
-627
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+3561
-627
lines changed

app/code/Magento/Backend/App/Area/FrontNameResolver.php

+6-2
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ public function getFrontName($checkHost = false)
117117
/**
118118
* Return whether the host from request is the backend host
119119
*
120+
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
121+
* @SuppressWarnings(PHPMD.NPathComplexity)
122+
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
120123
* @return bool
121124
*/
122125
public function isHostBackend()
@@ -128,10 +131,11 @@ public function isHostBackend()
128131
if ($this->scopeConfig->getValue(self::XML_PATH_USE_CUSTOM_ADMIN_URL, ScopeInterface::SCOPE_STORE)) {
129132
$backendUrl = $this->scopeConfig->getValue(self::XML_PATH_CUSTOM_ADMIN_URL, ScopeInterface::SCOPE_STORE);
130133
} else {
131-
$backendUrl = $this->config->getValue(Store::XML_PATH_UNSECURE_BASE_URL);
134+
$xmlPath = $this->request->isSecure() ? Store::XML_PATH_SECURE_BASE_URL : Store::XML_PATH_UNSECURE_BASE_URL;
135+
$backendUrl = $this->config->getValue($xmlPath);
132136
if ($backendUrl === null) {
133137
$backendUrl = $this->scopeConfig->getValue(
134-
Store::XML_PATH_UNSECURE_BASE_URL,
138+
$xmlPath,
135139
ScopeInterface::SCOPE_STORE
136140
);
137141
}

app/code/Magento/Backend/Test/Unit/App/Area/FrontNameResolverTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ public function testIsHostBackend(
129129
->willReturnMap(
130130
[
131131
[Store::XML_PATH_UNSECURE_BASE_URL, ScopeInterface::SCOPE_STORE, null, $url],
132+
[Store::XML_PATH_SECURE_BASE_URL, ScopeInterface::SCOPE_STORE, null, $url],
132133
[
133134
FrontNameResolver::XML_PATH_USE_CUSTOM_ADMIN_URL,
134135
ScopeInterface::SCOPE_STORE,
@@ -160,7 +161,6 @@ public function testIsHostBackend(
160161
->setHost(parse_url($url, PHP_URL_HOST))
161162
->setPort(parse_url($url, PHP_URL_PORT))
162163
);
163-
164164
$this->assertEquals($expectedValue, $this->model->isHostBackend());
165165
}
166166

app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/DataProviderTest.php

+2-12
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,7 @@
11
<?php
2-
/************************************************************************
3-
* Copyright 2024 Adobe
2+
/**
3+
* Copyright 2016 Adobe
44
* All Rights Reserved.
5-
*
6-
* NOTICE: All information contained herein is, and remains
7-
* the property of Adobe and its suppliers, if any. The intellectual
8-
* and technical concepts contained herein are proprietary to Adobe
9-
* and its suppliers and are protected by all applicable intellectual
10-
* property laws, including trade secret and copyright laws.
11-
* Dissemination of this information or reproduction of this material
12-
* is strictly forbidden unless prior written permission is obtained
13-
* from Adobe.
14-
* ***********************************************************************
155
*/
166
declare(strict_types=1);
177

app/code/Magento/Config/Block/System/Config/Form/Field/File.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ protected function _getDeleteCheckbox()
5555
$html .= '<input type="hidden" name="' .
5656
parent::getName() .
5757
'[value]" value="' .
58-
$this->_escaper->escapeHtml($this->getValue()) .
58+
$this->_escaper->escapeHtmlAttr($this->getValue()) .
5959
'" />';
6060
$html .= '</div>';
6161
}
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/Downloadable/Test/Fixture/DownloadableProduct.php

+24-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Magento\TestFramework\Fixture\Api\DataMerger;
2020
use Magento\TestFramework\Fixture\Api\ServiceFactory;
2121
use Magento\TestFramework\Fixture\Data\ProcessorInterface;
22+
use Magento\Downloadable\Api\DomainManagerInterface;
2223

2324
class DownloadableProduct extends Product
2425
{
@@ -43,6 +44,8 @@ class DownloadableProduct extends Product
4344
],
4445
];
4546

47+
private const DOMAINS = ['example.com','www.example.com'];
48+
4649
/**
4750
* DownloadableProduct constructor
4851
*
@@ -61,7 +64,8 @@ public function __construct(
6164
private readonly ProductRepositoryInterface $productRepository,
6265
private readonly DirectoryList $directoryList,
6366
private readonly Link $link,
64-
private readonly File $file
67+
private readonly File $file,
68+
private readonly DomainManagerInterface $domainManager
6569
) {
6670
parent::__construct($serviceFactory, $dataProcessor, $dataMerger, $productRepository);
6771
}
@@ -74,9 +78,17 @@ public function __construct(
7478
*/
7579
public function apply(array $data = []): ?DataObject
7680
{
81+
$this->domainManager->addDomains(self::DOMAINS);
82+
7783
return parent::apply($this->prepareData($data));
7884
}
7985

86+
public function revert(DataObject $data): void
87+
{
88+
$this->domainManager->removeDomains(self::DOMAINS);
89+
parent::revert($data);
90+
}
91+
8092
/**
8193
* Prepare product data
8294
*
@@ -112,13 +124,22 @@ private function prepareLinksData(array $data): array
112124
{
113125
$links = [];
114126
foreach ($data['extension_attributes']['downloadable_product_links'] as $link) {
127+
128+
if ($link['link_type'] == 'url') {
129+
$link['link_url'] = 'http://example.com/downloadable.txt';
130+
$link['link_file'] = '';
131+
} else {
132+
$link['link_file'] = $this->generateDownloadableLink($link['link_file'] ?? 'test-' . uniqid() . '.txt');
133+
$link['link_url'] = '';
134+
}
135+
115136
$links[] = [
116137
'id' => null,
117138
'title' => $link['title'] ?? 'Test Link%uniqid%',
118139
'price' => $link['price'] ?? 0,
119140
'link_type' => $link['link_type'] ?? 'file',
120-
'link_url' => null,
121-
'link_file' => $this->generateDownloadableLink($link['link_file'] ?? 'test-' . uniqid() . '.txt'),
141+
'link_url' => $link['link_url'],
142+
'link_file' => $link['link_file'],
122143
'is_shareable' => $link['is_shareable'] ?? 0,
123144
'number_of_downloads' => $link['number_of_downloads'] ?? 5,
124145
'sort_order' => $link['sort_order'] ?? 10,

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);

0 commit comments

Comments
 (0)