data = new \Joomla\Registry\Registry; // Need to destroy any existing sessions started with session.auto_start if (session_id()) { session_unset(); session_destroy(); } // 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'; } /** * Magic method to get read-only access to properties. * * @param string $name Name of property to retrieve * * @return mixed The value of the property * * @since 12.2 */ public function __get($name) { if ($name === 'storeName') { return $this->$name; } if ($name === 'state' || $name === 'expire') { $property = '_' . $name; return $this->$property; } } /** * 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. * * @return JSession The Session object. * * @since 11.1 */ public static function getInstance($handler, $options) { if (!is_object(self::$instance)) { self::$instance = new JSession($handler, $options); } return self::$instance; } /** * Get current state of session * * @return string The session state * * @since 11.1 */ public function getState() { return $this->_state; } /** * Get expiration time in minutes * * @return integer The session expiration time in minutes * * @since 11.1 */ public function getExpire() { return $this->_expire; } /** * Get a session token, if a token isn't set yet one will be generated. * * Tokens are used to secure forms from spamming attacks. Once a token * has been generated the system will check the post request to see if * it is present, if not it will invalidate the session. * * @param boolean $forceNew If true, force a new token to be created * * @return string The session token * * @since 11.1 */ public function getToken($forceNew = false) { $token = $this->get('session.token'); // Create a token if ($token === null || $forceNew) { $token = $this->_createToken(12); $this->set('session.token', $token); } return $token; } /** * Method to determine if a token exists in the session. If not the * session will be set to expired * * @param string $tCheck Hashed token to be verified * @param boolean $forceExpire If true, expires the session * * @return boolean * * @since 11.1 */ public function hasToken($tCheck, $forceExpire = true) { // Check if a token exists in the session $tStored = $this->get('session.token'); // Check token if (($tStored !== $tCheck)) { if ($forceExpire) { $this->_state = 'expired'; } return false; } return true; } /** * Method to determine a hash for anti-spoofing variable names * * @param boolean $forceNew If true, force a new token to be created * * @return string Hashed var name * * @since 11.1 */ 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; } /** * Retrieve an external iterator. * * @return ArrayIterator Return an ArrayIterator of $_SESSION. * * @since 12.2 */ public function getIterator() { return new ArrayIterator($this->getData()); } /** * Checks for a form token in the request. * * Use in conjunction with JHtml::_('form.token') or JSession::getFormToken. * * @param string $method The request method in which to look for the token key. * * @return boolean True if found and valid, false otherwise. * * @since 12.1 */ public static function checkToken($method = 'post') { $token = self::getFormToken(); $app = JFactory::getApplication(); if (!$app->input->$method->get($token, '', 'alnum')) { $session = JFactory::getSession(); if ($session->isNew()) { // Redirect to login screen. $app->enqueueMessage(JText::_('JLIB_ENVIRONMENT_SESSION_EXPIRED'), 'warning'); $app->redirect(JRoute::_('index.php')); } else { return false; } } else { return true; } } /** * Get session name * * @return string The session name * * @since 11.1 */ public function getName() { if ($this->_state === 'destroyed') { // @TODO : raise error return null; } return session_name(); } /** * Get session id * * @return string The session name * * @since 11.1 */ public function getId() { if ($this->_state === 'destroyed') { // @TODO : raise error return null; } return session_id(); } /** * Returns a clone of the internal data pointer * * @return \Joomla\Registry\Registry */ public function getData() { return clone $this->data; } /** * Get the session handlers * * @return array An array of available session handlers * * @since 11.1 */ public static function getStores() { $connectors = array(); // Get an iterator and loop trough the driver classes. $iterator = new DirectoryIterator(__DIR__ . '/storage'); /* @type $file DirectoryIterator */ foreach ($iterator as $file) { $fileName = $file->getFilename(); // Only load for php files. if (!$file->isFile() || $file->getExtension() != 'php') { continue; } // Derive the class name from the type. $class = str_ireplace('.php', '', 'JSessionStorage' . ucfirst(trim($fileName))); // If the class doesn't exist we have nothing left to do but look at the next type. We did our best. if (!class_exists($class)) { continue; } // Sweet! Our class exists, so now we just need to know if it passes its test method. if ($class::isSupported()) { // Connector names should not have file extensions. $connectors[] = str_ireplace('.php', '', $fileName); } } return $connectors; } /** * Shorthand to check if the session is active * * @return boolean * * @since 12.2 */ public function isActive() { return (bool) ($this->_state == 'active'); } /** * Check whether this session is currently created * * @return boolean True on success. * * @since 11.1 */ public function isNew() { $counter = $this->get('session.counter'); return (bool) ($counter === 1); } /** * Check whether this session is currently created * * @param JInput $input JInput object for the session to use. * @param JEventDispatcher $dispatcher Dispatcher object for the session to use. * * @return void. * * @since 12.2 */ public function initialise(JInput $input, JEventDispatcher $dispatcher = null) { $this->_input = $input; $this->_dispatcher = $dispatcher; } /** * Get data from the session store * * @param string $name Name of a variable * @param mixed $default Default value of a variable if not set * @param string $namespace Namespace to use, default to 'default' * * @return mixed Value of a variable * * @since 11.1 */ public function get($name, $default = null, $namespace = 'default') { // Add prefix to namespace to avoid collisions $namespace = '__' . $namespace; if ($this->_state === 'destroyed') { // @TODO :: generated error here $error = null; return $error; } return $this->data->get($namespace . '.' . $name, $default); } /** * Set data into the session store. * * @param string $name Name of a variable. * @param mixed $value Value of a variable. * @param string $namespace Namespace to use, default to 'default'. * * @return mixed Old value of a variable. * * @since 11.1 */ public function set($name, $value = null, $namespace = 'default') { // Add prefix to namespace to avoid collisions $namespace = '__' . $namespace; if ($this->_state !== 'active') { // @TODO :: generated error here return null; } return $this->data->set($namespace . '.' . $name, $value); } /** * Check whether data exists in the session store * * @param string $name Name of variable * @param string $namespace Namespace to use, default to 'default' * * @return boolean True if the variable exists * * @since 11.1 */ public function has($name, $namespace = 'default') { // Add prefix to namespace to avoid collisions. $namespace = '__' . $namespace; if ($this->_state !== 'active') { // @TODO :: generated error here return null; } return !is_null($this->data->get($namespace . '.' . $name, null)); } /** * Unset data from the session store * * @param string $name Name of variable * @param string $namespace Namespace to use, default to 'default' * * @return mixed The value from session or NULL if not set * * @since 11.1 */ public function clear($name, $namespace = 'default') { // Add prefix to namespace to avoid collisions $namespace = '__' . $namespace; if ($this->_state !== 'active') { // @TODO :: generated error here return null; } return $this->data->set($namespace . '.' . $name, null); } /** * Start a session. * * @return void * * @since 12.2 */ public function start() { if ($this->_state === 'active') { return; } $this->_start(); $this->_state = 'active'; // Initialise the session $this->_setCounter(); $this->_setTimers(); // Perform security checks if (!$this->_validate()) { // Destroy the session if it's not valid $this->destroy(); } if ($this->_dispatcher instanceof JEventDispatcher) { $this->_dispatcher->trigger('onAfterSessionStart'); } } /** * Start a session. * * Creates a session (or resumes the current one based on the state of the session) * * @return boolean true on success * * @since 11.1 */ 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. */ register_shutdown_function(array($this, 'close')); session_cache_limiter('none'); session_start(); // Ok let's unserialize the whole thing // Try loading data from the session if (isset($_SESSION['joomla']) && !empty($_SESSION['joomla'])) { $data = $_SESSION['joomla']; $data = base64_decode($data); $this->data = unserialize($data); } // Temporary, PARTIAL, data migration of existing session data to avoid logout on update from J < 3.4.7 if (isset($_SESSION['__default']) && !empty($_SESSION['__default'])) { $migratableKeys = array("user", "session.token", "session.counter", "session.timer.start", "session.timer.last", "session.timer.now"); foreach ($migratableKeys as $migratableKey) { if (!empty($_SESSION['__default'][$migratableKey])) { // Don't overwrite existing session data if (!is_null($this->data->get('__default.' . $migratableKey, null))) { continue; } $this->data->set('__default.' . $migratableKey, $_SESSION['__default'][$migratableKey]); unset($_SESSION['__default'][$migratableKey]); } } /** * Finally, empty the __default key since we no longer need it. Don't unset it completely, we need this * for the administrator/components/com_admin/script.php to detect upgraded sessions and perform a full * session cleanup. */ $_SESSION['__default'] = array(); } return true; } /** * Frees all session variables and destroys all data registered to a session * * This method resets the data pointer and destroys all of the data associated * with the current session in its storage (file or DB). It forces a new session to be * started after this method is called. It does not unset the session cookie. * * @return boolean True on success * * @see session_destroy() * @see session_unset() * @since 11.1 */ public function destroy() { // Session was already destroyed if ($this->_state === 'destroyed') { 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); } $this->data = new \Joomla\Registry\Registry; session_unset(); session_destroy(); $this->_state = 'destroyed'; return true; } /** * Restart an expired or locked session. * * @return boolean True on success * * @see JSession::destroy() * @since 11.1 */ public function restart() { $this->destroy(); if ($this->_state !== 'destroyed') { // @TODO :: generated error here return false; } // Re-register the session handler after a session has been destroyed, to avoid PHP bug $this->_store->register(); $this->_state = 'restart'; // Regenerate session id session_regenerate_id(true); $this->_start(); $this->_state = 'active'; if (!$this->_validate()) { // Destroy the session if it's not valid $this->destroy(); } $this->_setCounter(); return true; } /** * Create a new session and copy variables from the old one * * @return boolean $result true on success * * @since 11.1 */ public function fork() { if ($this->_state !== 'active') { // @TODO :: generated error here return false; } // Keep session config $cookie = session_get_cookie_params(); // Kill session session_destroy(); // Re-register the session store after a session has been destroyed, to avoid PHP bug $this->_store->register(); // Restore config session_set_cookie_params($cookie['lifetime'], $cookie['path'], $cookie['domain'], $cookie['secure'], true); // Restart session with new id session_regenerate_id(true); session_start(); return true; } /** * Writes session data and ends session * * Session data is usually stored after your script terminated without the need * to call JSession::close(), but as session data is locked to prevent concurrent * writes only one script may operate on a session at any time. When using * framesets together with sessions you will experience the frames loading one * by one due to this locking. You can reduce the time needed to load all the * frames by ending the session as soon as all changes to session variables are * done. * * @return boolean * * @see session_write_close() * @since 11.1 */ public function close() { $session = JFactory::getSession(); $data = $session->getData(); // Before storing it, let's serialize and encode the JRegistry object $_SESSION['joomla'] = base64_encode(serialize($data)); session_write_close(); return true; } /** * Set session cookie parameters * * @return void * * @since 11.1 */ 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); } /** * Create a token-string * * @param integer $length Length of string * * @return string Generated token * * @since 11.1 */ protected function _createToken($length = 32) { static $chars = '0123456789abcdef'; $max = strlen($chars) - 1; $token = ''; $name = session_name(); for ($i = 0; $i < $length; ++$i) { $token .= $chars[(rand(0, $max))]; } return md5($token . $name); } /** * Set counter of session usage * * @return boolean True on success * * @since 11.1 */ protected function _setCounter() { $counter = $this->get('session.counter', 0); ++$counter; $this->set('session.counter', $counter); return true; } /** * Set the session timers * * @return boolean True on success * * @since 11.1 */ protected function _setTimers() { if (!$this->has('session.timer.start')) { $start = time(); $this->set('session.timer.start', $start); $this->set('session.timer.last', $start); $this->set('session.timer.now', $start); } $this->set('session.timer.last', $this->get('session.timer.now')); $this->set('session.timer.now', time()); return true; } /** * Set additional session options * * @param array $options List of parameter * * @return boolean True on success * * @since 11.1 */ protected function _setOptions(array $options) { // Set name if (isset($options['name'])) { session_name(md5($options['name'])); } // Set id if (isset($options['id'])) { session_id($options['id']); } // Set expire time if (isset($options['expire'])) { $this->_expire = $options['expire']; } // Get security options if (isset($options['security'])) { $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); return true; } /** * Do some checks for security reason * * - timeout check (expire) * - ip-fixiation * - browser-fixiation * * If one check failed, session data has to be cleaned. * * @param boolean $restart Reactivate session * * @return boolean True on success * * @see http://shiflett.org/articles/the-truth-about-sessions * @since 11.1 */ protected function _validate($restart = false) { // Allow to restart a session if ($restart) { $this->_state = 'active'; $this->set('session.client.address', null); $this->set('session.client.forwarded', null); $this->set('session.client.browser', null); $this->set('session.token', null); } // Check if session has expired if ($this->_expire) { $curTime = $this->get('session.timer.now', 0); $maxTime = $this->get('session.timer.last', 0) + $this->_expire; // Empty session variables if ($maxTime < $curTime) { $this->_state = 'expired'; return false; } } // Check for client address if (in_array('fix_adress', $this->_security) && isset($_SERVER['REMOTE_ADDR']) && filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP) !== false) { $ip = $this->get('session.client.address'); if ($ip === null) { $this->set('session.client.address', $_SERVER['REMOTE_ADDR']); } elseif ($_SERVER['REMOTE_ADDR'] !== $ip) { $this->_state = 'error'; return false; } } // Record proxy forwarded for in the session in case we need it later if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && filter_var($_SERVER['HTTP_X_FORWARDED_FOR'], FILTER_VALIDATE_IP) !== false) { $this->set('session.client.forwarded', $_SERVER['HTTP_X_FORWARDED_FOR']); } return true; } }