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