mirror of
https://github.com/Combodo/iTop.git
synced 2026-03-05 09:04:19 +01:00
Compare commits
14 Commits
develop
...
feature/as
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fce1348d44 | ||
|
|
14d748aa97 | ||
|
|
6f79e1842c | ||
|
|
ecd4d77483 | ||
|
|
1a83149f14 | ||
|
|
828dbcf9b5 | ||
|
|
e4b8f31af0 | ||
|
|
393643b6f9 | ||
|
|
259c82b88e | ||
|
|
0001f078c3 | ||
|
|
a7ebf30746 | ||
|
|
d92bdf7b89 | ||
|
|
399f5bc449 | ||
|
|
d65412d61c |
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Login page extensibility
|
||||
*
|
||||
* @api
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 3.x.x
|
||||
*/
|
||||
interface iTokenLoginUIExtension
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
* @api
|
||||
*/
|
||||
public function GetTokenInfo(): array;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @api
|
||||
*/
|
||||
public function GetUserLogin(array $aTokenInfo): string;
|
||||
}
|
||||
@@ -9,7 +9,7 @@ use Combodo\iTop\Application\Helper\Session;
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class LoginBasic extends AbstractLoginFSMExtension
|
||||
class LoginBasic extends AbstractLoginFSMExtension implements iTokenLoginUIExtension
|
||||
{
|
||||
/**
|
||||
* Return the list of supported login modes for this plugin
|
||||
@@ -120,4 +120,20 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
}
|
||||
return [$sAuthUser, $sAuthPwd];
|
||||
}
|
||||
|
||||
public function GetTokenInfo(): array
|
||||
{
|
||||
return $this->GetAuthUserAndPassword();
|
||||
}
|
||||
|
||||
public function GetUserLogin(array $aTokenInfo): string
|
||||
{
|
||||
$sLogin = $aTokenInfo[0];
|
||||
$sLoginMode = 'basic';
|
||||
if (UserRights::CheckCredentials($sLogin, $aTokenInfo[1], $sLoginMode, 'internal')) {
|
||||
return $sLogin;
|
||||
}
|
||||
|
||||
throw new Exception("Cannot CheckCredentials user login ($sLogin) with ($sLoginMode) mode");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use Combodo\iTop\Application\Helper\Session;
|
||||
*
|
||||
* @since 2.7.0
|
||||
*/
|
||||
class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension, iTokenLoginUIExtension
|
||||
{
|
||||
private $bForceFormOnError = false;
|
||||
|
||||
@@ -32,8 +32,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
protected function OnReadCredentials(&$iErrorCode)
|
||||
{
|
||||
if (!Session::IsSet('login_mode') || Session::Get('login_mode') == 'form') {
|
||||
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
|
||||
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
|
||||
list($sAuthUser, $sAuthPwd) = $this->GetTokenInfo();
|
||||
if ($this->bForceFormOnError || empty($sAuthUser) || empty($sAuthPwd)) {
|
||||
if (array_key_exists('HTTP_X_COMBODO_AJAX', $_SERVER)) {
|
||||
// X-Combodo-Ajax is a special header automatically added to all ajax requests
|
||||
@@ -65,8 +64,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
protected function OnCheckCredentials(&$iErrorCode)
|
||||
{
|
||||
if (Session::Get('login_mode') == 'form') {
|
||||
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
|
||||
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
|
||||
list($sAuthUser, $sAuthPwd) = $this->GetTokenInfo();
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal')) {
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
@@ -145,4 +143,23 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
|
||||
return $oLoginContext;
|
||||
}
|
||||
|
||||
public function GetTokenInfo(): array
|
||||
{
|
||||
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
|
||||
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
|
||||
return [$sAuthUser, $sAuthPwd];
|
||||
}
|
||||
|
||||
public function GetUserLogin(array $aTokenInfo): string
|
||||
{
|
||||
$sLogin = $aTokenInfo[0];
|
||||
$sLoginMode = 'form';
|
||||
|
||||
if (UserRights::CheckCredentials($sLogin, $aTokenInfo[1], $sLoginMode, 'internal')) {
|
||||
return $sLogin;
|
||||
}
|
||||
|
||||
throw new Exception("Cannot CheckCredentials user login ($sLogin) with ($sLoginMode) mode");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use Combodo\iTop\Application\Helper\Session;
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class LoginURL extends AbstractLoginFSMExtension
|
||||
class LoginURL extends AbstractLoginFSMExtension implements iTokenLoginUIExtension
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
@@ -29,9 +29,8 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
protected function OnModeDetection(&$iErrorCode)
|
||||
{
|
||||
if (!Session::IsSet('login_mode') && !$this->bErrorOccurred) {
|
||||
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
|
||||
if (!empty($sAuthUser) && !empty($sAuthPwd)) {
|
||||
list($sAuthUser, $sAuthPwd) = $this->GetTokenInfo();
|
||||
{
|
||||
Session::Set('login_mode', 'url');
|
||||
}
|
||||
}
|
||||
@@ -49,8 +48,7 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
protected function OnCheckCredentials(&$iErrorCode)
|
||||
{
|
||||
if (Session::Get('login_mode') == 'url') {
|
||||
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
|
||||
list($sAuthUser, $sAuthPwd) = $this->GetTokenInfo();
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal')) {
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
@@ -84,4 +82,22 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
public function GetTokenInfo(): array
|
||||
{
|
||||
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
|
||||
return [$sAuthUser, $sAuthPwd];
|
||||
}
|
||||
|
||||
public function GetUserLogin(array $aTokenInfo): string
|
||||
{
|
||||
$sLogin = $aTokenInfo[0];
|
||||
$sLoginMode = 'url';
|
||||
if (UserRights::CheckCredentials($sLogin, $aTokenInfo[1], $sLoginMode, 'internal')) {
|
||||
return $sLogin;
|
||||
}
|
||||
|
||||
throw new Exception("Cannot CheckCredentials user login ($sLogin) with ($sLoginMode) mode");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,6 +530,23 @@ class LoginWebPage extends NiceWebPage
|
||||
return $aPlugins;
|
||||
}
|
||||
|
||||
public static function GetCurrentLoginPlugin(string $sCurrentLoginMode): iLoginExtension
|
||||
{
|
||||
/** @var iLoginExtension $oLoginExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins('iLoginFSMExtension') as $oLoginExtensionInstance) {
|
||||
$aLoginModes = $oLoginExtensionInstance->ListSupportedLoginModes();
|
||||
$aLoginModes = (is_array($aLoginModes) ? $aLoginModes : []);
|
||||
foreach ($aLoginModes as $sLoginMode) {
|
||||
// Keep only the plugins for the current login mode + before + after
|
||||
if ($sLoginMode == $sCurrentLoginMode) {
|
||||
return $oLoginExtensionInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new \Exception("should not happen");
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance Login Finite State Machine to the next step
|
||||
*
|
||||
|
||||
@@ -3226,4 +3226,43 @@ TXT
|
||||
|
||||
return $aTrace;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Ain Tohvri <https://mstdn.social/@tekkie>
|
||||
*
|
||||
* @since ???
|
||||
*/
|
||||
public static function ReadTail($sFilename, $iLines = 1): array
|
||||
{
|
||||
$handle = fopen($sFilename, "r");
|
||||
if (false === $handle) {
|
||||
throw new \Exception("Cannot read file $sFilename");
|
||||
}
|
||||
|
||||
$iLineCounter = $iLines;
|
||||
$iPos = -2;
|
||||
$bBeginning = false;
|
||||
$aLines = [];
|
||||
while ($iLineCounter > 0) {
|
||||
$sChar = " ";
|
||||
while ($sChar != "\n") {
|
||||
if (fseek($handle, $iPos, SEEK_END) == -1) {
|
||||
$bBeginning = true;
|
||||
break;
|
||||
}
|
||||
$sChar = fgetc($handle);
|
||||
$iPos--;
|
||||
}
|
||||
$iLineCounter--;
|
||||
if ($bBeginning) {
|
||||
rewind($handle);
|
||||
}
|
||||
$aLines[$iLines - $iLineCounter - 1] = fgets($handle);
|
||||
if ($bBeginning) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose($handle);
|
||||
return array_reverse($aLines);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,10 @@ if (PHP_VERSION_ID < 50600) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException($err);
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
@@ -3150,6 +3150,7 @@ return array(
|
||||
'iRestServiceProvider' => $baseDir . '/application/applicationextension.inc.php',
|
||||
'iScheduledProcess' => $baseDir . '/core/backgroundprocess.inc.php',
|
||||
'iSelfRegister' => $baseDir . '/core/userrights.class.inc.php',
|
||||
'iTokenLoginUIExtension' => $baseDir . '/application/applicationextension/login/iTokenLoginUIExtension.php',
|
||||
'iTopConfigParser' => $baseDir . '/core/iTopConfigParser.php',
|
||||
'iTopMutex' => $baseDir . '/core/mutex.class.inc.php',
|
||||
'iTopOwnershipLock' => $baseDir . '/core/ownershiplock.class.inc.php',
|
||||
|
||||
@@ -56,7 +56,7 @@ return array(
|
||||
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
|
||||
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
|
||||
'Pelago\\Emogrifier\\' => array($vendorDir . '/pelago/emogrifier/src'),
|
||||
'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-google/src', $vendorDir . '/league/oauth2-client/src'),
|
||||
'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-client/src', $vendorDir . '/league/oauth2-google/src'),
|
||||
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
|
||||
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
|
||||
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
|
||||
|
||||
@@ -314,8 +314,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
),
|
||||
'League\\OAuth2\\Client\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/league/oauth2-google/src',
|
||||
1 => __DIR__ . '/..' . '/league/oauth2-client/src',
|
||||
0 => __DIR__ . '/..' . '/league/oauth2-client/src',
|
||||
1 => __DIR__ . '/..' . '/league/oauth2-google/src',
|
||||
),
|
||||
'GuzzleHttp\\Psr7\\' =>
|
||||
array (
|
||||
@@ -3509,6 +3509,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'iRestServiceProvider' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
|
||||
'iScheduledProcess' => __DIR__ . '/../..' . '/core/backgroundprocess.inc.php',
|
||||
'iSelfRegister' => __DIR__ . '/../..' . '/core/userrights.class.inc.php',
|
||||
'iTokenLoginUIExtension' => __DIR__ . '/../..' . '/application/applicationextension/login/iTokenLoginUIExtension.php',
|
||||
'iTopConfigParser' => __DIR__ . '/../..' . '/core/iTopConfigParser.php',
|
||||
'iTopMutex' => __DIR__ . '/../..' . '/core/mutex.class.inc.php',
|
||||
'iTopOwnershipLock' => __DIR__ . '/../..' . '/core/ownershiplock.class.inc.php',
|
||||
|
||||
@@ -36,7 +36,8 @@ if ($issues) {
|
||||
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
||||
}
|
||||
}
|
||||
throw new \RuntimeException(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues)
|
||||
trigger_error(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues),
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
@@ -976,7 +976,7 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
|
||||
protected function AssertLastErrorLogEntryContains(string $sNeedle, string $sMessage = ''): void
|
||||
{
|
||||
$aLastLines = self::ReadTail(APPROOT.'/log/error.log');
|
||||
$aLastLines = Utils::ReadTail(APPROOT.'/log/error.log');
|
||||
$this->assertStringContainsString($sNeedle, $aLastLines[0], $sMessage);
|
||||
}
|
||||
|
||||
@@ -1470,4 +1470,21 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
@chmod($sConfigPath, 0440);
|
||||
@unlink($this->sConfigTmpBackupFile);
|
||||
}
|
||||
|
||||
protected function AddLoginModeAndSaveConfiguration($sLoginMode)
|
||||
{
|
||||
$aAllowedLoginTypes = $this->oiTopConfig->GetAllowedLoginTypes();
|
||||
if (!in_array($sLoginMode, $aAllowedLoginTypes)) {
|
||||
$aAllowedLoginTypes[] = $sLoginMode;
|
||||
$this->oiTopConfig->SetAllowedLoginTypes($aAllowedLoginTypes);
|
||||
$this->SaveItopConfFile();
|
||||
}
|
||||
}
|
||||
|
||||
protected function SaveItopConfFile()
|
||||
{
|
||||
@chmod($this->oiTopConfig->GetLoadedFile(), 0770);
|
||||
$this->oiTopConfig->WriteToFile();
|
||||
@chmod($this->oiTopConfig->GetLoadedFile(), 0440);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ use SetupUtils;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
|
||||
use Utils;
|
||||
|
||||
use const DEBUG_BACKTRACE_IGNORE_ARGS;
|
||||
|
||||
/**
|
||||
@@ -89,7 +91,7 @@ abstract class ItopTestCase extends KernelTestCase
|
||||
|
||||
if (method_exists('utils', 'GetConfig')) {
|
||||
// Reset the config by forcing the load from disk
|
||||
$oConfig = \utils::GetConfig(true);
|
||||
$oConfig = utils::GetConfig(true);
|
||||
if (method_exists('MetaModel', 'SetConfig')) {
|
||||
\MetaModel::SetConfig($oConfig);
|
||||
}
|
||||
@@ -625,32 +627,7 @@ abstract class ItopTestCase extends KernelTestCase
|
||||
*/
|
||||
protected static function ReadTail($sFilename, $iLines = 1)
|
||||
{
|
||||
$handle = fopen($sFilename, "r");
|
||||
$iLineCounter = $iLines;
|
||||
$iPos = -2;
|
||||
$bBeginning = false;
|
||||
$aLines = [];
|
||||
while ($iLineCounter > 0) {
|
||||
$sChar = " ";
|
||||
while ($sChar != "\n") {
|
||||
if (fseek($handle, $iPos, SEEK_END) == -1) {
|
||||
$bBeginning = true;
|
||||
break;
|
||||
}
|
||||
$sChar = fgetc($handle);
|
||||
$iPos--;
|
||||
}
|
||||
$iLineCounter--;
|
||||
if ($bBeginning) {
|
||||
rewind($handle);
|
||||
}
|
||||
$aLines[$iLines - $iLineCounter - 1] = fgets($handle);
|
||||
if ($bBeginning) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose($handle);
|
||||
return array_reverse($aLines);
|
||||
return Utils::ReadTail($sFilename, $iLines);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -710,4 +687,17 @@ abstract class ItopTestCase extends KernelTestCase
|
||||
|
||||
return $this->CallUrl($sUrl, $aPostFields, $aCurlOptions, $bXDebugEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a temporary file path. that will be cleaned up by tearDown()
|
||||
*
|
||||
* @return string: temporary file path: file prefix include phpunit test method name
|
||||
*/
|
||||
public function GetTemporaryFilePath(): string
|
||||
{
|
||||
$sPrefix = $this->getName(false);
|
||||
$sPath = tempnam(sys_get_temp_dir(), $sPrefix);
|
||||
$this->aFileToClean[] = $sPath;
|
||||
return $sPath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Webservices;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use Exception;
|
||||
use iTopMutex;
|
||||
use MetaModel;
|
||||
use utils;
|
||||
|
||||
/**
|
||||
* @group itopRequestMgmt
|
||||
* @group restApi
|
||||
* @group defaultProfiles
|
||||
*/
|
||||
class CronServiceTest extends ItopDataTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp(); // TODO: Change the autogenerated stub
|
||||
$this->RequireOnceItopFile('webservices/CronService.php');
|
||||
}
|
||||
|
||||
public function testIsStarted()
|
||||
{
|
||||
$sPath = $this->GetTemporaryFilePath();
|
||||
file_put_contents($sPath, file_get_contents(__DIR__.'/resources/cron_starting.log'));
|
||||
|
||||
$this->assertEquals(true, \CronService::GetInstance()->IsStarted($sPath));
|
||||
}
|
||||
|
||||
public function testIsFailed_MissingCredentialsFailure()
|
||||
{
|
||||
$sPath = $this->GetTemporaryFilePath();
|
||||
file_put_contents($sPath, file_get_contents(__DIR__.'/resources/cron_missingcreds_error.log'));
|
||||
|
||||
$this->assertEquals("Missing argument 'auth_user'", \CronService::GetInstance()->GetErrorMessage($sPath));
|
||||
$this->assertEquals(true, \CronService::GetInstance()->IsFailed($sPath));
|
||||
}
|
||||
|
||||
public static function ErrorProvider()
|
||||
{
|
||||
return [
|
||||
["Access wrong credentials ('user123')"],
|
||||
['gabuzomeu'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider ErrorProvider
|
||||
*/
|
||||
public function testIsFailed_AuthenticationFailure($sError)
|
||||
{
|
||||
$sPath = $this->GetTemporaryFilePath();
|
||||
$sContent = <<<LOG
|
||||
..
|
||||
..
|
||||
ERROR: $sError
|
||||
LOG;
|
||||
|
||||
file_put_contents($sPath, $sContent);
|
||||
|
||||
$this->assertEquals($sError, \CronService::GetInstance()->GetErrorMessage($sPath));
|
||||
$this->assertEquals(true, \CronService::GetInstance()->IsFailed($sPath));
|
||||
}
|
||||
|
||||
public static function CannotStartProvider()
|
||||
{
|
||||
return [
|
||||
["cron_alreadyrunning.log", 'Already running...'],
|
||||
['cron_maintenance.log', 'A maintenance is ongoing'],
|
||||
['cron_notanadmin.log', 'Access restricted to administrators'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider CannotStartProvider
|
||||
*/
|
||||
public function testCronCannotStart(string $sLogFile, $sError)
|
||||
{
|
||||
$sPath = $this->GetTemporaryFilePath();
|
||||
file_put_contents($sPath, file_get_contents(__DIR__.'/resources/'.$sLogFile));
|
||||
|
||||
$this->assertEquals($sError, \CronService::GetInstance()->GetErrorMessage($sPath));
|
||||
$this->assertEquals(true, \CronService::GetInstance()->IsFailed($sPath));
|
||||
}
|
||||
|
||||
public function testCronRunning()
|
||||
{
|
||||
$sPath = $this->GetTemporaryFilePath();
|
||||
$sContent = <<<LOG
|
||||
..
|
||||
..
|
||||
..
|
||||
..
|
||||
Starting: 1772551316 (2026-03-03 16:21:56)
|
||||
Creating backup: '/var/www/html/iTopLegacy/data/backups/auto/gabuzomeuuninstall-2026-03-03_16_21.tar.gz'
|
||||
backup: creating tmp dir '/var/www/html/iTopLegacy/data/tmp-backup-1024104636'
|
||||
backup: adding resource '/var/www/html/iTopLegacy/conf/production/config-itop.php'
|
||||
backup: adding resource '/var/www/html/iTopLegacy/data/production-modules/'
|
||||
Starting backup of localhost/gabuzomeuuninstall(suffix:'')
|
||||
backup: generate data file with command: "mysqldump" --defaults-extra-file="/tmp/itop-mysqldump-CVvm4G" --opt --skip-lock-tables --default-character-set=utf8mb4 --add-drop-database --single-transaction --host='localhost' --user=xxxxx --result-file='/var/www/html/iTopLegacy/data/tmp-backup-1024104636/itop-dump.sql' 'gabuzomeuuninstall'
|
||||
LOG;
|
||||
|
||||
file_put_contents($sPath, $sContent);
|
||||
|
||||
$this->assertEquals(null, \CronService::GetInstance()->GetErrorMessage($sPath));
|
||||
$this->assertEquals(false, \CronService::GetInstance()->IsFailed($sPath));
|
||||
}
|
||||
|
||||
public function testCronStopped()
|
||||
{
|
||||
|
||||
$sPath = $this->GetTemporaryFilePath();
|
||||
file_put_contents($sPath, file_get_contents(__DIR__.'/resources/cron_stopped.log'));
|
||||
|
||||
$this->assertEquals(null, \CronService::GetInstance()->GetErrorMessage($sPath));
|
||||
$this->assertEquals(false, \CronService::GetInstance()->IsFailed($sPath));
|
||||
}
|
||||
}
|
||||
318
tests/php-unit-tests/unitary-tests/webservices/CronTest.php
Normal file
318
tests/php-unit-tests/unitary-tests/webservices/CronTest.php
Normal file
@@ -0,0 +1,318 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Webservices;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use Exception;
|
||||
use iTopMutex;
|
||||
use MetaModel;
|
||||
use utils;
|
||||
|
||||
/**
|
||||
* @group itopRequestMgmt
|
||||
* @group restApi
|
||||
* @group defaultProfiles
|
||||
*/
|
||||
class CronTest extends ItopDataTestCase
|
||||
{
|
||||
public const USE_TRANSACTION = false;
|
||||
public const CREATE_TEST_ORG = false;
|
||||
|
||||
public static $sLogin;
|
||||
public static $sPassword = "Iuytrez9876543ç_è-(";
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->BackupConfiguration();
|
||||
$this->oiTopConfig->Set('log_level_min', 'Error');
|
||||
$this->oiTopConfig->Set('login_debug', true);
|
||||
$this->BackupConfiguration();
|
||||
|
||||
static::$sLogin = "rest-user-".date('dmYHis').uniqid();
|
||||
|
||||
$this->CreateTestOrganization();
|
||||
}
|
||||
|
||||
public function testRestWithFormMode()
|
||||
{
|
||||
$this->AddLoginModeAndSaveConfiguration('form');
|
||||
$this->CreateUserWithProfiles([self::$aURP_Profiles['Administrator'], self::$aURP_Profiles['REST Services User']]);
|
||||
$aPostFields = [
|
||||
'version' => '1.3',
|
||||
'auth_user' => static::$sLogin,
|
||||
'auth_pwd' => static::$sPassword,
|
||||
'json_data' => '{"operation": "list_operations"}',
|
||||
];
|
||||
|
||||
$sJSONResult = $this->CallItopUri("/webservices/rest.php", $aPostFields);
|
||||
|
||||
$this->assertEquals($this->GetExpectedRestResponse(), $sJSONResult);
|
||||
}
|
||||
|
||||
public function testRestWithBasicMode()
|
||||
{
|
||||
$this->AddLoginModeAndSaveConfiguration('basic');
|
||||
$this->CreateUserWithProfiles([self::$aURP_Profiles['Administrator'], self::$aURP_Profiles['REST Services User']]);
|
||||
$aPostFields = [
|
||||
'version' => '1.3',
|
||||
'json_data' => '{"operation": "list_operations"}',
|
||||
];
|
||||
|
||||
$sToken = base64_encode(sprintf("%s:%s", static::$sLogin, static::$sPassword));
|
||||
$aCurlOptions = [
|
||||
CURLOPT_HTTPHEADER => ["Authorization: Basic $sToken"],
|
||||
];
|
||||
|
||||
// Test regular JSON result
|
||||
$sJSONResult = $this->CallItopUri("/webservices/rest.php", $aPostFields, $aCurlOptions);
|
||||
|
||||
$this->assertEquals($this->GetExpectedRestResponse(), $sJSONResult);
|
||||
}
|
||||
|
||||
public function testRestWithUrlMode()
|
||||
{
|
||||
$this->AddLoginModeAndSaveConfiguration('url');
|
||||
$this->CreateUserWithProfiles([self::$aURP_Profiles['Administrator'], self::$aURP_Profiles['REST Services User']]);
|
||||
$aPostFields = [
|
||||
'version' => '1.3',
|
||||
'json_data' => '{"operation": "list_operations"}',
|
||||
];
|
||||
|
||||
$aGetFields = [
|
||||
'auth_user' => static::$sLogin,
|
||||
'auth_pwd' => static::$sPassword,
|
||||
];
|
||||
$sJSONResult = $this->CallItopUri("/webservices/rest.php?".http_build_query($aGetFields), $aPostFields);
|
||||
|
||||
$this->assertEquals($this->GetExpectedRestResponse(), $sJSONResult);
|
||||
}
|
||||
|
||||
public function testLaunchCronWithFormModeFailWhenNotAdmin()
|
||||
{
|
||||
$this->AddLoginModeAndSaveConfiguration('form');
|
||||
$this->CreateUserWithProfiles([self::$aURP_Profiles['REST Services User']]);
|
||||
|
||||
$sLogFileName = "crontest_".uniqid().'.log';
|
||||
$aPostFields = [
|
||||
'version' => '1.3',
|
||||
'auth_user' => static::$sLogin,
|
||||
'auth_pwd' => static::$sPassword,
|
||||
'verbose' => 1,
|
||||
'debug' => 1,
|
||||
'cron_log_file' => $sLogFileName,
|
||||
];
|
||||
|
||||
$sJSONResult = $this->CallItopUri("/webservices/asynchronously_cron.php", $aPostFields);
|
||||
|
||||
$this->assertEquals($this->GetExpectedCronResponse(), $sJSONResult);
|
||||
$sLogFile = $this->CheckLogFileIsGeneratedAndGetFullPath($sLogFileName);
|
||||
$this->CheckAdminAccessIssueWithCron($sLogFile);
|
||||
}
|
||||
|
||||
public function testLaunchCronWithBasicModeFailWhenNotAdmin()
|
||||
{
|
||||
$this->AddLoginModeAndSaveConfiguration('basic');
|
||||
$this->CreateUserWithProfiles([self::$aURP_Profiles['REST Services User']]);
|
||||
|
||||
$sLogFileName = "crontest_".uniqid().'.log';
|
||||
$aPostFields = [
|
||||
'version' => '1.3',
|
||||
'verbose' => 1,
|
||||
'debug' => 1,
|
||||
'cron_log_file' => $sLogFileName,
|
||||
];
|
||||
|
||||
$sToken = base64_encode(sprintf("%s:%s", static::$sLogin, static::$sPassword));
|
||||
$aCurlOptions = [
|
||||
CURLOPT_HTTPHEADER => ["Authorization: Basic $sToken"],
|
||||
];
|
||||
|
||||
$sJSONResult = $this->CallItopUri("/webservices/asynchronously_cron.php", $aPostFields, $aCurlOptions);
|
||||
|
||||
$this->assertEquals($this->GetExpectedCronResponse(), $sJSONResult);
|
||||
$sLogFile = $this->CheckLogFileIsGeneratedAndGetFullPath($sLogFileName);
|
||||
$this->CheckAdminAccessIssueWithCron($sLogFile);
|
||||
}
|
||||
|
||||
public function testLaunchCronWithUrlModeFailWhenNotAdmin()
|
||||
{
|
||||
$this->AddLoginModeAndSaveConfiguration('url');
|
||||
$this->CreateUserWithProfiles([self::$aURP_Profiles['REST Services User']]);
|
||||
|
||||
$sLogFileName = "crontest_".uniqid().'.log';
|
||||
$aPostFields = [
|
||||
'version' => '1.3',
|
||||
'verbose' => 1,
|
||||
'debug' => 1,
|
||||
'cron_log_file' => $sLogFileName,
|
||||
];
|
||||
|
||||
$aGetFields = [
|
||||
'auth_user' => static::$sLogin,
|
||||
'auth_pwd' => static::$sPassword,
|
||||
];
|
||||
|
||||
$sJSONResult = $this->CallItopUri("/webservices/asynchronously_cron.php?".http_build_query($aGetFields), $aPostFields);
|
||||
|
||||
$this->assertEquals($this->GetExpectedCronResponse(), $sJSONResult);
|
||||
$sLogFile = $this->CheckLogFileIsGeneratedAndGetFullPath($sLogFileName);
|
||||
$this->CheckAdminAccessIssueWithCron($sLogFile);
|
||||
}
|
||||
|
||||
public function ModeProvider()
|
||||
{
|
||||
return [
|
||||
'form' => [ 'LoginForm' ],
|
||||
'basic' => [ 'LoginBasic'],
|
||||
'url' => [ 'LoginURL'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider ModeProvider
|
||||
*/
|
||||
public function testGetUserLoginWithFormMode($sLoginModeClass)
|
||||
{
|
||||
$this->CreateUserWithProfiles([self::$aURP_Profiles['Administrator']]);
|
||||
|
||||
$oLoginMode = new $sLoginModeClass();
|
||||
$sUserLogin = $oLoginMode->GetUserLogin([static::$sLogin, static::$sPassword]);
|
||||
$this->assertEquals(static::$sLogin, $sUserLogin);
|
||||
}
|
||||
|
||||
public function testGetCronStatus_FailWhenNotAdmin()
|
||||
{
|
||||
$this->AddLoginModeAndSaveConfiguration('url');
|
||||
$this->CreateUserWithProfiles([self::$aURP_Profiles['REST Services User']]);
|
||||
|
||||
$sLogFileName = "crontest_".uniqid().'.log';
|
||||
$aPostFields = [
|
||||
'version' => '1.3',
|
||||
'verbose' => 1,
|
||||
'debug' => 1,
|
||||
];
|
||||
|
||||
$aGetFields = [
|
||||
'auth_user' => static::$sLogin,
|
||||
'auth_pwd' => static::$sPassword,
|
||||
];
|
||||
|
||||
$sJSONResult = $this->CallItopUri("/webservices/get_cron_status.php?".http_build_query($aGetFields), $aPostFields);
|
||||
|
||||
$this->assertEquals('{"message":"Access restricted to administrators"}', $sJSONResult);
|
||||
}
|
||||
|
||||
public function testGetCronStatus_FailWhenLogFileDoesNotExist()
|
||||
{
|
||||
$this->AddLoginModeAndSaveConfiguration('url');
|
||||
$this->CreateUserWithProfiles([self::$aURP_Profiles['Administrator']]);
|
||||
|
||||
$aPostFields = [
|
||||
'version' => '1.3',
|
||||
'verbose' => 1,
|
||||
'debug' => 1,
|
||||
'cron_log_file' => 'gabuzomeu.log',
|
||||
];
|
||||
|
||||
$aGetFields = [
|
||||
'auth_user' => static::$sLogin,
|
||||
'auth_pwd' => static::$sPassword,
|
||||
];
|
||||
|
||||
$sJSONResult = $this->CallItopUri("/webservices/get_cron_status.php?".http_build_query($aGetFields), $aPostFields);
|
||||
|
||||
$this->assertEquals('{"message":"Cannot read log file"}', $sJSONResult);
|
||||
}
|
||||
|
||||
public static function GetCronStatusProvider()
|
||||
{
|
||||
return [
|
||||
["cron_alreadyrunning.log", 'Already running...', "error"],
|
||||
["cron_dummyerror.log", 'Already running...', "error"],
|
||||
['cron_maintenance.log', 'A maintenance is ongoing', "error"],
|
||||
['cron_missingcreds_error.log', 'A maintenance is ongoing', "error"],
|
||||
['cron_notanadmin.log', 'Access restricted to administrators', "error"],
|
||||
['cron_starting.log', '', "running"],
|
||||
['cron_stopped.log', '', "stopped"],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider GetCronStatusProvider
|
||||
*/
|
||||
public function testGetCronStatus($sLogFilename, $expectedMsg, $expectedStatus)
|
||||
{
|
||||
$sLogFile = APPROOT."log/$sLogFilename";
|
||||
file_put_contents($sLogFile, file_get_contents(__DIR__.'/resources/'.$sLogFilename));
|
||||
$this->aFileToClean[] = $sLogFile;
|
||||
|
||||
$this->AddLoginModeAndSaveConfiguration('url');
|
||||
$this->CreateUserWithProfiles([self::$aURP_Profiles['Administrator']]);
|
||||
|
||||
$aPostFields = [
|
||||
'version' => '1.3',
|
||||
'verbose' => 1,
|
||||
'debug' => 1,
|
||||
'cron_log_file' => $sLogFile,
|
||||
];
|
||||
|
||||
$aGetFields = [
|
||||
'auth_user' => static::$sLogin,
|
||||
'auth_pwd' => static::$sPassword,
|
||||
];
|
||||
|
||||
$sJSONResult = $this->CallItopUri("/webservices/get_cron_status.php?".http_build_query($aGetFields), $aPostFields);
|
||||
|
||||
$this->assertEquals('{"message":"Cannot read log file"}', $sJSONResult);
|
||||
}
|
||||
|
||||
private function CreateUserWithProfiles(array $aProfileIds): ?string
|
||||
{
|
||||
if (count($aProfileIds) > 0) {
|
||||
$oUser = null;
|
||||
foreach ($aProfileIds as $iProfileId) {
|
||||
if (is_null($oUser)) {
|
||||
$oUser = $this->CreateContactlessUser(static::$sLogin, $iProfileId, static::$sPassword);
|
||||
} else {
|
||||
$this->AddProfileToUser($oUser, $iProfileId);
|
||||
}
|
||||
$oUser->DBWrite();
|
||||
}
|
||||
|
||||
return $oUser->GetKey();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function GetExpectedRestResponse(): string
|
||||
{
|
||||
return <<<JSON
|
||||
{"code":0,"message":"Operations: 7","version":"1.3","operations":[{"verb":"core\/create","description":"Create an object","extension":"CoreServices"},{"verb":"core\/update","description":"Update an object","extension":"CoreServices"},{"verb":"core\/apply_stimulus","description":"Apply a stimulus to change the state of an object","extension":"CoreServices"},{"verb":"core\/get","description":"Search for objects","extension":"CoreServices"},{"verb":"core\/delete","description":"Delete objects","extension":"CoreServices"},{"verb":"core\/get_related","description":"Get related objects through the specified relation","extension":"CoreServices"},{"verb":"core\/check_credentials","description":"Check user credentials","extension":"CoreServices"}]}
|
||||
JSON;
|
||||
}
|
||||
|
||||
private function GetExpectedCronResponse(): string
|
||||
{
|
||||
return '{"message":"OK"}';
|
||||
}
|
||||
|
||||
private function CheckLogFileIsGeneratedAndGetFullPath(string $sLogFileName): string
|
||||
{
|
||||
$sLogFile = APPROOT."log/$sLogFileName";
|
||||
$this->assertTrue(is_file($sLogFile));
|
||||
$this->aFileToClean[] = $sLogFile;
|
||||
return $sLogFile;
|
||||
}
|
||||
|
||||
private function CheckAdminAccessIssueWithCron(string $sLogFile)
|
||||
{
|
||||
$aLines = Utils::ReadTail($sLogFile);
|
||||
$sLastLine = array_shift($aLines);
|
||||
$this->assertMatchesRegularExpression('/^Access restricted to administrators/', $sLastLine, "@$sLastLine@");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
..
|
||||
..
|
||||
..
|
||||
..
|
||||
Already running...
|
||||
@@ -0,0 +1,5 @@
|
||||
..
|
||||
..
|
||||
..
|
||||
..
|
||||
Error: GABUZOMEU
|
||||
@@ -0,0 +1,5 @@
|
||||
..
|
||||
..
|
||||
..
|
||||
..
|
||||
A maintenance is ongoing
|
||||
@@ -0,0 +1,12 @@
|
||||
..
|
||||
..
|
||||
..
|
||||
..
|
||||
..
|
||||
..
|
||||
php /var/www/html/iTop/webservices/cron.php
|
||||
ERROR: Missing argument 'auth_user'
|
||||
|
||||
USAGE:
|
||||
|
||||
php cron.php --auth_user=<login> --auth_pwd=<password> [--param_file=<file>] [--verbose=1] [--debug=1] [--status_only=1]
|
||||
@@ -0,0 +1,5 @@
|
||||
..
|
||||
..
|
||||
..
|
||||
..
|
||||
Access restricted to administrators
|
||||
@@ -0,0 +1,5 @@
|
||||
..
|
||||
..
|
||||
..
|
||||
..
|
||||
Starting: 1772551316 (2026-03-03 16:21:56)
|
||||
@@ -0,0 +1,5 @@
|
||||
..
|
||||
..
|
||||
..
|
||||
..
|
||||
Exiting: 1772551316 (2026-03-03 16:21:56)
|
||||
100
webservices/CronService.php
Normal file
100
webservices/CronService.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
class CronService
|
||||
{
|
||||
public const HELP_MESSAGE = "php cron.php --auth_user=<login> --auth_pwd=<password> [--param_file=<file>] [--verbose=1] [--debug=1] [--status_only=1]";
|
||||
|
||||
private static CronService $oInstance;
|
||||
|
||||
//used for test only
|
||||
private ?iTopMutex $oMutex = null;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): CronService
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new CronService();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?CronService $oInstance): void
|
||||
{
|
||||
self::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
private function GetMutex(): iTopMutex
|
||||
{
|
||||
if (! is_null($this->oMutex)) {
|
||||
return $this->oMutex;
|
||||
}
|
||||
|
||||
return new iTopMutex('cron');
|
||||
}
|
||||
|
||||
private function IsMutexLocked(): bool
|
||||
{
|
||||
return $this->GetMutex()->IsLocked();
|
||||
}
|
||||
|
||||
public function IsStarted(string $sErrorFile): bool
|
||||
{
|
||||
$aLines = utils::ReadTail($sErrorFile);
|
||||
$sLine = array_shift($aLines);
|
||||
return (preg_match('/Starting: (.*)$/', $sLine));
|
||||
}
|
||||
|
||||
public function IsStopped(string $sErrorFile): bool
|
||||
{
|
||||
$aLines = utils::ReadTail($sErrorFile);
|
||||
$sLine = array_shift($aLines);
|
||||
return (preg_match('/Exiting: (.*)$/', $sLine));
|
||||
}
|
||||
|
||||
public function IsFailed(string $sLogFile): bool
|
||||
{
|
||||
return ! is_null($this->GetErrorMessage($sLogFile));
|
||||
}
|
||||
|
||||
public function GetErrorMessage(string $sLogFile): ?string
|
||||
{
|
||||
$aLines = utils::ReadTail($sLogFile);
|
||||
$sLine = array_shift($aLines);
|
||||
if (preg_match('/ERROR: (.*)$/', $sLine, $aMatches)) {
|
||||
return $aMatches[1];
|
||||
}
|
||||
|
||||
if (preg_match('/Exiting: (.*)$/', $sLine, $aMatches)) {
|
||||
//stopped
|
||||
return null;
|
||||
}
|
||||
|
||||
$aStoppedMessages = [
|
||||
'Already running',
|
||||
'Access restricted to administrators',
|
||||
'A maintenance is ongoing',
|
||||
'Maintenance detected',
|
||||
'iTop is not yet installed',
|
||||
];
|
||||
foreach ($aStoppedMessages as $sMsg) {
|
||||
if (preg_match("/$sMsg/", $sLine, $aMatches)) {
|
||||
return $sLine;
|
||||
}
|
||||
}
|
||||
|
||||
if (false !== strpos($sLine, self::HELP_MESSAGE)) {
|
||||
$aLines = utils::ReadTail($sLogFile, 5);
|
||||
foreach ($aLines as $sLine) {
|
||||
if (preg_match('/ERROR: (.*)$/', $sLine, $aMatches)) {
|
||||
return $aMatches[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
115
webservices/asynchronously_cron.php
Normal file
115
webservices/asynchronously_cron.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
use Hybridauth\Storage\Session;
|
||||
|
||||
require_once(__DIR__.'/../approot.inc.php');
|
||||
require_once(APPROOT.'/application/application.inc.php');
|
||||
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
|
||||
require_once(APPROOT.'/application/startup.inc.php');
|
||||
require_once(__DIR__.'/CronService.php');
|
||||
|
||||
function GetCliCommand(string $sPHPExec, string $sLogFile, array $aCronValues): string
|
||||
{
|
||||
$sCliParams = implode(" ", $aCronValues);
|
||||
return sprintf("$sPHPExec %s/cron.php $sCliParams 2>&1 >>$sLogFile &", __DIR__);
|
||||
}
|
||||
|
||||
function ReadParam($sParam, $sDefaultValue = null, $sSanitizationFilter = utils::ENUM_SANITIZATION_FILTER_RAW_DATA)
|
||||
{
|
||||
$sValue = utils::ReadParam($sParam, null, true, $sSanitizationFilter);
|
||||
if (is_null($sValue)) {
|
||||
$sValue = utils::ReadPostedParam($sParam, $sDefaultValue, $sSanitizationFilter);
|
||||
}
|
||||
|
||||
return trim($sValue);
|
||||
}
|
||||
|
||||
function LoginAndGetTokenInfo(): array
|
||||
{
|
||||
//handle authentication
|
||||
LoginWebPage::ResetSession();
|
||||
$iRet = LoginWebPage::DoLogin(false, false, LoginWebPage::EXIT_RETURN);
|
||||
if ($iRet != LoginWebPage::EXIT_CODE_OK) {
|
||||
throw new \Exception("Unknown authentication error (retCode=$iRet)", RestResult::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$sCurrentLoginMode = \Combodo\iTop\Application\Helper\Session::Get('login_mode', '');
|
||||
$oLoginFSMExtensionInstance = LoginWebPage::GetCurrentLoginPlugin($sCurrentLoginMode);
|
||||
|
||||
if (! $oLoginFSMExtensionInstance instanceof iTokenLoginUIExtension) {
|
||||
throw new \Exception("cannot call cron asynchronously via current login mode $sCurrentLoginMode");
|
||||
}
|
||||
|
||||
/** @var iTokenLoginUIExtension $oLoginFSMExtensionInstance */
|
||||
$aTokenInfo = $oLoginFSMExtensionInstance->GetTokenInfo();
|
||||
return [$sCurrentLoginMode, base64_encode(json_encode($aTokenInfo))];
|
||||
}
|
||||
|
||||
$sCliForLogs = null;
|
||||
try {
|
||||
$oCtx = new ContextTag(ContextTag::TAG_CRON);
|
||||
|
||||
list($sCurrentLoginMode, $sTokenInfo) = LoginAndGetTokenInfo();
|
||||
|
||||
$sLogFilename = ReadParam("cron_log_file", "cron.log");
|
||||
$sLogFile = APPROOT."log/$sLogFilename";
|
||||
if (! touch($sLogFile)) {
|
||||
throw new \Exception("Cannot touch $sLogFile");
|
||||
}
|
||||
\IssueLog::Enable($sLogFile);
|
||||
|
||||
$aCronValues = [];
|
||||
foreach ([ 'verbose', 'debug'] as $sParam) {
|
||||
$value = ReadParam($sParam, false);
|
||||
$aCronValues[] = "--$sParam=".escapeshellarg($value);
|
||||
}
|
||||
|
||||
$aCronValues[] = "--login_mode=".escapeshellarg($sCurrentLoginMode);
|
||||
|
||||
$sPHPExec = trim(\MetaModel::GetConfig()->Get('php_path'));
|
||||
$aCronValues[] = "--auth_info=".escapeshellarg('XXXX');
|
||||
$sCliForLogs = GetCliCommand($sPHPExec, $sLogFile, $aCronValues).PHP_EOL;
|
||||
\IssueLog::Info("Launch cron asynchronously", null, ['cli' => $sCliForLogs]);
|
||||
|
||||
$aCronValues[] = "--auth_info=".escapeshellarg($sTokenInfo);
|
||||
$sCli = GetCliCommand($sPHPExec, $sLogFile, $aCronValues);
|
||||
|
||||
$process = popen($sCli, 'r');
|
||||
if (false === $process) {
|
||||
throw new \Exception("CLI execution issue");
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
$bCronStartedOrFailed = true;
|
||||
while ($i < 20) {
|
||||
usleep(100000);
|
||||
|
||||
if (CronService::GetInstance()->IsStarted($sLogFile) || CronService::GetInstance()->IsFailed($sLogFile)) {
|
||||
//make sure cron is really started or failed before answering by HTTP
|
||||
$bCronStartedOrFailed = false;
|
||||
break;
|
||||
}
|
||||
|
||||
$i++;
|
||||
}
|
||||
|
||||
if ($bCronStartedOrFailed) {
|
||||
throw new \Exception("Cron execution timeout");
|
||||
}
|
||||
|
||||
http_response_code(200);
|
||||
$oP = new JsonPage();
|
||||
$oP->add_header('Access-Control-Allow-Origin: *');
|
||||
$oP->SetData(["message" => "OK"]);
|
||||
$oP->SetOutputDataOnly(true);
|
||||
$oP->Output();
|
||||
} catch (\Exception $e) {
|
||||
\IssueLog::Error('Cannot run cron', null, ['msg' => $e->getMessage(), 'stack' => $e->getTraceAsString(), 'cli' => $sCliForLogs ?? '', ]);
|
||||
|
||||
http_response_code(500);
|
||||
$oP = new JsonPage();
|
||||
$oP->add_header('Access-Control-Allow-Origin: *');
|
||||
$oP->SetData(['msg' => $e->getMessage()]);
|
||||
$oP->SetOutputDataOnly(true);
|
||||
$oP->Output();
|
||||
}
|
||||
@@ -24,9 +24,11 @@ use Combodo\iTop\Application\WebPage\WebPage;
|
||||
use Combodo\iTop\Service\InterfaceDiscovery\InterfaceDiscovery;
|
||||
|
||||
require_once(__DIR__.'/../approot.inc.php');
|
||||
require_once(__DIR__.'/CronService.php');
|
||||
|
||||
const EXIT_CODE_ERROR = -1;
|
||||
const EXIT_CODE_FATAL = -2;
|
||||
|
||||
// early exit
|
||||
if (file_exists(READONLY_MODE_FILE)) {
|
||||
echo "iTop is read-only. Exiting...\n";
|
||||
@@ -36,6 +38,7 @@ if (file_exists(READONLY_MODE_FILE)) {
|
||||
require_once(APPROOT.'/application/application.inc.php');
|
||||
require_once(APPROOT.'/core/background.inc.php');
|
||||
|
||||
IssueLog::Enable(APPROOT.'log/error.log');
|
||||
$sConfigFile = APPCONF.ITOP_DEFAULT_ENV.'/'.ITOP_CONFIG_FILE;
|
||||
if (!file_exists($sConfigFile)) {
|
||||
echo "iTop is not yet installed. Exiting...\n";
|
||||
@@ -63,7 +66,7 @@ function UsageAndExit($oP)
|
||||
|
||||
if ($bModeCLI) {
|
||||
$oP->p("USAGE:\n");
|
||||
$oP->p("php cron.php --auth_user=<login> --auth_pwd=<password> [--param_file=<file>] [--verbose=1] [--debug=1] [--status_only=1]\n");
|
||||
$oP->p(\CronService::HELP_MESSAGE."\n");
|
||||
} else {
|
||||
$oP->p("Optional parameters: verbose, param_file, status_only\n");
|
||||
}
|
||||
@@ -457,23 +460,40 @@ if ($bIsModeCLI) {
|
||||
}
|
||||
|
||||
try {
|
||||
utils::UseParamFile();
|
||||
|
||||
$bVerbose = utils::ReadParam('verbose', false, true /* Allow CLI */);
|
||||
$bDebug = utils::ReadParam('debug', false, true /* Allow CLI */);
|
||||
|
||||
if ($bIsModeCLI) {
|
||||
utils::UseParamFile();
|
||||
|
||||
// Next steps:
|
||||
// specific arguments: 'csv file'
|
||||
//
|
||||
$sAuthUser = ReadMandatoryParam($oP, 'auth_user', 'raw_data');
|
||||
$sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd', 'raw_data');
|
||||
if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) {
|
||||
UserRights::Login($sAuthUser); // Login & set the user's language
|
||||
$sTokenInfo = utils::ReadParam('auth_info', null, true, 'raw_data');
|
||||
$sLoginMode = utils::ReadParam('login_mode', null, true, 'raw_data');
|
||||
if (is_null($sLoginMode) || is_null($sTokenInfo)) {
|
||||
$sAuthUser = ReadMandatoryParam($oP, 'auth_user', 'raw_data');
|
||||
$sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd', 'raw_data');
|
||||
if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) {
|
||||
UserRights::Login($sAuthUser); // Login & set the user's language
|
||||
} else {
|
||||
$oP->p("Access wrong credentials ('$sAuthUser')");
|
||||
$oP->output();
|
||||
exit(EXIT_CODE_ERROR);
|
||||
}
|
||||
} else {
|
||||
$oP->p("Access wrong credentials ('$sAuthUser')");
|
||||
$oP->output();
|
||||
exit(EXIT_CODE_ERROR);
|
||||
$oLoginFSMExtensionInstance = LoginWebPage::GetCurrentLoginPlugin($sLoginMode);
|
||||
if ($oLoginFSMExtensionInstance instanceof iTokenLoginUIExtension) {
|
||||
$aTokenInfo = json_decode(base64_decode($sTokenInfo), true);
|
||||
|
||||
IssueLog::Error("TTTTTTT $sLoginMode TTTTTTTTTTTTTt");
|
||||
/** @var iTokenLoginUIExtension $oLoginFSMExtensionInstance */
|
||||
$sAuthUser = $oLoginFSMExtensionInstance->GetUserLogin($aTokenInfo);
|
||||
UserRights::Login($sAuthUser); // Login & set the user's language
|
||||
} else {
|
||||
$oP->p("Access wrong credentials via current login mode $sLoginMode");
|
||||
$oP->output();
|
||||
exit(EXIT_CODE_ERROR);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
|
||||
@@ -520,7 +540,9 @@ try {
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
$oMutex->Unlock();
|
||||
if (isset($oMutex)) {
|
||||
$oMutex->Unlock();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$oP->p("ERROR: '".$e->getMessage()."'");
|
||||
if ($bDebug) {
|
||||
|
||||
68
webservices/get_cron_status.php
Normal file
68
webservices/get_cron_status.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
require_once(__DIR__.'/../approot.inc.php');
|
||||
require_once(APPROOT.'/application/application.inc.php');
|
||||
require_once(APPROOT.'/application/startup.inc.php');
|
||||
require_once(__DIR__.'/CronService.php');
|
||||
|
||||
const RUNNING = "running";
|
||||
const STOPPED = "stopped";
|
||||
const ERROR = "error";
|
||||
|
||||
try {
|
||||
$oCtx = new ContextTag(ContextTag::TAG_CRON);
|
||||
LoginWebPage::ResetSession();
|
||||
$iRet = LoginWebPage::DoLogin(false, false, LoginWebPage::EXIT_RETURN);
|
||||
if ($iRet != LoginWebPage::EXIT_CODE_OK) {
|
||||
throw new Exception("Unknown authentication error (retCode=$iRet)", RestResult::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if (!UserRights::IsAdministrator()) {
|
||||
throw new Exception("Access restricted to administrators");
|
||||
}
|
||||
|
||||
$sLogFilename = ReadParam("cron_log_file", "cron.log");
|
||||
$sLogFile = APPROOT."log/$sLogFilename";
|
||||
if (! is_file($sLogFile)) {
|
||||
throw new \Exception("Cannot read log file");
|
||||
}
|
||||
|
||||
$sMsg = "";
|
||||
if (CronService::GetInstance()->IsStopped($sLogFile)) {
|
||||
$sStatus = STOPPED;
|
||||
} else {
|
||||
$sErrorMsg = CronService::GetInstance()->GetErrorMessage($sLogFile);
|
||||
if (is_null($sErrorMsg)) {
|
||||
$sStatus = RUNNING;
|
||||
} else {
|
||||
$sMsg = $sErrorMsg;
|
||||
$sStatus = ERROR;
|
||||
}
|
||||
$sStatus = is_null($sMsg) ? RUNNING : ERROR;
|
||||
}
|
||||
|
||||
http_response_code(200);
|
||||
$oP = new JsonPage();
|
||||
$oP->add_header('Access-Control-Allow-Origin: *');
|
||||
$oP->SetData(["status" => $sStatus, 'message' => $sMsg]);
|
||||
$oP->SetOutputDataOnly(true);
|
||||
$oP->Output();
|
||||
} catch (Exception $e) {
|
||||
\IssueLog::Error("Cannot get cron status", null, ['msg' => $e->getMessage(), 'stack' => $e->getTraceAsString()]);
|
||||
http_response_code(500);
|
||||
$oP = new JsonPage();
|
||||
$oP->add_header('Access-Control-Allow-Origin: *');
|
||||
$oP->SetData(["message" => $e->getMessage()]);
|
||||
$oP->SetOutputDataOnly(true);
|
||||
$oP->Output();
|
||||
}
|
||||
|
||||
function ReadParam($sParam, $sDefaultValue = null, $sSanitizationFilter = utils::ENUM_SANITIZATION_FILTER_RAW_DATA)
|
||||
{
|
||||
$sValue = utils::ReadParam($sParam, null, true, $sSanitizationFilter);
|
||||
if (is_null($sValue)) {
|
||||
$sValue = utils::ReadPostedParam($sParam, $sDefaultValue, $sSanitizationFilter);
|
||||
}
|
||||
|
||||
return trim($sValue);
|
||||
}
|
||||
Reference in New Issue
Block a user