Skip to content

Commit 5517446

Browse files
authored
Merge pull request #4407 from magento-borg/borg-security-2.2
[borg] Bug fixes
2 parents 2fcb0f9 + ac1a866 commit 5517446

File tree

25 files changed

+1193
-79
lines changed

25 files changed

+1193
-79
lines changed

app/code/Magento/CatalogImportExport/Model/Import/Product.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,9 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
306306
ValidatorInterface::ERROR_INVALID_WEIGHT => 'Product weight is invalid',
307307
ValidatorInterface::ERROR_DUPLICATE_URL_KEY => 'Url key: \'%s\' was already generated for an item with the SKU: \'%s\'. You need to specify the unique URL key manually',
308308
ValidatorInterface::ERROR_NEW_TO_DATE => 'Make sure new_to_date is later than or the same as new_from_date',
309+
// Can't add new translated strings in patch release
310+
'invalidLayoutUpdate' => 'Invalid format.',
311+
'insufficientPermissions' => 'Invalid format.',
309312
];
310313

311314
/**
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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\CatalogImportExport\Model\Import\Product\Validator;
10+
11+
use Magento\Framework\Config\ValidationStateInterface;
12+
use Magento\Framework\View\Model\Layout\Update\ValidatorFactory;
13+
14+
/**
15+
* Validates layout and custom layout update fields
16+
*/
17+
class LayoutUpdate extends AbstractImportValidator
18+
{
19+
/**
20+
* @var ValidatorFactory
21+
*/
22+
private $layoutValidatorFactory;
23+
24+
/**
25+
* @var ValidationStateInterface
26+
*/
27+
private $validationState;
28+
29+
/**
30+
* @param ValidatorFactory $layoutValidatorFactory
31+
* @param ValidationStateInterface $validationState
32+
*/
33+
public function __construct(
34+
ValidatorFactory $layoutValidatorFactory,
35+
ValidationStateInterface $validationState
36+
) {
37+
$this->layoutValidatorFactory = $layoutValidatorFactory;
38+
$this->validationState = $validationState;
39+
}
40+
41+
/**
42+
* @inheritdoc
43+
*/
44+
public function isValid($value): bool
45+
{
46+
if (!empty($value['custom_layout_update']) && !$this->validateXml($value['custom_layout_update'])) {
47+
$this->_addMessages(
48+
[
49+
$this->context->retrieveMessageTemplate('invalidLayoutUpdate')
50+
]
51+
);
52+
return false;
53+
}
54+
55+
return true;
56+
}
57+
58+
/**
59+
* Validate XML layout update
60+
*
61+
* @param string $xml
62+
* @return bool
63+
*/
64+
private function validateXml(string $xml): bool
65+
{
66+
/** @var $layoutXmlValidator \Magento\Framework\View\Model\Layout\Update\Validator */
67+
$layoutXmlValidator = $this->layoutValidatorFactory->create(
68+
[
69+
'validationState' => $this->validationState,
70+
]
71+
);
72+
73+
try {
74+
if (!$layoutXmlValidator->isValid($xml)) {
75+
return false;
76+
}
77+
} catch (\Exception $e) {
78+
return false;
79+
}
80+
81+
return true;
82+
}
83+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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\CatalogImportExport\Model\Import\Product\Validator;
10+
11+
use Magento\Authorization\Model\UserContextInterface;
12+
use Magento\Framework\AuthorizationInterface;
13+
use Magento\CatalogImportExport\Model\Import\Product\Validator\AbstractImportValidator;
14+
15+
/**
16+
* Validator to assert that the current user is allowed to make design updates if a layout is provided in the import
17+
*/
18+
class LayoutUpdatePermissions extends AbstractImportValidator
19+
{
20+
/**
21+
* @var UserContextInterface
22+
*/
23+
private $userContext;
24+
25+
/**
26+
* @var AuthorizationInterface
27+
*/
28+
private $authorization;
29+
30+
/**
31+
* @var array
32+
*/
33+
private $allowedUserTypes = [
34+
UserContextInterface::USER_TYPE_ADMIN,
35+
UserContextInterface::USER_TYPE_INTEGRATION
36+
];
37+
38+
/**
39+
* @param UserContextInterface $userContext
40+
* @param AuthorizationInterface $authorization
41+
*/
42+
public function __construct(
43+
UserContextInterface $userContext,
44+
AuthorizationInterface $authorization
45+
) {
46+
$this->userContext = $userContext;
47+
$this->authorization = $authorization;
48+
}
49+
50+
/**
51+
* Validate that the current user is allowed to make design updates
52+
*
53+
* @param array $data
54+
* @return boolean
55+
*/
56+
public function isValid($data): bool
57+
{
58+
if (empty($data['custom_layout_update'])) {
59+
return true;
60+
}
61+
62+
$userType = $this->userContext->getUserType();
63+
$isValid = in_array($userType, $this->allowedUserTypes)
64+
&& $this->authorization->isAllowed('Magento_Catalog::edit_product_design');
65+
66+
if (!$isValid) {
67+
$this->_addMessages(
68+
[
69+
$this->context->retrieveMessageTemplate('insufficientPermissions'),
70+
]
71+
);
72+
}
73+
74+
return $isValid;
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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\CatalogImportExport\Test\Unit\Model\Import\Product\Validator;
10+
11+
use Magento\CatalogImportExport\Model\Import\Product;
12+
use Magento\Authorization\Model\UserContextInterface;
13+
use Magento\CatalogImportExport\Model\Import\Product\Validator\LayoutUpdatePermissions;
14+
use Magento\Framework\AuthorizationInterface;
15+
use PHPUnit\Framework\TestCase;
16+
use PHPUnit_Framework_MockObject_MockObject as MockObject;
17+
18+
/**
19+
* Test validation for layout update permissions
20+
*/
21+
class LayoutUpdatePermissionsTest extends TestCase
22+
{
23+
/**
24+
* @var LayoutUpdatePermissions|MockObject
25+
*/
26+
private $validator;
27+
28+
/**
29+
* @var UserContextInterface|MockObject
30+
*/
31+
private $userContext;
32+
33+
/**
34+
* @var AuthorizationInterface|MockObject
35+
*/
36+
private $authorization;
37+
38+
/**
39+
* @var Product
40+
*/
41+
private $context;
42+
43+
protected function setUp()
44+
{
45+
$this->userContext = $this->createMock(UserContextInterface::class);
46+
$this->authorization = $this->createMock(AuthorizationInterface::class);
47+
$this->context = $this->createMock(Product::class);
48+
$this->context
49+
->method('retrieveMessageTemplate')
50+
->with('insufficientPermissions')
51+
->willReturn('oh no');
52+
$this->validator = new LayoutUpdatePermissions(
53+
$this->userContext,
54+
$this->authorization
55+
);
56+
$this->validator->init($this->context);
57+
}
58+
59+
/**
60+
* @param $value
61+
* @param $userContext
62+
* @param $isAllowed
63+
* @param $isValid
64+
* @dataProvider configurationsProvider
65+
*/
66+
public function testValidationConfiguration($value, $userContext, $isAllowed, $isValid)
67+
{
68+
$this->userContext
69+
->method('getUserType')
70+
->willReturn($userContext);
71+
72+
$this->authorization
73+
->method('isAllowed')
74+
->with('Magento_Catalog::edit_product_design')
75+
->willReturn($isAllowed);
76+
77+
$result = $this->validator->isValid(['custom_layout_update' => $value]);
78+
$messages = $this->validator->getMessages();
79+
80+
self::assertSame($isValid, $result);
81+
82+
if ($isValid) {
83+
self::assertSame([], $messages);
84+
} else {
85+
self::assertSame(['oh no'], $messages);
86+
}
87+
}
88+
89+
public function configurationsProvider()
90+
{
91+
return [
92+
['', null, null, true],
93+
[null, null, null, true],
94+
['foo', UserContextInterface::USER_TYPE_ADMIN, true, true],
95+
['foo', UserContextInterface::USER_TYPE_INTEGRATION, true, true],
96+
['foo', UserContextInterface::USER_TYPE_ADMIN, false, false],
97+
['foo', UserContextInterface::USER_TYPE_INTEGRATION, false, false],
98+
['foo', 'something', null, false],
99+
];
100+
}
101+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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\CatalogImportExport\Test\Unit\Model\Import\Product\Validator;
10+
11+
use Magento\CatalogImportExport\Model\Import\Product;
12+
use Magento\CatalogImportExport\Model\Import\Product\Validator\LayoutUpdate;
13+
use Magento\Framework\Config\ValidationStateInterface;
14+
use Magento\Framework\View\Model\Layout\Update\Validator;
15+
use Magento\Framework\View\Model\Layout\Update\ValidatorFactory;
16+
use PHPUnit\Framework\TestCase;
17+
use PHPUnit_Framework_MockObject_MockObject as MockObject;
18+
19+
/**
20+
* Test validation for layout update
21+
*/
22+
class LayoutUpdateTest extends TestCase
23+
{
24+
/**
25+
* @var LayoutUpdate|MockObject
26+
*/
27+
private $validator;
28+
29+
/**
30+
* @var Validator|MockObject
31+
*/
32+
private $layoutValidator;
33+
34+
protected function setUp()
35+
{
36+
$validatorFactory = $this->createMock(ValidatorFactory::class);
37+
$validationState = $this->createMock(ValidationStateInterface::class);
38+
$this->layoutValidator = $this->createMock(Validator::class);
39+
$validatorFactory->method('create')
40+
->with(['validationState' => $validationState])
41+
->willReturn($this->layoutValidator);
42+
43+
$this->validator = new LayoutUpdate(
44+
$validatorFactory,
45+
$validationState
46+
);
47+
}
48+
49+
public function testValidationIsSkippedWithDataNotPresent()
50+
{
51+
$this->layoutValidator
52+
->expects($this->never())
53+
->method('isValid');
54+
55+
$result = $this->validator->isValid([]);
56+
self::assertTrue($result);
57+
}
58+
59+
public function testValidationFailsProperly()
60+
{
61+
$this->layoutValidator
62+
->method('isValid')
63+
->with('foo')
64+
->willReturn(false);
65+
66+
$contextMock = $this->createMock(Product::class);
67+
$contextMock
68+
->method('retrieveMessageTemplate')
69+
->with('invalidLayoutUpdate')
70+
->willReturn('oh no');
71+
$this->validator->init($contextMock);
72+
73+
$result = $this->validator->isValid(['custom_layout_update' => 'foo']);
74+
$messages = $this->validator->getMessages();
75+
self::assertFalse($result);
76+
self::assertSame(['oh no'], $messages);
77+
}
78+
79+
public function testInvalidDataException()
80+
{
81+
$this->layoutValidator
82+
->method('isValid')
83+
->willThrowException(new \Exception('foo'));
84+
85+
$contextMock = $this->createMock(Product::class);
86+
$contextMock
87+
->method('retrieveMessageTemplate')
88+
->with('invalidLayoutUpdate')
89+
->willReturn('oh no');
90+
$this->validator->init($contextMock);
91+
92+
$result = $this->validator->isValid(['custom_layout_update' => 'foo']);
93+
$messages = $this->validator->getMessages();
94+
self::assertFalse($result);
95+
self::assertSame(['oh no'], $messages);
96+
}
97+
}

app/code/Magento/CatalogImportExport/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"magento/module-catalog-inventory": "100.2.*",
1313
"magento/module-media-storage": "100.2.*",
1414
"magento/module-customer": "101.0.*",
15+
"magento/module-authorization": "*",
1516
"magento/framework": "101.0.*",
1617
"ext-ctype": "*"
1718
},

app/code/Magento/CatalogImportExport/etc/di.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,14 @@
2424
<item name="website" xsi:type="object">Magento\CatalogImportExport\Model\Import\Product\Validator\Website</item>
2525
<item name="weight" xsi:type="object">Magento\CatalogImportExport\Model\Import\Product\Validator\Weight</item>
2626
<item name="quantity" xsi:type="object">Magento\CatalogImportExport\Model\Import\Product\Validator\Quantity</item>
27+
<item name="layout_update" xsi:type="object">Magento\CatalogImportExport\Model\Import\Product\Validator\LayoutUpdate</item>
28+
<item name="layout_update_permissions" xsi:type="object">Magento\CatalogImportExport\Model\Import\Product\Validator\LayoutUpdatePermissions</item>
2729
</argument>
2830
</arguments>
2931
</type>
32+
<type name="Magento\CatalogImportExport\Model\Import\Product\Validator\LayoutUpdate">
33+
<arguments>
34+
<argument name="validationState" xsi:type="object">Magento\Framework\Config\ValidationState\Required</argument>
35+
</arguments>
36+
</type>
3037
</config>

0 commit comments

Comments
 (0)