Skip to content

Commit be28086

Browse files
author
hwyu@adobe.com
committed
MC-36035: Prevent input based resource allocation
1 parent dcbbe78 commit be28086

File tree

20 files changed

+766
-31
lines changed

20 files changed

+766
-31
lines changed

app/etc/di.xml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1927,4 +1927,36 @@
19271927
</argument>
19281928
</arguments>
19291929
</type>
1930+
<preference for="Magento\Framework\Webapi\Validator\ServiceInputValidatorInterface" type="Magento\Framework\Webapi\Validator\CompositeServiceInputValidator"/>
1931+
<type name="Magento\Framework\Webapi\Validator\CompositeServiceInputValidator">
1932+
<arguments>
1933+
<argument name="validators" xsi:type="array">
1934+
<item name="entityArrayValidator" xsi:type="object">Magento\Framework\Webapi\Validator\EntityArrayValidator</item>
1935+
<item name="searchCriteriaValidator" xsi:type="object">Magento\Framework\Webapi\Validator\SearchCriteriaValidator</item>
1936+
</argument>
1937+
</arguments>
1938+
</type>
1939+
<type name="Magento\Framework\Webapi\Validator\EntityArrayValidator">
1940+
<arguments>
1941+
<argument name="complexArrayItemLimit" xsi:type="number">20</argument>
1942+
</arguments>
1943+
</type>
1944+
<type name="Magento\Framework\Webapi\Validator\SearchCriteriaValidator">
1945+
<arguments>
1946+
<argument name="maximumPageSize" xsi:type="number">300</argument>
1947+
</arguments>
1948+
</type>
1949+
<preference for="Magento\Framework\GraphQl\Query\Resolver\Argument\ValidatorInterface" type="Magento\Framework\GraphQl\Query\Resolver\Argument\Validator\CompositeValidator"/>
1950+
<type name="Magento\Framework\GraphQl\Query\Resolver\Argument\Validator\CompositeValidator">
1951+
<arguments>
1952+
<argument name="validators" xsi:type="array">
1953+
<item name="searchCriteriaValidator" xsi:type="object">Magento\Framework\GraphQl\Query\Resolver\Argument\Validator\SearchCriteriaValidator</item>
1954+
</argument>
1955+
</arguments>
1956+
</type>
1957+
<type name="Magento\Framework\GraphQl\Query\Resolver\Argument\Validator\SearchCriteriaValidator">
1958+
<arguments>
1959+
<argument name="maxPageSize" xsi:type="number">300</argument>
1960+
</arguments>
1961+
</type>
19301962
</config>

dev/tests/api-functional/testsuite/Magento/Framework/Api/Search/SearchTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function testCatalogSearch()
3838
]
3939
]
4040
],
41-
'page_size' => 999,
41+
'page_size' => 300,
4242
'current_page' => 0,
4343
],
4444
];

dev/tests/api-functional/testsuite/Magento/SalesRule/Api/CouponManagementTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ protected function getList($ruleId)
156156
],
157157
],
158158
'current_page' => 1,
159-
'page_size' => 9999,
159+
'page_size' => 300,
160160
],
161161
];
162162

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Framework\GraphQl\Query\Resolver\Argument\Validator;
10+
11+
use Magento\Framework\GraphQl\Config\Element\Field;
12+
use Magento\Framework\GraphQl\Query\Resolver\Argument\ValidatorInterface;
13+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
14+
15+
/**
16+
* Validate with multiple validators
17+
*/
18+
class CompositeValidator implements ValidatorInterface
19+
{
20+
/**
21+
* @var array
22+
*/
23+
private $validators;
24+
25+
/**
26+
* @param ValidatorInterface[] $validators
27+
* @throws GraphQlInputException
28+
*/
29+
public function __construct(array $validators)
30+
{
31+
foreach ($validators as $validator) {
32+
if (!$validator instanceof ValidatorInterface) {
33+
throw new GraphQlInputException(__("Validators must implement " . ValidatorInterface::class));
34+
}
35+
}
36+
$this->validators = $validators;
37+
}
38+
39+
/**
40+
* @inheritDoc
41+
*/
42+
public function validate(Field $field, $args): void
43+
{
44+
foreach ($this->validators as $validator) {
45+
$validator->validate($field, $args);
46+
}
47+
}
48+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Framework\GraphQl\Query\Resolver\Argument\Validator;
10+
11+
use Magento\Framework\GraphQl\Config\Element\Field;
12+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
13+
use Magento\Framework\GraphQl\Query\Resolver\Argument\ValidatorInterface;
14+
15+
/**
16+
* Enforces limits on SearchCriteria arguments
17+
*/
18+
class SearchCriteriaValidator implements ValidatorInterface
19+
{
20+
/**
21+
* @var int
22+
*/
23+
private $maxPageSize;
24+
25+
/**
26+
* @param int $maxPageSize
27+
*/
28+
public function __construct(int $maxPageSize)
29+
{
30+
$this->maxPageSize = $maxPageSize;
31+
}
32+
33+
/**
34+
* @inheritDoc
35+
* @throws GraphQlInputException
36+
*/
37+
public function validate(Field $field, $args): void
38+
{
39+
if (isset($args['pageSize']) && $args['pageSize'] > $this->maxPageSize) {
40+
throw new GraphQlInputException(
41+
__("Maximum pageSize is %max", ['max' => $this->maxPageSize])
42+
);
43+
}
44+
}
45+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Framework\GraphQl\Query\Resolver\Argument;
10+
11+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
12+
use Magento\Framework\GraphQl\Config\Element\Field;
13+
14+
/**
15+
* Validation support for resolver arguments
16+
*/
17+
interface ValidatorInterface
18+
{
19+
/**
20+
* Validate resolver args
21+
*
22+
* @param Field $field
23+
* @param mixed $args
24+
* @throws GraphQlInputException
25+
*/
26+
public function validate(Field $field, $args): void;
27+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Framework\GraphQl\Query\Resolver;
10+
11+
use Magento\Framework\GraphQl\Config\Element\Field;
12+
use Magento\Framework\GraphQl\Query\Resolver\Argument\ValidatorInterface;
13+
use Magento\Framework\GraphQl\Query\Resolver\Factory as ResolverFactory;
14+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfoFactory;
15+
16+
/**
17+
* Create promises that return resolver results
18+
*/
19+
class PromiseFactory
20+
{
21+
/**
22+
* @var Factory
23+
*/
24+
private $resolverFactory;
25+
26+
/**
27+
* @var ResolveInfoFactory
28+
*/
29+
private $resolveInfoFactory;
30+
31+
/**
32+
* @var ValidatorInterface
33+
*/
34+
private $argumentValidator;
35+
36+
/**
37+
* @param Factory $resolverFactory
38+
* @param ResolveInfoFactory $resolveInfoFactory
39+
* @param ValidatorInterface $argumentValidator
40+
*/
41+
public function __construct(
42+
ResolverFactory $resolverFactory,
43+
ResolveInfoFactory $resolveInfoFactory,
44+
ValidatorInterface $argumentValidator
45+
) {
46+
$this->resolverFactory = $resolverFactory;
47+
$this->resolveInfoFactory = $resolveInfoFactory;
48+
$this->argumentValidator = $argumentValidator;
49+
}
50+
51+
/**
52+
* Create a resolver promise
53+
*
54+
* @param Field $field
55+
* @return callable
56+
*/
57+
public function create(Field $field): callable
58+
{
59+
$resolver = $this->resolverFactory->createByClass($field->getResolver());
60+
return function ($value, $args, $context, $info) use ($resolver, $field) {
61+
$wrapperInfo = $this->resolveInfoFactory->create($info);
62+
$this->argumentValidator->validate($field, $args);
63+
return $resolver->resolve($field, $context, $wrapperInfo, $value, $args);
64+
};
65+
}
66+
}

lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,13 @@
1111
use Magento\Framework\GraphQl\Config\Element\Field;
1212
use Magento\Framework\GraphQl\Config\Element\TypeInterface;
1313
use Magento\Framework\GraphQl\Config\ConfigElementInterface;
14+
use Magento\Framework\GraphQl\Query\Resolver\PromiseFactory;
1415
use Magento\Framework\GraphQl\Schema\Type\Input\InputMapper;
1516
use Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\FormatterInterface;
1617
use Magento\Framework\GraphQl\Schema\Type\Output\OutputMapper;
1718
use Magento\Framework\GraphQl\Schema\Type\OutputTypeInterface;
1819
use Magento\Framework\GraphQl\Schema\Type\ScalarTypes;
1920
use Magento\Framework\ObjectManagerInterface;
20-
use Magento\Framework\GraphQl\Schema\Type\ResolveInfoFactory;
21-
use Magento\Framework\GraphQl\Query\Resolver\Factory as ResolverFactory;
2221

2322
/**
2423
* Convert fields of the given 'type' config element to the objects compatible with GraphQL schema generator.
@@ -51,40 +50,37 @@ class Fields implements FormatterInterface
5150
private $wrappedTypeProcessor;
5251

5352
/**
54-
* @var ResolveInfoFactory
53+
* @var PromiseFactory
5554
*/
56-
private $resolveInfoFactory;
57-
58-
/**
59-
* @var ResolverFactory
60-
*/
61-
private $resolverFactory;
55+
private $promiseFactory;
6256

6357
/**
6458
* @param ObjectManagerInterface $objectManager
6559
* @param OutputMapper $outputMapper
6660
* @param InputMapper $inputMapper
6761
* @param ScalarTypes $scalarTypes
6862
* @param WrappedTypeProcessor $wrappedTypeProcessor
69-
* @param ResolveInfoFactory $resolveInfoFactory
70-
* @param ResolverFactory $resolverFactory
63+
* @param mixed $resolveInfoFactory
64+
* @param mixed $resolverFactory
65+
* @param PromiseFactory|null $promiseFactory
66+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
7167
*/
7268
public function __construct(
7369
ObjectManagerInterface $objectManager,
7470
OutputMapper $outputMapper,
7571
InputMapper $inputMapper,
7672
ScalarTypes $scalarTypes,
7773
WrappedTypeProcessor $wrappedTypeProcessor,
78-
ResolveInfoFactory $resolveInfoFactory,
79-
?ResolverFactory $resolverFactory = null
74+
$resolveInfoFactory = null,
75+
$resolverFactory = null,
76+
?PromiseFactory $promiseFactory = null
8077
) {
8178
$this->objectManager = $objectManager;
8279
$this->outputMapper = $outputMapper;
8380
$this->inputMapper = $inputMapper;
8481
$this->scalarTypes = $scalarTypes;
8582
$this->wrappedTypeProcessor = $wrappedTypeProcessor;
86-
$this->resolveInfoFactory = $resolveInfoFactory;
87-
$this->resolverFactory = $resolverFactory ?? $this->objectManager->get(ResolverFactory::class);
83+
$this->promiseFactory = $promiseFactory ?? $this->objectManager->get(PromiseFactory::class);
8884
}
8985

9086
/**
@@ -161,14 +157,9 @@ private function getFieldConfig(
161157
}
162158

163159
if ($field->getResolver() != null) {
164-
$resolver = $this->resolverFactory->createByClass($field->getResolver());
165-
166-
$fieldConfig['resolve'] =
167-
function ($value, $args, $context, $info) use ($resolver, $field) {
168-
$wrapperInfo = $this->resolveInfoFactory->create($info);
169-
return $resolver->resolve($field, $context, $wrapperInfo, $value, $args);
170-
};
160+
$fieldConfig['resolve'] = $this->promiseFactory->create($field);
171161
}
162+
172163
return $this->formatArguments($field, $fieldConfig);
173164
}
174165

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Framework\GraphQl\Test\Unit\Query\Resolver\Argument\Validator;
10+
11+
use Magento\Framework\GraphQl\Config\Element\Field;
12+
use Magento\Framework\GraphQl\Query\Resolver\Argument\Validator\CompositeValidator;
13+
use Magento\Framework\GraphQl\Query\Resolver\Argument\ValidatorInterface;
14+
use PHPUnit\Framework\TestCase;
15+
16+
/**
17+
* Verify behavior of composite validator
18+
*/
19+
class CompositeValidatorTest extends TestCase
20+
{
21+
public function testValidate()
22+
{
23+
$field = self::getMockBuilder(Field::class)
24+
->disableOriginalConstructor()
25+
->getMock();
26+
$args = ['a' => 123];
27+
$validatorA = self::getMockBuilder(ValidatorInterface::class)->getMock();
28+
$validatorA->expects(self::once())
29+
->method('validate')
30+
->with($field, $args);
31+
$validatorB = self::getMockBuilder(ValidatorInterface::class)->getMock();
32+
$validatorB->expects(self::once())
33+
->method('validate')
34+
->with($field, $args);
35+
36+
$composite = new CompositeValidator([$validatorA, $validatorB]);
37+
$composite->validate($field, $args);
38+
}
39+
}

0 commit comments

Comments
 (0)