PageRenderTime 45ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/tests/ZendTest/Session/SessionManagerTest.php

http://github.com/zendframework/zf2
PHP | 646 lines | 449 code | 71 blank | 126 comment | 30 complexity | 51eaf8b5902f5c73a656796e325c21a0 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * Zend Framework (http://framework.zend.com/)
  4. *
  5. * @link http://github.com/zendframework/zf2 for the canonical source repository
  6. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  7. * @license http://framework.zend.com/license/new-bsd New BSD License
  8. */
  9. namespace ZendTest\Session;
  10. use Zend\Session\SessionManager;
  11. use Zend\Session;
  12. use Zend\Session\Validator\RemoteAddr;
  13. /**
  14. * @group Zend_Session
  15. * @preserveGlobalState disabled
  16. */
  17. class SessionManagerTest extends \PHPUnit_Framework_TestCase
  18. {
  19. public $error;
  20. public $cookieDateFormat = 'D, d-M-y H:i:s e';
  21. /**
  22. * @var SessionManager
  23. */
  24. protected $manager;
  25. public function setUp()
  26. {
  27. $this->forceAutoloader();
  28. $this->error = false;
  29. $this->manager = new SessionManager();
  30. }
  31. protected function forceAutoloader()
  32. {
  33. $splAutoloadFunctions = spl_autoload_functions();
  34. if (!$splAutoloadFunctions || !in_array('ZendTest_Autoloader', $splAutoloadFunctions)) {
  35. include __DIR__ . '/../../_autoload.php';
  36. }
  37. }
  38. public function handleErrors($errno, $errstr)
  39. {
  40. $this->error = $errstr;
  41. }
  42. public function getTimestampFromCookie($cookie)
  43. {
  44. if (preg_match('/expires=([^;]+)/', $cookie, $matches)) {
  45. $ts = new \DateTime($matches[1]);
  46. return $ts;
  47. }
  48. return false;
  49. }
  50. public function testManagerUsesSessionConfigByDefault()
  51. {
  52. $config = $this->manager->getConfig();
  53. $this->assertInstanceOf('Zend\Session\Config\SessionConfig', $config);
  54. }
  55. public function testCanPassConfigurationToConstructor()
  56. {
  57. $config = new Session\Config\StandardConfig();
  58. $manager = new SessionManager($config);
  59. $this->assertSame($config, $manager->getConfig());
  60. }
  61. public function testManagerUsesSessionStorageByDefault()
  62. {
  63. $storage = $this->manager->getStorage();
  64. $this->assertInstanceOf('Zend\Session\Storage\SessionArrayStorage', $storage);
  65. }
  66. public function testCanPassStorageToConstructor()
  67. {
  68. $storage = new Session\Storage\ArrayStorage();
  69. $manager = new SessionManager(null, $storage);
  70. $this->assertSame($storage, $manager->getStorage());
  71. }
  72. public function testCanPassSaveHandlerToConstructor()
  73. {
  74. $saveHandler = new TestAsset\TestSaveHandler();
  75. $manager = new SessionManager(null, null, $saveHandler);
  76. $this->assertSame($saveHandler, $manager->getSaveHandler());
  77. }
  78. public function testCanPassValidatorsToConstructor()
  79. {
  80. $validators = array(
  81. 'foo',
  82. 'bar',
  83. );
  84. $manager = new SessionManager(null, null, null, $validators);
  85. $this->assertAttributeEquals($validators, 'validators', $manager);
  86. }
  87. // Session-related functionality
  88. /**
  89. * @runInSeparateProcess
  90. */
  91. public function testSessionExistsReturnsFalseWhenNoSessionStarted()
  92. {
  93. $this->assertFalse($this->manager->sessionExists());
  94. }
  95. /**
  96. * @runInSeparateProcess
  97. */
  98. public function testSessionExistsReturnsTrueWhenSessionStarted()
  99. {
  100. session_start();
  101. $this->assertTrue($this->manager->sessionExists());
  102. }
  103. /**
  104. * @runInSeparateProcess
  105. */
  106. public function testSessionExistsReturnsTrueWhenSessionStartedThenWritten()
  107. {
  108. session_start();
  109. session_write_close();
  110. $this->assertTrue($this->manager->sessionExists());
  111. }
  112. /**
  113. * @runInSeparateProcess
  114. */
  115. public function testSessionExistsReturnsFalseWhenSessionStartedThenDestroyed()
  116. {
  117. session_start();
  118. session_destroy();
  119. $this->assertFalse($this->manager->sessionExists());
  120. }
  121. /**
  122. * @runInSeparateProcess
  123. */
  124. public function testSessionIsStartedAfterCallingStart()
  125. {
  126. $this->assertFalse($this->manager->sessionExists());
  127. $this->manager->start();
  128. $this->assertTrue($this->manager->sessionExists());
  129. }
  130. /**
  131. * @runInSeparateProcess
  132. */
  133. public function testStartDoesNothingWhenCalledAfterWriteCloseOperation()
  134. {
  135. $this->manager->start();
  136. $id1 = session_id();
  137. session_write_close();
  138. $this->manager->start();
  139. $id2 = session_id();
  140. $this->assertTrue($this->manager->sessionExists());
  141. $this->assertEquals($id1, $id2);
  142. }
  143. /**
  144. * @runInSeparateProcess
  145. */
  146. public function testStorageContentIsPreservedByWriteCloseOperation()
  147. {
  148. $this->manager->start();
  149. $storage = $this->manager->getStorage();
  150. $storage['foo'] = 'bar';
  151. $this->manager->writeClose();
  152. $this->assertArrayHasKey('foo', $storage);
  153. $this->assertEquals('bar', $storage['foo']);
  154. }
  155. /**
  156. * @runInSeparateProcess
  157. */
  158. public function testStartCreatesNewSessionIfPreviousSessionHasBeenDestroyed()
  159. {
  160. $this->manager->start();
  161. $id1 = session_id();
  162. session_destroy();
  163. $this->manager->start();
  164. $id2 = session_id();
  165. $this->assertTrue($this->manager->sessionExists());
  166. $this->assertNotEquals($id1, $id2);
  167. }
  168. /**
  169. * @outputBuffering disabled
  170. */
  171. public function testStartWillNotBlockHeaderSentNotices()
  172. {
  173. if ('cli' == PHP_SAPI) {
  174. $this->markTestSkipped('session_start() will not raise headers_sent warnings in CLI');
  175. }
  176. set_error_handler(array($this, 'handleErrors'), E_WARNING);
  177. echo ' ';
  178. $this->assertTrue(headers_sent());
  179. $this->manager->start();
  180. restore_error_handler();
  181. $this->assertInternalType('string', $this->error);
  182. $this->assertContains('already sent', $this->error);
  183. }
  184. /**
  185. * @runInSeparateProcess
  186. */
  187. public function testGetNameReturnsSessionName()
  188. {
  189. $ini = ini_get('session.name');
  190. $this->assertEquals($ini, $this->manager->getName());
  191. }
  192. /**
  193. * @runInSeparateProcess
  194. */
  195. public function testSetNameRaisesExceptionOnInvalidName()
  196. {
  197. $this->setExpectedException('Zend\Session\Exception\InvalidArgumentException', 'Name provided contains invalid characters; must be alphanumeric only');
  198. $this->manager->setName('foo bar!');
  199. }
  200. /**
  201. * @runInSeparateProcess
  202. */
  203. public function testSetNameSetsSessionNameOnSuccess()
  204. {
  205. $this->manager->setName('foobar');
  206. $this->assertEquals('foobar', $this->manager->getName());
  207. $this->assertEquals('foobar', session_name());
  208. }
  209. /**
  210. * @runInSeparateProcess
  211. */
  212. public function testCanSetNewSessionNameAfterSessionDestroyed()
  213. {
  214. $this->manager->start();
  215. session_destroy();
  216. $this->manager->setName('foobar');
  217. $this->assertEquals('foobar', $this->manager->getName());
  218. $this->assertEquals('foobar', session_name());
  219. }
  220. /**
  221. * @runInSeparateProcess
  222. */
  223. public function testSettingNameWhenAnActiveSessionExistsRaisesException()
  224. {
  225. $this->setExpectedException('Zend\Session\Exception\InvalidArgumentException',
  226. 'Cannot set session name after a session has already started');
  227. $this->manager->start();
  228. $this->manager->setName('foobar');
  229. }
  230. /**
  231. * @runInSeparateProcess
  232. */
  233. public function testDestroyByDefaultSendsAnExpireCookie()
  234. {
  235. if (!extension_loaded('xdebug')) {
  236. $this->markTestSkipped('Xdebug required for this test');
  237. }
  238. $config = $this->manager->getConfig();
  239. $config->setUseCookies(true);
  240. $this->manager->start();
  241. $this->manager->destroy();
  242. echo '';
  243. $headers = xdebug_get_headers();
  244. $found = false;
  245. $sName = $this->manager->getName();
  246. foreach ($headers as $header) {
  247. if (stristr($header, 'Set-Cookie:') && stristr($header, $sName)) {
  248. $found = true;
  249. }
  250. }
  251. $this->assertTrue($found, 'No session cookie found: ' . var_export($headers, true));
  252. }
  253. /**
  254. * @runInSeparateProcess
  255. */
  256. public function testSendingFalseToSendExpireCookieWhenCallingDestroyShouldNotSendCookie()
  257. {
  258. if (!extension_loaded('xdebug')) {
  259. $this->markTestSkipped('Xdebug required for this test');
  260. }
  261. $config = $this->manager->getConfig();
  262. $config->setUseCookies(true);
  263. $this->manager->start();
  264. $this->manager->destroy(array('send_expire_cookie' => false));
  265. echo '';
  266. $headers = xdebug_get_headers();
  267. $found = false;
  268. $sName = $this->manager->getName();
  269. foreach ($headers as $header) {
  270. if (stristr($header, 'Set-Cookie:') && stristr($header, $sName)) {
  271. $found = true;
  272. }
  273. }
  274. if ($found) {
  275. $this->assertNotContains('expires=', $header);
  276. } else {
  277. $this->assertFalse($found, 'Unexpected session cookie found: ' . var_export($headers, true));
  278. }
  279. }
  280. /**
  281. * @runInSeparateProcess
  282. */
  283. public function testDestroyDoesNotClearSessionStorageByDefault()
  284. {
  285. $this->manager->start();
  286. $storage = $this->manager->getStorage();
  287. $storage['foo'] = 'bar';
  288. $this->manager->destroy();
  289. $this->assertTrue(isset($storage['foo']));
  290. $this->assertEquals('bar', $storage['foo']);
  291. }
  292. /**
  293. * @runInSeparateProcess
  294. */
  295. public function testPassingClearStorageOptionWhenCallingDestroyClearsStorage()
  296. {
  297. $this->manager->start();
  298. $storage = $this->manager->getStorage();
  299. $storage['foo'] = 'bar';
  300. $this->manager->destroy(array('clear_storage' => true));
  301. $this->assertFalse(isset($storage['foo']));
  302. }
  303. /**
  304. * @runInSeparateProcess
  305. */
  306. public function testCallingWriteCloseMarksStorageAsImmutable()
  307. {
  308. $this->manager->start();
  309. $storage = $this->manager->getStorage();
  310. $storage['foo'] = 'bar';
  311. $this->manager->writeClose();
  312. $this->assertTrue($storage->isImmutable());
  313. }
  314. /**
  315. * @runInSeparateProcess
  316. */
  317. public function testCallingWriteCloseShouldNotAlterSessionExistsStatus()
  318. {
  319. $this->manager->start();
  320. $this->manager->writeClose();
  321. $this->assertTrue($this->manager->sessionExists());
  322. }
  323. /**
  324. * @runInSeparateProcess
  325. */
  326. public function testIdShouldBeEmptyPriorToCallingStart()
  327. {
  328. $this->assertSame('', $this->manager->getId());
  329. }
  330. /**
  331. * @runInSeparateProcess
  332. */
  333. public function testIdShouldBeMutablePriorToCallingStart()
  334. {
  335. $this->manager->setId(__CLASS__);
  336. $this->assertSame(__CLASS__, $this->manager->getId());
  337. $this->assertSame(__CLASS__, session_id());
  338. }
  339. /**
  340. * @runInSeparateProcess
  341. */
  342. public function testIdShouldNotBeMutableAfterSessionStarted()
  343. {
  344. $this->setExpectedException('RuntimeException',
  345. 'Session has already been started, to change the session ID call regenerateId()');
  346. $this->manager->start();
  347. $origId = $this->manager->getId();
  348. $this->manager->setId(__METHOD__);
  349. }
  350. /**
  351. * @runInSeparateProcess
  352. */
  353. public function testRegenerateIdShouldWorkAfterSessionStarted()
  354. {
  355. $this->manager->start();
  356. $origId = $this->manager->getId();
  357. $this->manager->regenerateId();
  358. $this->assertNotSame($origId, $this->manager->getId());
  359. }
  360. /**
  361. * @runInSeparateProcess
  362. */
  363. public function testRegeneratingIdAfterSessionStartedShouldSendExpireCookie()
  364. {
  365. if (!extension_loaded('xdebug')) {
  366. $this->markTestSkipped('Xdebug required for this test');
  367. }
  368. $config = $this->manager->getConfig();
  369. $config->setUseCookies(true);
  370. $this->manager->start();
  371. $origId = $this->manager->getId();
  372. $this->manager->regenerateId();
  373. $headers = xdebug_get_headers();
  374. $found = false;
  375. $sName = $this->manager->getName();
  376. foreach ($headers as $header) {
  377. if (stristr($header, 'Set-Cookie:') && stristr($header, $sName)) {
  378. $found = true;
  379. }
  380. }
  381. $this->assertTrue($found, 'No session cookie found: ' . var_export($headers, true));
  382. }
  383. /**
  384. * @runInSeparateProcess
  385. */
  386. public function testRememberMeShouldSendNewSessionCookieWithUpdatedTimestamp()
  387. {
  388. if (!extension_loaded('xdebug')) {
  389. $this->markTestSkipped('Xdebug required for this test');
  390. }
  391. $config = $this->manager->getConfig();
  392. $config->setUseCookies(true);
  393. $this->manager->start();
  394. $this->manager->rememberMe(18600);
  395. $headers = xdebug_get_headers();
  396. $found = false;
  397. $sName = $this->manager->getName();
  398. $cookie = false;
  399. foreach ($headers as $header) {
  400. if (stristr($header, 'Set-Cookie:') && stristr($header, $sName) && !stristr($header, '=deleted')) {
  401. $found = true;
  402. $cookie = $header;
  403. }
  404. }
  405. $this->assertTrue($found, 'No session cookie found: ' . var_export($headers, true));
  406. $ts = $this->getTimestampFromCookie($cookie);
  407. if (!$ts) {
  408. $this->fail('Cookie did not contain expiry? ' . var_export($headers, true));
  409. }
  410. $this->assertGreaterThan($_SERVER['REQUEST_TIME'], $ts->getTimestamp(), 'Session cookie: ' . var_export($headers, 1));
  411. }
  412. /**
  413. * @runInSeparateProcess
  414. */
  415. public function testRememberMeShouldSetTimestampBasedOnConfigurationByDefault()
  416. {
  417. if (!extension_loaded('xdebug')) {
  418. $this->markTestSkipped('Xdebug required for this test');
  419. }
  420. $config = $this->manager->getConfig();
  421. $config->setUseCookies(true);
  422. $config->setRememberMeSeconds(3600);
  423. $ttl = $config->getRememberMeSeconds();
  424. $this->manager->start();
  425. $this->manager->rememberMe();
  426. $headers = xdebug_get_headers();
  427. $found = false;
  428. $sName = $this->manager->getName();
  429. $cookie = false;
  430. foreach ($headers as $header) {
  431. if (stristr($header, 'Set-Cookie:') && stristr($header, $sName) && !stristr($header, '=deleted')) {
  432. $found = true;
  433. $cookie = $header;
  434. }
  435. }
  436. $this->assertTrue($found, 'No session cookie found: ' . var_export($headers, true));
  437. $ts = $this->getTimestampFromCookie($cookie);
  438. if (!$ts) {
  439. $this->fail('Cookie did not contain expiry? ' . var_export($headers, true));
  440. }
  441. $compare = $_SERVER['REQUEST_TIME'] + $ttl;
  442. $cookieTs = $ts->getTimestamp();
  443. $this->assertContains($cookieTs, range($compare, $compare + 10), 'Session cookie: ' . var_export($headers, 1));
  444. }
  445. /**
  446. * @runInSeparateProcess
  447. */
  448. public function testForgetMeShouldSendCookieWithZeroTimestamp()
  449. {
  450. if (!extension_loaded('xdebug')) {
  451. $this->markTestSkipped('Xdebug required for this test');
  452. }
  453. $config = $this->manager->getConfig();
  454. $config->setUseCookies(true);
  455. $this->manager->start();
  456. $this->manager->forgetMe();
  457. $headers = xdebug_get_headers();
  458. $found = false;
  459. $sName = $this->manager->getName();
  460. foreach ($headers as $header) {
  461. if (stristr($header, 'Set-Cookie:') && stristr($header, $sName) && !stristr($header, '=deleted')) {
  462. $found = true;
  463. }
  464. }
  465. $this->assertTrue($found, 'No session cookie found: ' . var_export($headers, true));
  466. $this->assertNotContains('expires=', $header);
  467. }
  468. /**
  469. * @runInSeparateProcess
  470. */
  471. public function testStartingSessionThatFailsAValidatorShouldRaiseException()
  472. {
  473. $chain = $this->manager->getValidatorChain();
  474. $chain->attach('session.validate', array(new TestAsset\TestFailingValidator(), 'isValid'));
  475. $this->setExpectedException('Zend\Session\Exception\RuntimeException', 'failed');
  476. $this->manager->start();
  477. }
  478. /**
  479. * @runInSeparateProcess
  480. */
  481. public function testResumeSessionThatFailsAValidatorShouldRaiseException()
  482. {
  483. $this->manager->setSaveHandler(new TestAsset\TestSaveHandlerWithValidator);
  484. $this->setExpectedException('Zend\Session\Exception\RuntimeException', 'failed');
  485. $this->manager->start();
  486. }
  487. /**
  488. * @runInSeparateProcess
  489. */
  490. public function testSessionWriteCloseStoresMetadata()
  491. {
  492. $this->manager->start();
  493. $storage = $this->manager->getStorage();
  494. $storage->setMetadata('foo', 'bar');
  495. $metaData = $storage->getMetadata();
  496. $this->manager->writeClose();
  497. $this->assertSame($_SESSION['__ZF'], $metaData);
  498. }
  499. /**
  500. * @runInSeparateProcess
  501. */
  502. public function testSessionValidationDoesNotHaltOnNoopListener()
  503. {
  504. $validator = $this->getMock('stdClass', array('__invoke'));
  505. $validator->expects($this->once())->method('__invoke');
  506. $this->manager->getValidatorChain()->attach('session.validate', $validator);
  507. $this->assertTrue($this->manager->isValid());
  508. }
  509. /**
  510. * @runInSeparateProcess
  511. */
  512. public function testProducedSessionManagerWillNotReplaceSessionSuperGlobalValues()
  513. {
  514. $_SESSION['foo'] = 'bar';
  515. $this->manager->start();
  516. $this->assertArrayHasKey('foo', $_SESSION);
  517. $this->assertSame('bar', $_SESSION['foo']);
  518. }
  519. /**
  520. * @runInSeparateProcess
  521. */
  522. public function testValidatorChainSessionMetadataIsPreserved()
  523. {
  524. $this
  525. ->manager
  526. ->getValidatorChain()
  527. ->attach('session.validate', array(new RemoteAddr(), 'isValid'));
  528. $this->assertFalse($this->manager->sessionExists());
  529. $this->manager->start();
  530. $this->assertSame(
  531. array(
  532. 'Zend\Session\Validator\RemoteAddr' => '',
  533. ),
  534. $_SESSION['__ZF']['_VALID']
  535. );
  536. }
  537. /**
  538. * @runInSeparateProcess
  539. */
  540. public function testRemoteAddressValidationWillFailOnInvalidAddress()
  541. {
  542. $this
  543. ->manager
  544. ->getValidatorChain()
  545. ->attach('session.validate', array(new RemoteAddr('123.123.123.123'), 'isValid'));
  546. $this->setExpectedException('Zend\Session\Exception\RuntimeException', 'Session validation failed');
  547. $this->manager->start();
  548. }
  549. /**
  550. * @runInSeparateProcess
  551. */
  552. public function testRemoteAddressValidationWillSucceedWithValidPreSetData()
  553. {
  554. $_SESSION = array(
  555. '__ZF' => array(
  556. '_VALID' => array('Zend\Session\Validator\RemoteAddr' => ''),
  557. ),
  558. );
  559. $this->manager->start();
  560. $this->assertTrue($this->manager->isValid());
  561. }
  562. /**
  563. * @runInSeparateProcess
  564. */
  565. public function testRemoteAddressValidationWillFailWithInvalidPreSetData()
  566. {
  567. $_SESSION = array(
  568. '__ZF' => array(
  569. '_VALID' => array('Zend\Session\Validator\RemoteAddr' => '123.123.123.123'),
  570. ),
  571. );
  572. $this->setExpectedException('Zend\Session\Exception\RuntimeException', 'Session validation failed');
  573. $this->manager->start();
  574. }
  575. }