[!!!][TASK] Drop constant PATH_site
[Packages/TYPO3.CMS.git] / typo3 / sysext / felogin / Tests / Unit / Controller / FrontendLoginControllerTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Felogin\Tests\Unit\Controller;
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 Psr\Log\NullLogger;
19 use TYPO3\CMS\Core\Authentication\LoginType;
20 use TYPO3\CMS\Core\Core\Environment;
21 use TYPO3\CMS\Core\Site\Entity\Site;
22 use TYPO3\CMS\Core\Site\SiteFinder;
23 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
24 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
25 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
26
27 /**
28 * Test case
29 */
30 class FrontendLoginControllerTest extends UnitTestCase
31 {
32 /**
33 * @var bool Restore Environment after tests
34 */
35 protected $backupEnvironment = true;
36
37 /**
38 * @var \TYPO3\CMS\Felogin\Controller\FrontendLoginController|\TYPO3\TestingFramework\Core\AccessibleObjectInterface
39 */
40 protected $accessibleFixture;
41
42 /**
43 * @var string
44 */
45 protected $testHostName;
46
47 /**
48 * @var string
49 */
50 protected $testSitePath;
51
52 protected $resetSingletonInstances = true;
53
54 /**
55 * Set up
56 */
57 protected function setUp()
58 {
59 parent::setUp();
60 $GLOBALS['TSFE'] = new \stdClass();
61 $this->testHostName = 'hostname.tld';
62 $this->testSitePath = '/';
63 $this->accessibleFixture = $this->getAccessibleMock(\TYPO3\CMS\Felogin\Controller\FrontendLoginController::class, ['dummy']);
64 $this->accessibleFixture->cObj = $this->createMock(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class);
65 $this->accessibleFixture->_set('frontendController', $this->createMock(\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::class));
66 $this->accessibleFixture->setLogger(new NullLogger());
67
68 $site = new Site('dummy', 1, ['base' => 'http://sub.domainhostname.tld/path/']);
69 $mockedSiteFinder = $this->getAccessibleMock(SiteFinder::class, ['getSiteByPageId'], [], '', false, false);
70 $mockedSiteFinder->method('getSiteByPageId')->willReturn($site);
71 $this->accessibleFixture->_set('siteFinder', $mockedSiteFinder);
72
73 $this->setUpFakeSitePathAndHost();
74 }
75
76 /**
77 * Set up a fake site path and host
78 */
79 protected function setUpFakeSitePathAndHost()
80 {
81 $_SERVER['ORIG_PATH_INFO'] = $_SERVER['PATH_INFO'] = $_SERVER['ORIG_SCRIPT_NAME'] = $_SERVER['SCRIPT_NAME'] = $this->testSitePath . TYPO3_mainDir;
82 $_SERVER['HTTP_HOST'] = $this->testHostName;
83 }
84
85 /**
86 * Data provider for validateRedirectUrlClearsUrl
87 *
88 * @return array
89 */
90 public function validateRedirectUrlClearsUrlDataProvider()
91 {
92 return [
93 'absolute URL, hostname not in site, trailing slash' => ['http://badhost.tld/'],
94 'absolute URL, hostname not in site, no trailing slash' => ['http://badhost.tld'],
95 'absolute URL, subdomain in site, but main domain not, trailing slash' => ['http://domainhostname.tld.badhost.tld/'],
96 'absolute URL, subdomain in site, but main domain not, no trailing slash' => ['http://domainhostname.tld.badhost.tld'],
97 'non http absolute URL 1' => ['its://domainhostname.tld/itunes/'],
98 'non http absolute URL 2' => ['ftp://domainhostname.tld/download/'],
99 'XSS attempt 1' => ['javascript:alert(123)'],
100 'XSS attempt 2' => ['" onmouseover="alert(123)"'],
101 'invalid URL, HTML break out attempt' => ['" >blabuubb'],
102 'invalid URL, UNC path' => ['\\\\foo\\bar\\'],
103 'invalid URL, backslashes in path' => ['http://domainhostname.tld\\bla\\blupp'],
104 'invalid URL, linefeed in path' => ['http://domainhostname.tld/bla/blupp' . LF],
105 'invalid URL, only one slash after scheme' => ['http:/domainhostname.tld/bla/blupp'],
106 'invalid URL, illegal chars' => ['http://(<>domainhostname).tld/bla/blupp'],
107 ];
108 }
109
110 /**
111 * @test
112 * @dataProvider validateRedirectUrlClearsUrlDataProvider
113 * @param string $url Invalid Url
114 */
115 public function validateRedirectUrlClearsUrl($url)
116 {
117 Environment::initialize(
118 Environment::getContext(),
119 true,
120 false,
121 Environment::getProjectPath(),
122 Environment::getPublicPath(),
123 Environment::getVarPath(),
124 Environment::getConfigPath(),
125 Environment::getBackendPath() . '/index.php',
126 Environment::isWindows() ? 'WINDOWS' : 'UNIX'
127 );
128 $this->assertEquals('', $this->accessibleFixture->_call('validateRedirectUrl', $url));
129 }
130
131 /**
132 * Data provider for validateRedirectUrlKeepsCleanUrl
133 *
134 * @return array
135 */
136 public function validateRedirectUrlKeepsCleanUrlDataProvider()
137 {
138 return [
139 'sane absolute URL' => ['http://sub.domainhostname.tld/path/'],
140 'sane absolute URL with script' => ['http://sub.domainhostname.tld/path/index.php?id=1'],
141 'sane absolute URL with realurl' => ['http://sub.domainhostname.tld/path/foo/bar/foo.html'],
142 'sane absolute URL with homedir' => ['http://sub.domainhostname.tld/path/~user/'],
143 'sane absolute URL with some strange chars encoded' => ['http://sub.domainhostname.tld/path/~user/a%cc%88o%cc%88%c3%9fa%cc%82/foo.html'],
144 'relative URL, no leading slash 1' => ['index.php?id=1'],
145 'relative URL, no leading slash 2' => ['foo/bar/index.php?id=2'],
146 'relative URL, leading slash, no realurl' => ['/index.php?id=1'],
147 'relative URL, leading slash, realurl' => ['/de/service/imprint.html'],
148 ];
149 }
150
151 /**
152 * @test
153 * @dataProvider validateRedirectUrlKeepsCleanUrlDataProvider
154 * @param string $url Clean URL to test
155 */
156 public function validateRedirectUrlKeepsCleanUrl($url)
157 {
158 Environment::initialize(
159 Environment::getContext(),
160 true,
161 false,
162 Environment::getProjectPath(),
163 Environment::getPublicPath(),
164 Environment::getVarPath(),
165 Environment::getConfigPath(),
166 Environment::getBackendPath() . '/index.php',
167 Environment::isWindows() ? 'WINDOWS' : 'UNIX'
168 );
169 $this->assertEquals($url, $this->accessibleFixture->_call('validateRedirectUrl', $url));
170 }
171
172 /**
173 * Data provider for validateRedirectUrlClearsInvalidUrlInSubdirectory
174 *
175 * @return array
176 */
177 public function validateRedirectUrlClearsInvalidUrlInSubdirectoryDataProvider()
178 {
179 return [
180 'absolute URL, missing subdirectory' => ['http://hostname.tld/'],
181 'absolute URL, wrong subdirectory' => ['http://hostname.tld/hacker/index.php'],
182 'absolute URL, correct subdirectory, no trailing slash' => ['http://hostname.tld/subdir'],
183 'relative URL, leading slash, no path' => ['/index.php?id=1'],
184 'relative URL, leading slash, wrong path' => ['/de/sub/site.html'],
185 'relative URL, leading slash, slash only' => ['/'],
186 ];
187 }
188
189 /**
190 * @test
191 * @dataProvider validateRedirectUrlClearsInvalidUrlInSubdirectoryDataProvider
192 * @param string $url Invalid Url
193 */
194 public function validateRedirectUrlClearsInvalidUrlInSubdirectory($url)
195 {
196 $this->testSitePath = '/subdir/';
197 $this->setUpFakeSitePathAndHost();
198 $this->assertEquals('', $this->accessibleFixture->_call('validateRedirectUrl', $url));
199 }
200
201 /**
202 * Data provider for validateRedirectUrlKeepsCleanUrlInSubdirectory
203 *
204 * @return array
205 */
206 public function validateRedirectUrlKeepsCleanUrlInSubdirectoryDataProvider()
207 {
208 return [
209 'absolute URL, correct subdirectory' => ['http://hostname.tld/subdir/'],
210 'absolute URL, correct subdirectory, realurl' => ['http://hostname.tld/subdir/de/imprint.html'],
211 'absolute URL, correct subdirectory, no realurl' => ['http://hostname.tld/subdir/index.php?id=10'],
212 'absolute URL, correct subdirectory of site base' => ['http://sub.domainhostname.tld/path/'],
213 'relative URL, no leading slash, realurl' => ['de/service/imprint.html'],
214 'relative URL, no leading slash, no realurl' => ['index.php?id=1'],
215 'relative nested URL, no leading slash, no realurl' => ['foo/bar/index.php?id=2']
216 ];
217 }
218
219 /**
220 * @test
221 * @dataProvider validateRedirectUrlKeepsCleanUrlInSubdirectoryDataProvider
222 * @param string $url Invalid Url
223 */
224 public function validateRedirectUrlKeepsCleanUrlInSubdirectory($url)
225 {
226 Environment::initialize(
227 Environment::getContext(),
228 true,
229 false,
230 Environment::getProjectPath(),
231 Environment::getPublicPath(),
232 Environment::getVarPath(),
233 Environment::getConfigPath(),
234 Environment::getBackendPath() . '/index.php',
235 Environment::isWindows() ? 'WINDOWS' : 'UNIX'
236 );
237 $this->testSitePath = '/subdir/';
238 $this->setUpFakeSitePathAndHost();
239 $this->assertEquals($url, $this->accessibleFixture->_call('validateRedirectUrl', $url));
240 }
241
242 /*************************
243 * Test concerning getPreverveGetVars
244 *************************/
245
246 /**
247 * @return array
248 */
249 public function getPreserveGetVarsReturnsCorrectResultDataProvider()
250 {
251 return [
252 'special get var id is not preserved' => [
253 [
254 'id' => 42,
255 ],
256 '',
257 [],
258 ],
259 'simple additional parameter is not preserved if not specified in preservedGETvars' => [
260 [
261 'id' => 42,
262 'special' => 23,
263 ],
264 '',
265 [],
266 ],
267 'all params except ignored ones are preserved if preservedGETvars is set to "all"' => [
268 [
269 'id' => 42,
270 'special1' => 23,
271 'special2' => [
272 'foo' => 'bar',
273 ],
274 'tx_felogin_pi1' => [
275 'forgot' => 1,
276 ],
277 ],
278 'all',
279 [
280 'special1' => 23,
281 'special2' => [
282 'foo' => 'bar',
283 ],
284 ]
285 ],
286 'preserve single parameter' => [
287 [
288 'L' => 42,
289 ],
290 'L',
291 [
292 'L' => 42,
293 ],
294 ],
295 'preserve whole parameter array' => [
296 [
297 'L' => 3,
298 'tx_someext' => [
299 'foo' => 'simple',
300 'bar' => [
301 'baz' => 'simple',
302 ],
303 ],
304 ],
305 'L,tx_someext',
306 [
307 'L' => 3,
308 'tx_someext' => [
309 'foo' => 'simple',
310 'bar' => [
311 'baz' => 'simple',
312 ],
313 ],
314 ],
315 ],
316 'preserve part of sub array' => [
317 [
318 'L' => 3,
319 'tx_someext' => [
320 'foo' => 'simple',
321 'bar' => [
322 'baz' => 'simple',
323 ],
324 ],
325 ],
326 'L,tx_someext[bar]',
327 [
328 'L' => 3,
329 'tx_someext' => [
330 'bar' => [
331 'baz' => 'simple',
332 ],
333 ],
334 ],
335 ],
336 'preserve keys on different levels' => [
337 [
338 'L' => 3,
339 'no-preserve' => 'whatever',
340 'tx_ext2' => [
341 'foo' => 'simple',
342 ],
343 'tx_ext3' => [
344 'bar' => [
345 'baz' => 'simple',
346 ],
347 'go-away' => '',
348 ],
349 ],
350 'L,tx_ext2,tx_ext3[bar]',
351 [
352 'L' => 3,
353 'tx_ext2' => [
354 'foo' => 'simple',
355 ],
356 'tx_ext3' => [
357 'bar' => [
358 'baz' => 'simple',
359 ],
360 ],
361 ],
362 ],
363 'preserved value that does not exist in get' => [
364 [],
365 'L,foo%5Bbar%5D',
366 [],
367 ],
368 ];
369 }
370
371 /**
372 * @test
373 * @dataProvider getPreserveGetVarsReturnsCorrectResultDataProvider
374 * @param array $getArray
375 * @param string $preserveVars
376 * @param string $expected
377 */
378 public function getPreserveGetVarsReturnsCorrectResult(array $getArray, $preserveVars, $expected)
379 {
380 $_GET = $getArray;
381 $this->accessibleFixture->conf['preserveGETvars'] = $preserveVars;
382 $this->assertSame($expected, $this->accessibleFixture->_call('getPreserveGetVars'));
383 }
384
385 /**************************************************
386 * Tests concerning isInLocalDomain
387 **************************************************/
388
389 /**
390 * Dataprovider for isInCurrentDomainIgnoresScheme
391 *
392 * @return array
393 */
394 public function isInCurrentDomainIgnoresSchemeDataProvider()
395 {
396 return [
397 'url https, current host http' => [
398 'example.com', // HTTP_HOST
399 '0', // HTTPS
400 'https://example.com/foo.html' // URL
401 ],
402 'url http, current host https' => [
403 'example.com',
404 '1',
405 'http://example.com/foo.html'
406 ],
407 'url https, current host https' => [
408 'example.com',
409 '1',
410 'https://example.com/foo.html'
411 ],
412 'url http, current host http' => [
413 'example.com',
414 '0',
415 'http://example.com/foo.html'
416 ]
417 ];
418 }
419
420 /**
421 * @test
422 * @dataProvider isInCurrentDomainIgnoresSchemeDataProvider
423 * @param string $host $_SERVER['HTTP_HOST']
424 * @param string $https $_SERVER['HTTPS']
425 * @param string $url The url to test
426 */
427 public function isInCurrentDomainIgnoresScheme($host, $https, $url)
428 {
429 Environment::initialize(
430 Environment::getContext(),
431 true,
432 false,
433 Environment::getProjectPath(),
434 Environment::getPublicPath(),
435 Environment::getVarPath(),
436 Environment::getConfigPath(),
437 Environment::getBackendPath() . '/index.php',
438 Environment::isWindows() ? 'WINDOWS' : 'UNIX'
439 );
440 $_SERVER['HTTP_HOST'] = $host;
441 $_SERVER['HTTPS'] = $https;
442 $this->assertTrue($this->accessibleFixture->_call('isInCurrentDomain', $url));
443 }
444
445 /**
446 * @return array
447 */
448 public function isInCurrentDomainReturnsFalseIfDomainsAreDifferentDataProvider()
449 {
450 return [
451 'simple difference' => [
452 'example.com', // HTTP_HOST
453 'http://typo3.org/foo.html' // URL
454 ],
455 'subdomain different' => [
456 'example.com',
457 'http://foo.example.com/bar.html'
458 ]
459 ];
460 }
461
462 /**
463 * @test
464 * @dataProvider isInCurrentDomainReturnsFalseIfDomainsAreDifferentDataProvider
465 * @param string $host $_SERVER['HTTP_HOST']
466 * @param string $url The url to test
467 */
468 public function isInCurrentDomainReturnsFalseIfDomainsAreDifferent($host, $url)
469 {
470 $_SERVER['HTTP_HOST'] = $host;
471 $this->assertFalse($this->accessibleFixture->_call('isInCurrentDomain', $url));
472 }
473
474 /**
475 * @test
476 */
477 public function processRedirectReferrerDomainsMatchesDomains()
478 {
479 $conf = [
480 'redirectMode' => 'refererDomains',
481 'domains' => 'example.com'
482 ];
483
484 $this->accessibleFixture->_set('conf', $conf);
485 $this->accessibleFixture->_set('logintype', LoginType::LOGIN);
486 $this->accessibleFixture->_set('referer', 'http://www.example.com/snafu');
487 /** @var TypoScriptFrontendController $tsfe */
488 $tsfe = $this->accessibleFixture->_get('frontendController');
489 $this->accessibleFixture->_set('userIsLoggedIn', true);
490 $this->assertSame(['http://www.example.com/snafu'], $this->accessibleFixture->_call('processRedirect'));
491 }
492
493 /**
494 *
495 */
496 public function processUserFieldsRespectsDefaultConfigurationForStdWrapDataProvider()
497 {
498 return [
499 'Simple casing' => [
500 [
501 'username' => 'Holy',
502 'lastname' => 'Wood',
503 ],
504 [
505 'username.' => [
506 'case' => 'upper'
507 ]
508 ],
509 [
510 '###FEUSER_USERNAME###' => 'HOLY',
511 '###FEUSER_LASTNAME###' => 'Wood',
512 '###USER###' => 'HOLY'
513 ]
514 ],
515 'Default config applies' => [
516 [
517 'username' => 'Holy',
518 'lastname' => 'O" Mally',
519 ],
520 [
521 'username.' => [
522 'case' => 'upper'
523 ]
524 ],
525 [
526 '###FEUSER_USERNAME###' => 'HOLY',
527 '###FEUSER_LASTNAME###' => 'O&quot; Mally',
528 '###USER###' => 'HOLY'
529 ]
530 ],
531 'Specific config overrides default config' => [
532 [
533 'username' => 'Holy',
534 'lastname' => 'O" Mally',
535 ],
536 [
537 'username.' => [
538 'case' => 'upper'
539 ],
540 'lastname.' => [
541 'htmlSpecialChars' => '0'
542 ]
543 ],
544 [
545 '###FEUSER_USERNAME###' => 'HOLY',
546 '###FEUSER_LASTNAME###' => 'O" Mally',
547 '###USER###' => 'HOLY'
548 ]
549 ],
550 'No given user returns empty array' => [
551 null,
552 [
553 'username.' => [
554 'case' => 'upper'
555 ],
556 'lastname.' => [
557 'htmlSpecialChars' => '0'
558 ]
559 ],
560 []
561 ],
562 ];
563 }
564
565 /**
566 * @test
567 * @dataProvider processUserFieldsRespectsDefaultConfigurationForStdWrapDataProvider
568 */
569 public function processUserFieldsRespectsDefaultConfigurationForStdWrap($userRecord, $fieldConf, $expectedMarkers)
570 {
571 $tsfe = new \stdClass();
572 $tsfe->fe_user = new \stdClass();
573 $tsfe->fe_user->user = $userRecord;
574 $conf = ['userfields.' => $fieldConf];
575 $this->accessibleFixture->_set('cObj', new ContentObjectRenderer());
576 $this->accessibleFixture->_set('frontendController', $tsfe);
577 $this->accessibleFixture->_set('conf', $conf);
578 $actualResult = $this->accessibleFixture->_call('getUserFieldMarkers');
579 $this->assertEquals($expectedMarkers, $actualResult);
580 }
581 }