SessionTest.php 17 KB

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