1
0
mirror of https://github.com/e107inc/e107.git synced 2025-06-21 16:32:50 +02:00

Issue #5443 Fix for session_set_save_handler(): Providing individual callbacks instead of an object implementing SessionHandlerInterface is deprecated

This commit is contained in:
camer0n
2025-04-17 04:16:27 -07:00
parent 94fa36ade2
commit acb0c595b3
2 changed files with 1051 additions and 1122 deletions

View File

@ -260,7 +260,7 @@
* @param bool $debug
* @param string $log_type
* @param string $log_remark
* @return integer Number of rows or false on error
* @return integer|false Number of rows or false on error
*/
public function select($table, $fields = '*', $arg = '', $noWhere = false, $debug = false, $log_type = '', $log_remark = '');

View File

@ -35,7 +35,7 @@ if (!defined('e107_INIT'))
* as per e_SECURITY_LEVEL setting.
*
* Security levels:
* - SECURITY_LEVEL_NONE [0]: security disabled - no token checks, all session validation settings dsiabled
* - SECURITY_LEVEL_NONE [0]: security disabled - no token checks, all session validation settings disabled
* - SECURITY_LEVEL_BALANCED [5]: ValidateRemoteAddr, ValidateHttpXForwardedFor are on,
* session token is created/checked, but not regenerated on every page load
* - SECURITY_LEVEL_HIGH [7]: Same as above but ValidateHttpVia, ValidateHttpUserAgent are on.
@ -46,8 +46,8 @@ if (!defined('e107_INIT'))
* of every page request.
*
* Session objects are created by namespace:
* $_SESSION['e107'] is default namesapce auto created with
* <code><?php e107::getSession();</code>
* $_SESSION['e107'] is default namespace auto created with
* <code><?php e107::getSession(); ?></code>
* Session handler is validating corresponding session COOKIE
* (named as current session name, keeping the session id)
* on regular basis (session lifetime/4). If validation
@ -57,10 +57,7 @@ if (!defined('e107_INIT'))
* ensure proper session handling for sites using language sub-domains (e.g. fr.site.com)
*
* Some important system session data will be kept outside of the object for now (e.g. user validation data)
*
*/
class e_session
{
/**
@ -69,7 +66,6 @@ class e_session
*/
const SECURITY_LEVEL_NONE = 0;
const SECURITY_LEVEL_LOW = 3;
/**
* Default system protection, balanced for best user experience,
@ -95,7 +91,7 @@ class e_session
/**
* Highest system protection, session id and token values are regenerated on every page request,
* label 'Insane'
* @var int unknown_type
* @var int
*/
const SECURITY_LEVEL_INSANE = 10;
@ -109,7 +105,7 @@ class e_session
* Session save method
* @var string files|db
*/
protected $_sessionSaveMethod = 'files';//'files';
protected $_sessionSaveMethod = 'files';
/**
* Session cache limiter, ignored if empty
@ -145,7 +141,7 @@ class e_session
* @var array
*/
protected $_options = array(
'lifetime' => 3600 , // 1 hour
'lifetime' => 3600, // 1 hour
'path' => '',
'domain' => '',
'secure' => false,
@ -178,17 +174,15 @@ class e_session
return $this->_options;
}
/**
* Get session option
* @param string $key
* @param mixed $default
* @return mixed value
* @return mixed
*/
public function getOption($key, $default = null)
{
return (isset($this->_options[$key]) ? $this->_options[$key] : $default);
return ($this->_options[$key] ?? $default);
}
/**
@ -209,16 +203,13 @@ class e_session
);
$options = array(
// 'httponly' => (e_SECURITY_LEVEL >= self::SECURITY_LEVEL_PARANOID),
'httponly' => true,
);
if (!defined('E107_INSTALL'))
{
$systemSaveMethod = ini_get('session.save_handler');
$saveMethod = (!empty($systemSaveMethod)) ? $systemSaveMethod : 'files';
$config['SavePath'] = e107::getPref('session_save_path', false); // FIXME - new pref
$config['SaveMethod'] = e107::getPref('session_save_method', $saveMethod);
$options['lifetime'] = (int) e107::getPref('session_lifetime', 86400);
@ -242,13 +233,6 @@ class e_session
$hashes = hash_algos();
// if ((e_SECURITY_LEVEL >= self::SECURITY_LEVEL_BALANCED) && in_array('sha512', $hashes))
{
// ini_set('session.hash_function', 'sha512'); Removed in PHP 7.1
// ini_set('session.hash_bits_per_character', 5); Removed in PHP 7.1
}
$this->fixSessionFileGarbageCollection();
$this->setConfig($config)
@ -278,12 +262,12 @@ class e_session
* Retrieve value from current session namespace
* Equals to $_SESSION[NAMESPACE][$key]
* @param string $key
* @param boolean $clear unset key
* @param bool $clear unset key
* @return mixed
*/
public function get($key, $clear = false)
{
$ret = isset($this->_data[$key]) ? $this->_data[$key] : null;
$ret = $this->_data[$key] ?? null;
if($clear) $this->clear($key);
return $ret;
}
@ -293,7 +277,7 @@ class e_session
* If key is null, returns all current session namespace data
*
* @param string|null $key
* @param boolean $clear
* @param bool $clear
* @return mixed
*/
public function getData($key = null, $clear = false)
@ -310,7 +294,7 @@ class e_session
/**
* Set value in current session namespace
* Equals to $_SESSION[NAMESPACE][$key] = $value
* @param string $key Also accepts multi-dimensinal format. key1/key2
* @param string $key Also accepts multi-dimensional format. key1/key2
* @param mixed $value
* @return e_session
*/
@ -331,7 +315,6 @@ class e_session
list($k1, $k2, $k3) = $keyArr;
$this->_data[$k1][$k2][$k3] = $value;
}
}
else
{
@ -345,7 +328,7 @@ class e_session
* Set value in current session namespace
* If $key is array, the whole namespace array will be replaced with it,
* $value will be ignored
* @param string|null $key
* @param string|array|null $key
* @param mixed $value
* @return e_session
*/
@ -363,7 +346,7 @@ class e_session
* Check if given key is set in current session namespace
* Equals to isset($_SESSION[NAMESPACE][$key])
* @param string $key
* @return boolean
* @return bool
*/
public function is($key)
{
@ -374,7 +357,7 @@ class e_session
* Check if given key is set and not empty in current session namespace
* Equals to !empty($_SESSION[NAMESPACE][$key]) check
* @param string $key
* @return boolean
* @return bool
*/
public function has($key)
{
@ -384,7 +367,7 @@ class e_session
/**
* Checks if current session namespace contains any data
* Equals to !empty($_SESSION[NAMESPACE]) check
* @return boolean
* @return bool
*/
public function hasData()
{
@ -394,12 +377,12 @@ class e_session
/**
* Unset member of current session namespace array
* Equals to unset($_SESSION[NAMESPACE][$key])
* @param string $key
* @param string|null $key
* @return e_session
*/
public function clear($key=null)
public function clear($key = null)
{
if($key == null) // clear all under this namespace.
if($key === null) // clear all under this namespace.
{
$this->_data = array(); // must be set to array() not unset.
return $this;
@ -420,14 +403,12 @@ class e_session
list($k1, $k2, $k3) = $keyArr;
unset($this->_data[$k1][$k2][$k3]);
}
}
else
{
unset($this->_data[$key]);
}
return $this;
}
@ -488,7 +469,7 @@ class e_session
case 'secure':
case 'httponly':
$v = $v ? true : false;
$v = (bool) $v;
break;
default:
@ -505,8 +486,9 @@ class e_session
}
/**
* @param $namespace
* @param $sessionName
* Initialize session with namespace and optional session name
* @param string $namespace
* @param string|null $sessionName
* @return void
*/
public function init($namespace, $sessionName = null)
@ -525,14 +507,13 @@ class e_session
}
/**
* Conigure and start session
* Configure and start session
*
* @param string $sessionName optional session name
* @param string|null $sessionName optional session name
* @return e_session
*/
public function start($sessionName = null)
{
if (isset($_SESSION) && (self::$_sessionStarted === true))
{
return $this;
@ -546,18 +527,8 @@ class e_session
switch ($this->_sessionSaveMethod)
{
case 'db':
// ini_set('session.save_handler', 'user');
$session = new e_session_db;
session_set_save_handler(
[$session, 'open'],
[$session, 'close'],
[$session, 'read'],
[$session, 'write'],
[$session, 'destroy'],
[$session, 'gc']
);
$session->setSaveHandler();
session_set_save_handler($session);
break;
default:
@ -571,7 +542,7 @@ class e_session
if (empty($this->_options['domain']))
{
// MULTILANG_SUBDOMAIN set during initial language detection in language handler
$doma = ((deftrue('e_SUBDOMAIN') || deftrue('MULTILANG_SUBDOMAIN')) && e_DOMAIN != FALSE) ? ".".e_DOMAIN : FALSE; // from v1.x
$doma = ((deftrue('e_SUBDOMAIN') || deftrue('MULTILANG_SUBDOMAIN')) && e_DOMAIN !== false) ? ".".e_DOMAIN : false; // from v1.x
$this->_options['domain'] = $doma;
}
@ -602,10 +573,9 @@ class e_session
if ($this->_sessionCacheLimiter)
{
session_cache_limiter((string) $this->_sessionCacheLimiter); //XXX Remove and have e_headers class handle it?
session_cache_limiter( $this->_sessionCacheLimiter); // TODO: Consider moving to e_headers class
}
session_start();
self::$_sessionStarted = true;
return $this;
@ -613,7 +583,7 @@ class e_session
/**
* Set session ID
* @param string $sid
* @param string|null $sid
* @return e_session
*/
public function setSessionId($sid = null)
@ -636,7 +606,7 @@ class e_session
}
/**
* Retrieve current session save method.
* Retrieve current session save method
* @return string
*/
public function getSaveMethod()
@ -647,14 +617,13 @@ class e_session
/**
* Set new session name
* @param string $name alphanumeric characters only
* @return false old session name or false on error
* @return false false on error
*/
public function setSessionName($name)
{
if (!empty($name) && preg_match('#^[0-9a-z_]+$#i', $name))
{
$this->_name = $name;
// return session_name($name);
}
return false;
}
@ -725,7 +694,7 @@ class e_session
$sessionData = $this->_data['_session_validate_data'];
$validateData = $this->getValidateData();
$details = 'USER INFORMATION: '.(isset($_COOKIE[e_COOKIE]) ? $_COOKIE[e_COOKIE] : (isset($_SESSION[e_COOKIE]) ? $_SESSION[e_COOKIE] : 'n/a'))."\n";
$details = 'USER INFORMATION: '.($_COOKIE[e_COOKIE] ?? ($_SESSION[e_COOKIE] ?? 'n/a'))."\n";
$details .= "HOST: ".$_SERVER['HTTP_HOST']."\n";
$details .= "REQUEST_URI: ".$_SERVER['REQUEST_URI']."\n";
$details .= "SESSION OPTIONS: ".print_r($this->_options, true)."\n";
@ -743,14 +712,7 @@ class e_session
$this->cookieDelete()->destroy();
// TODO event trigger
// e107::getAdminLog()->add('Session validation failed!', $details, E_LOG_FATAL);
// TODO session exception, handle it proper on live site
// throw new Exception('');
// just for now
$msg = 'Session validation failed! <a href="'.strip_tags($_SERVER['REQUEST_URI']).'">Go Back</a>';
// die($msg); //FIXME not functioning as intended.
}
return $this;
@ -817,8 +779,8 @@ class e_session
}
/**
* Retrieve (create if doesn't exist) XSF protection token
* @param boolean $in_form if true (default) - value for forms, else raw session value
* Retrieve (create if doesn't exist) CSRF protection token
* @param bool $in_form if true (default) - value for forms, else raw session value
* @return string
*/
public function getFormToken($in_form = true)
@ -849,7 +811,7 @@ class e_session
/**
* Do a check against passed token
* @param string $token
* @return boolean
* @return bool
*/
public function checkFormToken($token)
{
@ -860,7 +822,7 @@ class e_session
/**
* Clear and Unset current namespace, unregister session singleton
* e107::getSession('namespace') if needed.
* @param boolean $unregister if true (default) - unregister Singleton, destroy namespace,
* @param bool $unregister if true (default) - unregister Singleton, destroy namespace,
* else alias of self::clearData()
* @return void
*/
@ -870,7 +832,7 @@ class e_session
if($unregister)
{
unset($_SESSION[$this->_namespace]);
e107::setRegistry('core/e107/session/'.$this->_namespace, null);
e107::setRegistry('core/e107/session/'.$this->_namespace);
}
}
@ -903,17 +865,17 @@ class e_session
}
/**
* Replace the session object in the registry
* @return void
*/
public function replaceRegistry()
{
e107::setRegistry('core/e107/session/'.$this->_namespace, $this, true);
e107::setRegistry('core/e107/session/'.$this->_namespace, $this);
}
}
/**
*
* Core session handler extending base session class
*/
class e_core_session extends e_session
{
@ -931,8 +893,8 @@ class e_core_session extends e_session
$this->setDefaultSystemConfig();
$namespace = 'e107sess'; // Quick Fix for Fatal Error "Cannot use object of type e107 as array" on line 550
$name = (isset($data['name']) && !empty($data['name']) ? $data['name'] : deftrue('e_COOKIE', 'e107')).'SID';
if(isset($data['namespace']) && !empty($data['namespace'])) $namespace = $data['namespace'];
$name = (!empty($data['name']) ? $data['name'] : deftrue('e_COOKIE', 'e107')).'SID';
if(!empty($data['namespace'])) $namespace = $data['namespace'];
// create $_SESSION['e107'] namespace by default
$this->init($namespace, $name);
@ -957,7 +919,7 @@ class e_core_session extends e_session
// regenerate SID
$oldSID = session_id(); // old SID
$oldSData = $_SESSION; // old session data
session_regenerate_id(false); // true don't work on php4 - so time to move on people!
session_regenerate_id();
$newSID = session_id(); // new SID
// Clean
@ -976,20 +938,18 @@ class e_core_session extends e_session
}
/**
* @param $status
* @param $type
* @return void|null
* Log session activity
* @param string $status
* @param int $type
* @return void
*/
private function log($status, $type=E_LOG_FATAL)
private function log($status, $type = E_LOG_FATAL)
{
if(!deftrue('e_DEBUG_SESSION'))
{
return null;
return;
}
// $details = "USER: ".USERNAME."\n";
$details = "HOST: ".$_SERVER['HTTP_HOST']."\n";
$details .= "REQUEST_URI: ".$_SERVER['REQUEST_URI']."\n";
@ -1012,13 +972,7 @@ class e_core_session extends e_session
$details .= "\n";
$details .= "_SESSION:\n";
$details .= print_r($_SESSION,true);
/* if($pref['plug_installed'])
{
$details .= "\nPlugins:\n";
$details .= print_r($pref['plug_installed'],true);
}*/
$details .= print_r($_SESSION, true);
$details .= $status."\n\n---------------------------------\n\n";
@ -1027,37 +981,32 @@ class e_core_session extends e_session
if(deftrue('e_DEBUG_SESSION'))
{
$log->toFile('Unauthorized_access','Unauthorized access Log', true);
$log->toFile('Unauthorized_access', 'Unauthorized access Log', true);
}
$log->add($status, $details, $type);
}
/**
* Core CSF protection, see class2.php
* Core CSRF protection, see class2.php
* Could be adopted by plugins for their own (different) protection logic
* @param boolean $die
* @return boolean
* @param bool $die
* @return bool
*/
public function check($die = true)
{
// define('e_TOKEN_NAME', 'e107_token_'.md5($_SERVER['HTTP_HOST'].e_HTTP));
// TODO e-token required for all system forms?
// only if not disabled and not in 'cli' mod
// only if not disabled and not in 'cli' mode
if(e_SECURITY_LEVEL < e_session::SECURITY_LEVEL_LOW || e107::getE107('cli')) return true;
if($this->getSessionId())
{
if((isset($_POST['e-token']) && !$this->checkFormToken($_POST['e-token']))
|| (isset($_GET['e-token']) && !$this->checkFormToken($_GET['e-token']))
|| (isset($_POST['e_token']) && !$this->checkFormToken($_POST['e_token']))) // '-' is not allowed in jquery. b
|| (isset($_POST['e_token']) && !$this->checkFormToken($_POST['e_token']))) // '-' is not allowed in jQuery
{
$this->log('Unauthorized access!');
// do not redirect, prevent dead loop, save server resources
if($die == true)
if($die)
{
die('Unauthorized access!');
}
@ -1066,7 +1015,6 @@ class e_core_session extends e_session
}
$this->log('Session Token Okay!', defset('E_LOG_NOTICE', 1));
}
if(!defined('e_TOKEN'))
@ -1089,24 +1037,20 @@ class e_core_session extends e_session
return true;
}
/**
* Manually Reset the Token.
* @see e107forum::ajaxQuickReply();
* @return void
*/
public function reset()
{
$this->_regenerateFormToken()->clear('__form_token_regenerate');
}
/**
* Make sure there is unique challenge string for CHAP login
* @see class2.php
* @return e_core_session
@TODO: Remove debug code
*/
public function challenge()
{
@ -1124,14 +1068,9 @@ class e_core_session extends e_session
$this->set('prevchallenge', ''); // Dummy value
$this->set('prevprevchallenge', ''); // Dummy value
}
//$this->set('challenge', sha1(time().rand().$this->getSessionId())); // Temporarily disabled
// FIXME - session id will be regenerated if e_SECURITY_LEVEL is 'paranoid|insane' - generate (might be OK as long as values retained)
//$extra_text = 'C: '.$this->get('challenge').' PC: '.$this->get('prevchallenge').' PPC: '.$this->get('prevprevchallenge');
//$logfp = fopen(e_LOG.'authlog.txt', 'a+'); fwrite($logfp, strftime('%H:%M:%S').' CHAP start: '.$extra_text."\n"); fclose($logfp);
// could go, see _validate()
$user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$ubrowser = md5('E107'.$user_agent);
if (!$this->is('ubrowser'))
{
@ -1141,13 +1080,10 @@ class e_core_session extends e_session
}
}
/**
* Database session handler
*
* @todo PHP 8.1 support with {@see SessionHandlerInterface}
*/
class e_session_db #implements SessionHandlerInterface
class e_session_db implements SessionHandlerInterface
{
/**
* @var e_db
@ -1161,7 +1097,7 @@ class e_session_db #implements SessionHandlerInterface
protected $_table = 'session';
/**
* @var integer
* @var int|null
*/
protected $_lifetime = null;
@ -1171,7 +1107,7 @@ class e_session_db #implements SessionHandlerInterface
}
/**
*
* Ensure session data is written before object destruction
*/
public function __destruct()
{
@ -1197,7 +1133,7 @@ class e_session_db #implements SessionHandlerInterface
}
/**
* @return integer
* @return int
*/
public function getLifetime()
{
@ -1209,11 +1145,11 @@ class e_session_db #implements SessionHandlerInterface
$this->_lifetime = 3600;
}
}
return (integer) $this->_lifetime;
return (int) $this->_lifetime;
}
/**
* @param integer $seconds
* @param int|null $seconds
* @return e_session_db
*/
public function setLifetime($seconds = null)
@ -1223,38 +1159,31 @@ class e_session_db #implements SessionHandlerInterface
}
/**
* Set session save handler
* Set session save handler (no-op as handler is set in e_session::start)
* @return e_session_db
*/
public function setSaveHandler()
{
session_set_save_handler(
array($this, 'open'),
array($this, 'close'),
array($this, 'read'),
array($this, 'write'),
array($this, 'destroy'),
array($this, 'gc')
);
return $this;
}
/**
* Open session, parameters are ignored (see e_session handler)
* @param string $save_path
* @param string $sess_name
* @return boolean
*
* @param string $path
* @param string $name
* @return bool
*/
public function open($save_path, $sess_name)
public function open(string $path, string $name): bool
{
return true;
}
/**
* Close session
* @return boolean
* @return bool
*/
public function close()
public function close(): bool
{
$this->gc($this->getLifetime());
return true;
@ -1262,13 +1191,13 @@ class e_session_db #implements SessionHandlerInterface
/**
* Get session data
* @param string $session_id
* @return string
* @param string $id
* @return string|false
*/
public function read($session_id)
public function read(string $id): string|false
{
$data = false;
$check = $this->_db->select($this->getTable(), 'session_data', "session_id='".$this->_sanitize($session_id)."' AND session_expires>".time());
$check = $this->_db->select($this->getTable(), 'session_data', "session_id='".$this->_sanitize($id)."' AND session_expires>".time());
if($check)
{
$tmp = $this->_db->fetch();
@ -1283,16 +1212,16 @@ class e_session_db #implements SessionHandlerInterface
/**
* Write session data
* @param string $session_id
* @param string $session_data
* @return boolean
* @param string $id
* @param string $data
* @return bool
*/
public function write($session_id, $session_data)
public function write(string $id, string $data): bool
{
$data = array(
$session_data = array(
'data' => array(
'session_expires' => time() + $this->getLifetime(),
'session_data' => base64_encode($session_data),
'session_data' => base64_encode($data),
'session_user' => defset('USERID'),
),
'_FIELD_TYPES' => array(
@ -1303,25 +1232,25 @@ class e_session_db #implements SessionHandlerInterface
),
'_DEFAULT' => 'str'
);
if(!($session_id = $this->_sanitize($session_id)))
if(!($id = $this->_sanitize($id)))
{
return false;
}
$check = $this->_db->select($this->getTable(), 'session_id', "`session_id`='{$session_id}'");
$check = $this->_db->select($this->getTable(), 'session_id', "`session_id`='$id'");
if($check)
{
$data['WHERE'] = "`session_id`='{$session_id}'";
if(false !== $this->_db->update($this->getTable(), $data))
$session_data['WHERE'] = "`session_id`='$id'";
if(false !== $this->_db->update($this->getTable(), $session_data))
{
return true;
}
}
else
{
$data['data']['session_id'] = $session_id;
if($this->_db->insert($this->getTable(), $data))
$session_data['data']['session_id'] = $id;
if($this->_db->insert($this->getTable(), $session_data))
{
return true;
}
@ -1331,22 +1260,22 @@ class e_session_db #implements SessionHandlerInterface
/**
* Destroy session
* @param string $session_id
* @return boolean
* @param string $id
* @return bool
*/
public function destroy($session_id)
public function destroy(string $id): bool
{
$session_id = $this->_sanitize($session_id);
$this->_db->delete($this->getTable(), "`session_id`='{$session_id}'");
$id = $this->_sanitize($id);
$this->_db->delete($this->getTable(), "`session_id`='$id'");
return true;
}
/**
* Garbage collection
* @param integer $session_maxlf ignored - see write()
* @return boolean
* @param int $max_lifetime
* @return bool
*/
public function gc($session_maxlf)
public function gc(int $max_lifetime): bool
{
$this->_db->delete($this->getTable(), '`session_expires`<'.time());
return true;
@ -1354,11 +1283,11 @@ class e_session_db #implements SessionHandlerInterface
/**
* Allow only well formed session id string
* @param string $session_id
* @param string $id
* @return string
*/
protected function _sanitize($session_id)
protected function _sanitize(string $id): string
{
return preg_replace('#[^0-9a-zA-Z,-]#', '', $session_id);
return preg_replace('#[^0-9a-zA-Z,-]#', '', $id);
}
}