RequireOnceUnitTestFile('./iSessionHandlerExtensionExamples.php'); $this->aFiles = []; $this->oTag = new ContextTag(ContextTag::TAG_REST); } protected function tearDown(): void { parent::tearDown(); $this->oTag = null; foreach ($this->aFiles as $sFile) { if (is_file($sFile)) { @unlink($sFile); } } } private function CreateUserAndLogIn(): ?string { $_SESSION = []; $oUser = $this->CreateContactlessUser("admin".uniqid(), 1, "1234@Abcdefg"); \UserRights::Login($oUser->Get('login')); return $oUser->GetKey(); } private function GenerateSessionContent(SessionHandler $oSessionHandler, ?string $sPreviousFileVersionContent): ?string { return $this->InvokeNonPublicMethod(SessionHandler::class, "generate_session_content", $oSessionHandler, $aArgs = [$sPreviousFileVersionContent]); } /* * @covers SessionHandler::generate_session_content */ public function testGenerateSessionContentNoUserLoggedIn() { $oSessionHandler = new SessionHandler(); $sContent = $this->GenerateSessionContent($oSessionHandler, null); $this->assertNull($sContent, "Session content should be null when there is no user logged in"); } public function GenerateSessionContentCorruptedPreviousFileContentProvider() { return [ 'not a json' => ["not a json"], 'not an array' => [json_encode("not an array")], 'array without creation_time key' => [json_encode([])], ]; } /** * @covers SessionHandler::generate_session_content * @dataProvider GenerateSessionContentCorruptedPreviousFileContentProvider */ public function testGenerateSessionContent_SessionFileRepairment(?string $sFileContent) { $sUserId = $this->CreateUserAndLogIn(); $oSessionHandler = new SessionHandler(); Session::Set('login_mode', 'foo_login_mode'); $sContent = $this->GenerateSessionContent($oSessionHandler, $sFileContent); $this->assertNotNull($sContent, 'Should not return null'); $aJson = json_decode($sContent, true); $this->assertNotEquals(false, $aJson, 'Should return a valid json string, found: '.$sContent); $this->assertEquals($sUserId, $aJson['user_id'] ?? '', "Should report the login of the logged in user in [user_id]: $sContent"); $this->assertEquals(ContextTag::TAG_REST, $aJson['context'] ?? '', "Should report the context tag(s) in [context]: $sContent"); $this->assertIsInt($aJson['creation_time'] ?? '', "Should report the session start timestamp in [creation_time]: $sContent"); $this->assertEquals('foo_login_mode', $aJson['login_mode'] ?? '', "Should report the current login mode in [login_mode]: $sContent"); } /* * @covers SessionHandler::generate_session_content */ public function testGenerateSessionContent() { $sUserId = $this->CreateUserAndLogIn(); $oSessionHandler = new SessionHandler(); Session::Set('login_mode', 'foo_login_mode'); //first time $sFirstContent = $this->GenerateSessionContent($oSessionHandler, null); $this->assertNotNull($sFirstContent, 'Should not return null'); $aJson = json_decode($sFirstContent, true); $this->assertNotEquals(false, $aJson, 'Should return a valid json string, found: '.$sFirstContent); $this->assertEquals($sUserId, $aJson['user_id'] ?? '', "Should report the login of the logged in user in [user_id]: $sFirstContent"); $this->assertEquals(ContextTag::TAG_REST, $aJson['context'] ?? '', "Should report the context tag(s) in [context]: $sFirstContent"); $this->assertIsInt($aJson['creation_time'] ?? '', "Should report the session start timestamp in [creation_time]: $sFirstContent"); $this->assertEquals('foo_login_mode', $aJson['login_mode'] ?? '', "Should report the current login mode in [login_mode]: $sFirstContent"); $iFirstSessionCreationTime = $aJson['creation_time']; // Switch context + change user id via impersonation // check it is still tracked in session files $oOtherUser = $this->CreateContactlessUser("admin".uniqid(), 1, "1234@Abcdefg"); $this->assertTrue(\UserRights::Impersonate($oOtherUser->Get('login')), "Failed to execute impersonate on: ".$oOtherUser->Get('login')); $oTag2 = new ContextTag(ContextTag::TAG_SYNCHRO); $sNewContent = $this->GenerateSessionContent($oSessionHandler, $sFirstContent); $this->assertNotNull($sNewContent, 'Should not return null'); $aJson = json_decode($sNewContent, true); $this->assertNotEquals(false, $aJson, 'Should return a valid json string, found: '.$sNewContent); $this->assertEquals(ContextTag::TAG_REST.'|'.ContextTag::TAG_SYNCHRO, $aJson['context'] ?? '', "After impersonation, should report the new context tags in [context]: $sNewContent"); $this->assertEquals($iFirstSessionCreationTime, $aJson['creation_time'] ?? '', "After impersonation, should still report the the session start timestamp in [creation_time]: $sNewContent"); $this->assertEquals('foo_login_mode', $aJson['login_mode'] ?? '', "After impersonation, should still report the login mode in [login_mode]: $sNewContent"); $this->assertEquals($oOtherUser->GetKey(), $aJson['user_id'] ?? '', "Should report the impersonate user in [user_id]: $sNewContent"); } private function touchSessionFile(SessionHandler $oSessionHandler, $session_id): ?string { $sRes = $this->InvokeNonPublicMethod(SessionHandler::class, "touch_session_file", $oSessionHandler, $aArgs = [$session_id]); if (!is_null($sRes) && is_file($sRes)) { // Record the file for cleanup on tearDown $this->aFiles[] = $sRes; } clearstatcache(); return $sRes; } /* * @covers SessionHandler::touch_session_file */ public function testTouchSessionFile_NoUserLoggedIn() { $oSessionHandler = new SessionHandler(); $session_id = uniqid(); $sFile = $this->touchSessionFile($oSessionHandler, $session_id); $this->assertEquals(true, is_file($sFile), "Should return a file name: '$sFile' is not a valid file name"); $sContent = file_get_contents($sFile); $this->assertEquals(null, $sContent, 'Should create an empty file, found: '.$sContent); } /* * @covers SessionHandler::touch_session_file */ public function testTouchSessionFile_UserLoggedIn() { $sUserId = $this->CreateUserAndLogIn(); Session::Set('login_mode', 'foo_login_mode'); $oSessionHandler = new SessionHandler(); $session_id = uniqid(); $sFile = $this->touchSessionFile($oSessionHandler, $session_id); $this->assertEquals(true, is_file($sFile), "Should return a file name: '$sFile' is not a valid file name"); $sFirstContent = file_get_contents($sFile); $iFirstCTime = filectime($sFile) - 1; // Set it in the past to check that it will be further updated (without the need to sleep...) touch($sFile, $iFirstCTime); $this->assertNotNull($sFirstContent, 'Should not return null'); $aJson = json_decode($sFirstContent, true); $this->assertNotEquals(false, $aJson, 'Should return a valid json string, found: '.$sFirstContent); $this->assertEquals($sUserId, $aJson['user_id'] ?? '', "Should report the login of the logged in user in [user_id]: $sFirstContent"); $this->assertStringContainsString(ContextTag::TAG_REST, $aJson['context'] ?? '', "Should report the context tag(s) in [context]: $sFirstContent"); $this->assertIsInt($aJson['creation_time'] ?? '', "Should report the session start timestamp in [creation_time]: $sFirstContent"); $this->assertEquals('foo_login_mode', $aJson['login_mode'] ?? '', "Should report the current login mode in [login_mode]: $sFirstContent"); $this->touchSessionFile($oSessionHandler, $session_id); $sNewContent = file_get_contents($sFile); $this->assertEquals($sFirstContent, $sNewContent, 'On successive calls, should not modify an existing session file'); $this->assertGreaterThan($iFirstCTime, filectime($sFile), 'On successive calls, should have changed the file ctime'); } /** * @covers SessionHandler::touch_session_file */ public function testTouchSessionFileWithEmptySessionId() { $this->CreateUserAndLogIn(); Session::Set('login_mode', 'toto'); $oSessionHandler = new SessionHandler(); $this->assertNull($this->touchSessionFile($oSessionHandler, ''), 'Should return null when session id is an empty string'); $this->assertNull($this->touchSessionFile($oSessionHandler, false), 'Should return null when session id (boolean) false'); } private function GetFilePath(SessionHandler $oSessionHandler, $session_id): string { $sFile = $this->InvokeNonPublicMethod(SessionHandler::class, "get_file_path", $oSessionHandler, $aArgs = [$session_id]); // Record file for cleanup on tearDown $this->aFiles[] = $sFile; return $sFile; } public function GgcWithTimeLimitProvider() { return [ 'no cleanup time limit' => [ 'iTimeLimit' => -1, 'iExpectedProcessed' => 2, ], 'cleanup time limit in the pass => first file removed only' => [ 'iTimeLimit' => time() - 1, 'iExpectedProcessed' => 1, ], ]; } /** * @covers SessionHandler::gc_with_time_limit * @covers SessionHandler::list_session_files * @dataProvider GgcWithTimeLimitProvider */ public function testGgcWithTimeLimit($iTimeLimit, $iExpectedProcessed) { $oSessionHandler = new SessionHandler(); //remove all first $oSessionHandler->gc_with_time_limit(-1); $this->assertEquals([], $oSessionHandler->list_session_files(), 'list_session_files should report no file at startup'); $max_lifetime = 1440; $iNbExpiredFiles = 2; $iNbFiles = 5; $iExpiredTimeStamp = time() - $max_lifetime - 1; for ($i = 0; $i < $iNbFiles; $i++) { $sFile = $this->GetFilePath($oSessionHandler, uniqid()); file_put_contents($sFile, "fakedata"); if ($iNbExpiredFiles > 0) { $iNbExpiredFiles--; touch($sFile, $iExpiredTimeStamp); } } $aFoundSessionFiles = $oSessionHandler->list_session_files(); $this->assertEquals($iNbFiles, sizeof($aFoundSessionFiles), 'list_session_files should reports all files'); foreach ($aFoundSessionFiles as $sFile) { $this->assertTrue(is_file($sFile), 'list_session_files should return a valid file paths, found: '.$sFile); } $iProcessed = $oSessionHandler->gc_with_time_limit($max_lifetime, $iTimeLimit); $this->assertEquals($iExpectedProcessed, $iProcessed, 'gc_with_time_limit should report the count of expired files'); $this->assertEquals($iNbFiles - $iExpectedProcessed, sizeof($oSessionHandler->list_session_files()), 'gc_with_time_limit should actually remove all processed files'); } public function testGetSessionHandlerExtension_NoExtension() { \utils::GetConfig()->Set('sessions_tracking.session_handler_extension', ''); $oSessionHandler = new SessionHandler(); $oSessionHandlerExtension = $this->InvokeNonPublicMethod(SessionHandler::class, "GetSessionHandlerExtension", $oSessionHandler); $this->assertNull($oSessionHandlerExtension, "by default no extension"); } public function testGetSessionHandlerExtension_InvalidClassExtension() { \utils::GetConfig()->Set('sessions_tracking.session_handler_extension', 'dddf'); $oSessionHandler = new SessionHandler(); $oSessionHandlerExtension = $this->InvokeNonPublicMethod(SessionHandler::class, "GetSessionHandlerExtension", $oSessionHandler); $this->assertNull($oSessionHandlerExtension); } public function testGetSessionHandlerExtension_InvalidExtension() { \utils::GetConfig()->Set('sessions_tracking.session_handler_extension', Session::class); $oSessionHandler = new SessionHandler(); $oSessionHandlerExtension = $this->InvokeNonPublicMethod(SessionHandler::class, "GetSessionHandlerExtension", $oSessionHandler); $this->assertNull($oSessionHandlerExtension); } public function testGetSessionHandlerExtension_OK() { \utils::GetConfig()->Set('sessions_tracking.session_handler_extension', 'Combodo\iTop\Test\UnitTest\SessionTracker\BasicSessionHandlerExtension'); $oSessionHandler = new SessionHandler(); $oSessionHandlerExtension = $this->InvokeNonPublicMethod(SessionHandler::class, "GetSessionHandlerExtension", $oSessionHandler); $this->assertNotNull($oSessionHandlerExtension, "by default no extension"); $this->assertTrue($oSessionHandlerExtension instanceof iSessionHandlerExtension); $this->assertInstanceOf(BasicSessionHandlerExtension::class, $oSessionHandlerExtension); } public function testGenerateSessionContent_WithAdditionalDataProvidedBySessionHandlerExtension() { \utils::GetConfig()->Set('sessions_tracking.session_handler_extension', 'Combodo\iTop\Test\UnitTest\SessionTracker\BasicSessionHandlerExtension'); $sUserId = $this->CreateUserAndLogIn(); $oSessionHandler = new SessionHandler(); Session::Set('login_mode', 'foo_login_mode'); //first time $sFirstContent = $this->GenerateSessionContent($oSessionHandler, null); $this->assertNotNull($sFirstContent, 'Should not return null'); $aJson = json_decode($sFirstContent, true); $this->assertNotEquals(false, $aJson, 'Should return a valid json string, found: '.$sFirstContent); $this->assertEquals($sUserId, $aJson['user_id'] ?? '', "Should report the login of the logged in user in [user_id]: $sFirstContent"); $this->assertEquals(ContextTag::TAG_REST, $aJson['context'] ?? '', "Should report the context tag(s) in [context]: $sFirstContent"); $this->assertIsInt($aJson['creation_time'] ?? '', "Should report the session start timestamp in [creation_time]: $sFirstContent"); $this->assertEquals('foo_login_mode', $aJson['login_mode'] ?? '', "Should report the current login mode in [login_mode]: $sFirstContent"); $this->assertEquals('gabuzomeu', $aJson['shadok'] ?? '', "Should report the current login mode in [shadok]: $sFirstContent"); } public function testGenerateSessionContentWithPreviousNonArrayContentAndWithAdditionalDataProvidedBySessionHandlerExtension() { \utils::GetConfig()->Set('sessions_tracking.session_handler_extension', 'Combodo\iTop\Test\UnitTest\SessionTracker\BasicSessionHandlerExtension'); $this->CreateUserAndLogIn(); $oSessionHandler = new SessionHandler(); $sContent = $this->GenerateSessionContent($oSessionHandler, json_encode(null)); $this->assertNotNull($sContent, "Call to CompleteSessionData should NOT fail due to Argument #1 (\$aJson) must be of type array, null given"); } }