[TASK] Use SiteFinder in EXT:felogin
[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 Prophecy\Argument;
19 use Prophecy\Prophecy\ObjectProphecy;
20 use Psr\Log\NullLogger;
21 use TYPO3\CMS\Core\Authentication\LoginType;
22 use TYPO3\CMS\Core\Database\Connection;
23 use TYPO3\CMS\Core\Database\ConnectionPool;
24 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
25 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
26 use TYPO3\CMS\Core\Exception\SiteNotFoundException;
27 use TYPO3\CMS\Core\Site\SiteFinder;
28 use TYPO3\CMS\Core\Tests\Unit\Database\Mocks\MockPlatform;
29 use TYPO3\CMS\Core\Utility\GeneralUtility;
30 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
31 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
32
33 /**
34 * Test case
35 */
36 class FrontendLoginControllerTest extends UnitTestCase
37 {
38 /**
39 * @var \TYPO3\CMS\Felogin\Controller\FrontendLoginController|\TYPO3\TestingFramework\Core\AccessibleObjectInterface
40 */
41 protected $accessibleFixture;
42
43 /**
44 * @var string
45 */
46 protected $testHostName;
47
48 /**
49 * @var string
50 */
51 protected $testSitePath;
52
53 /**
54 * @var string
55 */
56 protected $testTableName;
57
58 /**
59 * Set up
60 */
61 protected function setUp()
62 {
63 $GLOBALS['TSFE'] = new \stdClass();
64 $this->testTableName = 'sys_domain';
65 $this->testHostName = 'hostname.tld';
66 $this->testSitePath = '/';
67 $this->accessibleFixture = $this->getAccessibleMock(\TYPO3\CMS\Felogin\Controller\FrontendLoginController::class, ['dummy']);
68 $this->accessibleFixture->cObj = $this->createMock(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class);
69 $this->accessibleFixture->_set('frontendController', $this->createMock(\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::class));
70 $this->accessibleFixture->setLogger(new NullLogger());
71
72 $mockedSiteFinder = $this->getAccessibleMock(SiteFinder::class, ['getSiteByPageId'], [], '', false, false);
73 $mockedSiteFinder->method('getSiteByPageId')->willThrowException(new SiteNotFoundException('Site not found', 1536819047));
74 $this->accessibleFixture->_set('siteFinder', $mockedSiteFinder);
75
76 $this->setUpFakeSitePathAndHost();
77 }
78
79 /**
80 * Tear down
81 */
82 protected function tearDown()
83 {
84 // setUpDatabaseMock() prepares some instances via addInstance(), but not all
85 // tests use that instance. purgeInstances() removes left overs
86 GeneralUtility::purgeInstances();
87 parent::tearDown();
88 }
89
90 /**
91 * Set up a fake site path and host
92 */
93 protected function setUpFakeSitePathAndHost()
94 {
95 $_SERVER['ORIG_PATH_INFO'] = $_SERVER['PATH_INFO'] = $_SERVER['ORIG_SCRIPT_NAME'] = $_SERVER['SCRIPT_NAME'] = $this->testSitePath . TYPO3_mainDir;
96 $_SERVER['HTTP_HOST'] = $this->testHostName;
97 }
98
99 /**
100 * Mock database
101 */
102 protected function setUpDatabaseMock()
103 {
104 /** @var Connection|ObjectProphecy $connection */
105 $connection = $this->prophesize(Connection::class);
106 $connection->getDatabasePlatform()->willReturn(new MockPlatform());
107 $connection->getExpressionBuilder()->willReturn(new ExpressionBuilder($connection->reveal()));
108 $connection->quoteIdentifier(Argument::cetera())->willReturnArgument(0);
109
110 // TODO: This should rather be a functional test if we need a query builder
111 // or we should clean up the code itself to not need to mock internal behavior here
112 $queryBuilder = new QueryBuilder(
113 $connection->reveal(),
114 null,
115 new \Doctrine\DBAL\Query\QueryBuilder($connection->reveal())
116 );
117
118 /** @var \Doctrine\DBAL\Driver\Statement|ObjectProphecy $resultSet */
119 $resultSet = $this->prophesize(\Doctrine\DBAL\Driver\Statement::class);
120 $resultSet->fetchAll()->willReturn([
121 ['domainName' => 'domainhostname.tld'],
122 ['domainName' => 'otherhostname.tld/path'],
123 ['domainName' => 'sub.domainhostname.tld/path/']
124 ]);
125
126 /** @var ConnectionPool|ObjectProphecy $connectionPool */
127 $connectionPool = $this->prophesize(ConnectionPool::class);
128 $connectionPool->getQueryBuilderForTable('sys_domain')->willReturn($queryBuilder);
129 GeneralUtility::addInstance(ConnectionPool::class, $connectionPool->reveal());
130
131 $connection->executeQuery('SELECT domainName FROM sys_domain', Argument::cetera())
132 ->willReturn($resultSet->reveal());
133 }
134
135 /**
136 * @test
137 */
138 public function typo3SitePathEqualsStubSitePath()
139 {
140 $this->assertEquals(\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_SITE_PATH'), $this->testSitePath);
141 }
142
143 /**
144 * @test
145 */
146 public function typo3SiteUrlEqualsStubSiteUrl()
147 {
148 $this->assertEquals(\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_SITE_URL'), ('http://' . $this->testHostName) . $this->testSitePath);
149 }
150
151 /**
152 * @test
153 */
154 public function typo3SitePathEqualsStubSitePathAfterChangingInTest()
155 {
156 $this->testHostName = 'somenewhostname.com';
157 $this->testSitePath = '/somenewpath/';
158 $this->setUpFakeSitePathAndHost();
159 $this->assertEquals(\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_SITE_PATH'), $this->testSitePath);
160 }
161
162 /**
163 * @test
164 */
165 public function typo3SiteUrlEqualsStubSiteUrlAfterChangingInTest()
166 {
167 $this->testHostName = 'somenewhostname.com';
168 $this->testSitePath = '/somenewpath/';
169 $this->setUpFakeSitePathAndHost();
170 $this->assertEquals(\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_SITE_URL'), ('http://' . $this->testHostName) . $this->testSitePath);
171 }
172
173 /**
174 * Data provider for validateRedirectUrlClearsUrl
175 *
176 * @return array
177 */
178 public function validateRedirectUrlClearsUrlDataProvider()
179 {
180 return [
181 'absolute URL, hostname not in sys_domain, trailing slash' => ['http://badhost.tld/'],
182 'absolute URL, hostname not in sys_domain, no trailing slash' => ['http://badhost.tld'],
183 'absolute URL, subdomain in sys_domain, but main domain not, trailing slash' => ['http://domainhostname.tld.badhost.tld/'],
184 'absolute URL, subdomain in sys_domain, but main domain not, no trailing slash' => ['http://domainhostname.tld.badhost.tld'],
185 'non http absolute URL 1' => ['its://domainhostname.tld/itunes/'],
186 'non http absolute URL 2' => ['ftp://domainhostname.tld/download/'],
187 'XSS attempt 1' => ['javascript:alert(123)'],
188 'XSS attempt 2' => ['" onmouseover="alert(123)"'],
189 'invalid URL, HTML break out attempt' => ['" >blabuubb'],
190 'invalid URL, UNC path' => ['\\\\foo\\bar\\'],
191 'invalid URL, backslashes in path' => ['http://domainhostname.tld\\bla\\blupp'],
192 'invalid URL, linefeed in path' => ['http://domainhostname.tld/bla/blupp' . LF],
193 'invalid URL, only one slash after scheme' => ['http:/domainhostname.tld/bla/blupp'],
194 'invalid URL, illegal chars' => ['http://(<>domainhostname).tld/bla/blupp'],
195 ];
196 }
197
198 /**
199 * @test
200 * @dataProvider validateRedirectUrlClearsUrlDataProvider
201 * @param string $url Invalid Url
202 */
203 public function validateRedirectUrlClearsUrl($url)
204 {
205 $this->setUpDatabaseMock();
206 $this->assertEquals('', $this->accessibleFixture->_call('validateRedirectUrl', $url));
207 }
208
209 /**
210 * Data provider for validateRedirectUrlKeepsCleanUrl
211 *
212 * @return array
213 */
214 public function validateRedirectUrlKeepsCleanUrlDataProvider()
215 {
216 return [
217 'sane absolute URL' => ['http://domainhostname.tld/'],
218 'sane absolute URL with script' => ['http://domainhostname.tld/index.php?id=1'],
219 'sane absolute URL with realurl' => ['http://domainhostname.tld/foo/bar/foo.html'],
220 'sane absolute URL with homedir' => ['http://domainhostname.tld/~user/'],
221 'sane absolute URL with some strange chars encoded' => ['http://domainhostname.tld/~user/a%cc%88o%cc%88%c3%9fa%cc%82/foo.html'],
222 'sane absolute URL (domain record with path)' => ['http://otherhostname.tld/path/'],
223 'sane absolute URL with script (domain record with path)' => ['http://otherhostname.tld/path/index.php?id=1'],
224 'sane absolute URL with realurl (domain record with path)' => ['http://otherhostname.tld/path/foo/bar/foo.html'],
225 'sane absolute URL (domain record with path and slash)' => ['http://sub.domainhostname.tld/path/'],
226 'sane absolute URL with script (domain record with path slash)' => ['http://sub.domainhostname.tld/path/index.php?id=1'],
227 'sane absolute URL with realurl (domain record with path slash)' => ['http://sub.domainhostname.tld/path/foo/bar/foo.html'],
228 'relative URL, no leading slash 1' => ['index.php?id=1'],
229 'relative URL, no leading slash 2' => ['foo/bar/index.php?id=2'],
230 'relative URL, leading slash, no realurl' => ['/index.php?id=1'],
231 'relative URL, leading slash, realurl' => ['/de/service/imprint.html'],
232 ];
233 }
234
235 /**
236 * @test
237 * @dataProvider validateRedirectUrlKeepsCleanUrlDataProvider
238 * @param string $url Clean URL to test
239 */
240 public function validateRedirectUrlKeepsCleanUrl($url)
241 {
242 $this->setUpDatabaseMock();
243 $this->assertEquals($url, $this->accessibleFixture->_call('validateRedirectUrl', $url));
244 }
245
246 /**
247 * Data provider for validateRedirectUrlClearsInvalidUrlInSubdirectory
248 *
249 * @return array
250 */
251 public function validateRedirectUrlClearsInvalidUrlInSubdirectoryDataProvider()
252 {
253 return [
254 'absolute URL, missing subdirectory' => ['http://hostname.tld/'],
255 'absolute URL, wrong subdirectory' => ['http://hostname.tld/hacker/index.php'],
256 'absolute URL, correct subdirectory, no trailing slash' => ['http://hostname.tld/subdir'],
257 'absolute URL, correct subdirectory of sys_domain record, no trailing slash' => ['http://otherhostname.tld/path'],
258 'absolute URL, correct subdirectory of sys_domain record, no trailing slash, subdomain' => ['http://sub.domainhostname.tld/path'],
259 'relative URL, leading slash, no path' => ['/index.php?id=1'],
260 'relative URL, leading slash, wrong path' => ['/de/sub/site.html'],
261 'relative URL, leading slash, slash only' => ['/'],
262 ];
263 }
264
265 /**
266 * @test
267 * @dataProvider validateRedirectUrlClearsInvalidUrlInSubdirectoryDataProvider
268 * @param string $url Invalid Url
269 */
270 public function validateRedirectUrlClearsInvalidUrlInSubdirectory($url)
271 {
272 $this->testSitePath = '/subdir/';
273 $this->setUpFakeSitePathAndHost();
274 $this->setUpDatabaseMock();
275 $this->assertEquals('', $this->accessibleFixture->_call('validateRedirectUrl', $url));
276 }
277
278 /**
279 * Data provider for validateRedirectUrlKeepsCleanUrlInSubdirectory
280 *
281 * @return array
282 */
283 public function validateRedirectUrlKeepsCleanUrlInSubdirectoryDataProvider()
284 {
285 return [
286 'absolute URL, correct subdirectory' => ['http://hostname.tld/subdir/'],
287 'absolute URL, correct subdirectory, realurl' => ['http://hostname.tld/subdir/de/imprint.html'],
288 'absolute URL, correct subdirectory, no realurl' => ['http://hostname.tld/subdir/index.php?id=10'],
289 'absolute URL, correct subdirectory of sys_domain record' => ['http://otherhostname.tld/path/'],
290 'absolute URL, correct subdirectory of sys_domain record, subdomain' => ['http://sub.domainhostname.tld/path/'],
291 'relative URL, no leading slash, realurl' => ['de/service/imprint.html'],
292 'relative URL, no leading slash, no realurl' => ['index.php?id=1'],
293 'relative nested URL, no leading slash, no realurl' => ['foo/bar/index.php?id=2']
294 ];
295 }
296
297 /**
298 * @test
299 * @dataProvider validateRedirectUrlKeepsCleanUrlInSubdirectoryDataProvider
300 * @param string $url Invalid Url
301 */
302 public function validateRedirectUrlKeepsCleanUrlInSubdirectory($url)
303 {
304 $this->testSitePath = '/subdir/';
305 $this->setUpFakeSitePathAndHost();
306 $this->setUpDatabaseMock();
307 $this->assertEquals($url, $this->accessibleFixture->_call('validateRedirectUrl', $url));
308 }
309
310 /*************************
311 * Test concerning getPreverveGetVars
312 *************************/
313
314 /**
315 * @return array
316 */
317 public function getPreserveGetVarsReturnsCorrectResultDataProvider()
318 {
319 return [
320 'special get var id is not preserved' => [
321 [
322 'id' => 42,
323 ],
324 '',
325 '',
326 ],
327 'simple additional parameter is not preserved if not specified in preservedGETvars' => [
328 [
329 'id' => 42,
330 'special' => 23,
331 ],
332 '',
333 '',
334 ],
335 'all params except ignored ones are preserved if preservedGETvars is set to "all"' => [
336 [
337 'id' => 42,
338 'special1' => 23,
339 'special2' => [
340 'foo' => 'bar',
341 ],
342 'tx_felogin_pi1' => [
343 'forgot' => 1,
344 ],
345 ],
346 'all',
347 '&special1=23&special2[foo]=bar',
348 ],
349 'preserve single parameter' => [
350 [
351 'L' => 42,
352 ],
353 'L',
354 '&L=42'
355 ],
356 'preserve whole parameter array' => [
357 [
358 'L' => 3,
359 'tx_someext' => [
360 'foo' => 'simple',
361 'bar' => [
362 'baz' => 'simple',
363 ],
364 ],
365 ],
366 'L,tx_someext',
367 '&L=3&tx_someext[foo]=simple&tx_someext[bar][baz]=simple',
368 ],
369 'preserve part of sub array' => [
370 [
371 'L' => 3,
372 'tx_someext' => [
373 'foo' => 'simple',
374 'bar' => [
375 'baz' => 'simple',
376 ],
377 ],
378 ],
379 'L,tx_someext[bar]',
380 '&L=3&tx_someext[bar][baz]=simple',
381 ],
382 'preserve keys on different levels' => [
383 [
384 'L' => 3,
385 'no-preserve' => 'whatever',
386 'tx_ext2' => [
387 'foo' => 'simple',
388 ],
389 'tx_ext3' => [
390 'bar' => [
391 'baz' => 'simple',
392 ],
393 'go-away' => '',
394 ],
395 ],
396 'L,tx_ext2,tx_ext3[bar]',
397 '&L=3&tx_ext2[foo]=simple&tx_ext3[bar][baz]=simple',
398 ],
399 'preserved value that does not exist in get' => [
400 [],
401 'L,foo[bar]',
402 ''
403 ],
404 'url params are encoded' => [
405 ['tx_ext1' => 'param with spaces and \\ %<>& /'],
406 'L,tx_ext1',
407 '&tx_ext1=param%20with%20spaces%20and%20%5C%20%25%3C%3E%26%20%2F'
408 ],
409 ];
410 }
411
412 /**
413 * @test
414 * @dataProvider getPreserveGetVarsReturnsCorrectResultDataProvider
415 * @param array $getArray
416 * @param string $preserveVars
417 * @param string $expected
418 */
419 public function getPreserveGetVarsReturnsCorrectResult(array $getArray, $preserveVars, $expected)
420 {
421 $_GET = $getArray;
422 $this->accessibleFixture->conf['preserveGETvars'] = $preserveVars;
423 $this->assertSame($expected, $this->accessibleFixture->_call('getPreserveGetVars'));
424 }
425
426 /**************************************************
427 * Tests concerning isInLocalDomain
428 **************************************************/
429
430 /**
431 * Dataprovider for isInCurrentDomainIgnoresScheme
432 *
433 * @return array
434 */
435 public function isInCurrentDomainIgnoresSchemeDataProvider()
436 {
437 return [
438 'url https, current host http' => [
439 'example.com', // HTTP_HOST
440 '0', // HTTPS
441 'https://example.com/foo.html' // URL
442 ],
443 'url http, current host https' => [
444 'example.com',
445 '1',
446 'http://example.com/foo.html'
447 ],
448 'url https, current host https' => [
449 'example.com',
450 '1',
451 'https://example.com/foo.html'
452 ],
453 'url http, current host http' => [
454 'example.com',
455 '0',
456 'http://example.com/foo.html'
457 ]
458 ];
459 }
460
461 /**
462 * @test
463 * @dataProvider isInCurrentDomainIgnoresSchemeDataProvider
464 * @param string $host $_SERVER['HTTP_HOST']
465 * @param string $https $_SERVER['HTTPS']
466 * @param string $url The url to test
467 */
468 public function isInCurrentDomainIgnoresScheme($host, $https, $url)
469 {
470 $_SERVER['HTTP_HOST'] = $host;
471 $_SERVER['HTTPS'] = $https;
472 $this->assertTrue($this->accessibleFixture->_call('isInCurrentDomain', $url));
473 }
474
475 /**
476 * @return array
477 */
478 public function isInCurrentDomainReturnsFalseIfDomainsAreDifferentDataProvider()
479 {
480 return [
481 'simple difference' => [
482 'example.com', // HTTP_HOST
483 'http://typo3.org/foo.html' // URL
484 ],
485 'subdomain different' => [
486 'example.com',
487 'http://foo.example.com/bar.html'
488 ]
489 ];
490 }
491
492 /**
493 * @test
494 * @dataProvider isInCurrentDomainReturnsFalseIfDomainsAreDifferentDataProvider
495 * @param string $host $_SERVER['HTTP_HOST']
496 * @param string $url The url to test
497 */
498 public function isInCurrentDomainReturnsFalseIfDomainsAreDifferent($host, $url)
499 {
500 $_SERVER['HTTP_HOST'] = $host;
501 $this->assertFalse($this->accessibleFixture->_call('isInCurrentDomain', $url));
502 }
503
504 /**
505 * @test
506 */
507 public function processRedirectReferrerDomainsMatchesDomains()
508 {
509 $conf = [
510 'redirectMode' => 'refererDomains',
511 'domains' => 'example.com'
512 ];
513
514 $this->accessibleFixture->_set('conf', $conf);
515 $this->accessibleFixture->_set('logintype', LoginType::LOGIN);
516 $this->accessibleFixture->_set('referer', 'http://www.example.com/snafu');
517 /** @var TypoScriptFrontendController $tsfe */
518 $tsfe = $this->accessibleFixture->_get('frontendController');
519 $this->accessibleFixture->_set('userIsLoggedIn', true);
520 $this->assertSame(['http://www.example.com/snafu'], $this->accessibleFixture->_call('processRedirect'));
521 }
522 }