diff --git a/libraries/joomla/factory.php b/libraries/joomla/factory.php index b5d807b4f7432..bf22ee5363ce0 100644 --- a/libraries/joomla/factory.php +++ b/libraries/joomla/factory.php @@ -587,14 +587,15 @@ protected static function createConfig($file, $type = 'PHP', $namespace = '') */ protected static function createSession(array $options = array()) { - // Get the editor configuration setting - $conf = self::getConfig(); + // Get the Joomla configuration settings + $conf = self::getConfig(); $handler = $conf->get('session_handler', 'none'); // Config time is in minutes $options['expire'] = ($conf->get('lifetime')) ? $conf->get('lifetime') * 60 : 900; - $session = JSession::getInstance($handler, $options); + $sessionHandler = new JSessionHandlerJoomla($options); + $session = JSession::getInstance($handler, $options, $sessionHandler); if ($session->getState() == 'expired') { diff --git a/libraries/joomla/session/handler/interface.php b/libraries/joomla/session/handler/interface.php new file mode 100644 index 0000000000000..94d989c732e10 --- /dev/null +++ b/libraries/joomla/session/handler/interface.php @@ -0,0 +1,115 @@ +setOptions($options); + $this->setCookieParams(); + } + + /** + * Starts the session + * + * @return boolean True if started + * + * @since 3.5 + * @throws RuntimeException If something goes wrong starting the session. + */ + public function start() + { + $session_name = $this->getName(); + + // Get the JInputCookie object + $cookie = $this->input->cookie; + + if (is_null($cookie->get($session_name))) + { + $session_clean = $this->input->get($session_name, false, 'string'); + + if ($session_clean) + { + $this->setId($session_clean); + $cookie->set($session_name, '', time() - 3600); + } + } + + return parent::start(); + } + + /** + * Clear all session data in memory. + * + * @return void + * + * @since 3.5 + */ + public function clear() + { + $session_name = $this->getName(); + + /* + * In order to kill the session altogether, such as to log the user out, the session id + * must also be unset. If a cookie is used to propagate the session id (default behavior), + * then the session cookie must be deleted. + */ + if (isset($_COOKIE[$session_name])) + { + $config = JFactory::getConfig(); + $cookie_domain = $config->get('cookie_domain', ''); + $cookie_path = $config->get('cookie_path', '/'); + setcookie($session_name, '', time() - 42000, $cookie_path, $cookie_domain); + } + + parent::clear(); + } + + /** + * Set session cookie parameters + * + * @return void + * + * @since 3.5 + */ + protected function setCookieParams() + { + $cookie = session_get_cookie_params(); + + if ($this->force_ssl) + { + $cookie['secure'] = true; + } + + $config = JFactory::getConfig(); + + if ($config->get('cookie_domain', '') != '') + { + $cookie['domain'] = $config->get('cookie_domain'); + } + + if ($config->get('cookie_path', '') != '') + { + $cookie['path'] = $config->get('cookie_path'); + } + + session_set_cookie_params($cookie['lifetime'], $cookie['path'], $cookie['domain'], $cookie['secure'], true); + } + + /** + * Set additional session options + * + * @param array $options List of parameter + * + * @return boolean True on success + * + * @since 3.5 + */ + protected function setOptions(array $options) + { + if (isset($options['force_ssl'])) + { + $this->force_ssl = (bool) $options['force_ssl']; + } + + return true; + } +} diff --git a/libraries/joomla/session/handler/native.php b/libraries/joomla/session/handler/native.php new file mode 100644 index 0000000000000..2904ee36a46f3 --- /dev/null +++ b/libraries/joomla/session/handler/native.php @@ -0,0 +1,257 @@ +isStarted()) + { + return true; + } + + /** + * Write and Close handlers are called after destructing objects since PHP 5.0.5. + * Thus destructors can use sessions but session handler can't use objects. + * So we are moving session closure before destructing objects. + */ + if (version_compare(PHP_VERSION, '5.4', 'ge')) + { + session_register_shutdown(); + } + else + { + register_shutdown_function('session_write_close'); + } + + // Disable the cache limiter + session_cache_limiter('none'); + + /* + * Extended checks to determine if the session has already been started + */ + + // If running PHP 5.4, try to use the native API + if (version_compare(PHP_VERSION, '5.4', 'ge') && PHP_SESSION_ACTIVE === session_status()) + { + throw new RuntimeException('Failed to start the session: already started by PHP.'); + } + + // Fallback check for PHP 5.3 + if (version_compare(PHP_VERSION, '5.4', 'lt') && !$this->closed && isset($_SESSION) && $this->getId()) + { + throw new RuntimeException('Failed to start the session: already started by PHP ($_SESSION is set).'); + } + + // If we are using cookies (default true) and headers have already been started (early output), + if (ini_get('session.use_cookies') && headers_sent($file, $line)) + { + throw new RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line)); + } + + // Ok to try and start the session + if (!session_start()) + { + throw new RuntimeException('Failed to start the session'); + } + + return true; + } + + /** + * Checks if the session is started. + * + * @return boolean True if started, false otherwise. + * + * @since 3.5 + */ + public function isStarted() + { + return $this->started; + } + + /** + * Returns the session ID + * + * @return string The session ID + * + * @since 3.5 + */ + public function getId() + { + return session_id(); + } + + /** + * Sets the session ID + * + * @param string $id The session ID + * + * @return void + * + * @since 3.5 + * @throws LogicException + */ + public function setId($id) + { + if ($this->isStarted()) + { + throw new LogicException('Cannot change the ID of an active session'); + } + + session_id($id); + } + + /** + * Returns the session name + * + * @return mixed The session name + * + * @since 3.5 + */ + public function getName() + { + return session_name(); + } + + /** + * Sets the session name + * + * @param string $name The name of the session + * + * @return void + * + * @since 3.5 + * @throws LogicException + */ + public function setName($name) + { + if ($this->isStarted()) + { + throw new LogicException('Cannot change the name of an active session'); + } + + session_name($name); + } + + /** + * Regenerates ID that represents this storage. + * + * Note regenerate+destroy should not clear the session data in memory only delete the session data from persistent storage. + * + * @param boolean $destroy Destroy session when regenerating? + * @param integer $lifetime Sets the cookie lifetime for the session cookie. A null value will leave the system settings unchanged, + * 0 sets the cookie to expire with browser session. Time is in seconds, and is not a Unix timestamp. + * + * @return boolean True if session regenerated, false if error + * + * @since 3.5 + */ + public function regenerate($destroy = false, $lifetime = null) + { + if (null !== $lifetime) + { + ini_set('session.cookie_lifetime', $lifetime); + } + + $return = session_regenerate_id($destroy); + + // Workaround for https://bugs.php.net/bug.php?id=61470 as suggested by David Grudl + session_write_close(); + + if (isset($_SESSION)) + { + $backup = $_SESSION; + session_start(); + $_SESSION = $backup; + } + else + { + session_start(); + } + + return $return; + } + + /** + * Force the session to be saved and closed. + * + * This method must invoke session_write_close() unless this interface is used for a storage object design for unit or functional testing where + * a real PHP session would interfere with testing, in which case it should actually persist the session data if required. + * + * @return void + * + * @see session_write_close() + * @since 3.5 + * @throws RuntimeException If the session is saved without being started, or if the session is already closed. + */ + public function save() + { + if (!$this->isStarted()) + { + throw new RuntimeException('The session is not started.'); + } + + session_write_close(); + + $this->closed = true; + $this->started = false; + } + + /** + * Clear all session data in memory. + * + * @return void + * + * @since 3.5 + */ + public function clear() + { + // Need to destroy any existing sessions started with session.auto_start + if ($this->getId()) + { + session_unset(); + session_destroy(); + } + + $this->closed = true; + $this->started = false; + } +} diff --git a/libraries/joomla/session/session.php b/libraries/joomla/session/session.php index 36a528df34e31..6d179e4ead4a2 100644 --- a/libraries/joomla/session/session.php +++ b/libraries/joomla/session/session.php @@ -60,15 +60,6 @@ class JSession implements IteratorAggregate */ protected $_security = array('fix_browser'); - /** - * Force cookies to be SSL only - * Default false - * - * @var boolean - * @since 11.1 - */ - protected $_force_ssl = false; - /** * JSession instances container. * @@ -101,39 +92,41 @@ class JSession implements IteratorAggregate */ private $_dispatcher = null; + /** + * Holds the event dispatcher object + * + * @var JSessionHandlerInterface + * @since 3.5 + */ + protected $_handler = null; + /** * Constructor * - * @param string $store The type of storage for the session. - * @param array $options Optional parameters + * @param string $store The type of storage for the session. + * @param array $options Optional parameters + * @param JSessionHandlerInterface $handlerInterface The session handler * * @since 11.1 */ - public function __construct($store = 'none', array $options = array()) + public function __construct($store = 'none', array $options = array(), JSessionHandlerInterface $handlerInterface = null) { - // Need to destroy any existing sessions started with session.auto_start - if (session_id()) + // Set the session handler + $this->_handler = $handlerInterface instanceof JSessionHandlerInterface ? $handlerInterface : new JSessionHandlerJoomla($options); + + // Clear any existing sessions + if ($this->_handler->getId()) { - session_unset(); - session_destroy(); + $this->_handler->clear(); } - // Disable transparent sid support - ini_set('session.use_trans_sid', '0'); - - // Only allow the session ID to come from cookies and nothing else. - ini_set('session.use_only_cookies', '1'); - // Create handler $this->_store = JSessionStorage::getInstance($store, $options); $this->storeName = $store; - // Set options $this->_setOptions($options); - $this->_setCookieParams(); - $this->_state = 'inactive'; } @@ -162,21 +155,21 @@ public function __get($name) } /** - * Returns the global Session object, only creating it - * if it doesn't already exist. + * Returns the global Session object, only creating it if it doesn't already exist. * - * @param string $handler The type of session handler. - * @param array $options An array of configuration options. + * @param string $store The type of storage for the session. + * @param array $options An array of configuration options. + * @param JSessionHandlerInterface $handlerInterface The session handler * * @return JSession The Session object. * * @since 11.1 */ - public static function getInstance($handler, $options) + public static function getInstance($store, $options, JSessionHandlerInterface $handlerInterface = null) { if (!is_object(self::$instance)) { - self::$instance = new JSession($handler, $options); + self::$instance = new JSession($store, $options, $handlerInterface); } return self::$instance; @@ -277,17 +270,7 @@ public static function getFormToken($forceNew = false) $user = JFactory::getUser(); $session = JFactory::getSession(); - // TODO: Decouple from legacy JApplication class. - if (is_callable(array('JApplication', 'getHash'))) - { - $hash = JApplication::getHash($user->get('id', 0) . $session->getToken($forceNew)); - } - else - { - $hash = md5(JFactory::getApplication()->get('secret') . $user->get('id', 0) . $session->getToken($forceNew)); - } - - return $hash; + return JApplicationHelper::getHash($user->get('id', 0) . $session->getToken($forceNew)); } /** @@ -327,6 +310,8 @@ public static function checkToken($method = 'post') // Redirect to login screen. $app->enqueueMessage(JText::_('JLIB_ENVIRONMENT_SESSION_EXPIRED'), 'warning'); $app->redirect(JRoute::_('index.php')); + + return true; } else { @@ -354,7 +339,7 @@ public function getName() return null; } - return session_name(); + return $this->_handler->getName(); } /** @@ -372,7 +357,7 @@ public function getId() return null; } - return session_id(); + return $this->_handler->getId(); } /** @@ -458,7 +443,16 @@ public function isNew() */ public function initialise(JInput $input, JEventDispatcher $dispatcher = null) { + // With the introduction of the handler class this variable is no longer required + // however we keep setting it for b/c $this->_input = $input; + + // Nasty workaround to deal in a b/c way with JInput being required in the 3.4+ Handler class. + if ($this->_handler instanceof JSessionHandlerJoomla) + { + $this->_handler->input = $input; + } + $this->_dispatcher = $dispatcher; } @@ -628,41 +622,7 @@ public function start() */ protected function _start() { - // Start session if not started - if ($this->_state === 'restart') - { - session_regenerate_id(true); - } - else - { - $session_name = session_name(); - - // Get the JInputCookie object - $cookie = $this->_input->cookie; - - if (is_null($cookie->get($session_name))) - { - $session_clean = $this->_input->get($session_name, false, 'string'); - - if ($session_clean) - { - session_id($session_clean); - $cookie->set($session_name, '', time() - 3600); - } - } - } - - /** - * Write and Close handlers are called after destructing objects since PHP 5.0.5. - * Thus destructors can use sessions but session handler can't use objects. - * So we are moving session closure before destructing objects. - * - * Replace with session_register_shutdown() when dropping compatibility with PHP 5.3 - */ - register_shutdown_function('session_write_close'); - - session_cache_limiter('none'); - session_start(); + $this->_handler->start(); return true; } @@ -688,21 +648,7 @@ public function destroy() return true; } - /* - * In order to kill the session altogether, such as to log the user out, the session id - * must also be unset. If a cookie is used to propagate the session id (default behavior), - * then the session cookie must be deleted. - */ - if (isset($_COOKIE[session_name()])) - { - $config = JFactory::getConfig(); - $cookie_domain = $config->get('cookie_domain', ''); - $cookie_path = $config->get('cookie_path', '/'); - setcookie(session_name(), '', time() - 42000, $cookie_path, $cookie_domain); - } - - session_unset(); - session_destroy(); + $this->_handler->clear(); $this->_state = 'destroyed'; @@ -733,7 +679,7 @@ public function restart() $this->_state = 'restart'; // Regenerate session id - session_regenerate_id(true); + $this->_handler->regenerate(true, null); $this->_start(); $this->_state = 'active'; @@ -762,7 +708,7 @@ public function fork() $cookie = session_get_cookie_params(); // Kill session - session_destroy(); + $this->_handler->clear(); // Re-register the session store after a session has been destroyed, to avoid PHP bug $this->_store->register(); @@ -771,8 +717,8 @@ public function fork() session_set_cookie_params($cookie['lifetime'], $cookie['path'], $cookie['domain'], $cookie['secure'], true); // Restart session with new id - session_regenerate_id(true); - session_start(); + $this->_handler->regenerate(true, null); + $this->_handler->start(); return true; } @@ -790,43 +736,23 @@ public function fork() * * @return void * - * @see session_write_close() * @since 11.1 */ public function close() { - session_write_close(); + $this->_handler->save(); } /** - * Set session cookie parameters + * Set the session handler * - * @return void + * @param JSessionHandlerInterface $handler The session handler * - * @since 11.1 + * @return void */ - protected function _setCookieParams() + public function setHandler(JSessionHandlerInterface $handler) { - $cookie = session_get_cookie_params(); - - if ($this->_force_ssl) - { - $cookie['secure'] = true; - } - - $config = JFactory::getConfig(); - - if ($config->get('cookie_domain', '') != '') - { - $cookie['domain'] = $config->get('cookie_domain'); - } - - if ($config->get('cookie_path', '') != '') - { - $cookie['path'] = $config->get('cookie_path'); - } - - session_set_cookie_params($cookie['lifetime'], $cookie['path'], $cookie['domain'], $cookie['secure'], true); + $this->_handler = $handler; } /** @@ -843,7 +769,7 @@ protected function _createToken($length = 32) static $chars = '0123456789abcdef'; $max = strlen($chars) - 1; $token = ''; - $name = session_name(); + $name = $this->_handler->getName(); for ($i = 0; $i < $length; ++$i) { @@ -908,13 +834,13 @@ protected function _setOptions(array $options) // Set name if (isset($options['name'])) { - session_name(md5($options['name'])); + $this->_handler->setName(md5($options['name'])); } // Set id if (isset($options['id'])) { - session_id($options['id']); + $this->_handler->setId($options['id']); } // Set expire time @@ -929,11 +855,6 @@ protected function _setOptions(array $options) $this->_security = explode(',', $options['security']); } - if (isset($options['force_ssl'])) - { - $this->_force_ssl = (bool) $options['force_ssl']; - } - // Sync the session maxlifetime ini_set('session.gc_maxlifetime', $this->_expire); diff --git a/libraries/joomla/user/helper.php b/libraries/joomla/user/helper.php index 9341da278dc63..c9d90d854e691 100644 --- a/libraries/joomla/user/helper.php +++ b/libraries/joomla/user/helper.php @@ -60,12 +60,12 @@ public static function addUserToGroup($userId, $groupId) $user->save(); } - if (session_id()) - { - // Set the group data for any preloaded user objects. - $temp = JFactory::getUser((int) $userId); - $temp->groups = $user->groups; + // Set the group data for any preloaded user objects. + $temp = JUser::getInstance((int) $userId); + $temp->groups = $user->groups; + if (JFactory::getSession()->getId()) + { // Set the group data for the user object in the session. $temp = JFactory::getUser(); diff --git a/tests/unit/suites/libraries/joomla/cache/storage/JCacheStorageMemcachedTest.php b/tests/unit/suites/libraries/joomla/cache/storage/JCacheStorageMemcachedTest.php index 788520e12c233..f35f5a20a3ee4 100644 --- a/tests/unit/suites/libraries/joomla/cache/storage/JCacheStorageMemcachedTest.php +++ b/tests/unit/suites/libraries/joomla/cache/storage/JCacheStorageMemcachedTest.php @@ -14,7 +14,7 @@ * @subpackage Cache * @since 11.1 */ -class JCacheStorageMemcachedTest extends PHPUnit_Framework_TestCase +class JCacheStorageMemcachedTest extends TestCase { /** * @var JCacheStorageMemcached @@ -48,8 +48,11 @@ protected function setUp() $this->extensionAvailable = $memcachedtest; + $this->saveFactoryState(); + if ($this->extensionAvailable) { + JFactory::$session = $this->getMockSession(); $this->object = JCacheStorage::getInstance('memcached'); } else @@ -58,6 +61,17 @@ protected function setUp() } } + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + * + * @return void + */ + protected function tearDown() + { + $this->restoreFactoryState(); + } + /** * Testing gc(). * diff --git a/tests/unit/suites/libraries/joomla/session/JSessionStorageTest.php b/tests/unit/suites/libraries/joomla/session/JSessionStorageTest.php new file mode 100644 index 0000000000000..75313b6b63c37 --- /dev/null +++ b/tests/unit/suites/libraries/joomla/session/JSessionStorageTest.php @@ -0,0 +1,160 @@ +markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * Test... + * + * @todo Implement testRegister(). + * + * @return void + */ + public function testRegister() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * Test... + * + * @todo Implement testOpen(). + * + * @return void + */ + public function testOpen() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * Test... + * + * @todo Implement testClose(). + * + * @return void + */ + public function testClose() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * Test... + * + * @todo Implement testRead(). + * + * @return void + */ + public function testRead() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * Test... + * + * @todo Implement testWrite(). + * + * @return void + */ + public function testWrite() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * Test... + * + * @todo Implement testDestroy(). + * + * @return void + */ + public function testDestroy() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * Test... + * + * @todo Implement testGc(). + * + * @return void + */ + public function testGc() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * Test... + * + * @todo Implement testIsSupported(). + * + * @return void + */ + public function testIsSupported() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + +} diff --git a/tests/unit/suites/libraries/joomla/session/JSessionTest.php b/tests/unit/suites/libraries/joomla/session/JSessionTest.php new file mode 100644 index 0000000000000..7bb3b905250c0 --- /dev/null +++ b/tests/unit/suites/libraries/joomla/session/JSessionTest.php @@ -0,0 +1,395 @@ +saveFactoryState(); + + $handler = new JSessionHandlerArray(md5('PHPSESSID')); + $config = array( + 'expire' => 20, + 'force_ssl' => true, + 'name' => 'PHPSESSID', + 'security' => 'security' + ); + + $this->object = JSession::getInstance('none', $config, $handler); + + $this->input = new JInput; + $this->input->cookie = $this->getMock('JInputCookie', array('set', 'get')); + $this->object->initialise($this->input); + + $this->input->cookie->expects($this->any()) + ->method('set'); + $this->input->cookie->expects($this->any()) + ->method('get') + ->will($this->returnValue(null)); + + $this->object->start(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + * + * @return void + */ + protected function tearDown() + { + $this->restoreFactoryState(); + } + + /** + * Test cases for getInstance + * string handler of type JSessionStorage: none or database + * array arguments for $options in form of associative array + * string message if test case fails + * + * @return array + */ + Public function casesGetInstance() + { + return array( + 'first_instance' => array( + 'none', + array('expire' => 99), + 'Line: ' . __LINE__ . ': ' . 'Should not be a different instance and options should not change' + ), + 'second_instance' => array( + 'database', + array(), + 'Line: ' . __LINE__ . ': ' . 'Should not be a different instance ' + ) + ); + } + + /** + * Test getInstance + * + * @param string $store @todo + * @param array $options @todo + * + * @dataProvider casesGetInstance + * @covers JSession::getInstance + * + * @return void + */ + public function testGetInstance($store, $options) + { + $oldSession = $this->object; + $handler = new JSessionHandlerArray; + $newSession = JSession::getInstance($store, $options, $handler); + + // The properties and values should be identical to each other. + $this->assertThat( + $oldSession, + $this->identicalTo($newSession) + ); + + // They should be the same object. + $this->assertSame($oldSession, $newSession); + } + + /** + * Test getState + * + * @covers JSession::getState + * + * @return void + */ + public function testGetState() + { + $this->assertEquals( + TestReflection::getValue($this->object, '_state'), + $this->object->getState(), + 'Session state should be the same' + ); + } + + /** + * Test getExpire() + * + * @covers JSession::getExpire + * + * @return void + */ + public function testGetExpire() + { + $this->assertEquals( + TestReflection::getValue($this->object, '_expire'), + $this->object->getExpire(), + 'Session expire time should be the same' + ); + } + + /** + * Test getToken + * + * @covers JSession::getToken + * + * @return void + */ + public function testGetToken() + { + $this->object->set('session.token', 'abc'); + $this->assertEquals('abc', $this->object->getToken(), 'Token should be abc'); + + $this->object->set('session.token', null); + $token = $this->object->getToken(); + $this->assertEquals(32, strlen($token), 'Line: ' . __LINE__ . ' Token should be length 32'); + + $token2 = $this->object->getToken(true); + $this->assertNotEquals($token, $token2, 'Line: ' . __LINE__ . ' New token should be different'); + } + + /** + * Test hasToken + * + * @covers JSession::hasToken + * + * @return void + */ + public function testHasToken() + { + $token = $this->object->getToken(); + $this->assertTrue($this->object->hasToken($token), 'Line: ' . __LINE__ . ' Correct token should be true'); + + $this->assertFalse($this->object->hasToken('abc', false), 'Line: ' . __LINE__ . ' Should return false with wrong token'); + $this->assertEquals('active', $this->object->getState(), 'Line: ' . __LINE__ . ' State should not be set to expired'); + + $this->assertFalse($this->object->hasToken('abc'), 'Line: ' . __LINE__ . ' Should return false with wrong token'); + $this->assertEquals('expired', $this->object->getState(), 'Line: ' . __LINE__ . ' State should be set to expired by default'); + } + + /** + * Test getFormToken + * + * @covers JSession::getFormToken + * + * @return void + */ + public function testGetFormToken() + { + // Set the factory session object for getting the token + JFactory::$session = $this->object; + + $user = JFactory::getUser(); + + $expected = md5($user->get('id', 0) . $this->object->getToken(false)); + $this->assertEquals($expected, $this->object->getFormToken(false), 'Form token should be calculated as above.'); + } + + /** + * Test getName + * + * @covers JSession::getName + * + * @return void + */ + public function testGetName() + { + // PHPUnit sets a session name of 'PHPSESSID' while our code uses an MD5 hash so we cannot test directly with session_name() + $this->assertEquals(md5('PHPSESSID'), $this->object->getName(), 'Session names should match.'); + } + + /** + * Test getId + * + * @covers JSession::getId + * + * @return void + */ + public function testGetId() + { + $this->assertEquals(session_id(), $this->object->getId(), 'Session ids should match.'); + } + + /** + * Test getStores + * + * @covers JSession::getStores + * + * @return void + */ + public function testGetStores() + { + $return = JSession::getStores(); + + $this->assertTrue( + is_array($return), + 'Line: ' . __LINE__ . ' JSession::getStores must return an array.' + ); + $this->assertContains( + 'database', + $return, + 'Line: ' . __LINE__ . ' session storage database should always be available.' + ); + $this->assertContains( + 'none', + $return, + 'Line: ' . __LINE__ . ' session storage "none" should always be available.' + ); + } + + /** + * Test isNew + * + * @return void + */ + public function testIsNew() + { + $this->object->set('session.counter', 1); + + $this->assertEquals(true, $this->object->isNew(), '$isNew should be true.'); + } + + /** + * Test... + * + * @todo Implement testGet(). + * + * @return void + */ + public function testGet() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * Test... + * + * @todo Implement testSet(). + * + * @return void + */ + public function testSet() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * Test... + * + * @todo Implement testHas(). + * + * @return void + */ + public function testHas() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * Test... + * + * @todo Implement testClear(). + * + * @return void + */ + public function testClear() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * Test... + * + * @todo Implement testDestroy(). + * + * @return void + */ + public function testDestroy() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * Test... + * + * @todo Implement testRestart(). + * + * @return void + */ + public function testRestart() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * Test... + * + * @todo Implement testFork(). + * + * @return void + */ + public function testFork() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * Test... + * + * @todo Implement testClose(). + * + * @return void + */ + public function testClose() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + +} diff --git a/tests/unit/suites/libraries/joomla/session/handler/array.php b/tests/unit/suites/libraries/joomla/session/handler/array.php new file mode 100644 index 0000000000000..6a1cf79712e70 --- /dev/null +++ b/tests/unit/suites/libraries/joomla/session/handler/array.php @@ -0,0 +1,252 @@ +name = $name; + } + + /** + * Sets the session data. + * + * @param array $array + */ + public function setSessionData(array $array) + { + $this->data = $array; + } + + /** + * Starts the session. + * + * @return bool True if started. + * + * @since 3.4 + * + * @throws RuntimeException If something goes wrong starting the session. + */ + public function start() + { + if ($this->started && !$this->closed) { + return true; + } + + if (empty($this->id)) { + $this->setId($this->generateId()); + } + + return true; + } + + /** + * Regenerates id that represents this storage. + * + * This method must invoke session_regenerate_id($destroy) unless + * this interface is used for a storage object designed for unit + * or functional testing where a real PHP session would interfere + * with testing. + * + * Note regenerate+destroy should not clear the session data in memory + * only delete the session data from persistent storage. + * + * @param bool $destroy Destroy session when regenerating? + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool True if session regenerated, false if error + * + * @since 3.4 + * + * @throws RuntimeException If an error occurs while regenerating this storage + */ + public function regenerate($destroy = false, $lifetime = null) + { + if (!$this->started) + { + $this->start(); + } + + $this->id = $this->generateId(); + + return true; + } + + /** + * Returns the session ID + * + * @return string The session ID or empty. + * + * @since 3.4 + */ + public function getId() + { + return $this->id; + } + + /** + * Sets the session ID + * + * @param string $id Set the session id + * + * @return void + * + * @since 3.4 + */ + public function setId($id) + { + if ($this->started) { + throw new LogicException('Cannot set session ID after the session has started.'); + } + + // Set the PHP Session ID here too, it just works + session_id($id); + + $this->id = $id; + } + + /** + * Returns the session name + * + * @return mixed The session name. + * + * @since 3.4 + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the session name + * + * @param string $name Set the name of the session + * + * @return void + * + * @since 3.4 + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Force the session to be saved and closed. + * + * This method must invoke session_write_close() unless this interface is + * used for a storage object design for unit or functional testing where + * a real PHP session would interfere with testing, in which case it + * it should actually persist the session data if required. + * + * @return void + * + * @since 3.4 + * + * @throws RuntimeException If the session is saved without being started, or if the session + * is already closed. + */ + public function save() + { + if (!$this->started || $this->closed) { + throw new \RuntimeException("Trying to save a session that was not started yet or was already closed"); + } + // nothing to do since we don't persist the session data + $this->closed = false; + $this->started = false; + } + + /** + * Clear all session data in memory. + * + * @return void + * + * @since 3.4 + */ + public function clear() + { + // clear out the session + $this->data = array(); + } + + /** + * Checks if the session is started. + * + * @return bool True if started, false otherwise. + * + * @since 3.4 + */ + public function isStarted() + { + return $this->started; + } + + /** + * Generates a session ID. + * + * This doesn't need to be particularly cryptographically secure since this is just + * a mock. + * + * @return string + */ + protected function generateId() + { + return hash('sha256', uniqid(mt_rand())); + } +} diff --git a/tests/unit/suites/libraries/joomla/session/storage/JSessionStorageApcTest.php b/tests/unit/suites/libraries/joomla/session/storage/JSessionStorageApcTest.php new file mode 100644 index 0000000000000..e0f2f4355e787 --- /dev/null +++ b/tests/unit/suites/libraries/joomla/session/storage/JSessionStorageApcTest.php @@ -0,0 +1,105 @@ +markTestSkipped('APC storage is not enabled on this system.'); + } + + $this->object = JSessionStorage::getInstance('APC'); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + * + * @return void + */ + protected function tearDown() + { + + } + + /** + * Test... + * + * @todo Implement testRead(). + * + * @return void + */ + public function testRead() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * Test... + * + * @todo Implement testWrite(). + * + * @return void + */ + public function testWrite() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * Test... + * + * @todo Implement testDestroy(). + * + * @return void + */ + public function testDestroy() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * Test... + * + * @todo Implement testIsSupported(). + * + * @return void + */ + public function testIsSupported() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + +} diff --git a/tests/unit/suites/libraries/joomla/session/storage/JSessionStorageDatabaseTest.php b/tests/unit/suites/libraries/joomla/session/storage/JSessionStorageDatabaseTest.php new file mode 100644 index 0000000000000..4398bc1a0bbf0 --- /dev/null +++ b/tests/unit/suites/libraries/joomla/session/storage/JSessionStorageDatabaseTest.php @@ -0,0 +1,98 @@ +object = JSessionStorage::getInstance('Database'); + } + + /** + * Test... + * + * @todo Implement testRead(). + * + * @return void + */ + public function testRead() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * Test... + * + * @todo Implement testWrite(). + * + * @return void + */ + public function testWrite() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * Test... + * + * @todo Implement testDestroy(). + * + * @return void + */ + public function testDestroy() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * Test... + * + * @todo Implement testGc(). + * + * @return void + */ + public function testGc() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + +} diff --git a/tests/unit/suites/libraries/joomla/session/storage/JSessionStorageMemcacheTest.php b/tests/unit/suites/libraries/joomla/session/storage/JSessionStorageMemcacheTest.php new file mode 100644 index 0000000000000..d660ae5e103e0 --- /dev/null +++ b/tests/unit/suites/libraries/joomla/session/storage/JSessionStorageMemcacheTest.php @@ -0,0 +1,144 @@ +markTestSkipped('Memcache storage is not enabled on this system.'); + } + + $this->object = JSessionStorage::getInstance('Memcache'); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + * + * @return void + */ + protected function tearDown() + { + + } + + /** + * Test... + * + * @todo Implement testOpen(). + * + * @return void + */ + public function testOpen() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * Test... + * + * @todo Implement testClose(). + * + * @return void + */ + public function testClose() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * Test... + * + * @todo Implement testRead(). + * + * @return void + */ + public function testRead() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * Test... + * + * @todo Implement testWrite(). + * + * @return void + */ + public function testWrite() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * Test... + * + * @todo Implement testDestroy(). + * + * @return void + */ + public function testDestroy() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * Test... + * + * @todo Implement testGc(). + * + * @return void + */ + public function testGc() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * Test... + * + * @todo Implement testIsSupported(). + * + * @return void + */ + public function testIsSupported() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + +} diff --git a/tests/unit/suites/libraries/joomla/session/storage/JSessionStorageNoneTest.php b/tests/unit/suites/libraries/joomla/session/storage/JSessionStorageNoneTest.php new file mode 100644 index 0000000000000..55d4a77559795 --- /dev/null +++ b/tests/unit/suites/libraries/joomla/session/storage/JSessionStorageNoneTest.php @@ -0,0 +1,50 @@ +object = JSessionStorage::getInstance('None'); + } + + /** + * Test JSessionStorageNone::Register(). + * + * @return void + */ + public function testRegister() + { + $this->assertThat( + $this->object->register(), + $this->equalTo(null) + ); + } +} diff --git a/tests/unit/suites/libraries/joomla/session/storage/JSessionStorageWincacheTest.php b/tests/unit/suites/libraries/joomla/session/storage/JSessionStorageWincacheTest.php new file mode 100644 index 0000000000000..8806c4ff4c567 --- /dev/null +++ b/tests/unit/suites/libraries/joomla/session/storage/JSessionStorageWincacheTest.php @@ -0,0 +1,102 @@ +markTestSkipped('WinCache storage is not enabled on this system.'); + } + + $this->object = JSessionStorage::getInstance('Wincache'); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + * + * @return void + */ + protected function tearDown() + { + + } + + /** + * Test... + * + * @todo Implement testRead(). + * + * @return void + */ + public function testRead() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * Test... + * + * @todo Implement testWrite(). + * + * @return void + */ + public function testWrite() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * Test... + * + * @todo Implement testDestroy(). + * + * @return void + */ + public function testDestroy() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * Test... + * + * @todo Implement testIsSupported(). + * + * @return void + */ + public function testIsSupported() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } +} diff --git a/tests/unit/suites/libraries/joomla/session/storage/JSessionStorageXcacheTest.php b/tests/unit/suites/libraries/joomla/session/storage/JSessionStorageXcacheTest.php new file mode 100644 index 0000000000000..a40b67218dc98 --- /dev/null +++ b/tests/unit/suites/libraries/joomla/session/storage/JSessionStorageXcacheTest.php @@ -0,0 +1,104 @@ +markTestSkipped('XCache storage is not enabled on this system.'); + } + + $this->object = JSessionStorage::getInstance('Xcache'); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + * + * @return void + */ + protected function tearDown() + { + + } + + /** + * Test... + * + * @todo Implement testRead(). + * + * @return void + */ + public function testRead() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * Test... + * + * @todo Implement testWrite(). + * + * @return void + */ + public function testWrite() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * Test... + * + * @todo Implement testDestroy(). + * + * @return void + */ + public function testDestroy() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * Test... + * + * @todo Implement testIsSupported(). + * + * @return void + */ + public function testIsSupported() + { + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } +} diff --git a/tests/unit/suites/libraries/joomla/user/JUserHelperTest.php b/tests/unit/suites/libraries/joomla/user/JUserHelperTest.php index 5ccc516c01560..c3d145b3f682f 100644 --- a/tests/unit/suites/libraries/joomla/user/JUserHelperTest.php +++ b/tests/unit/suites/libraries/joomla/user/JUserHelperTest.php @@ -36,6 +36,9 @@ protected function setUp() parent::setUp(); $this->saveFactoryState(); + + // Set the session object for JUserHelper::addUserToGroup() + JFactory::$session = $this->getMockSession(); } /**