Server IP : 184.154.167.98 / Your IP : 18.191.84.179 Web Server : Apache System : Linux pink.dnsnetservice.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 User : puertode ( 1767) PHP Version : 8.2.26 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /home/puertode/public_html/mesa/include/ |
Upload File : |
<?php /********************************************************************* class.session.php osTicket core Session Handlers & Backends Peter Rotich <peter@osticket.com> Copyright (c) 2022 osTicket http://www.osticket.com Released under the GNU General Public License WITHOUT ANY WARRANTY. See LICENSE.TXT for details. vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ namespace osTicket\Session { // Extend global Exception class Exception extends \Exception {} // Session backend factory // TODO: Allow plugins to register backends. class SessionBackend { static private $backends = [ // Database is the default backend for osTicket 'database' => ':AbstractSessionStorageBackend', // Session data are stored in memcache saving database the pain // of storing and retriving the records 'memcache' => ':AbstractMemcacheSessionStorageBackend', // Noop/Null SessionHandler doesn't care about sh*t 'noop' => 'NoopSessionStorageBackend', // Default SessionHandler 'system' => 'SystemSessionHandler', ]; static public function getBackends() { return self::$backends; } static public function getBackend(string $bk) { return (($backends=self::getBackends()) && isset($backends[$bk])) ? $backends[$bk] : null; } static public function has(string $bk) { return (self::getBackend($bk)); } static public function factory(string $bk, array $options = []) { list ($bk, $handler, $secondary) = explode(':', $bk); if (!($backend=self::getBackend($bk))) throw new Exception('Unknown Session Backend: '.$bk); if ($backend[0] == ':') { // External implementation required if (!$handler) throw new Exception(sprintf( '%s: requires storage backend driver', $bk)); // External handler needs global namespacing $handler ="\\$handler"; // handler must implement parent $impl = substr($backend, 1); } elseif ($handler) { $handler ="\\$handler"; } else { // local handler is being used force this namespace $handler = sprintf('%s\%s', __NAMESPACE__, $backend); $impl = ($bk == 'system') ? 'SystemSessionHandler' : 'AbstractSessionHandler'; } // Make sure handler / backend class exits if (!$handler || !class_exists($handler)) throw new Exception(sprintf('unknown storage backend - [%s]', $handler)); // Set secondary backend with global namespacing if it is // chained to primary backend ($bk). Primary backend will // validate and instanciate it if it can support the interface // of the said backend. if ($secondary) $options['secondary'] = "\\$secondary"; $sessionHandler = new $handler($options); // Make sure the handler implements the right interface $impl = sprintf('%s\%s', __NAMESPACE__, $impl); if ($impl && !is_a($sessionHandler, $impl)) throw new Exception(sprintf('%s: must implement %s', $handler, $impl)); return $sessionHandler; } static public function register(string $bk, array $options = [], bool $register_shutdown = true) { if (($handler=self::factory($bk, $options)) && session_set_save_handler($handler, $register_shutdown)) return $handler; } } interface SessionRecordInterface { // Basic setters and getters function getId(); function setId(string $id); function getData(); function setData(string $data); function setTTL(int $ttl); // expire in ttl & commit function expire(int $ttl); // commit / save the record to storage function commit(); // Checkers function isNew(); function isValid(); // to array [id, data] expected - other attributes can be returned // as well dependin on the storage backend function toArray(); } abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface { // Options protected $options; // Callback registry private $callbacks; // Flags private $isnew = false; private $isapi = false; // maxlife & ttl private $ttl; private $maxlife; // Secondary backend protected $secondary = null; public function __construct($options = []) { // Set flags based on passed in options $this->options = $options; // Default TTL $this->ttl = $this->options['session_ttl'] ?? ini_get('session.gc_maxlifetime'); // Dynamic maxlife (MLT) if (isset($this->options['session_maxlife'])) $this->maxlife = $this->options['session_maxlife']; // API Session Flag if (isset($this->options['api_session'])) $this->isapi = $this->options['api_session']; // callbacks if (isset($options['callbacks'])) { $this->callbacks = $options['callbacks']; unset($options['callbacks']); } // Set Secondary Backend if any if (isset($options['secondary'])) $this->setSecondaryBackend($options['secondary']); } /* * set a seconday backend... for now it's private but will be public * down the road. */ private function setSecondaryBackend($backend, $options = null) { // Ignore invalid backend if (!$backend // class exists || !class_exists($backend) // Not same backend as handler || !strcasecmp(get_class($this), $backend)) return false; // Init Secondary handler if set and valid $options = $options ?? $this->options; unset($options['secondary']); //unset secondary to avoid loop. $this->secondary = new $backend($options); // Make sure it's truly a storage backend and not a Ye! if (!is_a($this->secondary, __NAMESPACE__.'\AbstractSessionStorageBackend')) $this->secondary = null; // nah... return ($this->secondary); } /* * API Sessions are Stateless and new sessions shouldn't be created * when this flag is turned on. * */ protected function isApiSession() { return ($this->isapi); } /* * onEvent * * Storage Backends that's interested in monitoring / reporting on * events can implement this routine. * */ protected function onEvent($event) { return false; } // Handles callback for registered event listeners protected function callbackOn($event) { if (!$this->callbacks || !isset($this->callbacks[$event])) return false; return (($collable=$this->callbacks[$event]) && is_callable($collable)) ? call_user_func($collable, $this) : false; } /* * pre_save * * This is a hook called by session storage backends before saving a * session record. * */ public function pre_save(SessionRecordInterface $record) { // We're NOT creating new API Sessions since API is stateless. // However existing sessions are updated, allowing for External // Authentication / Authorization to be processed via API endpoints. if ($record->isNew() && $this->isApiSession()) return false; return true; } /* * Default osTicket TTL is the default Session's Maxlife */ public function getTTL() { return $this->ttl; } /* * Maxlife is based on logged in user (if any) * */ public function getMaxlife() { // Prefer maxlife defined based on settings for the // current session user - otherwise use default ttl if (!isset($this->maxlife)) $this->maxlife = (defined('SESSION_MAXLIFE') && is_numeric(SESSION_MAXLIFE)) ? SESSION_MAXLIFE : $this->getTTL(); return $this->maxlife; } public function setMaxLife(int $maxlife) { $this->maxlife = $maxlife; } public function open($save_path, $session_name) { return true; } public function close() { return true; } public function write($id, $data) { // Last chance session update - returns true if update is done if ($this->onEvent('close')) $data = session_encode(); return $this->update($id, $data); } public function validateId($id) { return true; } public function updateTimestamp($id, $data) { return true; } public function gc($maxlife) { $this->cleanup(); } abstract function read($id); abstract function update($id, $data); abstract function expire($id, $ttl); abstract function destroy($id); abstract function cleanup(); } abstract class AbstractSessionStorageBackend extends AbstractSessionHandler { // Record we cache between read & update/write private $record; protected function onEvent($event) { return $this->callbackOn($event); } public function getRecord($id, $autocreate = false) { if (!isset($this->record) || !is_object($this->record) // Mismatch here means new session id || strcmp($id, $this->record->getId())) $this->record = static::lookupRecord($id, $autocreate, $this); return $this->record; } // This is the wrapper for to ask the backend to lookup or // create/init a record when not found protected function getRecordOrCreate($id) { return $this->getRecord($id, true); } public function read($id) { // we're auto creating the record if it doesn't exist so we can // have a new cached recoed on write/update. return (($record = $this->getRecordOrCreate($id)) && !$record->isNew()) ? $record->getData() : ''; } public function update($id, $data) { if (!($record = $this->getRecord($id))) return false; // Upstream backend can overwrite saving the record via pre_save hook // depending on the type of session or class of user etc. if ($this->pre_save($record) === false) return true; // record is being ignored // Set id & data $record->setId($id); $record->setData($data); // Ask backend to save the record if (!$this->saveRecord($record)) return false; // See if we need to send the record to secondary backend to // send the record to for logging or audit reasons or whatever! try { if (isset($this->secondary)) $this->secondary->saveRecord($record, true); } catch (\Trowable $t) { // Ignore any BS! } // clear cache $this->record = null; return true; } public function expire($id, $ttl) { // Destroy session record if expire is now. if ($ttl == 0) return $this->destroy($id); if (!$this->expireRecord($id, $ttl)) return false; try { if (isset($this->secondary)) $this->secondary->expireRecord($id, $ttl); } catch (\Trowable $t) { // Ignore any BS! } return true; } public function destroy($id) { if (!($this->destroyRecord($id))) return false; try { if (isset($this->secondary)) $this->secondary->destroyRecord($id); } catch (\Trowable $t) { // Ignore any BS! } return true; } public function cleanup() { $this->cleanupExpiredRecords(); try { if (isset($this->secondary)) $this->secondary->cleanupExpiredRecords(); } catch (\Trowable $t) { // Ignore any BS! } return true; } // Backend must implement lookup method to return a record that // implements SessionRecordInterface. abstract function lookupRecord($id, $autocreate); // save record abstract function saveRecord($record, $secondary = false); // writeRecord is useful when replicating records without the need // to transcode it when backends are different abstract function writeRecord($id, $data); // expireRecord abstract function expireRecord($id, $ttl); // Backend should implement destroyRecord that takes $id to avoid // the need to do record lookup abstract function destroyRecord($id); // Clear expired records - backend knows best how abstract function cleanupExpiredRecords(); } abstract class AbstractMemcacheSessionStorageBackend extends AbstractSessionStorageBackend { private $memcache; private $servers = []; public function __construct($options) { parent::__construct($options); // Make sure we have memcache module installed if (!extension_loaded('memcache')) throw new Exception('Memcache extension is missing'); // Require servers to be defined if (!isset($options['servers']) || !is_array($options['servers']) || !count($options['servers'])) throw new Exception('Memcache severs required'); // Init Memchache module $this->memcache = new \Memcache(); // Add servers foreach ($options['servers'] as $server) { list($host, $port) = explode(':', $server); // Use port '0' for unix sockets if (strpos($host, '/') !== false) $port = 0; elseif (!$port) $port = ini_get('memcache.default_port') ?: 11211; $this->addServer(trim($host), (int) trim($port)); } if (!$this->getNumServers()) throw new Exception('At least one memcache severs required'); // Init Memchache module $this->memcache = new \Memcache(); } protected function addServer($host, $port) { // TODO: Support add options // FIXME: Crash or warn if invalid $host or $port // Cache Servers locally $this->servers[] = [$host, $port]; // Add Server $this->memcache->addServer($host, $port); } protected function getNumServers() { return count($this->getServers()); } protected function getServers() { return $this->servers; } protected function getKey($id) { return sha1($id.SECRET_SALT); } protected function get($id) { // get key $key = $this->getKey($id); // Attempt distributed read $data = $this->memcache->get($key); // Read from other servers on failure if ($data === false && $this->getNumServers()) { foreach ($this->getServers() as $server) { list($host, $port) = $server; $this->memcache->pconnect($host, $port); if ($data = $this->memcache->get($key)) break; } } return $data; } protected function set($id, $data) { // Since memchache takes care of carbage collection internally // we want to make sure we set data to expire based on the session's // maxidletime (if available) otherwise it defailts to SESSION_TTL. // Memchache has a maximum ttl of 30 days $ttl = min($this->getMaxlife(), 2592000); $key = $this->getKey($id); foreach ($this->getServers() as $server) { list($host, $port) = $server; $this->memcache->pconnect($host, $port); if (!$this->memcache->replace($key, $data, 0, $ttl)) $this->memcache->set($key, $data, 0, $ttl); } // FIXME: Return false if we fail to write to at least one server return true; } public function expireRecord($id, $ttl) { return true; } public function destroyRecord($id) { $key = $this->getKey($id); foreach ($this->getServers() as $server) { list($host, $port) = $server; $this->memcache->pconnect($host, $port); $this->memcache->replace($key, '', 0, 1); $this->memcache->delete($key, 0); } return true; } public function cleanupExpiredRecords() { // Memcache does this automatically return true; } abstract function writeRecord($id, $data); abstract function saveRecord($record, $secondary = false); } /* * NoopSessionHandler * * Use this session handler when you don't care about session data. * */ class NoopSessionStorageBackend extends AbstractSessionHandler { public function read($id) { return ""; } public function update($id, $data) { return true; } public function expire($id, $ttl) { return true; } public function destroy($id) { return true; } public function gc($maxlife) { return true; } public function cleanup() { return true; } } // Delegate everything to PHP Default SessionHandler class SystemSessionHandler extends \SessionHandler { public function __construct() { } } }