Skip to content

Commit f9bda58

Browse files
committed
ACP2E-3544: Async Bulk Operation remains in open state for async.magento.configurableproduct.api.optionrepositoryinterface.save.post
- Add validation to Bulk endpoints to only allow an array of array in the request body with the topmost array keys consisting of consecutive numbers from 0 to count. This will prevent consumers from defining invalid request item IDs which must be a numeric value between 0 and 4294967295.
1 parent 6cfb9b6 commit f9bda58

File tree

3 files changed

+154
-2
lines changed

3 files changed

+154
-2
lines changed

app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
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

77
declare(strict_types=1);
@@ -119,6 +119,12 @@ public function resolve()
119119
$routeServiceMethod = $route->getServiceMethod();
120120
$this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit());
121121

122+
if (!array_is_list($inputData)) {
123+
throw new Exception(
124+
__('Request body must be an array'),
125+
);
126+
}
127+
122128
foreach ($inputData as $key => $singleEntityParams) {
123129
$webapiResolvedParams[$key] = $this->resolveBulkItemParams(
124130
$singleEntityParams,
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\WebapiAsync\Test\Unit\Controller\Rest\Asynchronous;
9+
10+
use Magento\Framework\Webapi\Rest\Request;
11+
use Magento\Framework\Webapi\ServiceInputProcessor;
12+
use Magento\Framework\Webapi\Validator\EntityArrayValidator\InputArraySizeLimitValue;
13+
use Magento\Webapi\Controller\Rest\InputParamsResolver as WebapiInputParamsResolver;
14+
use Magento\Webapi\Controller\Rest\ParamsOverrider;
15+
use Magento\Webapi\Controller\Rest\RequestValidator;
16+
use Magento\Webapi\Controller\Rest\Router;
17+
use Magento\Webapi\Controller\Rest\Router\Route;
18+
use Magento\WebapiAsync\Controller\Rest\Asynchronous\InputParamsResolver;
19+
use PHPUnit\Framework\Attributes\DataProvider;
20+
use PHPUnit\Framework\MockObject\MockObject;
21+
use PHPUnit\Framework\TestCase;
22+
23+
class InputParamsResolverTest extends TestCase
24+
{
25+
/**
26+
* @var Request|MockObject
27+
*/
28+
private $requestMock;
29+
30+
/**
31+
* @var ParamsOverrider|MockObject
32+
*/
33+
private $paramsOverriderMock;
34+
35+
/**
36+
* @var ServiceInputProcessor|MockObject
37+
*/
38+
private $serviceInputProcessorMock;
39+
40+
/**
41+
* @var Router|MockObject
42+
*/
43+
private $routerMock;
44+
45+
/**
46+
* @var RequestValidator|MockObject
47+
*/
48+
private $requestValidatorMock;
49+
50+
/**
51+
* @var WebapiInputParamsResolver|MockObject
52+
*/
53+
private $webapiInputParamsResolverMock;
54+
55+
/**
56+
* @var InputArraySizeLimitValue|MockObject
57+
*/
58+
private $inputArraySizeLimitValueMock;
59+
60+
protected function setUp(): void
61+
{
62+
$this->requestMock = $this->createMock(Request::class);
63+
$this->paramsOverriderMock = $this->createMock(ParamsOverrider::class);
64+
$this->serviceInputProcessorMock = $this->createMock(ServiceInputProcessor::class);
65+
$this->routerMock = $this->createMock(Router::class);
66+
$this->requestValidatorMock = $this->createMock(RequestValidator::class);
67+
$this->webapiInputParamsResolverMock = $this->createMock(WebapiInputParamsResolver::class);
68+
$this->inputArraySizeLimitValueMock = $this->createMock(InputArraySizeLimitValue::class);
69+
}
70+
71+
#[DataProvider('requestBodyDataProvider')]
72+
public function testResolveAsyncBulkShouldThrowAnErrorForInvalidRequestData(
73+
array $requestData,
74+
string $expectedExceptionMessage
75+
): void {
76+
$routeMock = $this->createMock(Route::class);
77+
$this->webapiInputParamsResolverMock->expects($this->once())
78+
->method('getRoute')
79+
->willReturn($routeMock);
80+
$this->requestMock->expects($this->once())
81+
->method('getRequestData')
82+
->willReturn($requestData);
83+
$this->expectException(\Magento\Framework\Webapi\Exception::class);
84+
$this->expectExceptionMessage($expectedExceptionMessage);
85+
$this->getModel(true)->resolve();
86+
}
87+
88+
public function testResolveAsyncBulk(): void
89+
{
90+
$requestData = [['param1' => 'value1'], ['param1' => 'value1']];
91+
$routeMock = $this->createMock(Route::class);
92+
$routeMock->expects($this->once())
93+
->method('getServiceClass')
94+
->willReturn('serviceClass');
95+
$routeMock->expects($this->once())
96+
->method('getServiceMethod')
97+
->willReturn('serviceMethod');
98+
$this->webapiInputParamsResolverMock->expects($this->once())
99+
->method('getRoute')
100+
->willReturn($routeMock);
101+
$this->requestMock->expects($this->once())
102+
->method('getRequestData')
103+
->willReturn($requestData);
104+
$this->serviceInputProcessorMock->expects($this->exactly(2))
105+
->method('process')
106+
->willReturnArgument(2);
107+
$this->assertEquals($requestData, $this->getModel(true)->resolve());
108+
}
109+
110+
#[DataProvider('requestBodyDataProvider')]
111+
public function testResolveAsync(array $requestData): void
112+
{
113+
$this->webapiInputParamsResolverMock->expects($this->once())
114+
->method('resolve')
115+
->willReturn($requestData);
116+
$this->requestMock->method('getRequestData')
117+
->willReturn($requestData);
118+
$this->assertEquals([$requestData], $this->getModel(false)->resolve());
119+
}
120+
121+
public static function requestBodyDataProvider(): array
122+
{
123+
return [
124+
[[1 => []], 'Request body must be an array'],
125+
[['0str' => []], 'Request body must be an array'],
126+
[['str' => []], 'Request body must be an array'],
127+
[['str' => [], 1 => []], 'Request body must be an array'],
128+
[['str' => [], 0 => [], 1 => []], 'Request body must be an array'],
129+
];
130+
}
131+
132+
private function getModel(bool $isBulk = false): InputParamsResolver
133+
{
134+
return new InputParamsResolver(
135+
$this->requestMock,
136+
$this->paramsOverriderMock,
137+
$this->serviceInputProcessorMock,
138+
$this->routerMock,
139+
$this->requestValidatorMock,
140+
$this->webapiInputParamsResolverMock,
141+
$isBulk,
142+
$this->inputArraySizeLimitValueMock
143+
);
144+
}
145+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"Request body must be an array","Request body must be an array"

0 commit comments

Comments
 (0)