Skip to content

Commit 3519add

Browse files
committed
Merge branch 'MC-41488' into cia-2.4.3-bugfixes-4272021
2 parents a77ff7c + 33293c2 commit 3519add

File tree

11 files changed

+266
-32
lines changed

11 files changed

+266
-32
lines changed

app/code/Magento/Backup/Model/Fs/Collection.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* Copyright © Magento, Inc. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
declare(strict_types=1);
7+
68
namespace Magento\Backup\Model\Fs;
79

810
use Magento\Framework\App\Filesystem\DirectoryList;
@@ -88,12 +90,17 @@ public function __construct(
8890
* Create .htaccess file and deny backups directory access from web
8991
*
9092
* @return void
93+
* @throws \Magento\Framework\Exception\FileSystemException
9194
*/
9295
protected function _hideBackupsForApache()
9396
{
9497
$filename = '.htaccess';
95-
if (!$this->_varDirectory->isFile($filename)) {
96-
$this->_varDirectory->writeFile($filename, 'deny from all');
98+
$driver = $this->_varDirectory->getDriver();
99+
$absolutePath = $driver->getAbsolutePath($this->_varDirectory->getAbsolutePath(), $filename);
100+
if (!$driver->isFile($absolutePath)) {
101+
$resource = $driver->fileOpen($absolutePath, 'w+');
102+
$driver->fileWrite($resource, 'deny from all');
103+
$driver->fileClose($resource);
97104
}
98105
}
99106

app/code/Magento/Backup/Test/Unit/Model/Fs/CollectionTest.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,15 @@ public function testConstructor()
3434
)->disableOriginalConstructor()
3535
->getMock();
3636
$backupData->expects($this->any())->method('getExtensions')->willReturn([]);
37-
37+
$driver = $this->getMockBuilder(
38+
Filesystem\DriverInterface::class
39+
)->disableOriginalConstructor()
40+
->getMock();
3841
$directoryWrite->expects($this->any())->method('create')->with('backups');
39-
$directoryWrite->expects($this->any())->method('getAbsolutePath')->with('backups');
42+
$directoryWrite->expects($this->any())->method('getAbsolutePath')->willReturn('');
43+
$directoryWrite->expects($this->at(3))->method('getAbsolutePath')->with('backups');
4044
$directoryWrite->expects($this->any())->method('isDirectory')->willReturn(true);
45+
$directoryWrite->expects($this->any())->method('getDriver')->willReturn($driver);
4146
$targetDirectory = $this->getMockBuilder(TargetDirectory::class)
4247
->disableOriginalConstructor()
4348
->getMock();

app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
* Copyright © Magento, Inc. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
declare(strict_types=1);
67

78
namespace Magento\Catalog\Model\Product\Option\Type\File;
89

910
use Magento\Catalog\Model\Product;
10-
use Magento\Framework\App\Filesystem\DirectoryList;
1111
use Magento\Catalog\Model\Product\Exception as ProductException;
12+
use Magento\Framework\App\Filesystem\DirectoryList;
13+
use Magento\Framework\App\ObjectManager;
1214
use Magento\Framework\Exception\LocalizedException;
1315
use Magento\Framework\Math\Random;
14-
use Magento\Framework\App\ObjectManager;
1516
use Magento\MediaStorage\Model\File\Uploader;
1617

1718
/**
@@ -254,8 +255,12 @@ protected function initFilesystem()
254255

255256
// Directory listing and hotlink secure
256257
$path = $this->path . '/.htaccess';
257-
if (!$this->mediaDirectory->isFile($path)) {
258-
$this->mediaDirectory->writeFile($path, "Order deny,allow\nDeny from all");
258+
$driver = $this->mediaDirectory->getDriver();
259+
$absolutePath = $driver->getAbsolutePath($this->mediaDirectory->getAbsolutePath(), $path);
260+
if (!$driver->isFile($absolutePath)) {
261+
$resource = $driver->fileOpen($absolutePath, 'w+');
262+
$driver->fileWrite($resource, "Order deny,allow\nDeny from all");
263+
$driver->fileClose($resource);
259264
}
260265
}
261266

app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ public function createDirectory($name, $path)
436436
);
437437
}
438438

439-
$relativePath = $this->_directory->getRelativePath($path);
439+
$relativePath = (string) $this->_directory->getRelativePath($path);
440440
if (!$this->_directory->isDirectory($relativePath) || !$this->_directory->isWritable($relativePath)) {
441441
$path = $this->_cmsWysiwygImages->getStorageRoot();
442442
}

dev/tests/api-functional/testsuite/Magento/Customer/Api/AccountManagementCustomAttributesTest.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,21 @@
33
* Copyright © Magento, Inc. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
7+
declare(strict_types=1);
8+
69
namespace Magento\Customer\Api;
710

811
use Magento\Customer\Api\Data\CustomerInterface;
9-
use Magento\Customer\Model\AccountManagement;
1012
use Magento\Framework\Api\AttributeValue;
1113
use Magento\Framework\Api\CustomAttributesDataInterface;
1214
use Magento\Framework\App\Filesystem\DirectoryList;
15+
use Magento\Framework\Filesystem\Directory\DenyListPathValidator;
16+
use Magento\Framework\Filesystem\Directory\WriteFactory;
17+
use Magento\Framework\Webapi\Exception as HTTPExceptionCodes;
1318
use Magento\TestFramework\Helper\Bootstrap;
1419
use Magento\TestFramework\Helper\Customer as CustomerHelper;
1520
use Magento\TestFramework\TestCase\WebapiAbstract;
16-
use Magento\Framework\Webapi\Exception as HTTPExceptionCodes;
1721

1822
/**
1923
* Test class for Customer's custom attributes
@@ -104,8 +108,16 @@ protected function tearDown(): void
104108
}
105109
}
106110
$this->accountManagement = null;
107-
$mediaDirectory = $this->fileSystem->getDirectoryWrite(DirectoryList::MEDIA);
108-
$mediaDirectory->delete(CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER);
111+
$writeFactory = Bootstrap::getObjectManager()
112+
->get(WriteFactory::class);
113+
$mediaDirectory = $writeFactory->create(DirectoryList::MEDIA);
114+
$denyListPathValidator = Bootstrap::getObjectManager()
115+
->create(DenyListPathValidator::class, ['driver' => $mediaDirectory->getDriver()]);
116+
$denyListPathValidator->addException($mediaDirectory->getAbsolutePath() . ".htaccess");
117+
$writeFactoryBypassDenyList = Bootstrap::getObjectManager()
118+
->create(WriteFactory::class, ['denyListPathValidator' => $denyListPathValidator]);
119+
$mediaDirectoryBypassDenyList = $writeFactoryBypassDenyList->create(DirectoryList::MEDIA);
120+
$mediaDirectoryBypassDenyList->delete(CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER);
109121
}
110122

111123
/**

dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/CreateHandlerTest.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,8 @@ public function testExecuteWithImageDuplicate(): void
107107
*/
108108
public function testExecuteWithIllegalFilename(string $imageFileName): void
109109
{
110-
$this->expectException(\Magento\Framework\Exception\FileSystemException::class);
111-
$this->expectExceptionMessageMatches('".+ file doesn\'t exist."');
112-
$this->expectExceptionMessageMatches('/^((?!\.\.\/).)*$/');
110+
$this->expectException(\Magento\Framework\Exception\ValidatorException::class);
111+
$this->expectExceptionMessageMatches('".+ is not a valid file path"');
113112

114113
$data = [
115114
'media_gallery' => ['images' => ['image' => ['file' => $imageFileName, 'label' => 'New image']]],

dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFilesTest.php

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@
44
* See COPYING.txt for license details.
55
*/
66

7+
declare(strict_types=1);
8+
79
namespace Magento\Cms\Controller\Adminhtml\Wysiwyg\Images;
810

911
use Magento\Framework\App\Filesystem\DirectoryList;
12+
use Magento\Framework\Exception\FileSystemException;
13+
use Magento\Framework\Filesystem\Directory\DenyListPathValidator;
14+
use Magento\Framework\Filesystem\Directory\WriteFactory;
15+
use Magento\Framework\Filesystem\Directory\WriteInterface;
1016

1117
/**
1218
* Test for \Magento\Cms\Controller\Adminhtml\Wysiwyg\Images\DeleteFiles class.
@@ -26,7 +32,7 @@ class DeleteFilesTest extends \PHPUnit\Framework\TestCase
2632
private $imagesHelper;
2733

2834
/**
29-
* @var \Magento\Framework\Filesystem\Directory\WriteInterface
35+
* @var WriteInterface
3036
*/
3137
private $mediaDirectory;
3238

@@ -50,25 +56,45 @@ class DeleteFilesTest extends \PHPUnit\Framework\TestCase
5056
*/
5157
private $objectManager;
5258

59+
/**
60+
* @var DirectoryList
61+
*/
62+
private $directoryList;
63+
64+
/**
65+
* @var WriteInterface
66+
*/
67+
private $bypassDenyListWrite;
68+
5369
/**
5470
* @inheritdoc
71+
* @throws FileSystemException
5572
*/
5673
protected function setUp(): void
5774
{
5875
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
5976
$directoryName = 'directory1';
77+
$this->directoryList = $this->objectManager->get(DirectoryList::class);
6078
$this->filesystem = $this->objectManager->get(\Magento\Framework\Filesystem::class);
6179
/** @var \Magento\Cms\Helper\Wysiwyg\Images $imagesHelper */
6280
$this->imagesHelper = $this->objectManager->get(\Magento\Cms\Helper\Wysiwyg\Images::class);
6381
$this->mediaDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA);
64-
$this->fullDirectoryPath = $this->imagesHelper->getStorageRoot() . '/' . $directoryName;
82+
$this->fullDirectoryPath = $this->imagesHelper->getStorageRoot() . $directoryName;
6583
$this->mediaDirectory->create($this->mediaDirectory->getRelativePath($this->fullDirectoryPath));
6684
$filePath = $this->fullDirectoryPath . DIRECTORY_SEPARATOR . $this->fileName;
6785
$fixtureDir = realpath(__DIR__ . '/../../../../../Catalog/_files');
6886
copy($fixtureDir . '/' . $this->fileName, $filePath);
6987
$path = $this->fullDirectoryPath . '/.htaccess';
70-
if (!$this->mediaDirectory->isFile($path)) {
71-
$this->mediaDirectory->writeFile($path, "Order deny,allow\nDeny from all");
88+
$denyListPathValidator = $this->objectManager
89+
->create(DenyListPathValidator::class, ['driver' => $this->mediaDirectory->getDriver()]);
90+
$denyListPathValidator->addException($path);
91+
$bypassDenyListWriteFactory = $this->objectManager->create(WriteFactory::class, [
92+
'denyListPathValidator' => $denyListPathValidator
93+
]);
94+
$this->bypassDenyListWrite = $bypassDenyListWriteFactory
95+
->create($this->directoryList->getPath(DirectoryList::MEDIA));
96+
if (!$this->bypassDenyListWrite->isFile($path)) {
97+
$this->bypassDenyListWrite->writeFile($path, "Order deny,allow\nDeny from all");
7298
}
7399
$this->model = $this->objectManager->get(\Magento\Cms\Controller\Adminhtml\Wysiwyg\Images\DeleteFiles::class);
74100
}
@@ -132,8 +158,8 @@ public function testDeleteHtaccess()
132158
$this->model->execute();
133159

134160
$this->assertTrue(
135-
$this->mediaDirectory->isExist(
136-
$this->mediaDirectory->getRelativePath($this->fullDirectoryPath . '/' . '.htaccess')
161+
$this->bypassDenyListWrite->isExist(
162+
$this->bypassDenyListWrite->getRelativePath($this->fullDirectoryPath . '/' . '.htaccess')
137163
)
138164
);
139165
}
@@ -186,7 +212,7 @@ public static function tearDownAfterClass(): void
186212
{
187213
$filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
188214
->get(\Magento\Framework\Filesystem::class);
189-
/** @var \Magento\Framework\Filesystem\Directory\WriteInterface $directory */
215+
/** @var WriteInterface $directory */
190216
$directory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA);
191217
if ($directory->isExist('wysiwyg')) {
192218
$directory->delete('wysiwyg');
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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\Filesystem\Directory;
10+
11+
/**
12+
* Validates paths using driver.
13+
*/
14+
class CompositePathValidator implements PathValidatorInterface
15+
{
16+
/**
17+
* @var PathValidatorInterface[]
18+
*/
19+
private $validators;
20+
21+
/**
22+
* @param PathValidatorInterface[] $validators
23+
*/
24+
public function __construct(array $validators)
25+
{
26+
$this->validators = $validators;
27+
}
28+
29+
/**
30+
* @inheritDoc
31+
*/
32+
public function validate(
33+
string $directoryPath,
34+
string $path,
35+
?string $scheme = null,
36+
bool $absolutePath = false
37+
): void {
38+
foreach ($this->validators as $validator) {
39+
$validator->validate($directoryPath, $path, $scheme, $absolutePath);
40+
}
41+
}
42+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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\Filesystem\Directory;
10+
11+
use Magento\Framework\Exception\ValidatorException;
12+
use Magento\Framework\Filesystem\DriverInterface;
13+
use Magento\Framework\Phrase;
14+
15+
/**
16+
* Validates paths using driver.
17+
*/
18+
class DenyListPathValidator implements PathValidatorInterface
19+
{
20+
/**
21+
* File deny list using regular expressions
22+
*
23+
* @var string[]
24+
*/
25+
private $fileDenyList = ["htaccess"];
26+
27+
/**
28+
* Deny list exception list
29+
*
30+
* @var string[]
31+
*/
32+
private $exceptionList = [];
33+
34+
/**
35+
* @var DriverInterface
36+
*/
37+
private $driver;
38+
39+
/**
40+
* @param DriverInterface $driver
41+
*/
42+
public function __construct(DriverInterface $driver)
43+
{
44+
$this->driver = $driver;
45+
}
46+
47+
/**
48+
* @inheritDoc
49+
*/
50+
public function validate(
51+
string $directoryPath,
52+
string $path,
53+
?string $scheme = null,
54+
bool $absolutePath = false
55+
): void {
56+
$realDirectoryPath = $this->driver->getRealPathSafety($directoryPath);
57+
$fullPath = $this->driver->getAbsolutePath(
58+
$realDirectoryPath . DIRECTORY_SEPARATOR,
59+
$path,
60+
$scheme
61+
);
62+
if (!$absolutePath) {
63+
$actualPath = $this->driver->getRealPathSafety($fullPath);
64+
} else {
65+
$actualPath = $this->driver->getRealPathSafety($path);
66+
}
67+
68+
if (in_array($fullPath, $this->exceptionList, true)) {
69+
return;
70+
}
71+
72+
foreach ($this->fileDenyList as $file) {
73+
$baseName = pathinfo($actualPath, PATHINFO_BASENAME);
74+
if (str_contains($baseName, $file) || preg_match('#' . "\." . $file . '#', $fullPath)) {
75+
throw new ValidatorException(
76+
new Phrase('"%1" is not a valid file path', [$path])
77+
);
78+
}
79+
}
80+
}
81+
82+
/**
83+
* Allow addition of new exceptions given full path
84+
*
85+
* @param string $fullPath
86+
*/
87+
public function addException(string $fullPath)
88+
{
89+
if (!in_array($fullPath, $this->exceptionList)) {
90+
array_push($this->exceptionList, $fullPath);
91+
}
92+
}
93+
94+
/**
95+
* Allow addition of new exceptions given full path
96+
*
97+
* @param string $fullPath
98+
*/
99+
public function removeException(string $fullPath)
100+
{
101+
if (($key = array_search($fullPath, $this->exceptionList)) !== false) {
102+
unset($this->exceptionList[$key]);
103+
}
104+
}
105+
}

0 commit comments

Comments
 (0)