Skip to content

Commit 2c8c55a

Browse files
Nishant RanaNishant Rana
authored andcommitted
Merge remote-tracking branch 'origin/AC-14323' into cia-2.4.9-alpha2-develop-bugfix-07022025
2 parents 7bbac19 + f83cc26 commit 2c8c55a

File tree

9 files changed

+286
-11
lines changed

9 files changed

+286
-11
lines changed

app/code/Magento/GraphQl/etc/adminhtml/system.xml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
<?xml version="1.0"?>
22
<!--
33
/**
4-
* Representation of Webapi module in System Configuration (Magento admin panel).
5-
*
6-
* Copyright © Magento, Inc. All rights reserved.
7-
* See COPYING.txt for license details.
4+
* Copyright 2020 Adobe
5+
* All Rights Reserved.
86
*/
97
-->
108
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
@@ -25,6 +23,19 @@
2523
<field id="input_limit_enabled">1</field>
2624
</depends>
2725
</field>
26+
<field id="alias_limit_enabled" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1">
27+
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
28+
<label>Enable Maximum Alias Limit</label>
29+
<config_path>graphql/validation/alias_limit_enabled</config_path>
30+
</field>
31+
<field id="maximum_alias_allowed" translate="label comment" type="text" sortOrder="15" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
32+
<label>Maximum Alias Allowed</label>
33+
<comment>Maximum number of aliases allowed in a graphql request.</comment>
34+
<config_path>graphql/validation/maximum_alias_allowed</config_path>
35+
<depends>
36+
<field id="alias_limit_enabled">1</field>
37+
</depends>
38+
</field>
2839
</group>
2940
<group id="graphql_session" translate="label" type="text" sortOrder="20" showInDefault="0" showInWebsite="0" showInStore="0">
3041
<label>GraphQl Session Management</label>

app/code/Magento/GraphQl/etc/config.xml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<?xml version="1.0"?>
22
<!--
33
/**
4-
* Copyright © Magento, Inc. All rights reserved.
5-
* See COPYING.txt for license details.
4+
* Copyright 2020 Adobe
5+
* All Rights Reserved.
66
*/
77
-->
88
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -17,6 +17,10 @@
1717
<session>
1818
<disable>0</disable>
1919
</session>
20+
<validation>
21+
<alias_limit_enabled>1</alias_limit_enabled>
22+
<maximum_alias_allowed>10</maximum_alias_allowed>
23+
</validation>
2024
</graphql>
2125
</default>
2226
</config>

app/design/adminhtml/Magento/backend/i18n/en_US.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ dummy,dummy
224224
"The ""%1"" field name is unknown. Verify the field name and try again.","The ""%1"" field name is unknown. Verify the field name and try again."
225225
"Enum type ""%1"" not defined","Enum type ""%1"" not defined"
226226
"Max query complexity should be %1 but got %2.","Max query complexity should be %1 but got %2."
227+
"Max Aliases in query should be %1 but got %2.","Max Aliases in query should be %1 but got %2."
227228
"%1 operator not supported","%1 operator not supported"
228229
"Can't support nested operators","Can't support nested operators"
229230
"Nesting ""OR"" node type not supported","Nesting ""OR"" node type not supported"

app/design/frontend/Magento/blank/i18n/en_US.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@
197197
"Key must not exceed %1 bytes.","Key must not exceed %1 bytes."
198198
"Layout must be initialized","Layout must be initialized"
199199
"Max query complexity should be %1 but got %2.","Max query complexity should be %1 but got %2."
200+
"Max Aliases in query should be %1 but got %2.","Max Aliases in query should be %1 but got %2."
200201
"Maximum items of type ""%type"" is %max","Maximum items of type ""%type"" is %max"
201202
"Maximum pageSize is %max","Maximum pageSize is %max"
202203
"Maximum SearchCriteria pageSize is %max","Maximum SearchCriteria pageSize is %max"

app/design/frontend/Magento/luma/i18n/en_US.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@
212212
"Layout must be initialized","Layout must be initialized"
213213
"Manage Addresses","Manage Addresses"
214214
"Max query complexity should be %1 but got %2.","Max query complexity should be %1 but got %2."
215+
"Max Aliases in query should be %1 but got %2.","Max Aliases in query should be %1 but got %2."
215216
"Maximum items of type ""%type"" is %max","Maximum items of type ""%type"" is %max"
216217
"Maximum pageSize is %max","Maximum pageSize is %max"
217218
"Maximum SearchCriteria pageSize is %max","Maximum SearchCriteria pageSize is %max"
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\GraphQl\Query;
9+
10+
use Magento\TestFramework\Fixture\Config;
11+
use Magento\TestFramework\TestCase\GraphQlAbstract;
12+
use Magento\TestFramework\TestCase\GraphQl\ResponseContainsErrorsException;
13+
14+
/**
15+
* Test to validate input of Graphql requests
16+
*/
17+
class QueryComplexityLimiterTest extends GraphQlAbstract
18+
{
19+
/**
20+
* Test for validating query within alias limit
21+
*/
22+
#[
23+
Config('graphql/validation/alias_limit_enabled', 1),
24+
Config('graphql/validation/maximum_alias_allowed', 3)
25+
]
26+
public function testQueryWithinAliasLimit()
27+
{
28+
$query = <<<QUERY
29+
{
30+
productOne: products(filter: {sku: {eq: "1"}}) {
31+
items {
32+
id
33+
name
34+
}
35+
}
36+
productTwo: products(filter: {sku: {eq: "2"}}) {
37+
items {
38+
id
39+
name
40+
}
41+
}
42+
productThree: products(filter: {sku: {eq: "3"}}) {
43+
items {
44+
id
45+
name
46+
}
47+
}
48+
}
49+
QUERY;
50+
51+
$response = $this->graphQlQuery($query);
52+
$this->assertArrayHasKey('productOne', $response);
53+
$this->assertArrayHasKey('productTwo', $response);
54+
$this->assertArrayHasKey('productThree', $response);
55+
}
56+
57+
/**
58+
* Test for validating query exceeding complexity limit
59+
*/
60+
#[
61+
Config('graphql/validation/alias_limit_enabled', 1),
62+
Config('graphql/validation/maximum_alias_allowed', 2)
63+
]
64+
public function testQueryExeedingAliasLimit()
65+
{
66+
$query = <<<QUERY
67+
{
68+
productOne: products(filter: {sku: {eq: "1"}}) {
69+
items {
70+
id
71+
name
72+
}
73+
}
74+
productTwo: products(filter: {sku: {eq: "2"}}) {
75+
items {
76+
id
77+
name
78+
}
79+
}
80+
productThree: products(filter: {sku: {eq: "3"}}) {
81+
items {
82+
id
83+
name
84+
}
85+
}
86+
}
87+
QUERY;
88+
89+
$this->expectException(ResponseContainsErrorsException::class);
90+
$this->expectExceptionMessage('Max Aliases in query should be 2 but got 3.');
91+
$this->graphQlQuery($query);
92+
}
93+
94+
/**
95+
* Test for validating query with alias limit disabled
96+
*/
97+
#[
98+
Config('graphql/validation/alias_limit_enabled', 0),
99+
Config('graphql/validation/maximum_alias_allowed', 1)
100+
]
101+
public function testQueryWithinAliasLimitDisabled()
102+
{
103+
104+
$query = <<<QUERY
105+
{
106+
productOne: products(filter: {sku: {eq: "1"}}) {
107+
items {
108+
id
109+
name
110+
}
111+
}
112+
productTwo: products(filter: {sku: {eq: "2"}}) {
113+
items {
114+
id
115+
name
116+
}
117+
}
118+
}
119+
QUERY;
120+
121+
$response = $this->graphQlQuery($query);
122+
$this->assertArrayHasKey('productOne', $response);
123+
$this->assertArrayHasKey('productTwo', $response);
124+
}
125+
}
Lines changed: 60 additions & 0 deletions
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\Framework\GraphQl\Query;
9+
10+
use Magento\Framework\App\Config\ScopeConfigInterface;
11+
12+
/**
13+
* Class for fetching the maximum allowed alias count in graphql request
14+
*/
15+
class MaximumAliasConfiguration
16+
{
17+
/**
18+
* Constant for maximum alias allowed value config path
19+
*/
20+
private const CONFIG_PATH_MAXIMUM_ALIAS_ALLOWED = 'graphql/validation/maximum_alias_allowed';
21+
22+
/**
23+
* Constant for maximum alias enable config path
24+
*/
25+
private const CONFIG_PATH_MAXIMUM_ALIAS_ENABLED = 'graphql/validation/alias_limit_enabled';
26+
27+
/**
28+
* @var ScopeConfigInterface
29+
*/
30+
private $scopeConfigInterface;
31+
32+
/**
33+
* @param scopeConfigInterfacee $scopeConfigInterface
34+
*/
35+
public function __construct(
36+
ScopeConfigInterface $scopeConfigInterface
37+
) {
38+
$this->scopeConfigInterface = $scopeConfigInterface;
39+
}
40+
41+
/**
42+
* Check the environment config to get the maximum allowed alias in graphql request.
43+
*
44+
* @return int
45+
*/
46+
public function getMaximumAliasAllowed(): int
47+
{
48+
return (int) $this->scopeConfigInterface->getValue(self::CONFIG_PATH_MAXIMUM_ALIAS_ALLOWED);
49+
}
50+
51+
/**
52+
* Check the environment config to check if maximum alias limit is enabled.
53+
*
54+
* @return bool
55+
*/
56+
public function isMaximumAliasLimitEnabled(): bool
57+
{
58+
return (bool) $this->scopeConfigInterface->getValue(self::CONFIG_PATH_MAXIMUM_ALIAS_ENABLED);
59+
}
60+
}

lib/internal/Magento/Framework/GraphQl/Query/QueryComplexityLimiter.php

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2018 Adobe
4+
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

88
namespace Magento\Framework\GraphQl\Query;
99

1010
use GraphQL\Language\AST\DocumentNode;
11+
use GraphQL\Language\AST\FieldNode;
1112
use GraphQL\Language\AST\NodeKind;
13+
use GraphQL\Language\AST\SelectionSetNode;
1214
use GraphQL\Language\Visitor;
1315
use GraphQL\Validator\DocumentValidator;
1416
use GraphQL\Validator\Rules\DisableIntrospection;
@@ -49,6 +51,11 @@ class QueryComplexityLimiter
4951
*/
5052
private $queryParser;
5153

54+
/**
55+
* @var MaximumAliasConfiguration
56+
*/
57+
private $maximumAliasConfiguration;
58+
5259
/**
5360
* @var array
5461
*/
@@ -61,17 +68,21 @@ class QueryComplexityLimiter
6168
* @param int $queryComplexity
6269
* @param IntrospectionConfiguration $introspectionConfig
6370
* @param QueryParser|null $queryParser
71+
* @param MaximumAliasConfiguration|null $maximumAliasConfiguration
6472
*/
6573
public function __construct(
6674
int $queryDepth,
6775
int $queryComplexity,
6876
IntrospectionConfiguration $introspectionConfig,
69-
?QueryParser $queryParser = null
77+
?QueryParser $queryParser = null,
78+
?MaximumAliasConfiguration $maximumAliasConfiguration = null
7079
) {
7180
$this->queryDepth = $queryDepth;
7281
$this->queryComplexity = $queryComplexity;
7382
$this->introspectionConfig = $introspectionConfig;
7483
$this->queryParser = $queryParser ?: ObjectManager::getInstance()->get(QueryParser::class);
84+
$this->maximumAliasConfiguration = $maximumAliasConfiguration ?:
85+
ObjectManager::getInstance()->get(MaximumAliasConfiguration::class);
7586
}
7687

7788
/**
@@ -138,4 +149,64 @@ public function validateFieldCount(DocumentNode|string $query): void
138149
}
139150
}
140151
}
152+
153+
/**
154+
* Performs a preliminary Alias count check before performing more extensive query validation.
155+
*
156+
* This is necessary for performance optimization, as extremely large number of alias in a request
157+
* require a substantial amount of resource can affect server performance.
158+
*
159+
* @param DocumentNode $query
160+
* @return void
161+
* @throws GraphQlInputException
162+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
163+
*/
164+
public function validateAliasCount(DocumentNode $query): void
165+
{
166+
if ($this->maximumAliasConfiguration->isMaximumAliasLimitEnabled()) {
167+
$aliasCount = 0;
168+
foreach ($query->definitions as $definition) {
169+
if (property_exists($definition, 'selectionSet')) {
170+
$aliasCount += $this->countAliasesInSelectionSet($definition->selectionSet);
171+
}
172+
}
173+
$allowedAliasCount = $this->maximumAliasConfiguration->getMaximumAliasAllowed();
174+
if ($aliasCount > $allowedAliasCount) {
175+
throw new GraphQlInputException(__(
176+
'Max Aliases in query should be %1 but got %2.',
177+
$allowedAliasCount,
178+
$aliasCount
179+
));
180+
}
181+
}
182+
}
183+
184+
/**
185+
* Performs counting of aliases in a graphql request
186+
*
187+
* @param ?SelectionSetNode $selectionSet
188+
* @return int
189+
*/
190+
private function countAliasesInSelectionSet(?SelectionSetNode $selectionSet): int
191+
{
192+
if ($selectionSet === null) {
193+
return 0;
194+
}
195+
196+
$aliasCount = 0;
197+
198+
foreach ($selectionSet->selections as $selection) {
199+
if ($selection instanceof FieldNode) {
200+
if ($selection->alias !== null) {
201+
$aliasCount++;
202+
}
203+
204+
if ($selection->selectionSet !== null) {
205+
$aliasCount += $this->countAliasesInSelectionSet($selection->selectionSet);
206+
}
207+
}
208+
}
209+
210+
return $aliasCount;
211+
}
141212
}

0 commit comments

Comments
 (0)