Skip to content

Commit 4130a93

Browse files
committed
[TASK] Verify cache headers in CSP request handling test
Resolves: #107554 Releases: main, 13.4 Change-Id: I5f326c5c289e7add07e37de66574f52944260c94 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/90819 Reviewed-by: Oliver Hader <oliver.hader@typo3.org> Tested-by: Garvin Hicking <gh@faktor-e.de> Reviewed-by: Garvin Hicking <gh@faktor-e.de> Tested-by: Simon Schaufelberger <simonschaufi+typo3@gmail.com> Reviewed-by: Simon Schaufelberger <simonschaufi+typo3@gmail.com> Tested-by: Oliver Hader <oliver.hader@typo3.org> Tested-by: core-ci <typo3@b13.com> Tested-by: Oliver Klee <typo3-coding@oliverklee.de>
1 parent a834535 commit 4130a93

File tree

3 files changed

+49
-12
lines changed

3 files changed

+49
-12
lines changed

typo3/sysext/core/Tests/Functional/SiteHandling/SiteBasedTestTrait.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
namespace TYPO3\CMS\Core\Tests\Functional\SiteHandling;
1919

20+
use Symfony\Component\Yaml\Yaml;
2021
use TYPO3\CMS\Core\Configuration\SiteConfiguration;
2122
use TYPO3\CMS\Core\Configuration\SiteWriter;
2223
use TYPO3\CMS\Core\Tests\Functional\Fixtures\Frontend\PhpError;
@@ -53,6 +54,7 @@ protected function writeSiteConfiguration(
5354
array $languages = [],
5455
array $errorHandling = [],
5556
array $dependencies = [],
57+
?array $csp = null,
5658
): void {
5759
$configuration = $site;
5860
if (!empty($languages)) {
@@ -69,6 +71,13 @@ protected function writeSiteConfiguration(
6971
// ensure no previous site configuration influences the test
7072
GeneralUtility::rmdir($this->instancePath . '/typo3conf/sites/' . $identifier, true);
7173
$siteWriter->write($identifier, $configuration);
74+
if ($csp !== null) {
75+
GeneralUtility::writeFile(
76+
$this->instancePath . '/typo3conf/sites/' . $identifier . '/csp.yaml',
77+
Yaml::dump($csp, 99, 2, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE | Yaml::DUMP_OBJECT_AS_MAP),
78+
true
79+
);
80+
}
7281
} catch (\Exception $exception) {
7382
$this->markTestSkipped($exception->getMessage());
7483
}

typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/RequestHandler.typoscript

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
config {
22
debug = 1
33
no_cache = 0
4+
sendCacheHeaders = 1
45
}
56

67
page = PAGE

typo3/sysext/frontend/Tests/Functional/SiteHandling/RequestHandlerTest.php

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ final class RequestHandlerTest extends AbstractTestCase
5151

5252
protected function setUp(): void
5353
{
54-
$this->configurationToUseInTestInstance['SYS']['features']['security.frontend.enforceContentSecurityPolicy'] = true;
5554
$this->configurationToUseInTestInstance['FE']['debug'] = true;
5655
parent::setUp();
5756

@@ -66,17 +65,26 @@ protected function setUp(): void
6665
static::failIfArrayIsNotEmpty($writer->getErrors());
6766
$this->setUpFrontendRootPage(
6867
1000,
69-
[
70-
'EXT:frontend/Tests/Functional/SiteHandling/Fixtures/RequestHandler.typoscript',
71-
],
72-
[
73-
'title' => 'ACME Root',
74-
]
68+
['EXT:frontend/Tests/Functional/SiteHandling/Fixtures/RequestHandler.typoscript'],
69+
['title' => 'ACME Root']
70+
);
71+
$this->setUpFrontendRootPage(
72+
1200,
73+
['EXT:frontend/Tests/Functional/SiteHandling/Fixtures/RequestHandler.typoscript'],
74+
['title' => 'ACME Features']
7575
);
7676
});
7777
$this->writeSiteConfiguration(
78-
'website-local',
79-
$this->buildSiteConfiguration(1000, 'https://website.local/')
78+
'website-default',
79+
$this->buildSiteConfiguration(1000, 'https://website.local/default/'),
80+
);
81+
$this->writeSiteConfiguration(
82+
'website-csp-enabled',
83+
$this->buildSiteConfiguration(1200, 'https://website.local/csp-enabled/'),
84+
csp: [
85+
'enforce' => true,
86+
'report' => true,
87+
],
8088
);
8189
}
8290

@@ -86,10 +94,25 @@ protected function tearDown(): void
8694
parent::tearDown();
8795
}
8896

97+
#[Test]
98+
public function nonceAttributesAreNotAssignedWhenCspIsDisabled(): void
99+
{
100+
$response = $this->executeFrontendSubRequest(new InternalRequest('https://website.local/default/welcome'));
101+
$dom = new \DOMDocument();
102+
$dom->loadHTML((string)$response->getBody());
103+
$xpath = new \DOMXPath($dom);
104+
$nonceAttrs = $xpath->query('//*[@nonce]');
105+
106+
self::assertStringStartsWith('max-age=', $response->getHeaderLine('Cache-Control'));
107+
self::assertSame('public', $response->getHeaderLine('Pragma'));
108+
self::assertEmpty($response->getHeaderLine('Content-Security-Policy'));
109+
self::assertCount(0, $nonceAttrs);
110+
}
111+
89112
#[Test]
90113
public function nonceAttributesForAssetsAreUpdated(): void
91114
{
92-
$firstResponse = $this->executeFrontendSubRequest(new InternalRequest('https://website.local/welcome'));
115+
$firstResponse = $this->executeFrontendSubRequest(new InternalRequest('https://website.local/csp-enabled/features'));
93116
$firstCspHeader = $firstResponse->getHeaderLine('Content-Security-Policy');
94117
$dom = new \DOMDocument();
95118
$dom->loadHTML((string)$firstResponse->getBody());
@@ -101,8 +124,10 @@ public function nonceAttributesForAssetsAreUpdated(): void
101124
self::assertSame($firstScriptNonce, $firstLinkNonce);
102125
self::assertStringContainsString(sprintf("'nonce-%s'", $firstScriptNonce), $firstCspHeader);
103126
self::assertEmpty($firstResponse->getHeaderLine('X-TYPO3-Debug-Cache'));
127+
self::assertNotEmpty($firstResponse->getHeaderLine('Content-Security-Policy-Report-Only'));
128+
self::assertSame('private, no-store', $firstResponse->getHeaderLine('Cache-Control'));
104129

105-
$secondResponse = $this->executeFrontendSubRequest(new InternalRequest('https://website.local/welcome'));
130+
$secondResponse = $this->executeFrontendSubRequest(new InternalRequest('https://website.local/csp-enabled/features'));
106131
$secondCspHeader = $secondResponse->getHeaderLine('Content-Security-Policy');
107132
$dom = new \DOMDocument();
108133
$dom->loadHTML((string)$secondResponse->getBody());
@@ -115,6 +140,8 @@ public function nonceAttributesForAssetsAreUpdated(): void
115140
self::assertNotSame($firstScriptNonce, $secondScriptNonce);
116141
self::assertStringContainsString(sprintf("'nonce-%s'", $secondScriptNonce), $secondCspHeader);
117142
self::assertStringStartsWith('Cached page generated', $secondResponse->getHeaderLine('X-TYPO3-Debug-Cache'));
143+
self::assertNotEmpty($secondResponse->getHeaderLine('Content-Security-Policy-Report-Only'));
144+
self::assertSame('private, no-store', $secondResponse->getHeaderLine('Cache-Control'));
118145
}
119146

120147
#[Test]
@@ -126,6 +153,6 @@ public function nonceValueSubstitutionIsInvoked(): void
126153
->with(self::isArray())
127154
->willReturnCallback(static fn(array $context) => $context['content'] ?? null);
128155
GeneralUtility::addInstance(NonceValueSubstitution::class, $nonceValueSubstitutionMock);
129-
$this->executeFrontendSubRequest(new InternalRequest('https://website.local/welcome'));
156+
$this->executeFrontendSubRequest(new InternalRequest('https://website.local/csp-enabled/features'));
130157
}
131158
}

0 commit comments

Comments
 (0)