SessionTest.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 1.2.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\Network;
  16. use Cake\Core\Plugin;
  17. use Cake\Http\Session;
  18. use Cake\Http\Session\CacheSession;
  19. use Cake\Http\Session\DatabaseSession;
  20. use Cake\TestSuite\TestCase;
  21. use RuntimeException;
  22. /**
  23. * TestCacheSession
  24. */
  25. class TestCacheSession extends CacheSession
  26. {
  27. protected function _writeSession()
  28. {
  29. return true;
  30. }
  31. }
  32. /**
  33. * TestDatabaseSession
  34. */
  35. class TestDatabaseSession extends DatabaseSession
  36. {
  37. protected function _writeSession()
  38. {
  39. return true;
  40. }
  41. }
  42. /**
  43. * Overwrite Session to simulate a web session even if the test runs on CLI.
  44. */
  45. class TestWebSession extends Session
  46. {
  47. protected function _hasSession()
  48. {
  49. $isCLI = $this->_isCLI;
  50. $this->_isCLI = false;
  51. $result = parent::_hasSession();
  52. $this->_isCLI = $isCLI;
  53. return $result;
  54. }
  55. }
  56. /**
  57. * SessionTest class
  58. */
  59. class SessionTest extends TestCase
  60. {
  61. protected static $_gcDivisor;
  62. /**
  63. * Fixtures used in the SessionTest
  64. *
  65. * @var array
  66. */
  67. public $fixtures = ['core.cake_sessions', 'core.sessions'];
  68. /**
  69. * tearDown method
  70. *
  71. * @return void
  72. */
  73. public function tearDown()
  74. {
  75. unset($_SESSION);
  76. parent::tearDown();
  77. Plugin::unload();
  78. }
  79. /**
  80. * test setting ini properties with Session configuration.
  81. *
  82. * @preserveGlobalState disabled
  83. * @runInSeparateProcess
  84. * @return void
  85. */
  86. public function testSessionConfigIniSetting()
  87. {
  88. $_SESSION = null;
  89. $config = [
  90. 'cookie' => 'test',
  91. 'checkAgent' => false,
  92. 'timeout' => 86400,
  93. 'ini' => [
  94. 'session.referer_check' => 'example.com',
  95. 'session.use_trans_sid' => false
  96. ]
  97. ];
  98. Session::create($config);
  99. $this->assertEquals('', ini_get('session.use_trans_sid'), 'Ini value is incorrect');
  100. $this->assertEquals('example.com', ini_get('session.referer_check'), 'Ini value is incorrect');
  101. $this->assertEquals('test', ini_get('session.name'), 'Ini value is incorrect');
  102. }
  103. /**
  104. * test session cookie path setting
  105. *
  106. * @preserveGlobalState disabled
  107. * @runInSeparateProcess
  108. * @return void
  109. */
  110. public function testCookiePath()
  111. {
  112. ini_set('session.cookie_path', '/foo');
  113. new Session();
  114. $this->assertEquals('/', ini_get('session.cookie_path'));
  115. new Session(['cookiePath' => '/base']);
  116. $this->assertEquals('/base', ini_get('session.cookie_path'));
  117. }
  118. /**
  119. * testCheck method
  120. *
  121. * @return void
  122. */
  123. public function testCheck()
  124. {
  125. $session = new Session();
  126. $session->write('SessionTestCase', 'value');
  127. $this->assertTrue($session->check('SessionTestCase'));
  128. $this->assertFalse($session->check('NotExistingSessionTestCase'));
  129. $this->assertFalse($session->check('Crazy.foo'));
  130. $session->write('Crazy.foo', ['bar' => 'baz']);
  131. $this->assertTrue($session->check('Crazy.foo'));
  132. $this->assertTrue($session->check('Crazy.foo.bar'));
  133. }
  134. /**
  135. * test read with simple values
  136. *
  137. * @return void
  138. */
  139. public function testReadSimple()
  140. {
  141. $session = new Session();
  142. $session->write('testing', '1,2,3');
  143. $result = $session->read('testing');
  144. $this->assertEquals('1,2,3', $result);
  145. $session->write('testing', ['1' => 'one', '2' => 'two', '3' => 'three']);
  146. $result = $session->read('testing.1');
  147. $this->assertEquals('one', $result);
  148. $result = $session->read('testing');
  149. $this->assertEquals(['1' => 'one', '2' => 'two', '3' => 'three'], $result);
  150. $result = $session->read();
  151. $this->assertArrayHasKey('testing', $result);
  152. $session->write('This.is.a.deep.array.my.friend', 'value');
  153. $result = $session->read('This.is.a.deep.array');
  154. $this->assertEquals(['my' => ['friend' => 'value']], $result);
  155. }
  156. /**
  157. * testReadEmpty
  158. *
  159. * @return void
  160. */
  161. public function testReadEmpty()
  162. {
  163. $session = new Session();
  164. $this->assertNull($session->read(''));
  165. }
  166. /**
  167. * Test writing simple keys
  168. *
  169. * @return void
  170. */
  171. public function testWriteSimple()
  172. {
  173. $session = new Session();
  174. $session->write('', 'empty');
  175. $this->assertEquals('empty', $session->read(''));
  176. $session->write('Simple', ['values']);
  177. $this->assertEquals(['values'], $session->read('Simple'));
  178. }
  179. /**
  180. * test writing a hash of values
  181. *
  182. * @return void
  183. */
  184. public function testWriteArray()
  185. {
  186. $session = new Session();
  187. $session->write([
  188. 'one' => 1,
  189. 'two' => 2,
  190. 'three' => ['something'],
  191. 'null' => null
  192. ]);
  193. $this->assertEquals(1, $session->read('one'));
  194. $this->assertEquals(['something'], $session->read('three'));
  195. $this->assertNull($session->read('null'));
  196. }
  197. /**
  198. * Test overwriting a string value as if it were an array.
  199. *
  200. * @return void
  201. */
  202. public function testWriteOverwriteStringValue()
  203. {
  204. $session = new Session();
  205. $session->write('Some.string', 'value');
  206. $this->assertEquals('value', $session->read('Some.string'));
  207. $session->write('Some.string.array', ['values']);
  208. $this->assertEquals(['values'], $session->read('Some.string.array'));
  209. }
  210. /**
  211. * Test consuming session data.
  212. *
  213. * @return void
  214. */
  215. public function testConsume()
  216. {
  217. $session = new Session();
  218. $session->write('Some.string', 'value');
  219. $session->write('Some.array', ['key1' => 'value1', 'key2' => 'value2']);
  220. $this->assertEquals('value', $session->read('Some.string'));
  221. $value = $session->consume('Some.string');
  222. $this->assertEquals('value', $value);
  223. $this->assertFalse($session->check('Some.string'));
  224. $value = $session->consume('');
  225. $this->assertNull($value);
  226. $value = $session->consume(null);
  227. $this->assertNull($value);
  228. $value = $session->consume('Some.array');
  229. $expected = ['key1' => 'value1', 'key2' => 'value2'];
  230. $this->assertEquals($expected, $value);
  231. $this->assertFalse($session->check('Some.array'));
  232. }
  233. /**
  234. * testId method
  235. *
  236. * @preserveGlobalState disabled
  237. * @runInSeparateProcess
  238. * @return void
  239. */
  240. public function testId()
  241. {
  242. $session = new Session();
  243. $session->start();
  244. $result = $session->id();
  245. $this->assertNotEmpty($result);
  246. $this->assertSame(session_id(), $result);
  247. $session->id('MySessionId');
  248. $this->assertSame('MySessionId', $session->id());
  249. $this->assertSame('MySessionId', session_id());
  250. $session->id('');
  251. $this->assertSame('', session_id());
  252. }
  253. /**
  254. * testStarted method
  255. *
  256. * @return void
  257. */
  258. public function testStarted()
  259. {
  260. $session = new Session();
  261. $this->assertFalse($session->started());
  262. $this->assertTrue($session->start());
  263. $this->assertTrue($session->started());
  264. }
  265. /**
  266. * test close method
  267. *
  268. * @return void
  269. */
  270. public function testCloseFailure()
  271. {
  272. $session = new Session();
  273. $session->started();
  274. $this->assertTrue($session->start());
  275. try {
  276. $session->close();
  277. } catch (RuntimeException $e) {
  278. // closing the session in CLI should raise an error
  279. // and won't close the session.
  280. $this->assertTrue($session->started());
  281. }
  282. }
  283. /**
  284. * testClear method
  285. *
  286. * @return void
  287. */
  288. public function testClear()
  289. {
  290. $session = new Session();
  291. $session->write('Delete.me', 'Clearing out');
  292. $session->clear();
  293. $this->assertFalse($session->check('Delete.me'));
  294. $this->assertFalse($session->check('Delete'));
  295. }
  296. /**
  297. * testDelete method
  298. *
  299. * @return void
  300. */
  301. public function testDelete()
  302. {
  303. $session = new Session();
  304. $session->write('Delete.me', 'Clearing out');
  305. $session->delete('Delete.me');
  306. $this->assertFalse($session->check('Delete.me'));
  307. $this->assertTrue($session->check('Delete'));
  308. $session->write('Clearing.sale', 'everything must go');
  309. $session->delete('');
  310. $this->assertTrue($session->check('Clearing.sale'));
  311. $session->delete(null);
  312. $this->assertTrue($session->check('Clearing.sale'));
  313. $session->delete('Clearing');
  314. $this->assertFalse($session->check('Clearing.sale'));
  315. $this->assertFalse($session->check('Clearing'));
  316. }
  317. /**
  318. * test delete
  319. *
  320. * @return void
  321. */
  322. public function testDeleteEmptyString()
  323. {
  324. $session = new Session();
  325. $session->write('', 'empty string');
  326. $session->delete('');
  327. $this->assertFalse($session->check(''));
  328. }
  329. /**
  330. * testDestroy method
  331. *
  332. * @return void
  333. */
  334. public function testDestroy()
  335. {
  336. $session = new Session();
  337. $session->start();
  338. $session->write('bulletProof', 'invincible');
  339. $session->id('foo');
  340. $session->destroy();
  341. $this->assertFalse($session->check('bulletProof'));
  342. }
  343. /**
  344. * testCheckingSavedEmpty method
  345. *
  346. * @return void
  347. */
  348. public function testCheckingSavedEmpty()
  349. {
  350. $session = new Session();
  351. $session->write('SessionTestCase', 0);
  352. $this->assertTrue($session->check('SessionTestCase'));
  353. $session->write('SessionTestCase', '0');
  354. $this->assertTrue($session->check('SessionTestCase'));
  355. $session->write('SessionTestCase', false);
  356. $this->assertTrue($session->check('SessionTestCase'));
  357. $session->write('SessionTestCase', null);
  358. $this->assertFalse($session->check('SessionTestCase'));
  359. }
  360. /**
  361. * testCheckKeyWithSpaces method
  362. *
  363. * @return void
  364. */
  365. public function testCheckKeyWithSpaces()
  366. {
  367. $session = new Session();
  368. $session->write('Session Test', 'test');
  369. $this->assertTrue($session->check('Session Test'));
  370. $session->delete('Session Test');
  371. $session->write('Session Test.Test Case', 'test');
  372. $this->assertTrue($session->check('Session Test.Test Case'));
  373. }
  374. /**
  375. * testCheckEmpty
  376. *
  377. * @return void
  378. */
  379. public function testCheckEmpty()
  380. {
  381. $session = new Session();
  382. $this->assertFalse($session->check());
  383. }
  384. /**
  385. * test key exploitation
  386. *
  387. * @return void
  388. */
  389. public function testKeyExploit()
  390. {
  391. $session = new Session();
  392. $key = "a'] = 1; phpinfo(); \$_SESSION['a";
  393. $session->write($key, 'haxored');
  394. $result = $session->read($key);
  395. $this->assertNull($result);
  396. }
  397. /**
  398. * testReadingSavedEmpty method
  399. *
  400. * @return void
  401. */
  402. public function testReadingSavedEmpty()
  403. {
  404. $session = new Session();
  405. $session->write('', 'empty string');
  406. $this->assertTrue($session->check(''));
  407. $this->assertEquals('empty string', $session->read(''));
  408. $session->write('SessionTestCase', 0);
  409. $this->assertEquals(0, $session->read('SessionTestCase'));
  410. $session->write('SessionTestCase', '0');
  411. $this->assertEquals('0', $session->read('SessionTestCase'));
  412. $this->assertNotSame($session->read('SessionTestCase'), 0);
  413. $session->write('SessionTestCase', false);
  414. $this->assertFalse($session->read('SessionTestCase'));
  415. $session->write('SessionTestCase', null);
  416. $this->assertNull($session->read('SessionTestCase'));
  417. }
  418. /**
  419. * test using a handler from app/Http/Session.
  420. *
  421. * @preserveGlobalState disabled
  422. * @runInSeparateProcess
  423. * @return void
  424. */
  425. public function testUsingAppLibsHandler()
  426. {
  427. static::setAppNamespace();
  428. $config = [
  429. 'defaults' => 'cake',
  430. 'handler' => [
  431. 'engine' => 'TestAppLibSession',
  432. 'these' => 'are',
  433. 'a few' => 'options'
  434. ]
  435. ];
  436. $session = Session::create($config);
  437. $this->assertInstanceOf('TestApp\Http\Session\TestAppLibSession', $session->engine());
  438. $this->assertEquals('user', ini_get('session.save_handler'));
  439. $this->assertEquals(['these' => 'are', 'a few' => 'options'], $session->engine()->options);
  440. }
  441. /**
  442. * test using a handler from a plugin.
  443. *
  444. * @preserveGlobalState disabled
  445. * @runInSeparateProcess
  446. * @return void
  447. */
  448. public function testUsingPluginHandler()
  449. {
  450. static::setAppNamespace();
  451. $this->loadPlugins(['TestPlugin']);
  452. $config = [
  453. 'defaults' => 'cake',
  454. 'handler' => [
  455. 'engine' => 'TestPlugin.TestPluginSession'
  456. ]
  457. ];
  458. $session = Session::create($config);
  459. $this->assertInstanceOf('TestPlugin\Http\Session\TestPluginSession', $session->engine());
  460. $this->assertEquals('user', ini_get('session.save_handler'));
  461. }
  462. /**
  463. * Tests that it is possible to pass an already made instance as the session engine
  464. *
  465. * @preserveGlobalState disabled
  466. * @runInSeparateProcess
  467. * @return void
  468. */
  469. public function testEngineWithPreMadeInstance()
  470. {
  471. static::setAppNamespace();
  472. $engine = new \TestApp\Http\Session\TestAppLibSession;
  473. $session = new Session(['handler' => ['engine' => $engine]]);
  474. $this->assertSame($engine, $session->engine());
  475. $session = new Session();
  476. $session->engine($engine);
  477. $this->assertSame($engine, $session->engine());
  478. }
  479. /**
  480. * Tests instantiating a missing engine
  481. *
  482. * @return void
  483. */
  484. public function testBadEngine()
  485. {
  486. $this->expectException(\InvalidArgumentException::class);
  487. $this->expectExceptionMessage('The class "Derp" does not exist and cannot be used as a session engine');
  488. $session = new Session();
  489. $session->engine('Derp');
  490. }
  491. /**
  492. * Test that cookieTimeout matches timeout when unspecified.
  493. *
  494. * @preserveGlobalState disabled
  495. * @runInSeparateProcess
  496. * @return void
  497. */
  498. public function testCookieTimeoutFallback()
  499. {
  500. $config = [
  501. 'defaults' => 'cake',
  502. 'timeout' => 400,
  503. ];
  504. new Session($config);
  505. $this->assertEquals(0, ini_get('session.cookie_lifetime'));
  506. $this->assertEquals(400 * 60, ini_get('session.gc_maxlifetime'));
  507. }
  508. /**
  509. * Tests that the cookie name can be changed with configuration
  510. *
  511. * @preserveGlobalState disabled
  512. * @runInSeparateProcess
  513. * @return void
  514. */
  515. public function testSessionName()
  516. {
  517. new Session(['cookie' => 'made_up_name']);
  518. $this->assertEquals('made_up_name', session_name());
  519. }
  520. /**
  521. * Test that a call of check() starts the session when cookies are disabled in php.ini
  522. *
  523. * @preserveGlobalState disabled
  524. * @runInSeparateProcess
  525. */
  526. public function testCheckStartsSessionWithCookiesDisabled()
  527. {
  528. $_COOKIE = [];
  529. $_GET = [];
  530. $session = new TestWebSession([
  531. 'ini' => [
  532. 'session.use_cookies' => 0,
  533. 'session.use_trans_sid' => 0,
  534. ]
  535. ]);
  536. $this->assertFalse($session->started());
  537. $session->check('something');
  538. $this->assertTrue($session->started());
  539. }
  540. /**
  541. * Test that a call of check() starts the session when a cookie is already set
  542. */
  543. public function testCheckStartsSessionWithCookie()
  544. {
  545. $_COOKIE[session_name()] = '123abc';
  546. $_GET = [];
  547. $session = new TestWebSession([
  548. 'ini' => [
  549. 'session.use_cookies' => 1,
  550. 'session.use_trans_sid' => 0,
  551. ]
  552. ]);
  553. $this->assertFalse($session->started());
  554. $session->check('something');
  555. $this->assertTrue($session->started());
  556. }
  557. /**
  558. * Test that a call of check() starts the session when the session ID is passed via URL and session.use_trans_sid is enabled
  559. *
  560. * @preserveGlobalState disabled
  561. * @runInSeparateProcess
  562. * @return void
  563. */
  564. public function testCheckStartsSessionWithSIDinURL()
  565. {
  566. $_COOKIE = [];
  567. $_GET[session_name()] = '123abc';
  568. $session = new TestWebSession([
  569. 'ini' => [
  570. 'session.use_cookies' => 1,
  571. 'session.use_trans_sid' => 1,
  572. ]
  573. ]);
  574. $this->assertFalse($session->started());
  575. $session->check('something');
  576. $this->assertTrue($session->started());
  577. }
  578. /**
  579. * Test that a call of check() does not start the session when the session ID is passed via URL and session.use_trans_sid is disabled
  580. */
  581. public function testCheckDoesntStartSessionWithoutTransSID()
  582. {
  583. $_COOKIE = [];
  584. $_GET[session_name()] = '123abc';
  585. $session = new TestWebSession([
  586. 'ini' => [
  587. 'session.use_cookies' => 1,
  588. 'session.use_trans_sid' => 0,
  589. ]
  590. ]);
  591. $this->assertFalse($session->started());
  592. $session->check('something');
  593. $this->assertFalse($session->started());
  594. }
  595. }