diff --git a/datamodels/2.x/itop-config/config.php b/datamodels/2.x/itop-config/config.php index 5d0ad8370..4ebc1466c 100644 --- a/datamodels/2.x/itop-config/config.php +++ b/datamodels/2.x/itop-config/config.php @@ -4,6 +4,7 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ +use Combodo\iTop\Application\UI\Base\Component\Alert\Alert; use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Form\Form; @@ -15,6 +16,10 @@ use Combodo\iTop\Config\Validator\iTopConfigSyntaxValidator; require_once(APPROOT.'application/startup.inc.php'); +const CONFIG_ERROR = 0; +const CONFIG_WARNING = 1; +const CONFIG_INFO = 2; + /** * @param $sContents @@ -45,31 +50,51 @@ function DBPasswordInNewConfigIsOk($sSafeContent) if ($bIsWindows && (preg_match("@'db_pwd' => '[^%!\"]+',@U", $sSafeContent) === 0)) { return false; } + return true; } function CheckAsyncTasksRetryConfig(Config $oTempConfig, iTopWebPage $oP) { - $iWarnings = 0; - foreach(get_declared_classes() as $sPHPClass) - { - $oRefClass = new ReflectionClass($sPHPClass); - if ($oRefClass->isSubclassOf('AsyncTask') && !$oRefClass->isAbstract()) - { - $aMessages = AsyncTask::CheckRetryConfig($oTempConfig, $oRefClass->getName()); + $iWarnings = 0; + foreach (get_declared_classes() as $sPHPClass) { + $oRefClass = new ReflectionClass($sPHPClass); + if ($oRefClass->isSubclassOf('AsyncTask') && !$oRefClass->isAbstract()) { + $aMessages = AsyncTask::CheckRetryConfig($oTempConfig, $oRefClass->getName()); - if (count($aMessages) !== 0) - { - foreach($aMessages as $sMessage) - { - $oAlert = AlertUIBlockFactory::MakeForWarning('', $sMessage); - $oP->AddUiBlock($oAlert); - $iWarnings ++; - } - } - } - } - return $iWarnings; + if (count($aMessages) !== 0) { + foreach ($aMessages as $sMessage) { + $oAlert = AlertUIBlockFactory::MakeForWarning('', $sMessage); + $oP->AddUiBlock($oAlert); + $iWarnings++; + } + } + } + } + + return $iWarnings; +} + +/** + * @param \Exception $e + * + * @return \Combodo\iTop\Application\UI\Base\Component\Alert\Alert + */ +function GetAlertFromException(Exception $e): Alert +{ + switch ($e->getCode()) { + case CONFIG_WARNING: + $oAlert = AlertUIBlockFactory::MakeForWarning('', $e->getMessage()); + break; + case CONFIG_INFO: + $oAlert = AlertUIBlockFactory::MakeForInformation('', $e->getMessage()); + break; + case CONFIG_ERROR: + default: + $oAlert = AlertUIBlockFactory::MakeForDanger('', $e->getMessage()); + } + + return $oAlert; } ///////////////////////////////////////////////////////////////////// @@ -97,111 +122,112 @@ try { $oP->AddUiBlock(TitleUIBlockFactory::MakeForPage(Dict::S('config-edit-title'))); if (MetaModel::GetConfig()->Get('demo_mode')) { - $oAlert = AlertUIBlockFactory::MakeForInformation('', "Sorry, iTop is in demonstration mode: the configuration file cannot be edited."); - $oP->AddUiBlock($oAlert); - } else { - if (MetaModel::GetModuleSetting('itop-config', 'config_editor', '') == 'disabled') { - $oAlert = AlertUIBlockFactory::MakeForWarning('', "iTop interactive edition of the configuration as been disabled. See 'config_editor' => 'disabled' in the configuration file."); + throw new Exception(Dict::S('config-not-allowed-in-demo'), CONFIG_INFO); + } + + if (MetaModel::GetModuleSetting('itop-config', 'config_editor', '') == 'disabled') { + throw new Exception(Dict::S('config-interactive-not-allowed'), CONFIG_WARNING); + } + + $sConfigFile = APPROOT.'conf/'.utils::GetCurrentEnvironment().'/config-itop.php'; + + $iEditorTopMargin += 9; + $sConfigContent = file_get_contents($sConfigFile); + $sConfigChecksum = md5($sConfigContent); + $sConfig = str_replace("\r\n", "\n", $sConfigContent); + $sOriginalConfig = $sConfig; + + if (!empty($sOperation)) { + $iEditorTopMargin += 5; + $sConfig = utils::ReadParam('new_config', '', false, 'raw_data'); + } + + try { + if ($sOperation == 'revert') { + throw new Exception(Dict::S('config-reverted'), CONFIG_WARNING); + } + + if ($sOperation == 'save') { + $sTransactionId = utils::ReadParam('transaction_id', '', false, 'transaction_id'); + if (!utils::IsTransactionValid($sTransactionId, true)) { + throw new Exception(Dict::S('config-error-transaction'), CONFIG_ERROR); + } + + $sChecksum = utils::ReadParam('checksum'); + if ($sChecksum !== $sConfigChecksum) { + throw new Exception(Dict::S('config-error-file-changed'), CONFIG_ERROR); + } + + if ($sConfig === $sOriginalConfig) { + throw new Exception(Dict::S('config-no-change'), CONFIG_INFO); + } + TestConfig($sConfig, $oP); // throws exceptions + + @chmod($sConfigFile, 0770); // Allow overwriting the file + $sTmpFile = tempnam(SetupUtils::GetTmpDir(), 'itop-cfg-'); + // Don't write the file as-is since it would allow to inject any kind of PHP code. + // Instead, write the interpreted version of the file + // Note: + // The actual raw PHP code will anyhow be interpreted exactly twice: once in TestConfig() above + // and a second time during the load of the Config object below. + // If you are really concerned about an iTop administrator crafting some malicious + // PHP code inside the config file, then turn off the interactive configuration + // editor by adding the configuration parameter: + // 'itop-config' => array( + // 'config_editor' => 'disabled', + // ) + file_put_contents($sTmpFile, $sConfig); + $oTempConfig = new Config($sTmpFile, true); + $oTempConfig->WriteToFile($sConfigFile); + @unlink($sTmpFile); + @chmod($sConfigFile, 0440); // Read-only + + if (DBPasswordInNewConfigIsOk($sConfig)) { + $oAlert = AlertUIBlockFactory::MakeForSuccess('', Dict::S('config-saved')); + } else { + $oAlert = AlertUIBlockFactory::MakeForInformation('', Dict::S('config-saved-warning-db-password')); + } $oP->AddUiBlock($oAlert); - } else { - $sConfigFile = APPROOT.'conf/'.utils::GetCurrentEnvironment().'/config-itop.php'; - $iEditorTopMargin += 9; - $sConfig = str_replace("\r\n", "\n", file_get_contents($sConfigFile)); + $iWarnings = CheckAsyncTasksRetryConfig($oTempConfig, $oP); + + // Read the config from disk after save + $sConfigContent = file_get_contents($sConfigFile); + $sConfigChecksum = md5($sConfigContent); + $sConfig = str_replace("\r\n", "\n", $sConfigContent); $sOriginalConfig = $sConfig; + } + } + catch (Exception $e) { + $oAlert = GetAlertFromException($e); + $oP->AddUiBlock($oAlert); + } - if (!empty($sOperation)) { - $iEditorTopMargin += 5; - $sConfig = utils::ReadParam('new_config', '', false, 'raw_data'); - $sOriginalConfig = utils::ReadParam('prev_config', '', false, 'raw_data'); - } + // (remove EscapeHtml) N°5914 - Wrong encoding in modules configuration editor + $oP->AddUiBlock(new Html('
'.Dict::S('config-edit-intro').'
')); - if ($sOperation == 'revert') { - $iEditorTopMargin += 5; - $oAlert = AlertUIBlockFactory::MakeForWarning('', Dict::S('config-reverted')); - $oP->AddUiBlock($oAlert); - } - if ($sOperation == 'save') { - $sTransactionId = utils::ReadParam('transaction_id', '', false, 'transaction_id'); - if (!utils::IsTransactionValid($sTransactionId, true)) { - $iEditorTopMargin += 5; - $oAlert = AlertUIBlockFactory::MakeForFailure('', 'Error: invalid Transaction ID. The configuration was NOT modified.'); - $oP->AddUiBlock($oAlert); - } else { - if ($sConfig == $sOriginalConfig) { - $iEditorTopMargin += 5; - $oAlert = AlertUIBlockFactory::MakeForInformation('', Dict::S('config-no-change')); - $oP->AddUiBlock($oAlert); - } else { - try { - TestConfig($sConfig, $oP); // throws exceptions + $oForm = new Form(); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', 'save', 'operation')); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', utils::GetNewTransactionId())); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('checksum', $sConfigChecksum)); - @chmod($sConfigFile, 0770); // Allow overwriting the file - $sTmpFile = tempnam(SetupUtils::GetTmpDir(), 'itop-cfg-'); - // Don't write the file as-is since it would allow to inject any kind of PHP code. - // Instead write the interpreted version of the file - // Note: - // The actual raw PHP code will anyhow be interpreted exactly twice: once in TestConfig() above - // and a second time during the load of the Config object below. - // If you are really concerned about an iTop administrator crafting some malicious - // PHP code inside the config file, then turn off the interactive configuration - // editor by adding the configuration parameter: - // 'itop-config' => array( - // 'config_editor' => 'disabled', - // ) - file_put_contents($sTmpFile, $sConfig); - $oTempConfig = new Config($sTmpFile, true); - $oTempConfig->WriteToFile($sConfigFile); - @unlink($sTmpFile); - @chmod($sConfigFile, 0440); // Read-only + //--- Cancel button + $oCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('config-cancel'), 'cancel_button', null, true, 'cancel_button'); + $oCancelButton->SetOnClickJsCode("return ResetConfig();"); + $oForm->AddSubBlock($oCancelButton); - if (DBPasswordInNewConfigIsOk($sConfig)) { - $oAlert = AlertUIBlockFactory::MakeForSuccess('', Dict::S('config-saved')); - $iEditorTopMargin += 5; - } else { - $oAlert = AlertUIBlockFactory::MakeForInformation('', Dict::S('config-saved-warning-db-password')); - $iEditorTopMargin += 5; - } - $oP->AddUiBlock($oAlert); + //--- Submit button + $oSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('config-apply'), null, Dict::S('config-apply'), true, 'submit_button'); + $oForm->AddSubBlock($oSubmitButton); - $iWarnings = CheckAsyncTasksRetryConfig($oTempConfig, $oP); - $iEditorTopMargin += 5*$iWarnings; + //--- Config editor + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('prev_config', $sOriginalConfig, 'prev_config')); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('new_config', $sOriginalConfig)); + $oForm->AddHtml(""); + $oP->AddUiBlock($oForm); - $sOriginalConfig = str_replace("\r\n", "\n", file_get_contents($sConfigFile)); - } - catch (Exception $e) { - $oAlert = AlertUIBlockFactory::MakeForDanger('', $e->getMessage()); - $iEditorTopMargin += 5; - $oP->AddUiBlock($oAlert); - } - } - } - } - - // (remove EscapeHtml) N°5914 - Wrong encoding in modules configuration editor - $oP->AddUiBlock(new Html(''.Dict::S('config-edit-intro').'
')); - - $oForm = new Form(); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', 'save')); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', utils::GetNewTransactionId())); - - //--- Cancel button - $oCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('config-cancel'), 'cancel_button', null, true, 'cancel_button'); - $oCancelButton->SetOnClickJsCode("return ResetConfig();"); - $oForm->AddSubBlock($oCancelButton); - - //--- Submit button - $oSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('config-apply'), null, Dict::S('config-apply'), true, 'submit_button'); - $oForm->AddSubBlock($oSubmitButton); - - //--- Config editor - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('prev_config', $sOriginalConfig, 'prev_config')); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('new_config', $sConfig)); - $oForm->AddHtml(""); - $oP->AddUiBlock($oForm); - - $oP->add_script( - <<<'JS' + $oP->add_script( + <<<'JS' var EditorUtils = (function() { var STORAGE_RANGE_KEY = 'cfgEditorRange'; var STORAGE_LINE_KEY = 'cfgEditorFirstline'; @@ -271,9 +297,8 @@ var EditorUtils = (function() { }; })(); JS - ); - $oP->add_ready_script( - <<<'JS' + ); + $oP->add_ready_script(<<<'JS' var editor = ace.edit("new_config"); var configurationSource = $('input[name="new_config"]'); @@ -314,31 +339,25 @@ editorForm.on('submit', function() { EditorUtils.restoreEditorDisplay(editor); editor.focus(); JS - ); + ); - $sConfirmCancel = addslashes(Dict::S('config-confirm-cancel')); - $oP->add_script(<<'config_editor' => 'disabled' in the configuration file.",
));
diff --git a/datamodels/2.x/itop-config/dictionaries/fr.dict.itop-config.php b/datamodels/2.x/itop-config/dictionaries/fr.dict.itop-config.php
index 2d1ba746a..5a3347a5b 100644
--- a/datamodels/2.x/itop-config/dictionaries/fr.dict.itop-config.php
+++ b/datamodels/2.x/itop-config/dictionaries/fr.dict.itop-config.php
@@ -8,7 +8,7 @@
Dict::Add('FR FR', 'French', 'Français', array(
'Menu:ConfigEditor' => 'Configuration Générale',
- 'config-edit-title' => 'Editeur du Fichier de Configuration',
+ 'config-edit-title' => 'Éditeur du Fichier de Configuration',
'config-edit-intro' => 'Attention: une configuration incorrecte peut rendre '.ITOP_APPLICATION_SHORT.' inopérant pour tous les utilisateurs!',
'config-apply' => 'Enregistrer',
'config-apply-title' => 'Enregistrer (Ctrl+S)',
@@ -20,4 +20,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
'config-parse-error' => 'Ligne %2$d: %1$s.'config_editor' => 'disabled' dans le fichier de configuration.",
));