Compare commits

..

22 Commits

Author SHA1 Message Date
jf-cbd
6105255d76 Improve logs readability 2025-03-06 11:46:00 +01:00
jf-cbd
f3909eb50a Improve tests readability 2025-03-06 10:41:52 +01:00
jf-cbd
7f2a1a6416 N°4459 and N°7640 - Workaround for sodium default encrypted key invalid 2025-03-06 10:41:26 +01:00
jf-cbd
32dbf1490c WIP 2025-03-05 17:24:28 +01:00
jf-cbd
2dc9f5dcc6 WIP 2025-03-05 16:49:51 +01:00
jf-cbd
200b148c86 WIP 2025-03-05 16:28:29 +01:00
jf-cbd
6fcbf68013 WIP 2025-03-05 15:44:22 +01:00
jf-cbd
2fedfa1b4c WIP 2025-03-05 14:35:51 +01:00
jf-cbd
755ac3a15c WIP 2025-03-03 18:28:15 +01:00
jf-cbd
01e2d3b317 WIP 2025-03-03 17:27:36 +01:00
jf-cbd
39fd258544 Fix tests 2025-03-03 15:43:10 +01:00
jf-cbd
f58a2be620 N°8231 - making rest api log more readable 2025-03-03 15:00:54 +01:00
jf-cbd
e8c622a15d Making JSON more readable 2025-02-28 16:35:59 +01:00
jf-cbd
783fa01fc9 Fix tests 2025-02-28 16:19:09 +01:00
jf-cbd
b1f49ae487 Clean test 2025-02-28 15:51:11 +01:00
jf-cbd
896069d64b WIP 2025-02-28 11:45:44 +01:00
jf-cbd
85434578d3 WIP 2025-02-28 11:00:24 +01:00
jf-cbd
e142fba0e6 WIP 2025-02-28 10:50:06 +01:00
jf-cbd
c9f32311b4 WIP 2025-02-27 17:56:46 +01:00
jf-cbd
a9e10742ec WIP 2025-02-27 17:26:14 +01:00
jf-cbd
cb2a093498 WIP 2025-02-27 16:00:58 +01:00
denis.flaven@combodo.com
944b1f557d Work in progress 2025-02-27 12:17:52 +01:00
13 changed files with 112 additions and 102 deletions

View File

@@ -529,7 +529,10 @@ EOF
*/
public function Render($oPage, $bEditMode = false, $aExtraParams = array(), $bCanEdit = true)
{
$aExtraParams['dashboard_div_id'] = utils::Sanitize($aExtraParams['dashboard_div_id'] ?? null, $this->GetId(), utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
if (!array_key_exists('dashboard_div_id', $aExtraParams))
{
$aExtraParams['dashboard_div_id'] = utils::Sanitize($this->GetId(), '', 'element_identifier');
}
$oPage->add('<div class="dashboard-title-line"><div class="dashboard-title">'.htmlentities(Dict::S($this->sTitle), ENT_QUOTES, 'UTF-8', false).'</div></div>');
@@ -999,7 +1002,7 @@ EOF
$sSelectorHtml .= '</div>';
$sSelectorHtml = addslashes($sSelectorHtml);
$sFile = addslashes($this->GetDefinitionFile());
$sReloadURL = json_encode($this->GetReloadURL());
$sReloadURL = $this->GetReloadURL();
$oPage->add_ready_script(
<<<EOF
@@ -1056,7 +1059,7 @@ EOF
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.iframe-transport.js');
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.fileupload.js');
$sEditMenu = "<div id=\"DashboardMenu\"><ul><li><i class=\"top-right-icon icon-additional-arrow fas fa-pencil-alt\"></i><ul>";
$aActions = array();
$sFile = addslashes($this->sDefinitionFile);
$sJSExtraParams = json_encode($aExtraParams);
@@ -1088,7 +1091,6 @@ EOF
EOF
);
$sReloadURL = json_encode($this->GetReloadURL());
$oPage->add_script(
<<<EOF
function EditDashboard(sId, sDashboardFile, aExtraParams)
@@ -1186,7 +1188,7 @@ EOF
$oPage->add('</div>');
$oPage->add('<div id="event_bus"/>'); // For exchanging messages between the panes, same as in the designer
$oPage->add('</div>');
$sDialogTitle = Dict::S('UI:DashboardEdit:Title');
$sOkButtonLabel = Dict::S('UI:Button:Save');
$sCancelButtonLabel = Dict::S('UI:Button:Cancel');
@@ -1198,7 +1200,7 @@ EOF
$sTitle = json_encode($this->sTitle);
$sFile = json_encode($this->GetDefinitionFile());
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php';
$sReloadURL = json_encode($this->GetReloadURL());
$sReloadURL = $this->GetReloadURL();
$sExitConfirmationMessage = addslashes(Dict::S('UI:NavigateAwayConfirmationMessage'));
$sCancelConfirmationMessage = addslashes(Dict::S('UI:CancelConfirmationMessage'));

View File

@@ -432,8 +432,8 @@ class utils
// For URL
case static::ENUM_SANITIZATION_FILTER_URL:
$retValue = filter_var($value, FILTER_SANITIZE_URL);
$retValue = filter_var($retValue, FILTER_VALIDATE_URL);
// N°6350 - returns only valid URLs
$retValue = filter_var($value, FILTER_VALIDATE_URL);
break;
default:

View File

@@ -53,7 +53,14 @@ class DBRestore extends DBBackup
$sUser = self::EscapeShellArg($this->sDBUser);
$sPwd = self::EscapeShellArg($this->sDBPwd);
$sDBName = self::EscapeShellArg($this->sDBName);
$sMySQLExe = DBBackup::MakeSafeMySQLCommand($this->sMySQLBinDir, 'mysql');
if (empty($this->sMySQLBinDir))
{
$sMySQLExe = 'mysql';
}
else
{
$sMySQLExe = '"'.$this->sMySQLBinDir.'/mysql"';
}
if (is_null($this->iDBPort))
{
$sPortOption = '';

View File

@@ -56,7 +56,15 @@ try
//
$sMySQLBinDir = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'mysql_bindir', '');
$sMySQLBinDir = utils::ReadParam('mysql_bindir', $sMySQLBinDir, true);
$sMySQLDump = DBBackup::MakeSafeMySQLCommand($sMySQLBinDir, 'mysqldump');
if (empty($sMySQLBinDir))
{
$sMySQLDump = 'mysqldump';
}
else
{
//echo 'Info - Found mysql_bindir: '.$sMySQLBinDir;
$sMySQLDump = '"'.$sMySQLBinDir.'/mysqldump"';
}
$sCommand = "$sMySQLDump -V 2>&1";
$aOutput = array();

View File

@@ -1107,17 +1107,15 @@ class ObjectFormManager extends FormManager
// First we need to update the Object with its new values in order to enable the dependents fields to update
$sObjectClass = get_class($this->oObject);
foreach ($aCurrentValues as $sAttCode => $value) {
if (count($this->aFieldsAtts) !== 0) {
if (!array_key_exists($sAttCode, $this->aFieldsAtts)) {
continue;
}
$iAttributeFlags = $this->aFieldsAtts[$sAttCode];
if ($iAttributeFlags & OPT_ATT_HIDDEN) {
continue;
}
if ($iAttributeFlags & OPT_ATT_READONLY) {
continue;
}
if (!array_key_exists($sAttCode, $this->aFieldsAtts)) {
continue;
}
$iAttributeFlags = $this->aFieldsAtts[$sAttCode];
if ($iAttributeFlags & OPT_ATT_HIDDEN) {
continue;
}
if ($iAttributeFlags & OPT_ATT_READONLY) {
continue;
}
if (MetaModel::IsValidAttCode($sObjectClass, $sAttCode)) {
@@ -1232,56 +1230,55 @@ class ObjectFormManager extends FormManager
$this->aFieldsAtts = array();
$this->aExtraData = array();
$aFieldsDMOnlyAttCodes = array();
if (array_key_exists('type', $this->aFormProperties)) {
switch ($this->aFormProperties['type']) {
case 'custom_list':
case 'static':
foreach ($this->aFormProperties['fields'] as $sAttCode => $aOptions) {
// When in a transition and no flags are specified for the field, we will retrieve its flags from DM later
if ($this->IsTransitionForm() && empty($aOptions)) {
$aFieldsDMOnlyAttCodes[] = $sAttCode;
continue;
}
// Otherwise we proceed as usual
$iFieldFlags = OPT_ATT_NORMAL;
// Checking if field should be slave
if (isset($aOptions['slave']) && ($aOptions['slave'] === true)) {
$iFieldFlags = $iFieldFlags | OPT_ATT_SLAVE;
}
// Checking if field should be must_change
if (isset($aOptions['must_change']) && ($aOptions['must_change'] === true)) {
$iFieldFlags = $iFieldFlags | OPT_ATT_MUSTCHANGE;
}
// Checking if field should be must prompt
if (isset($aOptions['must_prompt']) && ($aOptions['must_prompt'] === true)) {
$iFieldFlags = $iFieldFlags | OPT_ATT_MUSTPROMPT;
}
// Checking if field should be hidden
if (isset($aOptions['hidden']) && ($aOptions['hidden'] === true)) {
$iFieldFlags = $iFieldFlags | OPT_ATT_HIDDEN;
}
// Checking if field should be readonly
if (isset($aOptions['read_only']) && ($aOptions['read_only'] === true)) {
$iFieldFlags = $iFieldFlags | OPT_ATT_READONLY;
}
// Checking if field should be mandatory
if (isset($aOptions['mandatory']) && ($aOptions['mandatory'] === true)) {
$iFieldFlags = $iFieldFlags | OPT_ATT_MANDATORY;
}
// Finally, adding the attribute and its flags
$this->aFieldsAtts[$sAttCode] = $iFieldFlags;
switch ($this->aFormProperties['type']) {
case 'custom_list':
case 'static':
foreach ($this->aFormProperties['fields'] as $sAttCode => $aOptions) {
// When in a transition and no flags are specified for the field, we will retrieve its flags from DM later
if ($this->IsTransitionForm() && empty($aOptions)) {
$aFieldsDMOnlyAttCodes[] = $sAttCode;
continue;
}
break;
case 'zlist':
foreach (MetaModel::FlattenZList(MetaModel::GetZListItems($sObjectClass, $this->aFormProperties['fields'])) as $sAttCode) {
$this->aFieldsAtts[$sAttCode] = OPT_ATT_NORMAL;
// Otherwise we proceed as usual
$iFieldFlags = OPT_ATT_NORMAL;
// Checking if field should be slave
if (isset($aOptions['slave']) && ($aOptions['slave'] === true)) {
$iFieldFlags = $iFieldFlags | OPT_ATT_SLAVE;
}
break;
}
// Checking if field should be must_change
if (isset($aOptions['must_change']) && ($aOptions['must_change'] === true)) {
$iFieldFlags = $iFieldFlags | OPT_ATT_MUSTCHANGE;
}
// Checking if field should be must prompt
if (isset($aOptions['must_prompt']) && ($aOptions['must_prompt'] === true)) {
$iFieldFlags = $iFieldFlags | OPT_ATT_MUSTPROMPT;
}
// Checking if field should be hidden
if (isset($aOptions['hidden']) && ($aOptions['hidden'] === true)) {
$iFieldFlags = $iFieldFlags | OPT_ATT_HIDDEN;
}
// Checking if field should be readonly
if (isset($aOptions['read_only']) && ($aOptions['read_only'] === true)) {
$iFieldFlags = $iFieldFlags | OPT_ATT_READONLY;
}
// Checking if field should be mandatory
if (isset($aOptions['mandatory']) && ($aOptions['mandatory'] === true)) {
$iFieldFlags = $iFieldFlags | OPT_ATT_MANDATORY;
}
// Finally, adding the attribute and its flags
$this->aFieldsAtts[$sAttCode] = $iFieldFlags;
}
break;
case 'zlist':
foreach (MetaModel::FlattenZList(MetaModel::GetZListItems($sObjectClass, $this->aFormProperties['fields'])) as $sAttCode) {
$this->aFieldsAtts[$sAttCode] = OPT_ATT_NORMAL;
}
break;
}
if (isset($this->aFormProperties['layout'])) {
if ($this->aFormProperties['layout'] !== null) {
$oXPath = new DOMXPath($this->oHtmlDocument);
/** @var \DOMElement $oFieldNode */
foreach ($oXPath->query('//div[contains(@class, "form_field")][@data-field-id]') as $oFieldNode) {
@@ -1340,7 +1337,7 @@ class ObjectFormManager extends FormManager
// Also, retrieving mandatory attributes from metamodel to be able to complete the form with them if necessary
//
// Note: When in a transition, we don't do this for fields that should be set from DM
if (array_key_exists('type', $this->aFormProperties) && $this->aFormProperties['type'] !== 'static') {
if ($this->aFormProperties['type'] !== 'static') {
if ($this->IsTransitionForm()) {
$aDatamodelAttCodes = $this->oObject->GetTransitionAttributes($this->aFormProperties['stimulus_code']);
}
@@ -1451,7 +1448,7 @@ class ObjectFormManager extends FormManager
// - The layout
$this->oHtmlDocument = new DOMDocument();
if (isset($this->aFormProperties['layout'])) {
if ($this->aFormProperties['layout'] !== null) {
// Checking if we need to render the template from twig to html in order to parse the fields
if ($this->aFormProperties['layout']['type'] === 'twig') {
if ($this->oContainer !== null) {

View File

@@ -1218,7 +1218,6 @@ return array(
'SQLQuery' => $baseDir . '/core/sqlquery.class.inc.php',
'SQLUnionQuery' => $baseDir . '/core/sqlunionquery.class.inc.php',
'SVGDOMSanitizer' => $baseDir . '/core/htmlsanitizer.class.inc.php',
'SanitizeTrait' => $baseDir . '/core/restservices.class.inc.php',
'ScalarExpression' => $baseDir . '/core/oql/expression.class.inc.php',
'ScalarOqlExpression' => $baseDir . '/core/oql/oqlquery.class.inc.php',
'ScssPhp\\ScssPhp\\Base\\Range' => $vendorDir . '/scssphp/scssphp/src/Base/Range.php',
@@ -2733,7 +2732,6 @@ return array(
'iPreferencesExtension' => $baseDir . '/application/applicationextension.inc.php',
'iProcess' => $baseDir . '/core/backgroundprocess.inc.php',
'iQueryModifier' => $baseDir . '/core/querymodifier.class.inc.php',
'iRestInputSanitizer' => $baseDir . '/application/applicationextension.inc.php',
'iRestServiceProvider' => $baseDir . '/application/applicationextension.inc.php',
'iScheduledProcess' => $baseDir . '/core/backgroundprocess.inc.php',
'iSelfRegister' => $baseDir . '/core/userrights.class.inc.php',

View File

@@ -1586,7 +1586,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'SQLQuery' => __DIR__ . '/../..' . '/core/sqlquery.class.inc.php',
'SQLUnionQuery' => __DIR__ . '/../..' . '/core/sqlunionquery.class.inc.php',
'SVGDOMSanitizer' => __DIR__ . '/../..' . '/core/htmlsanitizer.class.inc.php',
'SanitizeTrait' => __DIR__ . '/../..' . '/core/restservices.class.inc.php',
'ScalarExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php',
'ScalarOqlExpression' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php',
'ScssPhp\\ScssPhp\\Base\\Range' => __DIR__ . '/..' . '/scssphp/scssphp/src/Base/Range.php',
@@ -3101,7 +3100,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'iPreferencesExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'iProcess' => __DIR__ . '/../..' . '/core/backgroundprocess.inc.php',
'iQueryModifier' => __DIR__ . '/../..' . '/core/querymodifier.class.inc.php',
'iRestInputSanitizer' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'iRestServiceProvider' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'iScheduledProcess' => __DIR__ . '/../..' . '/core/backgroundprocess.inc.php',
'iSelfRegister' => __DIR__ . '/../..' . '/core/userrights.class.inc.php',

View File

@@ -1989,7 +1989,7 @@ catch(CoreException $e)
{
$oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
}
$oP->error(Dict::Format('UI:Error_Details', Str::pure2html($e->getHtmlDesc())));
$oP->error(Dict::Format('UI:Error_Details', $e->getHtmlDesc()));
$oP->output();
if (MetaModel::IsLogEnabledIssue())
@@ -2025,7 +2025,7 @@ catch(Exception $e)
require_once(APPROOT.'/setup/setuppage.class.inc.php');
$oP = new ErrorPage(Dict::S('UI:PageTitle:FatalError'));
$oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
$oP->error(Dict::Format('UI:Error_Details', Str::pure2html($e->getMessage())));
$oP->error(Dict::Format('UI:Error_Details', $e->getMessage()));
$oP->output();
if (MetaModel::IsLogEnabledIssue())

View File

@@ -104,8 +104,6 @@ class DBBackup
/** @var string */
protected $sDBName;
/** @var string */
protected $sMySQLBinDir = '';
/** @var string */
protected $sDBSubName;
/**
@@ -133,6 +131,7 @@ class DBBackup
$this->sDBSubName = $oConfig->get('db_subname');
}
protected $sMySQLBinDir = '';
/**
* Create a normalized backup name, depending on the current date/time and Database
@@ -300,9 +299,8 @@ class DBBackup
}
$this->LogInfo("Starting backup of $this->sDBHost/$this->sDBName(suffix:'$this->sDBSubName')");
$sMySQLBinDir = utils::ReadParam('mysql_bindir', $this->sMySQLBinDir, true);
$sMySQLDump = $this->MakeSafeMySQLCommand($sMySQLBinDir, 'mysqldump');
$sMySQLDump = $this->GetMysqldumpCommand();
// Store the results in a temporary file
$sTmpFileName = self::EscapeShellArg($sBackupFileName);
@@ -559,22 +557,20 @@ EOF;
/**
* @return string the command to launch mysqldump (without its params)
* @throws \BackupException
*/
public static function MakeSafeMySQLCommand($sMySQLBinDir, string $sCmd)
private function GetMysqldumpCommand()
{
if (empty($sMySQLBinDir)) {
$sMySQLCommand = $sCmd;
$sMySQLBinDir = utils::ReadParam('mysql_bindir', $this->sMySQLBinDir, true);
if (empty($sMySQLBinDir))
{
$sMysqldumpCommand = 'mysqldump';
}
else {
$sMySQLBinDir = escapeshellcmd($sMySQLBinDir);
$sMySQLCommand = '"'.$sMySQLBinDir.'/$sCmd"';
if (!file_exists($sMySQLCommand)) {
throw new BackupException("$sCmd not found in $sMySQLBinDir");
}
else
{
$sMysqldumpCommand = '"'.$sMySQLBinDir.'/mysqldump"';
}
return $sMySQLCommand;
return $sMysqldumpCommand;
}
}

View File

@@ -484,15 +484,16 @@ class SetupUtils
{
$sMySQLBinDir = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'mysql_bindir', '');
}
try {
$sMySQLDump = DBBackup::MakeSafeMySQLCommand($sMySQLBinDir, 'mysqldump');
} catch (Exception $e) {
$aResult[] = new CheckResult(CheckResult::ERROR, $e->getMessage());
return $aResult;
if (empty($sMySQLBinDir))
{
$sMySQLDump = 'mysqldump';
}
else
{
SetupPage::log('Info - Found mysql_bindir: '.$sMySQLBinDir);
$sMySQLDump = '"'.$sMySQLBinDir.'/mysqldump"';
}
if (!empty($sMySQLBinDir)) {
SetupPage::log('Info - Found mysql_bindir: '.$sMySQLBinDir);
}
$sCommand = "$sMySQLDump -V 2>&1";
$aOutput = array();

View File

@@ -611,7 +611,7 @@ JS
if ($oAttDef->IsExternalKey())
{
/** @var \AttributeExternalKey $oAttDef */
$aAttProperties['value'] = \Str::pure2html($oRemoteItem->Get($sAttCode . '_friendlyname'));
$aAttProperties['value'] = $oRemoteItem->Get($sAttCode . '_friendlyname');
// Checking if user can access object's external key
$sObjectUrl = ApplicationContext::MakeObjectUrl($oAttDef->GetTargetClass(), $oRemoteItem->Get($sAttCode));

View File

@@ -495,7 +495,7 @@ class utilsTest extends ItopTestCase
'good element_identifier' => ['element_identifier', 'AD05nb', 'AD05nb'],
'bad element_identifier' => ['element_identifier', 'AD05nb+', 'AD05nb'],
'good url' => ['url', 'https://www.w3schools.com', 'https://www.w3schools.com'],
'bad url' => ['url', 'https//www.w3schools.com', null],
'bad url' => ['url', 'https://www.w3schoo<EFBFBD><EFBFBD>ls.co<EFBFBD>m', null],
'raw_data' => ['raw_data', '<Test>\s😃😃😃', '<Test>\s😃😃😃'],
];
}

View File

@@ -213,7 +213,10 @@ try
if ($oRS instanceof iRestInputSanitizer) {
$sSanitizedJsonInput = $oRS->SanitizeJsonInput($sJsonString);
}
else {
$sSanitizedJsonInput = $sJsonString;
}
CMDBObject::SetTrackOrigin('webservice-rest');
$oResult = $oRS->ExecOperation($sVersion, $sOperation, $aJsonData);
}
@@ -272,7 +275,7 @@ if (MetaModel::GetConfig()->Get('log_rest_service'))
$oLog->SetTrim('userinfo', UserRights::GetUser());
$oLog->Set('version', $sVersion);
$oLog->Set('operation', $sOperation);
$oLog->SetTrim('json_input', $sSanitizedJsonInput ?? $sJsonString);
$oLog->SetTrim('json_input', $sSanitizedJsonInput);
$oLog->Set('provider', $sProvider);
$sMessage = $oResult->message;