70239a470683395dd6b07d0e64f52c967c463991
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Tests / Functional / SiteHandling / PlainRequestTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Core\Core\Bootstrap;
19 use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
20 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory;
21 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter;
22 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
23 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequestContext;
24 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\ResponseContent;
25
26 /**
27 * Test case for frontend requests without having site handling configured
28 */
29 class PlainRequestTest extends AbstractTestCase
30 {
31 /**
32 * @var string
33 */
34 private $siteTitle = 'A Company that Manufactures Everything Inc';
35
36 /**
37 * @var InternalRequestContext
38 */
39 private $internalRequestContext;
40
41 protected function setUp()
42 {
43 parent::setUp();
44
45 // these settings are forwarded to the frontend sub-request as well
46 $this->internalRequestContext = (new InternalRequestContext())
47 ->withGlobalSettings(['TYPO3_CONF_VARS' => static::TYPO3_CONF_VARS]);
48
49 $backendUser = $this->setUpBackendUserFromFixture(1);
50 Bootstrap::initializeLanguageObject();
51
52 $scenarioFile = __DIR__ . '/Fixtures/scenario.yaml';
53 $factory = DataHandlerFactory::fromYamlFile($scenarioFile);
54 $writer = DataHandlerWriter::withBackendUser($backendUser);
55 $writer->invokeFactory($factory);
56 static::failIfArrayIsNotEmpty(
57 $writer->getErrors()
58 );
59
60 $this->setUpFrontendRootPage(
61 1000,
62 [
63 'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/JsonRenderer.typoscript',
64 'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/JsonRenderer.typoscript',
65 ],
66 [
67 'title' => 'ACME Root',
68 'sitetitle' => $this->siteTitle,
69 ]
70 );
71 $this->setUpFrontendRootPage(
72 3000,
73 [
74 'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/JsonRenderer.typoscript',
75 'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/JsonRenderer.typoscript',
76 ],
77 [
78 'title' => 'ACME Archive',
79 'sitetitle' => $this->siteTitle,
80 ]
81 );
82 }
83
84 protected function tearDown()
85 {
86 unset($this->internalRequestContext);
87 parent::tearDown();
88 }
89
90 /**
91 * @return array
92 */
93 public function requestsAreRedirectedDataProvider(): array
94 {
95 $domainPaths = [
96 '/',
97 'http://localhost/',
98 'https://localhost/',
99 'http://website.local/',
100 'https://website.local/',
101 ];
102
103 $queries = [
104 '?',
105 '?id=1000',
106 '?id=acme-root'
107 ];
108
109 return $this->wrapInArray(
110 $this->keysFromValues(
111 $this->meltStrings([$domainPaths, $queries])
112 )
113 );
114 }
115
116 /**
117 * @param string $uri
118 *
119 * @test
120 * @dataProvider requestsAreRedirectedDataProvider
121 */
122 public function requestsAreRedirected(string $uri)
123 {
124 $expectedStatusCode = 307;
125 $expectedHeaders = ['location' => ['index.php?id=acme-first']];
126
127 $response = $this->executeFrontendRequest(
128 new InternalRequest($uri),
129 $this->internalRequestContext
130 );
131 static::assertSame($expectedStatusCode, $response->getStatusCode());
132 static::assertSame($expectedHeaders, $response->getHeaders());
133 }
134
135 /**
136 * @return array
137 */
138 public function pageIsRenderedDataProvider(): array
139 {
140 $domainPaths = [
141 '/',
142 'http://localhost/',
143 'https://localhost/',
144 'http://website.local/',
145 'https://website.local/',
146 ];
147
148 $queries = [
149 '?id=1100',
150 '?id=acme-first',
151 ];
152
153 $languageQueries = [
154 '',
155 '&L=0',
156 '&L=1',
157 '&L=2',
158 ];
159
160 return array_map(
161 function (string $uri) {
162 if (strpos($uri, '&L=1') !== false) {
163 $expectedPageTitle = 'FR: Welcome';
164 } elseif (strpos($uri, '&L=2') !== false) {
165 $expectedPageTitle = 'FR-CA: Welcome';
166 } else {
167 $expectedPageTitle = 'EN: Welcome';
168 }
169 return [$uri, $expectedPageTitle];
170 },
171 $this->keysFromValues(
172 $this->meltStrings([$domainPaths, $queries, $languageQueries])
173 )
174 );
175 }
176
177 /**
178 * @param string $uri
179 * @param string $expectedPageTitle
180 *
181 * @test
182 * @dataProvider pageIsRenderedDataProvider
183 */
184 public function pageIsRendered(string $uri, string $expectedPageTitle)
185 {
186 $response = $this->executeFrontendRequest(
187 new InternalRequest($uri),
188 $this->internalRequestContext
189 );
190 $responseStructure = ResponseContent::fromString(
191 (string)$response->getBody()
192 );
193
194 static::assertSame(
195 200,
196 $response->getStatusCode()
197 );
198 static::assertSame(
199 $this->siteTitle,
200 $responseStructure->getScopePath('template/sitetitle')
201 );
202 static::assertSame(
203 $expectedPageTitle,
204 $responseStructure->getScopePath('page/title')
205 );
206 }
207
208 /**
209 * @return array
210 */
211 public function pageIsRenderedWithDomainsDataProvider(): array
212 {
213 $instructions = [
214 ['https://archive.acme.com/?id=3100', 'EN: Statistics'],
215 ['https://archive.acme.com/?id=3110', 'EN: Markets'],
216 ['https://archive.acme.com/?id=3120', 'EN: Products'],
217 ['https://archive.acme.com/?id=3130', 'EN: Partners'],
218 ];
219
220 return $this->keysFromTemplate($instructions, '%1$s');
221 }
222
223 /**
224 * @param string $uri
225 * @param string $expectedPageTitle
226 *
227 * @test
228 * @dataProvider pageIsRenderedWithDomainsDataProvider
229 */
230 public function pageIsRenderedWithDomains(string $uri, string $expectedPageTitle)
231 {
232 $response = $this->executeFrontendRequest(
233 new InternalRequest($uri),
234 $this->internalRequestContext
235 );
236 $responseStructure = ResponseContent::fromString(
237 (string)$response->getBody()
238 );
239
240 static::assertSame(
241 200,
242 $response->getStatusCode()
243 );
244 static::assertSame(
245 $this->siteTitle,
246 $responseStructure->getScopePath('template/sitetitle')
247 );
248 static::assertSame(
249 $expectedPageTitle,
250 $responseStructure->getScopePath('page/title')
251 );
252 }
253
254 /**
255 * @return array
256 */
257 public function restrictedPageIsRenderedDataProvider(): array
258 {
259 $instructions = [
260 // frontend user 1
261 ['https://website.local/?id=1510', 1, 'Whitepapers'],
262 ['https://website.local/?id=1511', 1, 'Products'],
263 ['https://website.local/?id=1512', 1, 'Solutions'],
264 // frontend user 2
265 ['https://website.local/?id=1510', 2, 'Whitepapers'],
266 ['https://website.local/?id=1511', 2, 'Products'],
267 ['https://website.local/?id=1515', 2, 'Research'],
268 ['https://website.local/?id=1520', 2, 'Forecasts'],
269 ['https://website.local/?id=1521', 2, 'Current Year'],
270 // frontend user 3
271 ['https://website.local/?id=1510', 3, 'Whitepapers'],
272 ['https://website.local/?id=1511', 3, 'Products'],
273 ['https://website.local/?id=1512', 3, 'Solutions'],
274 ['https://website.local/?id=1515', 3, 'Research'],
275 ['https://website.local/?id=1520', 3, 'Forecasts'],
276 ['https://website.local/?id=1521', 3, 'Current Year'],
277 ];
278
279 return $this->keysFromTemplate($instructions, '%1$s (user:%2$s)');
280 }
281
282 /**
283 * @param string $uri
284 * @param int $frontendUserId
285 * @param string $expectedPageTitle
286 *
287 * @test
288 * @dataProvider restrictedPageIsRenderedDataProvider
289 */
290 public function restrictedPageIsRendered(string $uri, int $frontendUserId, string $expectedPageTitle)
291 {
292 $response = $this->executeFrontendRequest(
293 new InternalRequest($uri),
294 $this->internalRequestContext
295 ->withFrontendUserId($frontendUserId)
296 );
297 $responseStructure = ResponseContent::fromString(
298 (string)$response->getBody()
299 );
300
301 static::assertSame(
302 200,
303 $response->getStatusCode()
304 );
305 static::assertSame(
306 $this->siteTitle,
307 $responseStructure->getScopePath('template/sitetitle')
308 );
309 static::assertSame(
310 $expectedPageTitle,
311 $responseStructure->getScopePath('page/title')
312 );
313 }
314
315 /**
316 * @return array
317 */
318 public function restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorDataProvider(): array
319 {
320 $instructions = [
321 // no frontend user given
322 ['https://website.local/?id=1510', 0],
323 ['https://website.local/?id=1511', 0],
324 ['https://website.local/?id=1512', 0],
325 ['https://website.local/?id=1515', 0],
326 ['https://website.local/?id=1520', 0],
327 ['https://website.local/?id=1521', 0],
328 // frontend user 1
329 ['https://website.local/?id=1515', 1],
330 ['https://website.local/?id=1520', 1],
331 ['https://website.local/?id=1521', 1],
332 // frontend user 2
333 ['https://website.local/?id=1512', 2],
334 ];
335
336 return $this->keysFromTemplate($instructions, '%1$s (user:%2$s)');
337 }
338
339 /**
340 * @param string $uri
341 * @param int $frontendUserId
342 *
343 * @test
344 * @dataProvider restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorDataProvider
345 */
346 public function restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorUsingDefaultErrorHandling(string $uri, int $frontendUserId)
347 {
348 $response = $this->executeFrontendRequest(
349 new InternalRequest($uri),
350 $this->internalRequestContext
351 ->withFrontendUserId($frontendUserId)
352 );
353
354 static::assertSame(
355 403,
356 $response->getStatusCode()
357 );
358 static::assertThat(
359 (string)$response->getBody(),
360 static::logicalOr(
361 static::stringContains('Reason: ID was not an accessible page'),
362 static::stringContains('Reason: Subsection was found and not accessible')
363 )
364 );
365 }
366
367 /**
368 * @param string $uri
369 * @param int $frontendUserId
370 *
371 * @test
372 * @dataProvider restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorDataProvider
373 */
374 public function restrictedPageSendsForbiddenResponseWithUnauthorizedVisitorUsingCustomErrorHandling(string $uri, int $frontendUserId)
375 {
376 $response = $this->executeFrontendRequest(
377 new InternalRequest($uri),
378 $this->internalRequestContext
379 ->withFrontendUserId($frontendUserId)
380 ->withMergedGlobalSettings([
381 'TYPO3_CONF_VARS' => [
382 'FE' => [
383 'pageNotFound_handling' => 'READFILE:typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/PageError.txt',
384 ]
385 ]
386 ])
387 );
388
389 static::assertSame(
390 403,
391 $response->getStatusCode()
392 );
393 static::assertThat(
394 (string)$response->getBody(),
395 static::logicalOr(
396 static::stringContains('reason: ID was not an accessible page'),
397 static::stringContains('reason: Subsection was found and not accessible')
398 )
399 );
400 }
401
402 /**
403 * @return array
404 */
405 public function pageRenderingStopsWithInvalidCacheHashDataProvider(): array
406 {
407 $domainPaths = [
408 '/',
409 'http://localhost/',
410 'https://localhost/',
411 'http://website.local/',
412 'https://website.local/',
413 ];
414
415 $queries = [
416 '?',
417 '?id=1000',
418 '?id=acme-root',
419 '?id=1100',
420 '?id=acme-first',
421 ];
422
423 $customQueries = [
424 '&testing[value]=1',
425 '&testing[value]=1&cHash=',
426 '&testing[value]=1&cHash=WRONG',
427 ];
428
429 return $this->wrapInArray(
430 $this->keysFromValues(
431 $this->meltStrings([$domainPaths, $queries, $customQueries])
432 )
433 );
434 }
435
436 /**
437 * @param string $uri
438 *
439 * @test
440 * @dataProvider pageRenderingStopsWithInvalidCacheHashDataProvider
441 * @todo In TYPO3 v8 this seemed to be rendered, without throwing that exception
442 */
443 public function pageRequestThrowsExceptionWithInvalidCacheHash(string $uri)
444 {
445 $this->expectExceptionCode(1518472189);
446 $this->expectException(PageNotFoundException::class);
447
448 $this->executeFrontendRequest(
449 new InternalRequest($uri),
450 $this->internalRequestContext
451 );
452 }
453
454 /**
455 * @param string $uri
456 *
457 * @test
458 * @dataProvider pageRenderingStopsWithInvalidCacheHashDataProvider
459 */
460 public function pageRequestSendsNotFoundResponseWithInvalidCacheHash(string $uri)
461 {
462 $response = $this->executeFrontendRequest(
463 new InternalRequest($uri),
464 $this->internalRequestContext->withMergedGlobalSettings([
465 'TYPO3_CONF_VARS' => [
466 'FE' => [
467 'pageNotFound_handling' => 'READFILE:typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/PageError.txt',
468 ]
469 ]
470 ])
471 );
472
473 static::assertSame(
474 404,
475 $response->getStatusCode()
476 );
477 static::assertThat(
478 (string)$response->getBody(),
479 static::logicalOr(
480 static::stringContains('reason: Request parameters could not be validated (&amp;cHash empty)'),
481 static::stringContains('reason: Request parameters could not be validated (&amp;cHash comparison failed)')
482 )
483 );
484 }
485
486 /**
487 * @return array
488 */
489 public function pageIsRenderedWithValidCacheHashDataProvider(): array
490 {
491 $domainPaths = [
492 '/',
493 'http://localhost/',
494 'https://localhost/',
495 'http://website.local/',
496 'https://website.local/',
497 ];
498
499 // cHash has been calculated with encryption key set to
500 // '4408d27a916d51e624b69af3554f516dbab61037a9f7b9fd6f81b4d3bedeccb6'
501 $queries = [
502 // @todo Currently fails since cHash is verified after(!) redirect to page 1100
503 // '?&cHash=7d1f13fa91159dac7feb3c824936b39d&id=1000',
504 // '?&cHash=7d1f13fa91159dac7feb3c824936b39d=acme-root',
505 '?&cHash=f42b850e435f0cedd366f5db749fc1af&id=1100',
506 '?&cHash=f42b850e435f0cedd366f5db749fc1af&id=acme-first',
507 ];
508
509 $customQueries = [
510 '&testing[value]=1',
511 ];
512
513 $dataSet = $this->wrapInArray(
514 $this->keysFromValues(
515 $this->meltStrings([$domainPaths, $queries, $customQueries])
516 )
517 );
518
519 return $dataSet;
520 }
521
522 /**
523 * @param string $uri
524 *
525 * @test
526 * @dataProvider pageIsRenderedWithValidCacheHashDataProvider
527 */
528 public function pageIsRenderedWithValidCacheHash($uri)
529 {
530 $response = $this->executeFrontendRequest(
531 new InternalRequest($uri),
532 $this->internalRequestContext
533 );
534 $responseStructure = ResponseContent::fromString(
535 (string)$response->getBody()
536 );
537 static::assertSame(
538 '1',
539 $responseStructure->getScopePath('getpost/testing.value')
540 );
541 }
542 }