first commit
This commit is contained in:
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Session\EventListener;
|
||||
|
||||
use Joomla\CMS\Session\MetadataManager;
|
||||
use Joomla\Registry\Registry;
|
||||
use Joomla\Session\SessionEvent;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Event listener for session events regarding the session metadata for users.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
final class MetadataManagerListener
|
||||
{
|
||||
/**
|
||||
* Session metadata manager.
|
||||
*
|
||||
* @var MetadataManager
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $metadataManager;
|
||||
|
||||
/**
|
||||
* Application configuration.
|
||||
*
|
||||
* @var Registry
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param MetadataManager $metadataManager Session metadata manager.
|
||||
* @param Registry $config Application configuration.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct(MetadataManager $metadataManager, Registry $config)
|
||||
{
|
||||
$this->metadataManager = $metadataManager;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for the `session.start` event.
|
||||
*
|
||||
* @param SessionEvent $event The session event.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onAfterSessionStart(SessionEvent $event)
|
||||
{
|
||||
// Whether to track Session Metadata
|
||||
if (!$this->config->get('session_metadata', true) || !$event->getSession()->has('user')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $event->getSession()->get('user');
|
||||
|
||||
// Whether to track Session Metadata for Guest user
|
||||
if (!$this->config->get('session_metadata_for_guest', true) && !$user->id) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->metadataManager->createOrUpdateRecord($event->getSession(), $user);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Session\Exception;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Exception class defining an unsupported session storage object
|
||||
*
|
||||
* @since 3.6.3
|
||||
*/
|
||||
class UnsupportedStorageException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
321
libraries/src/Session/MetadataManager.php
Normal file
321
libraries/src/Session/MetadataManager.php
Normal file
@ -0,0 +1,321 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Session;
|
||||
|
||||
use Joomla\Application\AbstractApplication;
|
||||
use Joomla\CMS\Application\CMSApplication;
|
||||
use Joomla\CMS\User\User;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\Database\Exception\ExecutionFailureException;
|
||||
use Joomla\Database\ParameterType;
|
||||
use Joomla\Session\SessionInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Manager for optional session metadata.
|
||||
*
|
||||
* @since 3.8.6
|
||||
* @internal
|
||||
*/
|
||||
final class MetadataManager
|
||||
{
|
||||
/**
|
||||
* Internal variable indicating a session record exists.
|
||||
*
|
||||
* @var integer
|
||||
* @since 4.0.0
|
||||
* @note Once PHP 7.1 is the minimum supported version this should become a private constant
|
||||
*/
|
||||
private static $sessionRecordExists = 1;
|
||||
|
||||
/**
|
||||
* Internal variable indicating a session record does not exist.
|
||||
*
|
||||
* @var integer
|
||||
* @since 4.0.0
|
||||
* @note Once PHP 7.1 is the minimum supported version this should become a private constant
|
||||
*/
|
||||
private static $sessionRecordDoesNotExist = 0;
|
||||
|
||||
/**
|
||||
* Internal variable indicating an unknown session record statue.
|
||||
*
|
||||
* @var integer
|
||||
* @since 4.0.0
|
||||
* @note Once PHP 7.1 is the minimum supported version this should become a private constant
|
||||
*/
|
||||
private static $sessionRecordUnknown = -1;
|
||||
|
||||
/**
|
||||
* Application object.
|
||||
*
|
||||
* @var AbstractApplication
|
||||
* @since 3.8.6
|
||||
*/
|
||||
private $app;
|
||||
|
||||
/**
|
||||
* Database driver.
|
||||
*
|
||||
* @var DatabaseInterface
|
||||
* @since 3.8.6
|
||||
*/
|
||||
private $db;
|
||||
|
||||
/**
|
||||
* MetadataManager constructor.
|
||||
*
|
||||
* @param AbstractApplication $app Application object.
|
||||
* @param DatabaseInterface $db Database driver.
|
||||
*
|
||||
* @since 3.8.6
|
||||
*/
|
||||
public function __construct(AbstractApplication $app, DatabaseInterface $db)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the metadata record if it does not exist.
|
||||
*
|
||||
* @param SessionInterface $session The session to create the metadata record for.
|
||||
* @param User $user The user to associate with the record.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.8.6
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function createRecordIfNonExisting(SessionInterface $session, User $user)
|
||||
{
|
||||
$exists = $this->checkSessionRecordExists($session->getId());
|
||||
|
||||
// Only touch the database if the record does not already exist
|
||||
if ($exists !== self::$sessionRecordExists) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->createSessionRecord($session, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the metadata record if it does not exist.
|
||||
*
|
||||
* @param SessionInterface $session The session to create or update the metadata record for.
|
||||
* @param User $user The user to associate with the record.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function createOrUpdateRecord(SessionInterface $session, User $user)
|
||||
{
|
||||
$exists = $this->checkSessionRecordExists($session->getId());
|
||||
|
||||
// Do not try to touch the database if we can't determine the record state
|
||||
if ($exists === self::$sessionRecordUnknown) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($exists === self::$sessionRecordDoesNotExist) {
|
||||
$this->createSessionRecord($session, $user);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->updateSessionRecord($session, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete records with a timestamp prior to the given time.
|
||||
*
|
||||
* @param integer $time The time records should be deleted if expired before.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.8.6
|
||||
*/
|
||||
public function deletePriorTo($time)
|
||||
{
|
||||
$query = $this->db->getQuery(true)
|
||||
->delete($this->db->quoteName('#__session'))
|
||||
->where($this->db->quoteName('time') . ' < :time')
|
||||
->bind(':time', $time, ParameterType::INTEGER);
|
||||
|
||||
$this->db->setQuery($query);
|
||||
|
||||
try {
|
||||
$this->db->execute();
|
||||
} catch (ExecutionFailureException $exception) {
|
||||
// Since garbage collection does not result in a fatal error when run in the session API, we don't allow it here either.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the session record exists
|
||||
*
|
||||
* @param string $sessionId The session ID to check
|
||||
*
|
||||
* @return integer Status value for record presence
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function checkSessionRecordExists(string $sessionId): int
|
||||
{
|
||||
$query = $this->db->getQuery(true)
|
||||
->select($this->db->quoteName('session_id'))
|
||||
->from($this->db->quoteName('#__session'))
|
||||
->where($this->db->quoteName('session_id') . ' = :session_id')
|
||||
->bind(':session_id', $sessionId)
|
||||
->setLimit(1);
|
||||
|
||||
$this->db->setQuery($query);
|
||||
|
||||
try {
|
||||
$exists = $this->db->loadResult();
|
||||
} catch (ExecutionFailureException $e) {
|
||||
return self::$sessionRecordUnknown;
|
||||
}
|
||||
|
||||
if ($exists) {
|
||||
return self::$sessionRecordExists;
|
||||
}
|
||||
|
||||
return self::$sessionRecordDoesNotExist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the session record
|
||||
*
|
||||
* @param SessionInterface $session The session to create the metadata record for.
|
||||
* @param User $user The user to associate with the record.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function createSessionRecord(SessionInterface $session, User $user)
|
||||
{
|
||||
$query = $this->db->getQuery(true);
|
||||
|
||||
$time = $session->isNew() ? time() : $session->get('session.timer.start');
|
||||
|
||||
$columns = [
|
||||
$this->db->quoteName('session_id'),
|
||||
$this->db->quoteName('guest'),
|
||||
$this->db->quoteName('time'),
|
||||
$this->db->quoteName('userid'),
|
||||
$this->db->quoteName('username'),
|
||||
];
|
||||
|
||||
// Add query placeholders
|
||||
$values = [
|
||||
':session_id',
|
||||
':guest',
|
||||
':time',
|
||||
':user_id',
|
||||
':username',
|
||||
];
|
||||
|
||||
// Bind query values
|
||||
$sessionId = $session->getId();
|
||||
$userIsGuest = $user->guest;
|
||||
$userId = $user->id;
|
||||
$username = $user->username === null ? '' : $user->username;
|
||||
|
||||
$query->bind(':session_id', $sessionId)
|
||||
->bind(':guest', $userIsGuest, ParameterType::INTEGER)
|
||||
->bind(':time', $time)
|
||||
->bind(':user_id', $userId, ParameterType::INTEGER)
|
||||
->bind(':username', $username);
|
||||
|
||||
if ($this->app instanceof CMSApplication && !$this->app->get('shared_session', false)) {
|
||||
$clientId = $this->app->getClientId();
|
||||
|
||||
$columns[] = $this->db->quoteName('client_id');
|
||||
$values[] = ':client_id';
|
||||
|
||||
$query->bind(':client_id', $clientId, ParameterType::INTEGER);
|
||||
}
|
||||
|
||||
$query->insert($this->db->quoteName('#__session'))
|
||||
->columns($columns)
|
||||
->values(implode(', ', $values));
|
||||
|
||||
$this->db->setQuery($query);
|
||||
|
||||
try {
|
||||
$this->db->execute();
|
||||
} catch (ExecutionFailureException $e) {
|
||||
// This failure isn't critical, we can go on without the metadata
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the session record
|
||||
*
|
||||
* @param SessionInterface $session The session to update the metadata record for.
|
||||
* @param User $user The user to associate with the record.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function updateSessionRecord(SessionInterface $session, User $user)
|
||||
{
|
||||
$query = $this->db->getQuery(true);
|
||||
|
||||
$time = time();
|
||||
|
||||
$setValues = [
|
||||
$this->db->quoteName('guest') . ' = :guest',
|
||||
$this->db->quoteName('time') . ' = :time',
|
||||
$this->db->quoteName('userid') . ' = :user_id',
|
||||
$this->db->quoteName('username') . ' = :username',
|
||||
];
|
||||
|
||||
// Bind query values
|
||||
$sessionId = $session->getId();
|
||||
$userIsGuest = $user->guest;
|
||||
$userId = $user->id;
|
||||
$username = $user->username === null ? '' : $user->username;
|
||||
|
||||
$query->bind(':session_id', $sessionId)
|
||||
->bind(':guest', $userIsGuest, ParameterType::INTEGER)
|
||||
->bind(':time', $time)
|
||||
->bind(':user_id', $userId, ParameterType::INTEGER)
|
||||
->bind(':username', $username);
|
||||
|
||||
if ($this->app instanceof CMSApplication && !$this->app->get('shared_session', false)) {
|
||||
$clientId = $this->app->getClientId();
|
||||
|
||||
$setValues[] = $this->db->quoteName('client_id') . ' = :client_id';
|
||||
|
||||
$query->bind(':client_id', $clientId, ParameterType::INTEGER);
|
||||
}
|
||||
|
||||
$query->update($this->db->quoteName('#__session'))
|
||||
->set($setValues)
|
||||
->where($this->db->quoteName('session_id') . ' = :session_id');
|
||||
|
||||
$this->db->setQuery($query);
|
||||
|
||||
try {
|
||||
$this->db->execute();
|
||||
} catch (ExecutionFailureException $e) {
|
||||
// This failure isn't critical, we can go on without the metadata
|
||||
}
|
||||
}
|
||||
}
|
||||
340
libraries/src/Session/Session.php
Normal file
340
libraries/src/Session/Session.php
Normal file
@ -0,0 +1,340 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Session;
|
||||
|
||||
use Joomla\CMS\Application\ApplicationHelper;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Session\Session as BaseSession;
|
||||
use Joomla\Session\StorageInterface;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Class for managing HTTP sessions
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
class Session extends BaseSession
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param StorageInterface $store A StorageInterface implementation.
|
||||
* @param DispatcherInterface $dispatcher DispatcherInterface for the session to use.
|
||||
* @param array $options Optional parameters. Supported keys include:
|
||||
* - name: The session name
|
||||
* - id: The session ID
|
||||
* - expire: The session lifetime in seconds
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public function __construct(StorageInterface $store = null, DispatcherInterface $dispatcher = null, array $options = [])
|
||||
{
|
||||
// Extra hash the name of the session for b/c with Joomla 3.x or the session is never found.
|
||||
if (isset($options['name'])) {
|
||||
$options['name'] = md5($options['name']);
|
||||
}
|
||||
|
||||
parent::__construct($store, $dispatcher, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for a form token in the request.
|
||||
*
|
||||
* Use in conjunction with HTMLHelper::_('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 2.5.4
|
||||
*/
|
||||
public static function checkToken($method = 'post')
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$token = static::getFormToken();
|
||||
|
||||
// Check from header first
|
||||
if ($token === $app->getInput()->server->get('HTTP_X_CSRF_TOKEN', '', 'alnum')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Then fallback to HTTP query
|
||||
if (!$app->getInput()->$method->get($token, '', 'alnum')) {
|
||||
if ($app->getSession()->isNew()) {
|
||||
// Redirect to login screen.
|
||||
$app->enqueueMessage(Text::_('JLIB_ENVIRONMENT_SESSION_EXPIRED'), 'warning');
|
||||
$app->redirect(Route::_('index.php'));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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 1.6
|
||||
*/
|
||||
public static function getFormToken($forceNew = false)
|
||||
{
|
||||
$user = Factory::getUser();
|
||||
|
||||
return ApplicationHelper::getHash($user->get('id', 0) . Factory::getApplication()->getSession()->getToken($forceNew));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the available session handlers
|
||||
*
|
||||
* @return array An array of available session handlers
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public static function getHandlers(): array
|
||||
{
|
||||
$connectors = [];
|
||||
|
||||
// Get an iterator and loop through the handler classes.
|
||||
$iterator = new \DirectoryIterator(JPATH_LIBRARIES . '/vendor/joomla/session/src/Handler');
|
||||
|
||||
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', '', '\\Joomla\\Session\\Handler\\' . $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 the handler suffix or the file extension.
|
||||
$connectors[] = str_ireplace('Handler.php', '', $fileName);
|
||||
}
|
||||
}
|
||||
|
||||
return $connectors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the global session object.
|
||||
*
|
||||
* @return static The Session object.
|
||||
*
|
||||
* @since 1.5
|
||||
*
|
||||
* @deprecated 4.3 will be removed in 6.0
|
||||
* Load the session service from the dependency injection container or via $app->getSession()
|
||||
* Example: Factory::getApplication()->getSession();
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
@trigger_error(
|
||||
__METHOD__ . '() is deprecated. Load the session from the dependency injection container or via Factory::getApplication()->getSession().',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
return Factory::getApplication()->getSession();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data from the session store
|
||||
*
|
||||
* @param string $name Name of a variable
|
||||
* @param mixed $default Default value of a variable if not set
|
||||
*
|
||||
* @return mixed Value of a variable
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
public function get($name, $default = null)
|
||||
{
|
||||
// Handle B/C by checking if a namespace was passed to the method, will be removed at 5.0
|
||||
if (\func_num_args() > 2) {
|
||||
$args = \func_get_args();
|
||||
|
||||
if (!empty($args[2])) {
|
||||
@trigger_error(
|
||||
'Passing a namespace as a parameter to ' . __METHOD__ . '() is deprecated. '
|
||||
. 'The namespace should be prepended to the name instead.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
$name = $args[2] . '.' . $name;
|
||||
}
|
||||
}
|
||||
|
||||
if (parent::has($name)) {
|
||||
// Parent is used because of b/c, can be changed in Joomla 6
|
||||
return parent::get($name, $default);
|
||||
}
|
||||
|
||||
/*
|
||||
* B/C for retrieving sessions that originated in Joomla 3.
|
||||
* A namespace before Joomla 4 has a prefix of 2 underscores (__).
|
||||
* This is no longer the case in Joomla 4 and will be converted
|
||||
* when saving new values in `self::set()`
|
||||
*/
|
||||
if (strpos($name, '.') !== false && parent::has('__' . $name)) {
|
||||
return parent::get('__' . $name, $default);
|
||||
}
|
||||
|
||||
// More b/c for retrieving sessions that originated in Joomla 3. This will be removed in Joomla 6
|
||||
// as no sessions should have this format anymore!
|
||||
if (parent::has('__default.' . $name)) {
|
||||
return parent::get('__default.' . $name, $default);
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set data into the session store.
|
||||
*
|
||||
* @param string $name Name of a variable.
|
||||
* @param mixed $value Value of a variable.
|
||||
*
|
||||
* @return mixed Old value of a variable.
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
public function set($name, $value = null)
|
||||
{
|
||||
// Handle B/C by checking if a namespace was passed to the method, will be removed at 5.0
|
||||
if (\func_num_args() > 2) {
|
||||
$args = \func_get_args();
|
||||
|
||||
if (!empty($args[2])) {
|
||||
@trigger_error(
|
||||
'Passing a namespace as a parameter to ' . __METHOD__ . '() is deprecated. '
|
||||
. 'The namespace should be prepended to the name instead.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
$name = $args[2] . '.' . $name;
|
||||
}
|
||||
}
|
||||
|
||||
return parent::set($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether data exists in the session store
|
||||
*
|
||||
* @param string $name Name of variable
|
||||
*
|
||||
* @return boolean True if the variable exists
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
public function has($name)
|
||||
{
|
||||
// Handle B/C by checking if a namespace was passed to the method, will be removed at 5.0
|
||||
if (\func_num_args() > 1) {
|
||||
$args = \func_get_args();
|
||||
|
||||
if (!empty($args[1])) {
|
||||
@trigger_error(
|
||||
'Passing a namespace as a parameter to ' . __METHOD__ . '() is deprecated. '
|
||||
. 'The namespace should be prepended to the name instead.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
$name = $args[1] . '.' . $name;
|
||||
}
|
||||
}
|
||||
|
||||
if (parent::has($name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* B/C for retrieving sessions that originated in Joomla 3.
|
||||
* A namespace before Joomla 4 has a prefix of 2 underscores (__).
|
||||
* This is no longer the case in Joomla 4 and will be converted
|
||||
* when saving new values in `self::set()`
|
||||
*/
|
||||
if (strpos($name, '.') !== false && parent::has('__' . $name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// More b/c for retrieving sessions that originated in Joomla 3. This will be removed in Joomla 6
|
||||
// as no sessions should have this format anymore!
|
||||
return parent::has('__default.' . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all variables from the session store
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
// Handle B/C by checking if parameters were passed to this method; if so proxy to the new remove() method, will be removed at 5.0
|
||||
if (\func_num_args() >= 1) {
|
||||
$args = \func_get_args();
|
||||
|
||||
if (!empty($args[0])) {
|
||||
@trigger_error(
|
||||
'Using ' . __METHOD__ . '() to remove a single element from the session is deprecated. Use ' . __CLASS__ . '::remove() instead.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
$name = $args[0];
|
||||
|
||||
// Also check for a namespace
|
||||
if (\func_num_args() > 1 && !empty($args[1])) {
|
||||
@trigger_error(
|
||||
'Passing a namespace as a parameter to ' . __METHOD__ . '() is deprecated. '
|
||||
. 'The namespace should be prepended to the name instead.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
$name = $args[1] . '.' . $name;
|
||||
}
|
||||
|
||||
$this->remove($name);
|
||||
|
||||
/*
|
||||
* B/C for cleaning sessions that originated in Joomla 3.
|
||||
* A namespace before Joomla 4 has a prefix of 2 underscores (__).
|
||||
* This is no longer the case in Joomla 4 so we clean both variants.
|
||||
*/
|
||||
$this->remove('__' . $name);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
parent::clear();
|
||||
}
|
||||
}
|
||||
156
libraries/src/Session/SessionFactory.php
Normal file
156
libraries/src/Session/SessionFactory.php
Normal file
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Session;
|
||||
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\DI\ContainerAwareInterface;
|
||||
use Joomla\DI\ContainerAwareTrait;
|
||||
use Joomla\Registry\Registry;
|
||||
use Joomla\Session\Handler;
|
||||
use Joomla\Session\HandlerInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Factory for creating session API objects
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class SessionFactory implements ContainerAwareInterface
|
||||
{
|
||||
use ContainerAwareTrait;
|
||||
|
||||
/**
|
||||
* Create a session handler based on the application configuration.
|
||||
*
|
||||
* @param array $options The options used to instantiate the SessionInterface instance.
|
||||
*
|
||||
* @return HandlerInterface
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function createSessionHandler(array $options): HandlerInterface
|
||||
{
|
||||
$resolver = new OptionsResolver();
|
||||
$this->configureSessionHandlerOptions($resolver);
|
||||
|
||||
$options = $resolver->resolve($options);
|
||||
|
||||
/** @var Registry $config */
|
||||
$config = $this->getContainer()->get('config');
|
||||
|
||||
$handlerType = $config->get('session_handler', 'filesystem');
|
||||
|
||||
switch ($handlerType) {
|
||||
case 'apcu':
|
||||
if (!Handler\ApcuHandler::isSupported()) {
|
||||
throw new \RuntimeException('APCu is not supported on this system.');
|
||||
}
|
||||
|
||||
return new Handler\ApcuHandler();
|
||||
|
||||
case 'database':
|
||||
return new Handler\DatabaseHandler($this->getContainer()->get(DatabaseInterface::class));
|
||||
|
||||
case 'filesystem':
|
||||
case 'none':
|
||||
// Try to use a custom configured path, fall back to the path in the PHP runtime configuration
|
||||
$path = $config->get('session_filesystem_path', ini_get('session.save_path'));
|
||||
|
||||
// If we still have no path, as a last resort fall back to the system's temporary directory
|
||||
if (empty($path)) {
|
||||
$path = sys_get_temp_dir();
|
||||
}
|
||||
|
||||
return new Handler\FilesystemHandler($path);
|
||||
|
||||
case 'memcached':
|
||||
if (!Handler\MemcachedHandler::isSupported()) {
|
||||
throw new \RuntimeException('Memcached is not supported on this system.');
|
||||
}
|
||||
|
||||
$host = $config->get('session_memcached_server_host', 'localhost');
|
||||
$port = $config->get('session_memcached_server_port', 11211);
|
||||
|
||||
$memcached = new \Memcached($config->get('session_memcached_server_id', 'joomla_cms'));
|
||||
$memcached->addServer($host, $port);
|
||||
|
||||
ini_set('session.save_path', "$host:$port");
|
||||
ini_set('session.save_handler', 'memcached');
|
||||
|
||||
return new Handler\MemcachedHandler($memcached, ['ttl' => $options['expire']]);
|
||||
|
||||
case 'redis':
|
||||
if (!Handler\RedisHandler::isSupported()) {
|
||||
throw new \RuntimeException('Redis is not supported on this system.');
|
||||
}
|
||||
|
||||
$redis = new \Redis();
|
||||
$host = $config->get('session_redis_server_host', '127.0.0.1');
|
||||
|
||||
// Use default port if connecting over a socket whatever the config value
|
||||
$port = $host[0] === '/' ? 0 : $config->get('session_redis_server_port', 6379);
|
||||
|
||||
if ($config->get('session_redis_persist', true)) {
|
||||
$redis->pconnect(
|
||||
$host,
|
||||
$port
|
||||
);
|
||||
} else {
|
||||
$redis->connect(
|
||||
$host,
|
||||
$port
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($config->get('session_redis_server_auth', ''))) {
|
||||
$redis->auth($config->get('session_redis_server_auth', null));
|
||||
}
|
||||
|
||||
$db = (int) $config->get('session_redis_server_db', 0);
|
||||
|
||||
if ($db !== 0) {
|
||||
$redis->select($db);
|
||||
}
|
||||
|
||||
return new Handler\RedisHandler($redis, ['ttl' => $options['expire']]);
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException(sprintf('The "%s" session handler is not recognised.', $handlerType));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the options for the session handler.
|
||||
*
|
||||
* @param OptionsResolver $resolver The options resolver.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected function configureSessionHandlerOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults(
|
||||
[
|
||||
'force_ssl' => false,
|
||||
]
|
||||
);
|
||||
|
||||
$resolver->setRequired(['name', 'expire']);
|
||||
|
||||
$resolver->setAllowedTypes('name', ['string']);
|
||||
$resolver->setAllowedTypes('expire', ['int']);
|
||||
$resolver->setAllowedTypes('force_ssl', ['bool']);
|
||||
}
|
||||
}
|
||||
78
libraries/src/Session/SessionManager.php
Normal file
78
libraries/src/Session/SessionManager.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Session;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Manager for interacting with the session handler to perform updates on sessions.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
final class SessionManager
|
||||
{
|
||||
/**
|
||||
* Session handler.
|
||||
*
|
||||
* @var \SessionHandlerInterface
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $sessionHandler;
|
||||
|
||||
/**
|
||||
* SessionManager constructor.
|
||||
*
|
||||
* @param \SessionHandlerInterface $sessionHandler Session handler.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct(\SessionHandlerInterface $sessionHandler)
|
||||
{
|
||||
$this->sessionHandler = $sessionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the given session ID.
|
||||
*
|
||||
* @param string $sessionId The session ID to destroy.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function destroySession(string $sessionId): bool
|
||||
{
|
||||
return $this->sessionHandler->destroy($sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the given session IDs.
|
||||
*
|
||||
* @param string[] $sessionIds The session IDs to destroy.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function destroySessions(array $sessionIds): bool
|
||||
{
|
||||
$result = true;
|
||||
|
||||
foreach ($sessionIds as $sessionId) {
|
||||
if (!$this->destroySession($sessionId)) {
|
||||
$result = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
320
libraries/src/Session/Storage/JoomlaStorage.php
Normal file
320
libraries/src/Session/Storage/JoomlaStorage.php
Normal file
@ -0,0 +1,320 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Joomla! Content Management System
|
||||
*
|
||||
* @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\CMS\Session\Storage;
|
||||
|
||||
use Joomla\Input\Input;
|
||||
use Joomla\Registry\Registry;
|
||||
use Joomla\Session\Storage\NativeStorage;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Service provider for the application's session dependency
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class JoomlaStorage extends NativeStorage
|
||||
{
|
||||
/**
|
||||
* Internal data store for the session data
|
||||
*
|
||||
* @var Registry
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* Force cookies to be SSL only
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $forceSSL = false;
|
||||
|
||||
/**
|
||||
* The domain to set in the session cookie
|
||||
*
|
||||
* @var string
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private $cookieDomain = '';
|
||||
|
||||
/**
|
||||
* The path to set in the session cookie
|
||||
*
|
||||
* @var string
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private $cookiePath = '/';
|
||||
|
||||
/**
|
||||
* Input object
|
||||
*
|
||||
* @var Input
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $input;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Input $input Input object
|
||||
* @param \SessionHandlerInterface $handler Session save handler
|
||||
* @param array $options Session options
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct(Input $input, \SessionHandlerInterface $handler = null, array $options = [])
|
||||
{
|
||||
// Disable transparent sid support and default use cookies
|
||||
$options += [
|
||||
'use_cookies' => 1,
|
||||
'use_trans_sid' => 0,
|
||||
];
|
||||
|
||||
if (!headers_sent() && !$this->isActive()) {
|
||||
session_cache_limiter('none');
|
||||
}
|
||||
|
||||
$this->setOptions($options);
|
||||
$this->setHandler($handler);
|
||||
$this->setCookieParams();
|
||||
|
||||
$this->data = new Registry();
|
||||
$this->input = $input;
|
||||
|
||||
// Register our function as shutdown method, so we can manipulate it
|
||||
register_shutdown_function([$this, 'close']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all variables from the session store
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
return $this->data->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all variables from the session store
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
$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])) {
|
||||
$cookie = session_get_cookie_params();
|
||||
setcookie($session_name, '', time() - 42000, $this->cookiePath, $this->cookieDomain, $cookie['secure'], true);
|
||||
}
|
||||
|
||||
$this->data = new Registry();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes session data and ends session
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @see session_write_close()
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
// Before storing data to the session, we serialize and encode the Registry
|
||||
$_SESSION['joomla'] = base64_encode(serialize($this->data));
|
||||
|
||||
parent::close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data from the session store
|
||||
*
|
||||
* @param string $name Name of a variable
|
||||
* @param mixed $default Default value of a variable if not set
|
||||
*
|
||||
* @return mixed Value of a variable
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function get(string $name, $default)
|
||||
{
|
||||
if (!$this->isStarted()) {
|
||||
$this->start();
|
||||
}
|
||||
|
||||
return $this->data->get($name, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether data exists in the session store
|
||||
*
|
||||
* @param string $name Name of variable
|
||||
*
|
||||
* @return boolean True if the variable exists
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function has(string $name): bool
|
||||
{
|
||||
if (!$this->isStarted()) {
|
||||
$this->start();
|
||||
}
|
||||
|
||||
return $this->data->exists($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset a variable from the session store
|
||||
*
|
||||
* @param string $name Name of variable
|
||||
*
|
||||
* @return mixed The value from session or NULL if not set
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function remove(string $name)
|
||||
{
|
||||
if (!$this->isStarted()) {
|
||||
$this->start();
|
||||
}
|
||||
|
||||
$old = $this->data->get($name);
|
||||
|
||||
unset($this->data[$name]);
|
||||
|
||||
return $old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set data into the session store
|
||||
*
|
||||
* @param string $name Name of a variable.
|
||||
* @param mixed $value Value of a variable.
|
||||
*
|
||||
* @return mixed Old value of a variable.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function set(string $name, $value = null)
|
||||
{
|
||||
if (!$this->isStarted()) {
|
||||
$this->start();
|
||||
}
|
||||
|
||||
$old = $this->data->get($name);
|
||||
|
||||
$this->data->set($name, $value);
|
||||
|
||||
return $old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set session cookie parameters
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected function setCookieParams(): void
|
||||
{
|
||||
if (headers_sent() || $this->isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cookie = session_get_cookie_params();
|
||||
|
||||
if ($this->forceSSL) {
|
||||
$cookie['secure'] = true;
|
||||
}
|
||||
|
||||
if ($this->cookieDomain !== '') {
|
||||
$cookie['domain'] = $this->cookieDomain;
|
||||
}
|
||||
|
||||
if ($this->cookiePath !== '') {
|
||||
$cookie['path'] = $this->cookiePath;
|
||||
}
|
||||
|
||||
session_set_cookie_params($cookie['lifetime'], $cookie['path'], $cookie['domain'], $cookie['secure'], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets session options
|
||||
*
|
||||
* @param array $options Session ini directives array(key => value).
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @link http://php.net/session.configuration
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function setOptions(array $options): NativeStorage
|
||||
{
|
||||
if (isset($options['cookie_domain'])) {
|
||||
$this->cookieDomain = $options['cookie_domain'];
|
||||
}
|
||||
|
||||
if (isset($options['cookie_path'])) {
|
||||
$this->cookiePath = $options['cookie_path'];
|
||||
}
|
||||
|
||||
if (isset($options['force_ssl'])) {
|
||||
$this->forceSSL = (bool) $options['force_ssl'];
|
||||
}
|
||||
|
||||
return parent::setOptions($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a session
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function start(): void
|
||||
{
|
||||
$session_name = $this->getName();
|
||||
|
||||
// Get the cookie object
|
||||
$cookie = $this->input->cookie;
|
||||
|
||||
if (\is_null($cookie->get($session_name))) {
|
||||
$session_clean = $this->input->getString($session_name);
|
||||
|
||||
if ($session_clean) {
|
||||
$this->setId($session_clean);
|
||||
$cookie->set($session_name, '', time() - 3600);
|
||||
}
|
||||
}
|
||||
|
||||
parent::start();
|
||||
|
||||
// Try loading data from the session
|
||||
if (!empty($_SESSION['joomla'])) {
|
||||
$this->data = unserialize(base64_decode($_SESSION['joomla']));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user