5b3ddffabfac71418dbe1af0965ffe919d1b3adb
[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 $this->assertEquals('', $this->accessibleFixture->_call('validateRedirectUrl', $url));
118 }
119
120 /**
121 * Data provider for validateRedirectUrlKeepsCleanUrl
122 *
123 * @return array
124 */
125 public function validateRedirectUrlKeepsCleanUrlDataProvider()
126 {
127 return [
128 'sane absolute URL' => ['http://sub.domainhostname.tld/path/'],
129 'sane absolute URL with script' => ['http://sub.domainhostname.tld/path/index.php?id=1'],
130 'sane absolute URL with realurl' => ['http://sub.domainhostname.tld/path/foo/bar/foo.html'],
131 'sane absolute URL with homedir' => ['http://sub.domainhostname.tld/path/~user/'],
132 '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'],
133 'relative URL, no leading slash 1' => ['index.php?id=1'],
134 'relative URL, no leading slash 2' => ['foo/bar/index.php?id=2'],
135 'relative URL, leading slash, no realurl' => ['/index.php?id=1'],
136 'relative URL, leading slash, realurl' => ['/de/service/imprint.html'],
137 ];
138 }
139
140 /**
141 * @test
142 * @dataProvider validateRedirectUrlKeepsCleanUrlDataProvider
143 * @param string $url Clean URL to test
144 */
145 public function validateRedirectUrlKeepsCleanUrl($url)
146 {
147 Environment::initialize(
148 Environment::getContext(),
149 true,
150 false,
151 Environment::getProjectPath(),
152 Environment::getPublicPath(),
153 Environment::getVarPath(),
154 Environment::getConfigPath(),
155 Environment::getBackendPath() . '/index.php',
156 Environment::isWindows() ? 'WINDOWS' : 'UNIX'
157 );
158 $this->assertEquals($url, $this->accessibleFixture->_call('validateRedirectUrl', $url));
159 }
160
161 /**
162 * Data provider for validateRedirectUrlClearsInvalidUrlInSubdirectory
163 *
164 * @return array
165 */
166 public function validateRedirectUrlClearsInvalidUrlInSubdirectoryDataProvider()
167 {
168 return [
169 'absolute URL, missing subdirectory' => ['http://hostname.tld/'],
170 'absolute URL, wrong subdirectory' => ['http://hostname.tld/hacker/index.php'],
171 'absolute URL, correct subdirectory, no trailing slash' => ['http://hostname.tld/subdir'],
172 'relative URL, leading slash, no path' => ['/index.php?id=1'],
173 'relative URL, leading slash, wrong path' => ['/de/sub/site.html'],
174 'relative URL, leading slash, slash only' => ['/'],
175 ];
176 }
177
178 /**
179 * @test
180 * @dataProvider validateRedirectUrlClearsInvalidUrlInSubdirectoryDataProvider
181 * @param string $url Invalid Url
182 */
183 public function validateRedirectUrlClearsInvalidUrlInSubdirectory($url)
184 {
185 $this->testSitePath = '/subdir/';
186 $this->setUpFakeSitePathAndHost();
187 $this->assertEquals('', $this->accessibleFixture->_call('validateRedirectUrl', $url));
188 }
189
190 /**
191 * Data provider for validateRedirectUrlKeepsCleanUrlInSubdirectory
192 *
193 * @return array
194 */
195 public function validateRedirectUrlKeepsCleanUrlInSubdirectoryDataProvider()
196 {
197 return [
198 'absolute URL, correct subdirectory' => ['http://hostname.tld/subdir/'],
199 'absolute URL, correct subdirectory, realurl' => ['http://hostname.tld/subdir/de/imprint.html'],
200 'absolute URL, correct subdirectory, no realurl' => ['http://hostname.tld/subdir/index.php?id=10'],
201 'absolute URL, correct subdirectory of site base' => ['http://sub.domainhostname.tld/path/'],
202 'relative URL, no leading slash, realurl' => ['de/service/imprint.html'],
203 'relative URL, no leading slash, no realurl' => ['index.php?id=1'],
204 'relative nested URL, no leading slash, no realurl' => ['foo/bar/index.php?id=2']
205 ];
206 }
207
208 /**
209 * @test
210 * @dataProvider validateRedirectUrlKeepsCleanUrlInSubdirectoryDataProvider
211 * @param string $url Invalid Url
212 */
213 public function validateRedirectUrlKeepsCleanUrlInSubdirectory($url)
214 {
215 Environment::initialize(
216 Environment::getContext(),
217 true,
218 false,
219 Environment::getProjectPath(),
220 Environment::getPublicPath(),
221 Environment::getVarPath(),
222 Environment::getConfigPath(),
223 Environment::getBackendPath() . '/index.php',
224 Environment::isWindows() ? 'WINDOWS' : 'UNIX'
225 );
226 $this->testSitePath = '/subdir/';
227 $this->setUpFakeSitePathAndHost();
228 $this->assertEquals($url, $this->accessibleFixture->_call('validateRedirectUrl', $url));
229 }
230
231 /*************************
232 * Test concerning getPreverveGetVars
233 *************************/
234
235 /**
236 * @return array
237 */
238 public function getPreserveGetVarsReturnsCorrectResultDataProvider()
239 {
240 return [
241 'special get var id is not preserved' => [
242 [
243 'id' => 42,
244 ],
245 '',
246 [],
247 ],
248 'simple additional parameter is not preserved if not specified in preservedGETvars' => [
249 [
250 'id' => 42,
251 'special' => 23,
252 ],
253 '',
254 [],
255 ],
256 'all params except ignored ones are preserved if preservedGETvars is set to "all"' => [
257 [
258 'id' => 42,
259 'special1' => 23,
260 'special2' => [
261 'foo' => 'bar',
262 ],
263 'tx_felogin_pi1' => [
264 'forgot' => 1,
265 ],
266 ],
267 'all',
268 [
269 'special1' => 23,
270 'special2' => [
271 'foo' => 'bar',
272 ],
273 ]
274 ],
275 'preserve single parameter' => [
276 [
277 'L' => 42,
278 ],
279 'L',
280 [
281 'L' => 42,
282 ],
283 ],
284 'preserve whole parameter array' => [
285 [
286 'L' => 3,
287 'tx_someext' => [
288 'foo' => 'simple',
289 'bar' => [
290 'baz' => 'simple',
291 ],
292 ],
293 ],
294 'L,tx_someext',
295 [
296 'L' => 3,
297 'tx_someext' => [
298 'foo' => 'simple',
299 'bar' => [
300 'baz' => 'simple',
301 ],
302 ],
303 ],
304 ],
305 'preserve part of sub array' => [
306 [
307 'L' => 3,
308 'tx_someext' => [
309 'foo' => 'simple',
310 'bar' => [
311 'baz' => 'simple',
312 ],
313 ],
314 ],
315 'L,tx_someext[bar]',
316 [
317 'L' => 3,
318 'tx_someext' => [
319 'bar' => [
320 'baz' => 'simple',
321 ],
322 ],
323 ],
324 ],
325 'preserve keys on different levels' => [
326 [
327 'L' => 3,
328 'no-preserve' => 'whatever',
329 'tx_ext2' => [
330 'foo' => 'simple',
331 ],
332 'tx_ext3' => [
333 'bar' => [
334 'baz' => 'simple',
335 ],
336 'go-away' => '',
337 ],
338 ],
339 'L,tx_ext2,tx_ext3[bar]',
340 [
341 'L' => 3,
342 'tx_ext2' => [
343 'foo' => 'simple',
344 ],
345 'tx_ext3' => [
346 'bar' => [
347 'baz' => 'simple',
348 ],
349 ],
350 ],
351 ],
352 'preserved value that does not exist in get' => [
353 [],
354 'L,foo%5Bbar%5D',
355 [],
356 ],
357 ];
358 }
359
360 /**
361 * @test
362 * @dataProvider getPreserveGetVarsReturnsCorrectResultDataProvider
363 * @param array $getArray
364 * @param string $preserveVars
365 * @param string $expected
366 */
367 public function getPreserveGetVarsReturnsCorrectResult(array $getArray, $preserveVars, $expected)
368 {
369 $_GET = $getArray;
370 $this->accessibleFixture->conf['preserveGETvars'] = $preserveVars;
371 $this->assertSame($expected, $this->accessibleFixture->_call('getPreserveGetVars'));
372 }
373
374 /**************************************************
375 * Tests concerning isInLocalDomain
376 **************************************************/
377
378 /**
379 * Dataprovider for isInCurrentDomainIgnoresScheme
380 *
381 * @return array
382 */
383 public function isInCurrentDomainIgnoresSchemeDataProvider()
384 {
385 return [
386 'url https, current host http' => [
387 'example.com', // HTTP_HOST
388 '0', // HTTPS
389 'https://example.com/foo.html' // URL
390 ],
391 'url http, current host https' => [
392 'example.com',
393 '1',
394 'http://example.com/foo.html'
395 ],
396 'url https, current host https' => [
397 'example.com',
398 '1',
399 'https://example.com/foo.html'
400 ],
401 'url http, current host http' => [
402 'example.com',
403 '0',
404 'http://example.com/foo.html'
405 ]
406 ];
407 }
408
409 /**
410 * @test
411 * @dataProvider isInCurrentDomainIgnoresSchemeDataProvider
412 * @param string $host $_SERVER['HTTP_HOST']
413 * @param string $https $_SERVER['HTTPS']
414 * @param string $url The url to test
415 */
416 public function isInCurrentDomainIgnoresScheme($host, $https, $url)
417 {
418 Environment::initialize(
419 Environment::getContext(),
420 true,
421 false,
422 Environment::getProjectPath(),
423 Environment::getPublicPath(),
424 Environment::getVarPath(),
425 Environment::getConfigPath(),
426 Environment::getBackendPath() . '/index.php',
427 Environment::isWindows() ? 'WINDOWS' : 'UNIX'
428 );
429 $_SERVER['HTTP_HOST'] = $host;
430 $_SERVER['HTTPS'] = $https;
431 $this->assertTrue($this->accessibleFixture->_call('isInCurrentDomain', $url));
432 }
433
434 /**
435 * @return array
436 */
437 public function isInCurrentDomainReturnsFalseIfDomainsAreDifferentDataProvider()
438 {
439 return [
440 'simple difference' => [
441 'example.com', // HTTP_HOST
442 'http://typo3.org/foo.html' // URL
443 ],
444 'subdomain different' => [
445 'example.com',
446 'http://foo.example.com/bar.html'
447 ]
448 ];
449 }
450
451 /**
452 * @test
453 * @dataProvider isInCurrentDomainReturnsFalseIfDomainsAreDifferentDataProvider
454 * @param string $host $_SERVER['HTTP_HOST']
455 * @param string $url The url to test
456 */
457 public function isInCurrentDomainReturnsFalseIfDomainsAreDifferent($host, $url)
458 {
459 $_SERVER['HTTP_HOST'] = $host;
460 $this->assertFalse($this->accessibleFixture->_call('isInCurrentDomain', $url));
461 }
462
463 /**
464 * @test
465 */
466 public function processRedirectReferrerDomainsMatchesDomains()
467 {
468 $conf = [
469 'redirectMode' => 'refererDomains',
470 'domains' => 'example.com'
471 ];
472
473 $this->accessibleFixture->_set('conf', $conf);
474 $this->accessibleFixture->_set('logintype', LoginType::LOGIN);
475 $this->accessibleFixture->_set('referer', 'http://www.example.com/snafu');
476 /** @var TypoScriptFrontendController $tsfe */
477 $tsfe = $this->accessibleFixture->_get('frontendController');
478 $this->accessibleFixture->_set('userIsLoggedIn', true);
479 $this->assertSame(['http://www.example.com/snafu'], $this->accessibleFixture->_call('processRedirect'));
480 }
481
482 /**
483 *
484 */
485 public function processUserFieldsRespectsDefaultConfigurationForStdWrapDataProvider()
486 {
487 return [
488 'Simple casing' => [
489 [
490 'username' => 'Holy',
491 'lastname' => 'Wood',
492 ],
493 [
494 'username.' => [
495 'case' => 'upper'
496 ]
497 ],
498 [
499 '###FEUSER_USERNAME###' => 'HOLY',
500 '###FEUSER_LASTNAME###' => 'Wood',
501 '###USER###' => 'HOLY'
502 ]
503 ],
504 'Default config applies' => [
505 [
506 'username' => 'Holy',
507 'lastname' => 'O" Mally',
508 ],
509 [
510 'username.' => [
511 'case' => 'upper'
512 ]
513 ],
514 [
515 '###FEUSER_USERNAME###' => 'HOLY',
516 '###FEUSER_LASTNAME###' => 'O&quot; Mally',
517 '###USER###' => 'HOLY'
518 ]
519 ],
520 'Specific config overrides default config' => [
521 [
522 'username' => 'Holy',
523 'lastname' => 'O" Mally',
524 ],
525 [
526 'username.' => [
527 'case' => 'upper'
528 ],
529 'lastname.' => [
530 'htmlSpecialChars' => '0'
531 ]
532 ],
533 [
534 '###FEUSER_USERNAME###' => 'HOLY',
535 '###FEUSER_LASTNAME###' => 'O" Mally',
536 '###USER###' => 'HOLY'
537 ]
538 ],
539 'No given user returns empty array' => [
540 null,
541 [
542 'username.' => [
543 'case' => 'upper'
544 ],
545 'lastname.' => [
546 'htmlSpecialChars' => '0'
547 ]
548 ],
549 []
550 ],
551 ];
552 }
553
554 /**
555 * @test
556 * @dataProvider processUserFieldsRespectsDefaultConfigurationForStdWrapDataProvider
557 */
558 public function processUserFieldsRespectsDefaultConfigurationForStdWrap($userRecord, $fieldConf, $expectedMarkers)
559 {
560 $tsfe = new \stdClass();
561 $tsfe->fe_user = new \stdClass();
562 $tsfe->fe_user->user = $userRecord;
563 $conf = ['userfields.' => $fieldConf];
564 $this->accessibleFixture->_set('cObj', new ContentObjectRenderer());
565 $this->accessibleFixture->_set('frontendController', $tsfe);
566 $this->accessibleFixture->_set('conf', $conf);
567 $actualResult = $this->accessibleFixture->_call('getUserFieldMarkers');
568 $this->assertEquals($expectedMarkers, $actualResult);
569 }
570 }