Skip to content

Commit 3f09d42

Browse files
committed
Merge remote-tracking branch 'origin/ACP2E-3854' into PR_2025_05_28
2 parents 8953613 + 10da29e commit 3f09d42

File tree

5 files changed

+429
-0
lines changed

5 files changed

+429
-0
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Csp\Plugin;
9+
10+
use Magento\Csp\Model\SubresourceIntegrity\HashGenerator;
11+
use Magento\Csp\Model\SubresourceIntegrityCollector;
12+
use Magento\Csp\Model\SubresourceIntegrityFactory;
13+
use Magento\Deploy\Service\Bundle;
14+
use Magento\Framework\App\Filesystem\DirectoryList;
15+
use Magento\Framework\Exception\FileSystemException;
16+
use Magento\Framework\Filesystem;
17+
use Magento\Framework\Filesystem\Io\File;
18+
19+
class GenerateBundleAssetIntegrity
20+
{
21+
/**
22+
* @var HashGenerator
23+
*/
24+
private HashGenerator $hashGenerator;
25+
26+
/**
27+
* @var SubresourceIntegrityFactory
28+
*/
29+
private SubresourceIntegrityFactory $integrityFactory;
30+
31+
/**
32+
* @var SubresourceIntegrityCollector
33+
*/
34+
private SubresourceIntegrityCollector $integrityCollector;
35+
36+
/**
37+
* @var Filesystem
38+
*/
39+
private Filesystem $filesystem;
40+
41+
/**
42+
* @var File
43+
*/
44+
private File $fileIo;
45+
46+
/**
47+
* @param HashGenerator $hashGenerator
48+
* @param SubresourceIntegrityFactory $integrityFactory
49+
* @param SubresourceIntegrityCollector $integrityCollector
50+
* @param Filesystem $filesystem
51+
* @param File $fileIo
52+
*/
53+
public function __construct(
54+
HashGenerator $hashGenerator,
55+
SubresourceIntegrityFactory $integrityFactory,
56+
SubresourceIntegrityCollector $integrityCollector,
57+
Filesystem $filesystem,
58+
File $fileIo,
59+
) {
60+
$this->hashGenerator = $hashGenerator;
61+
$this->integrityFactory = $integrityFactory;
62+
$this->integrityCollector = $integrityCollector;
63+
$this->filesystem = $filesystem;
64+
$this->fileIo = $fileIo;
65+
}
66+
67+
/**
68+
* Generate SRI hashes for JS files in the bundle directory.
69+
*
70+
* @param Bundle $subject
71+
* @param string|null $result
72+
* @param string $area
73+
* @param string $theme
74+
* @param string $locale
75+
* @return void
76+
* @throws FileSystemException
77+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
78+
*/
79+
public function afterDeploy(Bundle $subject, ?string $result, string $area, string $theme, string $locale)
80+
{
81+
if (PHP_SAPI == 'cli') {
82+
$pubStaticDir = $this->filesystem->getDirectoryRead(DirectoryList::STATIC_VIEW);
83+
$files = $pubStaticDir->search(
84+
$area ."/" . $theme . "/" . $locale . "/" . Bundle::BUNDLE_JS_DIR . "/*.js"
85+
);
86+
foreach ($files as $file) {
87+
$integrity = $this->integrityFactory->create(
88+
[
89+
"data" => [
90+
'hash' => $this->hashGenerator->generate(
91+
$pubStaticDir->readFile($file)
92+
),
93+
'path' => $area . '/' . $theme . '/' . $locale .
94+
"/" . Bundle::BUNDLE_JS_DIR . '/' . $this->fileIo->getPathInfo($file)['basename']
95+
]
96+
]
97+
);
98+
99+
$this->integrityCollector->collect($integrity);
100+
}
101+
}
102+
}
103+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Csp\Plugin;
9+
10+
use Magento\Csp\Model\SubresourceIntegrityRepositoryPool;
11+
use Magento\Csp\Model\SubresourceIntegrityRepository;
12+
use Magento\Csp\Model\SubresourceIntegrity\HashGenerator;
13+
use Magento\Csp\Model\SubresourceIntegrityFactory;
14+
use Magento\Framework\App\Area;
15+
use Magento\Framework\App\Filesystem\DirectoryList;
16+
use Magento\Framework\Exception\FileSystemException;
17+
use Magento\Framework\Filesystem;
18+
use Magento\Framework\View\Asset\LocalInterface;
19+
use Magento\Framework\View\Asset\MergeStrategy\FileExists;
20+
21+
class GenerateMergedAssetIntegrity
22+
{
23+
/**
24+
* @var SubresourceIntegrityRepository
25+
*/
26+
private SubresourceIntegrityRepository $sourceIntegrityRepository;
27+
28+
/**
29+
* @var HashGenerator
30+
*/
31+
private HashGenerator $hashGenerator;
32+
33+
/**
34+
* @var SubresourceIntegrityFactory
35+
*/
36+
private SubresourceIntegrityFactory $integrityFactory;
37+
38+
/**
39+
* @var Filesystem
40+
*/
41+
private Filesystem $filesystem;
42+
43+
/**
44+
* @param SubresourceIntegrityRepositoryPool $sourceIntegrityRepositoryPool
45+
* @param HashGenerator $hashGenerator
46+
* @param SubresourceIntegrityFactory $integrityFactory
47+
* @param Filesystem $filesystem
48+
*/
49+
public function __construct(
50+
SubresourceIntegrityRepositoryPool $sourceIntegrityRepositoryPool,
51+
HashGenerator $hashGenerator,
52+
SubresourceIntegrityFactory $integrityFactory,
53+
Filesystem $filesystem
54+
) {
55+
$this->sourceIntegrityRepository = $sourceIntegrityRepositoryPool->get(Area::AREA_FRONTEND);
56+
$this->hashGenerator = $hashGenerator;
57+
$this->integrityFactory = $integrityFactory;
58+
$this->filesystem = $filesystem;
59+
}
60+
61+
/**
62+
* Generate SRI hash for merged JS files.
63+
*
64+
* @param FileExists $subject
65+
* @param string|null $result
66+
* @param array $assetsToMerge
67+
* @param LocalInterface $resultAsset
68+
* @return string|null
69+
* @throws FileSystemException
70+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
71+
*/
72+
public function afterMerge(FileExists $subject, ?string $result, array $assetsToMerge, LocalInterface $resultAsset)
73+
{
74+
if ($resultAsset->getContentType() !== 'js') {
75+
return $result;
76+
}
77+
$pubStaticDir = $this->filesystem->getDirectoryWrite(DirectoryList::STATIC_VIEW);
78+
$integrity = $this->integrityFactory->create(
79+
[
80+
"data" => [
81+
'hash' => $this->hashGenerator->generate(
82+
$pubStaticDir->readFile($resultAsset->getPath())
83+
),
84+
'path' => $resultAsset->getPath()
85+
]
86+
]
87+
);
88+
89+
$this->sourceIntegrityRepository->save($integrity);
90+
91+
return $result;
92+
}
93+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Csp\Test\Unit\Plugin;
9+
10+
use Magento\Csp\Model\SubresourceIntegrity;
11+
use Magento\Csp\Model\SubresourceIntegrity\HashGenerator;
12+
use Magento\Csp\Model\SubresourceIntegrityCollector;
13+
use Magento\Csp\Model\SubresourceIntegrityFactory;
14+
use Magento\Csp\Plugin\GenerateBundleAssetIntegrity;
15+
use Magento\Deploy\Service\Bundle;
16+
use Magento\Framework\Exception\FileSystemException;
17+
use Magento\Framework\Filesystem;
18+
use Magento\Framework\Filesystem\Directory\ReadInterface;
19+
use Magento\Framework\Filesystem\Io\File;
20+
use PHPUnit\Framework\MockObject\Exception;
21+
use PHPUnit\Framework\MockObject\MockObject;
22+
use PHPUnit\Framework\TestCase;
23+
24+
class GenerateBundleAssetIntegrityTest extends TestCase
25+
{
26+
/**
27+
* @var HashGenerator|MockObject
28+
*/
29+
private HashGenerator $hashGenerator;
30+
31+
/**
32+
* @var SubresourceIntegrityFactory|MockObject
33+
*/
34+
private SubresourceIntegrityFactory $integrityFactory;
35+
36+
/**
37+
* @var SubresourceIntegrityCollector|MockObject
38+
*/
39+
private SubresourceIntegrityCollector $integrityCollector;
40+
41+
/**
42+
* @var Filesystem|MockObject
43+
*/
44+
private Filesystem $filesystem;
45+
46+
/**
47+
* @var File|MockObject
48+
*/
49+
private File $fileIo;
50+
51+
/**
52+
* Initialize Dependencies
53+
*
54+
* @return void
55+
* @throws Exception
56+
*/
57+
protected function setUp(): void
58+
{
59+
$this->hashGenerator = $this->createMock(HashGenerator::class);
60+
$this->integrityFactory = $this->createMock(SubresourceIntegrityFactory::class);
61+
$this->integrityCollector = $this->createMock(SubresourceIntegrityCollector::class);
62+
$this->filesystem = $this->createMock(Filesystem::class);
63+
$this->fileIo = $this->createMock(File::class);
64+
}
65+
66+
/**
67+
* @return void
68+
* @throws Exception
69+
* @throws FileSystemException
70+
*/
71+
public function testAfterDeploy(): void
72+
{
73+
$subject = $this->createMock(Bundle::class);
74+
$result = null;
75+
$area = 'frontend';
76+
$theme = 'Magento/blank';
77+
$locale = 'en_US';
78+
$file = '/path/to/file.js';
79+
$hash = 'asdfghjkl';
80+
$fileContent = 'content';
81+
82+
$pubStaticDir = $this->createMock(ReadInterface::class);
83+
$pubStaticDir->expects($this->once())->method('search')->with(
84+
$area ."/" . $theme . "/" . $locale . "/" . Bundle::BUNDLE_JS_DIR . "/*.js"
85+
)->willReturn([$file]);
86+
$pubStaticDir->expects($this->once())->method('readFile')->willReturn($fileContent);
87+
$this->filesystem->expects($this->once())->method('getDirectoryRead')->willReturn($pubStaticDir);
88+
$integrity = $this->createMock(SubresourceIntegrity::class);
89+
$this->hashGenerator->expects($this->once())
90+
->method('generate')
91+
->with($fileContent)
92+
->willReturn($hash);
93+
$this->fileIo->expects($this->once())
94+
->method('getPathInfo')
95+
->with($file)
96+
->willReturn(['basename' => 'file.js']);
97+
$this->integrityFactory->expects($this->once())
98+
->method('create')
99+
->with([
100+
'data' => [
101+
'hash' => $hash,
102+
'path' => $area . '/' . $theme . '/' . $locale . '/' . Bundle::BUNDLE_JS_DIR . '/file.js'
103+
]
104+
])
105+
->willReturn($integrity);
106+
$this->integrityCollector->expects($this->once())->method('collect')->with($integrity);
107+
108+
$plugin = new GenerateBundleAssetIntegrity(
109+
$this->hashGenerator,
110+
$this->integrityFactory,
111+
$this->integrityCollector,
112+
$this->filesystem,
113+
$this->fileIo
114+
);
115+
$plugin->afterDeploy($subject, $result, $area, $theme, $locale);
116+
}
117+
}

0 commit comments

Comments
 (0)