Files
iTop/sources/SessionTracker/SessionHandler.php

277 lines
6.8 KiB
PHP

<?php
namespace Combodo\iTop\SessionTracker;
use Combodo\iTop\Application\Helper\Session;
use ContextTag;
use Exception;
use IssueLog;
use ReturnTypeWillChange;
use UserRights;
use utils;
/**
* Class SessionHandler:
* defaut PHP SessionHandler already relies on files that are accessible by iTop.
* this new iTop SessionHandler creates additional session files that are located under iTop folders.
* these new session files are meant to monitor the application and contain additional data:
* - current user id
* - context
* - login_mode
* - session creation timestamp
*
* @author Olivier Dain <olivier.dain@combodo.com>
* @package Combodo\iTop\SessionTracker
* @since 3.1.1 3.2.0 N°6901
*/
class SessionHandler extends \SessionHandler
{
/**
* @param string $session_id
*
* @return bool
*/
#[ReturnTypeWillChange]
public function destroy($session_id): bool
{
IssueLog::Debug("Destroy PHP session", \LogChannels::SESSIONTRACKER, [
'session_id' => $session_id,
]);
$bRes = parent::destroy($session_id);
if ($bRes) {
$this->unlink_session_file($session_id);
}
return $bRes;
}
/**
* @param int $max_lifetime
*/
#[ReturnTypeWillChange]
public function gc($max_lifetime): bool
{
IssueLog::Debug("Run PHP sessions garbage collector", \LogChannels::SESSIONTRACKER, [
'max_lifetime' => $max_lifetime,
]);
$iRes = parent::gc($max_lifetime);
$this->gc_with_time_limit($max_lifetime);
return $iRes;
}
/**
* @param string $save_path
* @param string $session_name
*/
#[ReturnTypeWillChange]
public function open($save_path, $session_name): bool
{
$bRes = parent::open($save_path, $session_name);
$session_id = session_id();
IssueLog::Debug("Open PHP session", \LogChannels::SESSIONTRACKER, [
'session_id' => $session_id,
]);
if ($bRes) {
$this->touch_session_file($session_id);
}
return $bRes;
}
/**
* @param string $session_id
* @param string $data
*
* @return bool
*/
#[ReturnTypeWillChange]
public function write($session_id, $data): bool
{
$bRes = parent::write($session_id, $data);
IssueLog::Debug("Write PHP session", \LogChannels::SESSIONTRACKER, [
'session_id' => $session_id,
'data' => $data,
]);
if ($bRes) {
$this->touch_session_file($session_id);
}
return $bRes;
}
public static function session_set_save_handler(): void
{
if (false === utils::GetConfig()->Get('sessions_tracking.enabled')) {
//feature disabled
return;
}
$sessionhandler = new SessionHandler();
session_set_save_handler($sessionhandler, true);
$iThreshold = utils::GetConfig()->Get('sessions_tracking.gc_threshold');
$iThreshold = min(100, $iThreshold);
$iThreshold = max(1, $iThreshold);
if ((100 != $iThreshold) && (rand(1, 100) > $iThreshold)) {
return;
}
$iMaxLifetime = ini_get('session.gc_maxlifetime') ?? 60;
$iMaxDurationInSeconds = utils::GetConfig()->Get('sessions_tracking.gc_duration_in_seconds');
$sessionhandler->gc_with_time_limit($iMaxLifetime, time() + $iMaxDurationInSeconds);
}
private function generate_session_content(?string $sPreviousFileVersionContent): ?string
{
try {
$sUserId = UserRights::GetUserId();
if (null === $sUserId) {
return null;
}
// Default value in case of
// - First time file creation
// - Data corruption (not a json / not an array / no previous creation_time key)
$iCreationTime = time();
$aJson=[];
if (! is_null($sPreviousFileVersionContent)) {
$aJson = json_decode($sPreviousFileVersionContent, true);
if (is_array($aJson)){
if (array_key_exists('creation_time', $aJson)) {
$iCreationTime = $aJson['creation_time'];
}
} else {
$aJson=[];
}
}
$aData = [
'login_mode' => Session::Get('login_mode'),
'user_id' => $sUserId,
'creation_time' => $iCreationTime,
'context' => implode('|', ContextTag::GetStack())
];
$oiSessionHandlerExtension = $this->GetSessionHandlerExtension();
if (! is_null($oiSessionHandlerExtension)){
$oiSessionHandlerExtension->CompleteSessionData($aJson, $aData);
}
return json_encode (
$aData
);
} catch(Exception $e) {
IssueLog::Error(__METHOD__, null, [ 'error' => $e->getMessage() ]);
}
return null;
}
private function GetSessionHandlerExtension() : ?iSessionHandlerExtension
{
$sSessionHandlerExtensionClass = utils::GetConfig()->Get('sessions_tracking.session_handler_extension');
if (strlen($sSessionHandlerExtensionClass) !=0){
try{
if (! class_exists($sSessionHandlerExtensionClass)){
throw new \Exception("Cannot find class");
}
/** @var iSessionHandlerExtension $oSessionHandlerExtension */
$oSessionHandlerExtension = new $sSessionHandlerExtensionClass;
if ($oSessionHandlerExtension instanceof iSessionHandlerExtension){
return $oSessionHandlerExtension;
}
throw new \Exception("Not an instance of iSessionHandlerExtension");
} catch(\Exception $e) {
IssueLog::Error(__METHOD__ . ': cannot instanciate iSessionHandlerExtension', null, ['sessions_tracking.session_handler_extension' => $sSessionHandlerExtensionClass]);
}
}
return null;
}
private function get_file_path($session_id): string
{
return utils::GetDataPath()."sessions/session_$session_id";
}
private function touch_session_file($session_id): ?string
{
if (strlen($session_id) == 0) {
return null;
}
clearstatcache();
if (! is_dir(utils::GetDataPath()."sessions")) {
@mkdir(utils::GetDataPath()."sessions");
}
$sFilePath = $this->get_file_path($session_id);
$sPreviousFileVersionContent = null;
if (is_file($sFilePath)) {
$sPreviousFileVersionContent = file_get_contents($sFilePath);
}
$sNewContent = $this->generate_session_content($sPreviousFileVersionContent);
if (is_null($sNewContent) || ($sPreviousFileVersionContent === $sNewContent)) {
@touch($sFilePath);
} else {
file_put_contents($sFilePath, $sNewContent);
}
return $sFilePath;
}
private function unlink_session_file($session_id)
{
$sFilePath = $this->get_file_path($session_id);
if (is_file($sFilePath)) {
@unlink($sFilePath);
}
}
/**
* @param int $max_lifetime
* @param int $iTimeLimit Unix timestamp of time limit not to exceed. -1 for no limit.
*
* @return int
*/
public function gc_with_time_limit(int $max_lifetime, int $iTimeLimit = -1): int
{
$aFiles = $this->list_session_files();
$iProcessed = 0;
$now = time();
foreach ($aFiles as $sFile) {
if ($now - filemtime($sFile) > $max_lifetime) {
@unlink($sFile);
$iProcessed++;
}
if (-1 !== $iTimeLimit && time() > $iTimeLimit) {
//cleanup processing has to stop after $iTimeLimit
break;
}
}
return $iProcessed;
}
public function list_session_files(): array
{
clearstatcache();
if (! is_dir(utils::GetDataPath()."sessions")) {
@mkdir(utils::GetDataPath()."sessions");
}
return glob(utils::GetDataPath()."sessions/session_*");
}
}