Skip to content

Commit ade1293

Browse files
Merge pull request #4100 from magento-qwerty/2.1.18-bugfixes-230419
Fixed issues: - MAGETWO-97901: [2.1.x] SOAP gateway ignores enforced parameters - MAGETWO-99061: [2.1.x] No merged/per-file XSD for web API XML config
2 parents 3684da4 + f4311dd commit ade1293

File tree

25 files changed

+542
-599
lines changed

25 files changed

+542
-599
lines changed

app/code/Magento/Customer/etc/webapi.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@
128128
<resource ref="Magento_Customer::manage"/>
129129
</resources>
130130
</route>
131-
<route url="/V1/customers/me" method="PUT">
131+
<route url="/V1/customers/me" method="PUT" soapOperation="saveSelf">
132132
<service class="Magento\Customer\Api\CustomerRepositoryInterface" method="save"/>
133133
<resources>
134134
<resource ref="self"/>
@@ -137,7 +137,7 @@
137137
<parameter name="customer.id" force="true">%customer_id%</parameter>
138138
</data>
139139
</route>
140-
<route url="/V1/customers/me" method="GET">
140+
<route url="/V1/customers/me" method="GET" soapOperation="getSelf">
141141
<service class="Magento\Customer\Api\CustomerRepositoryInterface" method="getById"/>
142142
<resources>
143143
<resource ref="self"/>
@@ -238,7 +238,7 @@
238238
<resource ref="Magento_Customer::manage"/>
239239
</resources>
240240
</route>
241-
<route url="/V1/customers/me/billingAddress" method="GET">
241+
<route url="/V1/customers/me/billingAddress" method="GET" soapOperation="getMyDefaultBillingAddress">
242242
<service class="Magento\Customer\Api\AccountManagementInterface" method="getDefaultBillingAddress"/>
243243
<resources>
244244
<resource ref="self"/>
@@ -253,7 +253,7 @@
253253
<resource ref="Magento_Customer::manage"/>
254254
</resources>
255255
</route>
256-
<route url="/V1/customers/me/shippingAddress" method="GET">
256+
<route url="/V1/customers/me/shippingAddress" method="GET" soapOperation="getMyDefaultShippingAddress">
257257
<service class="Magento\Customer\Api\AccountManagementInterface" method="getDefaultShippingAddress"/>
258258
<resources>
259259
<resource ref="self"/>

app/code/Magento/Webapi/Controller/Rest/ParamsOverrider.php

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
namespace Magento\Webapi\Controller\Rest;
88

9+
use Magento\Framework\App\ObjectManager;
910
use Magento\Framework\Webapi\Rest\Request\ParamOverriderInterface;
1011
use Magento\Webapi\Model\Config\Converter;
1112
use Magento\Framework\Reflection\MethodsMap;
@@ -26,15 +27,24 @@ class ParamsOverrider
2627
*/
2728
private $methodsMap;
2829

30+
/**
31+
* @var SimpleDataObjectConverter
32+
*/
33+
private $dataObjectConverter;
34+
2935
/**
3036
* Initialize dependencies
3137
*
3238
* @param ParamOverriderInterface[] $paramOverriders
39+
* @param SimpleDataObjectConverter|null $dataObjectConverter
3340
*/
3441
public function __construct(
35-
array $paramOverriders = []
42+
array $paramOverriders = [],
43+
SimpleDataObjectConverter $dataObjectConverter = null
3644
) {
3745
$this->paramOverriders = $paramOverriders;
46+
$this->dataObjectConverter = $dataObjectConverter
47+
?: ObjectManager::getInstance()->get(SimpleDataObjectConverter::class);
3848
}
3949

4050
/**
@@ -64,15 +74,17 @@ public function override(array $inputData, array $parameters)
6474
/**
6575
* Determine if a nested array value is set.
6676
*
67-
* @param array &$nestedArray
77+
* @param array $nestedArray
6878
* @param string[] $arrayKeys
6979
* @return bool true if array value is set
7080
*/
71-
protected function isNestedArrayValueSet(&$nestedArray, $arrayKeys)
81+
protected function isNestedArrayValueSet($nestedArray, $arrayKeys)
7282
{
73-
$currentArray = &$nestedArray;
83+
//Converting input data to camelCase in order to process both snake and camel style data equally.
84+
$currentArray = $this->dataObjectConverter->convertKeysToCamelCase($nestedArray);
7485

7586
foreach ($arrayKeys as $key) {
87+
$key = SimpleDataObjectConverter::snakeCaseToCamelCase($key);
7688
if (!isset($currentArray[$key])) {
7789
return false;
7890
}
@@ -95,12 +107,22 @@ protected function setNestedArrayValue(&$nestedArray, $arrayKeys, $valueToSet)
95107
$lastKey = array_pop($arrayKeys);
96108

97109
foreach ($arrayKeys as $key) {
110+
if (!array_key_exists($key, $currentArray)) {
111+
//In case input data uses camelCase format
112+
$key = SimpleDataObjectConverter::snakeCaseToCamelCase($key);
113+
}
98114
if (!isset($currentArray[$key])) {
99115
$currentArray[$key] = [];
100116
}
101117
$currentArray = &$currentArray[$key];
102118
}
103119

120+
//In case input data uses camelCase format
121+
$camelCaseKey = SimpleDataObjectConverter::snakeCaseToCamelCase($lastKey);
122+
if (array_key_exists($camelCaseKey, $currentArray)) {
123+
$lastKey = $camelCaseKey;
124+
}
125+
104126
$currentArray[$lastKey] = $valueToSet;
105127
}
106128

app/code/Magento/Webapi/Controller/Soap/Request/Handler.php

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
use Magento\Framework\Api\ExtensibleDataInterface;
99
use Magento\Framework\Api\MetadataObjectInterface;
1010
use Magento\Framework\Api\SimpleDataObjectConverter;
11+
use Magento\Framework\App\ObjectManager;
1112
use Magento\Framework\Webapi\Authorization;
1213
use Magento\Framework\Exception\AuthorizationException;
1314
use Magento\Framework\Reflection\DataObjectProcessor;
1415
use Magento\Framework\Webapi\ServiceInputProcessor;
1516
use Magento\Framework\Webapi\Request as SoapRequest;
1617
use Magento\Framework\Webapi\Exception as WebapiException;
18+
use Magento\Webapi\Controller\Rest\ParamsOverrider;
1719
use Magento\Webapi\Model\Soap\Config as SoapConfig;
1820
use Magento\Framework\Reflection\MethodsMap;
1921
use Magento\Webapi\Model\ServiceMetadata;
@@ -53,6 +55,11 @@ class Handler
5355
/** @var MethodsMap */
5456
protected $methodsMapProcessor;
5557

58+
/**
59+
* @var ParamsOverrider
60+
*/
61+
private $paramsOverrider;
62+
5663
/**
5764
* Initialize dependencies.
5865
*
@@ -64,6 +71,7 @@ class Handler
6471
* @param ServiceInputProcessor $serviceInputProcessor
6572
* @param DataObjectProcessor $dataObjectProcessor
6673
* @param MethodsMap $methodsMapProcessor
74+
* @param ParamsOverrider|null $paramsOverrider
6775
*/
6876
public function __construct(
6977
SoapRequest $request,
@@ -73,7 +81,8 @@ public function __construct(
7381
SimpleDataObjectConverter $dataObjectConverter,
7482
ServiceInputProcessor $serviceInputProcessor,
7583
DataObjectProcessor $dataObjectProcessor,
76-
MethodsMap $methodsMapProcessor
84+
MethodsMap $methodsMapProcessor,
85+
ParamsOverrider $paramsOverrider = null
7786
) {
7887
$this->_request = $request;
7988
$this->_objectManager = $objectManager;
@@ -83,6 +92,7 @@ public function __construct(
8392
$this->serviceInputProcessor = $serviceInputProcessor;
8493
$this->_dataObjectProcessor = $dataObjectProcessor;
8594
$this->methodsMapProcessor = $methodsMapProcessor;
95+
$this->paramsOverrider = $paramsOverrider ?: ObjectManager::getInstance()->get(ParamsOverrider::class);
8696
}
8797

8898
/**
@@ -116,25 +126,52 @@ public function __call($operation, $arguments)
116126
);
117127
}
118128
$service = $this->_objectManager->get($serviceClass);
119-
$inputData = $this->_prepareRequestData($serviceClass, $serviceMethod, $arguments);
129+
$inputData = $this->prepareOperationInput($serviceClass, $serviceMethodInfo, $arguments);
120130
$outputData = call_user_func_array([$service, $serviceMethod], $inputData);
121131
return $this->_prepareResponseData($outputData, $serviceClass, $serviceMethod);
122132
}
123133

124134
/**
125-
* Convert SOAP operation arguments into format acceptable by service method.
135+
* Convert arguments received from SOAP server to arguments to pass to a service.
126136
*
127137
* @param string $serviceClass
128-
* @param string $serviceMethod
138+
* @param array $methodMetadata
129139
* @param array $arguments
130140
* @return array
141+
* @throws WebapiException
142+
* @throws \Magento\Framework\Exception\InputException
131143
*/
132-
protected function _prepareRequestData($serviceClass, $serviceMethod, $arguments)
144+
private function prepareOperationInput($serviceClass, array $methodMetadata, array $arguments)
133145
{
134146
/** SoapServer wraps parameters into array. Thus this wrapping should be removed to get access to parameters. */
135147
$arguments = reset($arguments);
136148
$arguments = $this->_dataObjectConverter->convertStdObjectToArray($arguments, true);
137-
return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $arguments);
149+
$arguments = $this->paramsOverrider->override($arguments, $methodMetadata[ServiceMetadata::KEY_ROUTE_PARAMS]);
150+
151+
return $this->serviceInputProcessor->process(
152+
$serviceClass,
153+
$methodMetadata[ServiceMetadata::KEY_METHOD],
154+
$arguments
155+
);
156+
}
157+
158+
/**
159+
* Convert SOAP operation arguments into format acceptable by service method.
160+
*
161+
* @param string $serviceClass
162+
* @param string $serviceMethod
163+
* @param array $arguments
164+
* @return array
165+
* @deprecated
166+
* @see Handler::prepareOperationInput()
167+
*/
168+
protected function _prepareRequestData($serviceClass, $serviceMethod, $arguments)
169+
{
170+
return $this->prepareOperationInput(
171+
$serviceClass,
172+
[ServiceMetadata::KEY_METHOD => $serviceMethod, ServiceMetadata::KEY_ROUTE_PARAMS => []],
173+
$arguments
174+
);
138175
}
139176

140177
/**

app/code/Magento/Webapi/Model/Config/ClassReflector.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function __construct(\Magento\Framework\Reflection\TypeProcessor $typePro
3131
* Reflect methods in given class and set retrieved data into reader.
3232
*
3333
* @param string $className
34-
* @param array $methods
34+
* @param string[]|array $methods List of methods of methods' metadata.
3535
* @return array <pre>array(
3636
* $firstMethod => array(
3737
* 'documentation' => $methodDocumentation,
@@ -68,7 +68,7 @@ public function reflectClassMethods($className, $methods)
6868
/** @var \Zend\Code\Reflection\MethodReflection $methodReflection */
6969
foreach ($classReflection->getMethods() as $methodReflection) {
7070
$methodName = $methodReflection->getName();
71-
if (array_key_exists($methodName, $methods)) {
71+
if (in_array($methodName, $methods) || array_key_exists($methodName, $methods)) {
7272
$data[$methodName] = $this->extractMethodData($methodReflection);
7373
}
7474
}

app/code/Magento/Webapi/Model/Config/Converter.php

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ class Converter implements \Magento\Framework\Config\ConverterInterface
2828
const KEY_METHOD = 'method';
2929
const KEY_METHODS = 'methods';
3030
const KEY_DESCRIPTION = 'description';
31+
const KEY_REAL_SERVICE_METHOD = 'realMethod';
3132
/**#@-*/
3233

3334
/**
34-
* {@inheritdoc}
35+
* @inheritdoc
3536
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
3637
* @SuppressWarnings(PHPMD.NPathComplexity)
3738
*/
@@ -49,6 +50,10 @@ public function convert($source)
4950
$service = $route->getElementsByTagName('service')->item(0);
5051
$serviceClass = $service->attributes->getNamedItem('class')->nodeValue;
5152
$serviceMethod = $service->attributes->getNamedItem('method')->nodeValue;
53+
$soapMethod = $serviceMethod;
54+
if ($soapOperationNode = $route->attributes->getNamedItem('soapOperation')) {
55+
$soapMethod = trim($soapOperationNode->nodeValue);
56+
}
5257
$url = trim($route->attributes->getNamedItem('url')->nodeValue);
5358
$version = $this->convertVersion($url);
5459

@@ -70,23 +75,25 @@ public function convert($source)
7075
// For SOAP
7176
$resourcePermissionSet[] = $ref;
7277
}
78+
$data = $this->convertMethodParameters($route->getElementsByTagName('parameter'));
79+
$serviceData = $data;
7380

74-
if (!isset($serviceClassData[self::KEY_METHODS][$serviceMethod])) {
75-
$serviceClassData[self::KEY_METHODS][$serviceMethod][self::KEY_ACL_RESOURCES] = $resourcePermissionSet;
81+
if (!isset($serviceClassData[self::KEY_METHODS][$soapMethod])) {
82+
$serviceClassData[self::KEY_METHODS][$soapMethod][self::KEY_ACL_RESOURCES] = $resourcePermissionSet;
7683
} else {
77-
$serviceClassData[self::KEY_METHODS][$serviceMethod][self::KEY_ACL_RESOURCES] =
84+
$serviceClassData[self::KEY_METHODS][$soapMethod][self::KEY_ACL_RESOURCES] =
7885
array_unique(
7986
array_merge(
80-
$serviceClassData[self::KEY_METHODS][$serviceMethod][self::KEY_ACL_RESOURCES],
87+
$serviceClassData[self::KEY_METHODS][$soapMethod][self::KEY_ACL_RESOURCES],
8188
$resourcePermissionSet
8289
)
8390
);
91+
$serviceData = [];
8492
}
8593

8694
$method = $route->attributes->getNamedItem('method')->nodeValue;
8795
$secureNode = $route->attributes->getNamedItem('secure');
8896
$secure = $secureNode ? (bool)trim($secureNode->nodeValue) : false;
89-
$data = $this->convertMethodParameters($route->getElementsByTagName('parameter'));
9097

9198
// We could handle merging here by checking if the route already exists
9299
$result[self::KEY_ROUTES][$url][$method] = [
@@ -100,10 +107,14 @@ public function convert($source)
100107
];
101108

102109
$serviceSecure = false;
103-
if (isset($serviceClassData[self::KEY_METHODS][$serviceMethod][self::KEY_SECURE])) {
104-
$serviceSecure = $serviceClassData[self::KEY_METHODS][$serviceMethod][self::KEY_SECURE];
110+
if (isset($serviceClassData[self::KEY_METHODS][$soapMethod][self::KEY_SECURE])) {
111+
$serviceSecure = $serviceClassData[self::KEY_METHODS][$soapMethod][self::KEY_SECURE];
105112
}
106-
$serviceClassData[self::KEY_METHODS][$serviceMethod][self::KEY_SECURE] = $serviceSecure || $secure;
113+
if (!isset($serviceClassData[self::KEY_METHODS][$soapMethod][self::KEY_REAL_SERVICE_METHOD])) {
114+
$serviceClassData[self::KEY_METHODS][$soapMethod][self::KEY_REAL_SERVICE_METHOD] = $serviceMethod;
115+
}
116+
$serviceClassData[self::KEY_METHODS][$soapMethod][self::KEY_SECURE] = $serviceSecure || $secure;
117+
$serviceClassData[self::KEY_METHODS][$soapMethod][self::KEY_DATA_PARAMETERS] = $serviceData;
107118

108119
$result[self::KEY_SERVICES][$serviceClass][$version] = $serviceClassData;
109120
}
@@ -147,7 +158,8 @@ protected function convertMethodParameters($parameters)
147158

148159
/**
149160
* Derive the version from the provided URL.
150-
* Assumes the version is the first portion of the URL. For example, '/V1/customers'
161+
*
162+
* Assumes the version is the first portion of the URL. For example, '/V1/customers'.
151163
*
152164
* @param string $url
153165
* @return string

app/code/Magento/Webapi/Model/Config/SchemaLocator.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ class SchemaLocator implements \Magento\Framework\Config\SchemaLocatorInterface
3131
*/
3232
public function __construct(\Magento\Framework\Module\Dir\Reader $moduleReader)
3333
{
34-
$this->_schema = $moduleReader->getModuleDir(Dir::MODULE_ETC_DIR, 'Magento_Webapi') . '/webapi.xsd';
34+
$this->_schema = $moduleReader->getModuleDir(Dir::MODULE_ETC_DIR, 'Magento_Webapi') . '/webapi_merged.xsd';
35+
$this->_perFileSchema = $moduleReader->getModuleDir(Dir::MODULE_ETC_DIR, 'Magento_Webapi') . '/webapi.xsd';
3536
}
3637

3738
/**

app/code/Magento/Webapi/Model/ServiceMetadata.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class ServiceMetadata
3434

3535
const KEY_ROUTE_PARAMS = 'parameters';
3636

37+
const KEY_METHOD_ALIAS = 'methodAlias';
38+
3739
const SERVICES_CONFIG_CACHE_ID = 'services-services-config';
3840

3941
const ROUTES_CONFIG_CACHE_ID = 'routes-services-config';
@@ -105,23 +107,31 @@ protected function initServicesMetadata()
105107
foreach ($this->config->getServices()[Converter::KEY_SERVICES] as $serviceClass => $serviceVersionData) {
106108
foreach ($serviceVersionData as $version => $serviceData) {
107109
$serviceName = $this->getServiceName($serviceClass, $version);
110+
$methods = [];
108111
foreach ($serviceData[Converter::KEY_METHODS] as $methodName => $methodMetadata) {
109112
$services[$serviceName][self::KEY_SERVICE_METHODS][$methodName] = [
110-
self::KEY_METHOD => $methodName,
113+
self::KEY_METHOD => $methodMetadata[Converter::KEY_REAL_SERVICE_METHOD],
111114
self::KEY_IS_REQUIRED => (bool)$methodMetadata[Converter::KEY_SECURE],
112115
self::KEY_IS_SECURE => $methodMetadata[Converter::KEY_SECURE],
113116
self::KEY_ACL_RESOURCES => $methodMetadata[Converter::KEY_ACL_RESOURCES],
117+
self::KEY_METHOD_ALIAS => $methodName,
118+
self::KEY_ROUTE_PARAMS => $methodMetadata[Converter::KEY_DATA_PARAMETERS]
114119
];
115120
$services[$serviceName][self::KEY_CLASS] = $serviceClass;
121+
$methods[] = $methodMetadata[Converter::KEY_REAL_SERVICE_METHOD];
116122
}
123+
unset($methodName, $methodMetadata);
117124
$reflectedMethodsMetadata = $this->classReflector->reflectClassMethods(
118125
$serviceClass,
119-
$services[$serviceName][self::KEY_SERVICE_METHODS]
120-
);
121-
$services[$serviceName][self::KEY_SERVICE_METHODS] = array_merge_recursive(
122-
$services[$serviceName][self::KEY_SERVICE_METHODS],
123-
$reflectedMethodsMetadata
126+
$methods
124127
);
128+
foreach ($services[$serviceName][self::KEY_SERVICE_METHODS] as $methodName => &$methodMetadata) {
129+
$methodMetadata = array_merge(
130+
$methodMetadata,
131+
$reflectedMethodsMetadata[$methodMetadata[self::KEY_METHOD]]
132+
);
133+
}
134+
unset($methodName, $methodMetadata);
125135
$services[$serviceName][Converter::KEY_DESCRIPTION] = $this->classReflector->extractClassDescription(
126136
$serviceClass
127137
);

0 commit comments

Comments
 (0)