Compare commits

...

62 Commits

Author SHA1 Message Date
odain
e9699846f2 N°8413 - Make data synchro work on DBObject
N°8413 - Make data synchro work on DBObject
2025-06-04 11:51:34 +02:00
bdalsass
3ff3dcba54 N°8308 - Broken URL in sending mail 2025-06-02 17:15:51 +02:00
jf-cbd
657fc912bf N°8198 - ModuleInstallation now extends DBObject + better exception message 2025-06-02 16:09:29 +02:00
Anne-Catherine
5ae5221f6f N°6925 - Enable on any search result edition of OQL (#711) 2025-05-28 10:59:50 +02:00
jf-cbd
b15ca2fbc9 N°8260 - Change format of REST logs when they are close to the SQL field size limit 2025-05-27 10:41:05 +02:00
Stephen Abello
cb382eab4e Fix CI following dict behavior change 2025-05-26 16:23:38 +02:00
Stephen Abello
9723cde24c N°7960 - Add a wrapper to vsprintf to handle argument number disparities (#717) 2025-05-26 15:41:25 +02:00
Stephen Abello
7ae49e2cf4 N°8076 - Fix cancel and close buttons in a modal blocking all buttons for the underlying object 2025-05-26 11:25:08 +02:00
v-dumas
cb13a7a5b4 N°2583 - Audit, User, Query classes can be Import with bulk_modify right 2025-05-23 17:37:47 +02:00
bdalsass
9618e47045 N°8201 - [CVE_Request]_Cross-Site-Script Reflected(XSS Reflected at the name="attr_installed" (Low or Medium) 2025-05-23 10:16:22 +02:00
bdalsass
d84506ea9e Merge remote-tracking branch 'origin/support/2.7' into support/3.2
# Conflicts:
#	pages/UI.php
2025-05-23 10:14:50 +02:00
bdalsass
13239c2751 N°8201 - [CVE_Request]_Cross-Site-Script Reflected(XSS Reflected at the name="attr_installed" (Low or Medium) 2025-05-23 10:06:01 +02:00
bdalsass
8b30e36dd1 N°8168 - Stored XSS in portals lnk 2025-05-23 08:52:18 +02:00
bdalsass
80b290ab88 Merge remote-tracking branch 'origin/support/2.7' into support/3.2
# Conflicts:
#	sources/renderer/bootstrap/fieldrenderer/bslinkedsetfieldrenderer.class.inc.php
2025-05-23 08:49:52 +02:00
bdalsass
81b20ee583 N°8168 - Stored XSS in portals lnk 2025-05-23 08:42:56 +02:00
Stephen Abello
a5545b0084 N°8205 - Security hardening 2025-05-20 09:48:00 +02:00
Stephen Abello
d72e861dfe N°8315 - Security hardening 2025-05-19 14:51:12 +02:00
Stephen Abello
92385273ff N°8315 - Security hardening 2025-05-19 14:45:24 +02:00
bdalsass
61c25f85e7 N°8313 - edit dashboard (fix broken test 3.2) 2025-05-19 13:12:02 +02:00
bdalsass
b1cf2ec137 N°8313 - edit dashboard (fix broken test 3.2) 2025-05-16 14:35:39 +02:00
bdalsass
7549ded51d Merge remote-tracking branch 'origin/support/2.7' into support/3.2
# Conflicts:
#	application/dashboard.class.inc.php
#	setup/setuputils.class.inc.php
#	tests/php-unit-tests/unitary-tests/application/utilsTest.php
2025-05-16 14:19:00 +02:00
bdalsass
38683c20b1 N°8313 - edit dashboard (fix broken test) 2025-05-16 14:05:55 +02:00
bdalsass
81791dd253 N°8313 - edit dashboard 2025-05-16 14:05:55 +02:00
bdalsass
e77e0eec9f N°8355 - render_dashboard 2025-05-16 14:05:55 +02:00
jf-cbd
f5ddbbbe0e Rollback typing parameter 2025-05-13 16:59:43 +02:00
jf-cbd
6811a82e1a Merge remote-tracking branch 'origin/support/2.7' into support/3.2
# Conflicts:
#	datamodels/2.x/itop-backup/status.php
#	setup/setuputils.class.inc.php
2025-05-13 16:40:11 +02:00
jf-cbd
960133c0df N°8379 - fix backup issue 2025-05-13 16:27:59 +02:00
jf-cbd
544c4ae888 N°8379 - fix backup issue 2025-05-13 16:05:39 +02:00
jbostoen
9ee18c2f36 Add helper method to create an ObjectResult from a DBObject. (#706)
Author: Jeffrey Bostoen <support@jeffreybostoen.be>
2025-04-25 14:46:33 +02:00
jf-cbd
43a10e6944 Revert "N°8259 - Problem with GetMaxSize on AttributeText"
This reverts commit 29c75f626b.
2025-04-22 09:37:54 +02:00
Stephen Abello
bf8269fee1 N°8293 - Make multiple modal in portal correctly stack the backdrops 2025-03-26 09:26:04 +01:00
jf-cbd
c99995563e Merge remote-tracking branch 'origin/support/3.2.1' into support/3.2
# Conflicts:
#	lib/composer/autoload_classmap.php
#	lib/composer/autoload_static.php
2025-03-24 11:31:25 +01:00
Tommaso Rossi
9a895a7fbd 🐛 N°8115 - Unattended Install: unable to connect to MySQL with TLS (#694)
🐛 Support TLS connections to MySQL in unattended-install process
2025-03-13 15:59:04 +01:00
jf-cbd
f5011bb200 N°8260 - Change format of REST logs when they are close to the SQL field size limit 2025-03-13 15:39:00 +01:00
jf-cbd
29c75f626b N°8259 - Problem with GetMaxSize on AttributeText 2025-03-13 15:29:00 +01:00
jf-cbd
0562563cbb Dump autoloader 2025-03-12 11:51:59 +01:00
jf-cbd
40068bd913 Merge remote-tracking branch 'origin/support/2.7' into support/3.2
# Conflicts:
#	lib/composer/autoload_classmap.php
#	lib/composer/autoload_static.php
2025-03-12 11:47:33 +01:00
jf-cbd
1ec139782e Merge remote-tracking branch 'origin/support/3.1' into support/3.2 2025-03-06 16:24:40 +01:00
jf-cbd
056dce4d78 Merge remote-tracking branch 'origin/support/2.7' into support/3.1
# Conflicts:
#	webservices/rest.php
2025-03-06 16:12:15 +01:00
jf-cbd
9b1395db03 Workaround for N°4459 doesn't work on 3.2 2025-03-06 15:23:38 +01:00
jf-cbd
8fd9eb6a84 Merge remote-tracking branch 'origin/support/3.1' into support/3.2 2025-03-06 14:58:24 +01:00
jf-cbd
1142bf327c Fix tests 2025-03-06 14:54:18 +01:00
jf-cbd
278496eaf6 Update tests for 3.1 2025-03-06 14:40:39 +01:00
jf-cbd
77ba0b398f Fix merge conflict 2025-03-06 12:11:20 +01:00
jf-cbd
04ca7bf603 Merge remote-tracking branch 'origin/support/2.7' into support/3.1
# Conflicts:
#	core/restservices.class.inc.php
#	datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php
#	tests/php-unit-tests/unitary-tests/core/Delta/delta_test_sanitize_output.xml
#	tests/php-unit-tests/unitary-tests/core/RestServicesSanitizeOutputTest.php
#	tests/php-unit-tests/unitary-tests/core/RestServicesTest.php
#	webservices/rest.php
2025-03-06 12:10:00 +01:00
Eric Espie
5aee7f4722 N°8242 - When editing a dashboard the parameters do not refresh as expected 2025-03-05 11:02:19 +01:00
Eric Espie
292701b71c N°8242 - When editing a dashboard the parameters do not refresh as expected 2025-03-04 16:51:58 +01:00
jf-cbd
533b57ab99 Merge branch 'support/3.1' into support/3.2 2025-03-03 17:07:24 +01:00
jf-cbd
be8d348b25 N°8231 - Update tests 2025-03-03 16:49:27 +01:00
jf-cbd
6c7a98fe3d Merge remote-tracking branch 'origin/support/2.7' into support/3.1 2025-03-03 16:38:32 +01:00
denis.flaven@combodo.com
ec2203229b N°8231 - making rest api log more readable 2025-03-03 16:23:25 +01:00
jf-cbd
2f699d355a Merge remote-tracking branch 'origin/support/3.1' into support/3.2 2025-03-03 14:37:57 +01:00
jf-cbd
da4457f5b4 Merge remote-tracking branch 'origin/support/2.7' into support/3.1 2025-03-03 14:36:50 +01:00
denis.flaven@combodo.com
97848cea4f N°8231 - making rest api log more readable 2025-03-03 11:38:52 +01:00
jf-cbd
2ccd883c9a 👥 Update contributor list 2025-02-25 11:38:54 +01:00
jf-cbd
eabd68ff77 👥 Update contributor list 2025-02-25 11:38:14 +01:00
jf-cbd
2b493787b1 Update itop-version-history.md with 2.7.12, 3.1.3 and 3.2.1 2025-02-25 09:19:47 +01:00
jf-cbd
d74c850621 N°7870 - Tests: access the symfony service container to instantiate services 2025-02-21 15:28:38 +01:00
jf-cbd
4e575e17a3 Merge remote-tracking branch 'origin/support/3.1' into support/3.2 2025-02-21 13:23:57 +01:00
jf-cbd
94d6eca0c1 N°8215 - When PHP warning are enabled, Global Request doesn't work 2025-02-21 13:20:27 +01:00
jf-cbd
355da8ec0a Merge remote-tracking branch 'origin/support/2.7' into support/3.1
# Conflicts:
#	datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php
2025-02-21 13:18:19 +01:00
jf-cbd
5f006c45db N°8215 - When PHP warning are enabled, Global Request doesn't work 2025-02-21 13:16:07 +01:00
39 changed files with 960 additions and 161 deletions

View File

@@ -93,6 +93,12 @@ gitGraph
checkout support/3.2
commit id: "2024-06-25" tag: "3.2.0-beta1" type: REVERSE
commit id: "2024-08-07" tag: "3.2.0"
checkout support/2.7
commit id: "2025-02-25" tag: "2.7.12"
checkout support/3.1
commit id: "2025-02-25 " tag: "3.1.3"
checkout support/3.2
commit id: "2025-02-25 " tag: "3.2.1"
```
To learn more, check the [iTop community versions history on the official wiki](https://www.itophub.io/wiki/page?id=latest:release:start).

View File

@@ -106,6 +106,7 @@ We would like to give a special thank you 🤗 to the people from the community
- Raenker, Martin
- Roháč, Richard (a.k.a [@RohacRichard](https://github.com/RohacRichard))
- Rosenke, Stephan
- Rossi, Tommaso (a.k.a [@tomrss](https://www.github.com/tomrss))
- Rudner, Björn (a.k.a [@rudnerbjoern](https://github.com/rudnerbjoern))
- Šafránek, Jaroslav (a.k.a [jkcinik](https://sourceforge.net/u/jkcinik/profile/) on SourceForge)
- Seki, Shoji
@@ -115,6 +116,7 @@ We would like to give a special thank you 🤗 to the people from the community
- Tarjányi, Csaba (a.k.a [@tacsaby](https://github.com/tacsaby))
- Tulio, Marco
- Turrubiates, Miguel
- Vlk, Karel (a.k.a [@vlk-charles](https://www.github.com/vlk-charles))
### Aliases

View File

@@ -3079,7 +3079,7 @@ JS
} else {
$sCancelButtonOnClickScript .= "function() { BackToDetails('$sClass', $iKey, '$sDefaultUrl', $sJSToken)};";
}
$sCancelButtonOnClickScript .= "$('#form_{$this->m_iFormId} button.cancel').on('click', fOnClick{$this->m_iFormId}CancelButton);";
$sCancelButtonOnClickScript .= "$('#form_{$this->m_iFormId} button.cancel').on('click.navigation.itop', fOnClick{$this->m_iFormId}CancelButton);";
$oPage->add_ready_script($sCancelButtonOnClickScript);
$iFieldsCount = count($aFieldsMap);

View File

@@ -524,9 +524,7 @@ EOF
*/
public function Render($oPage, $bEditMode = false, $aExtraParams = array(), $bCanEdit = true)
{
if (!array_key_exists('dashboard_div_id', $aExtraParams)) {
$aExtraParams['dashboard_div_id'] = utils::Sanitize($this->GetId(), '', 'element_identifier');
}
$aExtraParams['dashboard_div_id'] = utils::Sanitize($aExtraParams['dashboard_div_id'] ?? null, $this->GetId(), utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
/** @var \DashboardLayoutMultiCol $oLayout */
$oLayout = new $this->sLayoutClass();
@@ -1052,7 +1050,7 @@ EOF
$sSelectorHtml .= '</div>';
$sFile = addslashes($this->GetDefinitionFile());
$sReloadURL = $this->GetReloadURL();
$sReloadURL = json_encode($this->GetReloadURL());
$bFromDashboardPage = isset($aAjaxParams['from_dashboard_page']) ? isset($aAjaxParams['from_dashboard_page']) : false;
if ($bFromDashboardPage) {
@@ -1141,7 +1139,6 @@ JS
->AddCSSClass('ibo-action-button');
$oToolbar->AddSubBlock($oActionButton);
$aActions = array();
$sFile = addslashes(utils::LocalPath($this->sDefinitionFile));
$sJSExtraParams = json_encode($aExtraParams);
@@ -1166,7 +1163,7 @@ JS
$oToolbar->AddSubBlock($oActionButton)
->AddSubBlock($oActionsMenu);
$sReloadURL = $this->GetReloadURL();
$sReloadURL = json_encode($this->GetReloadURL());
$oPage->add_script(
<<<EOF
function EditDashboard(sId, sDashboardFile, aExtraParams)
@@ -1273,7 +1270,7 @@ EOF
$sTitle = json_encode($this->sTitle);
$sFile = json_encode($this->GetDefinitionFile());
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php';
$sReloadURL = $this->GetReloadURL();
$sReloadURL = json_encode($this->GetReloadURL());
$sExitConfirmationMessage = addslashes(Dict::S('UI:NavigateAwayConfirmationMessage'));
$sCancelConfirmationMessage = addslashes(Dict::S('UI:CancelConfirmationMessage'));

View File

@@ -250,7 +250,7 @@ class UIExtKeyWidget
foreach ($aAdditionalField as $sAdditionalField) {
array_push($aArguments, $oObj->Get($sAdditionalField));
}
$aOption['additional_field'] = utils::HtmlEntities(vsprintf($sFormatAdditionalField, $aArguments));
$aOption['additional_field'] = utils::HtmlEntities(utils::VSprintf($sFormatAdditionalField, $aArguments));
}
if (!empty($sObjectImageAttCode)) {
// Try to retrieve image for contact

View File

@@ -521,8 +521,8 @@ class utils
// For URL
case static::ENUM_SANITIZATION_FILTER_URL:
// N°6350 - returns only valid URLs
$retValue = filter_var($value, FILTER_VALIDATE_URL);
$retValue = filter_var($value, FILTER_SANITIZE_URL);
$retValue = filter_var($retValue, FILTER_VALIDATE_URL);
break;
default:
@@ -1555,6 +1555,11 @@ class utils
}
$aResult[] = new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL', '$sContext')");
$aResult[] = new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')");
if (ApplicationMenu::IsMenuIdEnabled('RunQueriesMenu')) {
$oMenuItemPlay = new JSPopupMenuItem('UI:Menu:OpenOQL', Dict::S('UI:Edit:TestQuery'), "OpenOql('$sOQL')");
$oMenuItemPlay->SetIconClass('fas fa-play');
$aResult[] = $oMenuItemPlay;
}
break;
@@ -2075,6 +2080,127 @@ SQL;
);
}
/**
* Format a string using vsprintf with safety checks to avoid ValueError
*
* This method fills missing arguments with their original format specifiers,
* then calls vsprintf with the complete array.
*
* @param string $sFormat The format string
* @param array $aArgs The arguments to format
* @param bool $bLogErrors Whether to log errors (defaults to true)
*
* @return string The formatted string
* @since 3.2.2
*/
public static function VSprintf(string $sFormat, array $aArgs, bool $bLogErrors = true): string
{
// Extract all format specifiers
$sPattern = '/%(?:(?:[1-9][0-9]*)\$)?[-+\'0# ]*(?:[0-9]*|\*)?(?:\.(?:[0-9]*|\*))?(?:[hlL])?[diouxXeEfFgGcrs%]/';
preg_match_all($sPattern, $sFormat, $aMatches, PREG_OFFSET_CAPTURE);
// Process matches, keeping track of their positions and excluding escaped percent signs (%%)
$aSpecifierMatches = [];
foreach ($aMatches[0] as $sMatch) {
if ($sMatch[0] !== '%%') {
$aSpecifierMatches[] = $sMatch;
}
}
// Check for positional specifiers and build position map
$bHasPositional = false;
$iMaxPosition = 0;
$aPositions = [];
$aUniquePositions = [];
foreach ($aSpecifierMatches as $index => $match) {
$sSpec = $match[0];
if (preg_match('/^%([1-9][0-9]*)\$/', $sSpec, $posMatch)) {
$bHasPositional = true;
$iPosition = (int)$posMatch[1] - 1; // Convert to 0-based
$aPositions[$index] = $iPosition;
$aUniquePositions[$iPosition] = true;
$iMaxPosition = max($iMaxPosition, $iPosition + 1);
} else {
$aPositions[$index] = $index;
$aUniquePositions[$index] = true;
$iMaxPosition = max($iMaxPosition, $index + 1);
}
}
// Count unique positions, this tells us how many arguments we actually need
$iExpectedCount = count($aUniquePositions);
$iActualCount = count($aArgs);
// If we have enough arguments, just use vsprintf
if ($iActualCount >= $iExpectedCount) {
return vsprintf($sFormat, $aArgs);
}
// else log the error if needed
if ($bLogErrors) {
IssueLog::Warning("Format string requires $iExpectedCount arguments, but only $iActualCount provided. Format: '$sFormat'" );
}
// Create a replacement map
if ($bHasPositional) {
// For positional, we need to handle the exact positions
$aReplacements = array_fill(0, $iMaxPosition, null);
// Fill in the real arguments first
foreach ($aArgs as $index => $sValue) {
if ($index < $iMaxPosition) {
$aReplacements[$index] = $sValue;
}
}
// For null values in the replacement map, use the original specifier
foreach ($aSpecifierMatches as $index => $sMatch) {
$iPosition = $aPositions[$index];
if ($aReplacements[$iPosition] === null) {
// Use the original format specifier when we don't have an argument
$aReplacements[$iPosition] = $sMatch[0];
}
}
// Remove any remaining nulls (for positions that weren't referenced)
$aReplacements = array_filter($aReplacements, static function($val) { return $val !== null; });
} else {
// For non-positional, we need to map each position
$aReplacements = [];
$iUsed = 0;
// Create a map of what values to use for each position
$aPositionValues = [];
for ($i = 0; $i < $iMaxPosition; $i++) {
if (isset($aUniquePositions[$i])) {
if ($iUsed < $iActualCount) {
// We have an actual argument for this position
$aPositionValues[$i] = $aArgs[$iUsed++];
} else {
// Mark this position to use the original specifier
$aPositionValues[$i] = null;
}
}
}
// Build the replacements array preserving the original order
foreach ($aSpecifierMatches as $index => $sMatch) {
$iPosition = $aPositions[$index];
if (isset($aPositionValues[$iPosition])) {
$aReplacements[] = $aPositionValues[$iPosition];
} else {
// Use the original format specifier when we don't have an argument
$aReplacements[] = $sMatch[0];
// Mark this position as used, so if it appears again, it gets the same replacement
$aPositionValues[$iPosition] = $sMatch[0];
}
}
}
// Process the format string with our filled-in arguments
return vsprintf($sFormat, $aReplacements);
}
/**
* Convert a string containing some (valid) HTML markup to plain text
*

View File

@@ -206,7 +206,7 @@ class Dict
}
try{
return vsprintf($sLocalizedFormat, $aArguments);
return utils::VSprintf($sLocalizedFormat, $aArguments);
} catch(\Throwable $e){
\IssueLog::Error("Cannot format dict key", null, ["sFormatCode" => $sFormatCode, "sLangCode" => $sLangCode, 'exception_msg' => $e->getMessage() ]);
return $sFormatCode.' - '.implode(', ', $aArguments);

View File

@@ -77,7 +77,7 @@ abstract class ModelReflection
return $sFormatCode.' - '.implode(', ', $aArguments);
}
return vsprintf($sLocalizedFormat, $aArguments);
return utils::VSprintf($sLocalizedFormat, $aArguments);
}
/**

View File

@@ -76,6 +76,52 @@ class ObjectResult
$this->fields = array();
}
/**
* Creates an ObjectResult from a DBObject.
*
* @param DBObject $oObj The object.
* @param array|null $aFieldSpec An array of class => attribute codes (Cf. RestUtils::GetFieldList). List of the attributes to be reported.
* @param boolean $bExtendedOutput Output all of the link set attributes ?
* @param integer $iCode An error code (RestResult::OK is no issue has been found)
* @param string $sMessage Description of the error if any, an empty string otherwise
*
* @return ObjectResult
*/
public static function FromDBObject(DBObject $oObj, ?array $aFieldSpec = null, $bExtendedOutput = false, $iCode = 0, $sMessage = '') : ObjectResult {
$oObjRes = new ObjectResult($oObj::class, $oObj->GetKey());
$oObjRes->code = $iCode;
$oObjRes->message = $sMessage;
$aFields = null;
if (!is_null($aFieldSpec))
{
// Enum all classes in the hierarchy, starting with the current one
foreach (MetaModel::EnumParentClasses($oObj::class, ENUM_PARENT_CLASSES_ALL, false) as $sRefClass)
{
if (array_key_exists($sRefClass, $aFieldSpec))
{
$aFields = $aFieldSpec[$sRefClass];
break;
}
}
}
if (is_null($aFields))
{
// No fieldspec given, or not found...
$aFields = array('id', 'friendlyname');
}
foreach ($aFields as $sAttCode)
{
$oObjRes->AddField($oObj, $sAttCode, $bExtendedOutput);
}
return $oObjRes;
}
/**
* Helper to make an output value for a given attribute
*
@@ -204,34 +250,7 @@ class RestResultWithObjects extends RestResult
*/
public function AddObject($iCode, $sMessage, $oObject, $aFieldSpec = null, $bExtendedOutput = false)
{
$sClass = get_class($oObject);
$oObjRes = new ObjectResult($sClass, $oObject->GetKey());
$oObjRes->code = $iCode;
$oObjRes->message = $sMessage;
$aFields = null;
if (!is_null($aFieldSpec))
{
// Enum all classes in the hierarchy, starting with the current one
foreach (MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL, false) as $sRefClass)
{
if (array_key_exists($sRefClass, $aFieldSpec))
{
$aFields = $aFieldSpec[$sRefClass];
break;
}
}
}
if (is_null($aFields))
{
// No fieldspec given, or not found...
$aFields = array('id', 'friendlyname');
}
foreach ($aFields as $sAttCode)
{
$oObjRes->AddField($oObject, $sAttCode, $bExtendedOutput);
}
$oObjRes = ObjectResult::FromDBObject($oObject, $aFieldSpec, $bExtendedOutput, $iCode, $sMessage);
$sObjKey = get_class($oObject).'::'.$oObject->GetKey();
$this->objects[$sObjKey] = $oObjRes;

View File

@@ -435,7 +435,7 @@ class ValueSetObjects extends ValueSetDefinition
foreach ($aAdditionalField as $sAdditionalField) {
array_push($aArguments, $oObject->Get($sAdditionalField));
}
$aData['additional_field'] = vsprintf($sFormatAdditionalField, $aArguments);
$aData['additional_field'] = utils::VSprintf($sFormatAdditionalField, $aArguments);
} else {
$aData['additional_field'] = '';
}

View File

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

View File

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

View File

@@ -53,13 +53,21 @@ $(document).ready(function()
}, 1000);
// Hide tooltips when a modal is opening, otherwise it might be overlapping it
oBodyElem.on('show.bs.modal', function ()
oBodyElem.on('show.bs.modal', '.modal', function ()
{
$(this).find('.tooltip.in').tooltip('hide');
// Set the z-index of the modal and its backdrop in case we have several modals opened
let zIndex = 1050 + (10 * $('.modal:visible').length);
$(this).css('z-index', zIndex);
// Set the z-index of the backdrop later because it is created after the modal
setTimeout(function() {
$('.modal-backdrop').not('.modal-stack').css('z-index', zIndex - 1).addClass('modal-stack');
}, 10);
});
/*
* Display a error message on modal if the content could not be loaded.
* Display an error message on modal if the content could not be loaded.
*
* Note : As of now, we can't display a more detailed message based on the response because Bootstrap doesn't pass response data with the loaded event.
*/

View File

@@ -168,6 +168,13 @@ CombodoModal._InstantiateModal = function(oModalElem, oOptions) {
// Show modal
if (oOptions.auto_open) {
// Append modal to body if not already in DOM, this is also done when the modal is shown, but it happens after show.bs.modal event is triggered
// As we put this event listener on the body, it is not triggered when the modal is not in the DOM yet
if (oModalElem.parent().length === 0) {
$('body').append(oModalElem);
}
oModalElem.modal('show');
}

View File

@@ -335,7 +335,7 @@ class BrowseBrickHelper
$aRow[$key] = array(
'level_alias' => $key,
'id' => $sCurrentObjectId,
'name' => $value->Get($sNameAttCode),
'name' => utils::EscapeHtml($value->Get($sNameAttCode)),
'class' => $sCurrentObjectClass,
'action_rules_token' => $this->PrepareActionRulesForItems($aItems, $key, $aLevelsProperties),
'metadata' => array(
@@ -513,7 +513,7 @@ class BrowseBrickHelper
$aItems[$sCurrentIndex] = array(
'level_alias' => $aCurrentRowKeys[0],
'id' => $aCurrentRowValues[0]->GetKey(),
'name' => $aCurrentRowValues[0]->Get($aLevelsProperties[$aCurrentRowKeys[0]]['name_att']),
'name' => utils::EscapeHtml($aCurrentRowValues[0]->Get($aLevelsProperties[$aCurrentRowKeys[0]]['name_att'])),
'class' => get_class($aCurrentRowValues[0]),
'subitems' => array(),
'filter_data' => $this->GetFilterData($aLevelsProperties[$aCurrentRowKeys[0]], $aCurrentRowKeys[0], $aCurrentRowValues[0]),

View File

@@ -156,8 +156,17 @@
return row.attributes[attribute_code].sort_value;
},
filter: function (attribute_code, type, row) {
return $.text($.parseHTML(row.attributes[attribute_code]['value_html']));
},
// Check if the attribute and value_html exist
if (!row.attributes[attribute_code] || !row.attributes[attribute_code]['value_html']) {
return '';
}
// Create a temporary div outside the DOM to filter out XSS
const tempDiv = document.createElement('div');
tempDiv.textContent = row.attributes[attribute_code]['value_html'];
return tempDiv.textContent;
},
},
});
}

View File

@@ -717,8 +717,11 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
window[sPromiseId].then(function () {
$('#ac_create_'+me.id).dialog('open');
$('#ac_create_'+me.id).dialog("option", "close", me.OnCloseCreateObject);
// Modify the action of the cancel button
$('#ac_create_'+me.id+' button.cancel').off('click').on('click', me.CloseCreateObject);
// Modify the action of the cancel button and the close button
$('#ac_create_'+me.id+' button.cancel').off('click.navigation.itop').on('click.navigation.itop', me.CloseCreateObject);
$('.ui-dialog-titlebar:has(+ #ac_create_'+me.id+') button.ui-dialog-titlebar-close').off('click').on('click', function() {
$('#ac_create_'+me.id+' button.cancel').trigger('click');
});
me.ajax_request = null;
me.sTargetClass = sLocalTargetClass;
// Adjust the dialog's size to fit into the screen

View File

@@ -369,6 +369,24 @@ function DashletCreationDlg(sOQL, sContext) {
return false;
}
function OpenOql(sOQL) {
sBaseUrl = GetAbsoluteUrlAppRoot() + 'pages/run_query.php';
var form = document.createElement("form");
form.setAttribute("method", "post");
form.setAttribute("action", sBaseUrl);
form.setAttribute("target", '_blank');
form.setAttribute("id", 'run_query_form');
var input = document.createElement('input');
input.type = 'hidden';
input.name = 'expression';
input.value = sOQL;
form.appendChild(input);
document.body.appendChild(form);
// form.submit() is blocked by the browser
$('#run_query_form').submit();
document.body.removeChild(form);
}
function ShortcutListDlg(sOQL, sDataTableId, sContext) {
var sDataTableName = 'datatable_'+sDataTableId;
var oTableSettings = {

View File

@@ -1560,6 +1560,7 @@ return array(
'Sabberworm\\CSS\\Value\\URL' => $vendorDir . '/sabberworm/php-css-parser/src/Value/URL.php',
'Sabberworm\\CSS\\Value\\Value' => $vendorDir . '/sabberworm/php-css-parser/src/Value/Value.php',
'Sabberworm\\CSS\\Value\\ValueList' => $vendorDir . '/sabberworm/php-css-parser/src/Value/ValueList.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',
@@ -3201,6 +3202,7 @@ 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',
@@ -3228,5 +3230,5 @@ return array(
'privUITransactionFile' => $baseDir . '/application/transaction.class.inc.php',
'privUITransactionSession' => $baseDir . '/application/transaction.class.inc.php',
'utils' => $baseDir . '/application/utils.inc.php',
'©' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php',
'<EFBFBD>' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php',
);

View File

@@ -1950,6 +1950,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Sabberworm\\CSS\\Value\\URL' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Value/URL.php',
'Sabberworm\\CSS\\Value\\Value' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Value/Value.php',
'Sabberworm\\CSS\\Value\\ValueList' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Value/ValueList.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',
@@ -3591,6 +3592,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'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',
@@ -3618,7 +3620,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'privUITransactionFile' => __DIR__ . '/../..' . '/application/transaction.class.inc.php',
'privUITransactionSession' => __DIR__ . '/../..' . '/application/transaction.class.inc.php',
'utils' => __DIR__ . '/../..' . '/application/utils.inc.php',
'©' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php',
'<EFBFBD>' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php',
);
public static function getInitializer(ClassLoader $loader)

View File

@@ -1520,7 +1520,7 @@ catch (Exception $e) {
$oErrorPage->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
}
$sErrorDetails = ($e instanceof CoreException) ? $e->getHtmlDesc() : $e->getMessage();
$oErrorPage->error(Dict::Format('UI:Error_Details', $sErrorDetails));
$oErrorPage->error(Dict::Format('UI:Error_Details', utils::EscapeHtml($sErrorDetails)));
$oErrorPage->output();
$sErrorStackTrace = ($e instanceof CoreException) ? $e->getFullStackTraceAsString() : $e->getTraceAsString();

View File

@@ -89,9 +89,9 @@ try {
$oOption = SelectOptionUIBlockFactory::MakeForSelectOption("", Dict::S('UI:CSVImport:ClassesSelectOne'), false);
$oSelectBlock->AddSubBlock($oOption);
$aValidClasses = array();
$aClassCategories = array('bizmodel', 'addon/authentication');
$aClassCategories = array('bizmodel', 'addon/authentication', 'grant_by_profile');
if (UserRights::IsAdministrator()) {
$aClassCategories = array('bizmodel', 'application', 'addon/authentication');
$aClassCategories = array('bizmodel', 'application', 'addon/authentication', 'grant_by_profile');
}
foreach ($aClassCategories as $sClassCategory) {
foreach (MetaModel::GetClasses($sClassCategory) as $sClassName) {

View File

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

View File

@@ -25,7 +25,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
class ModuleInstallation extends cmdbAbstractObject
class ModuleInstallation extends DBObject
{
public static function Init()
{

View File

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

View File

@@ -143,6 +143,8 @@ $sDBUser = $aDBXmlSettings['user'];
$sDBPwd = $aDBXmlSettings['pwd'];
$sDBName = $aDBXmlSettings['name'];
$sDBPrefix = $aDBXmlSettings['prefix'];
$bDBTlsEnabled = $aDBXmlSettings['db_tls_enabled'];
$sDBTlsCa = $aDBXmlSettings['db_tls_ca'];
if ($sMode == 'install')
{
@@ -219,13 +221,10 @@ if ($sMode == 'install')
die("Cleanup not implemented for a partial database (prefix= '$sDBPrefix')\nExiting.");
}
$oMysqli = new mysqli($sDBServer, $sDBUser, $sDBPwd);
if ($oMysqli->connect_errno)
{
die("Cannot connect to the MySQL server (".$oMysqli->connect_errno . ") ".$oMysqli->connect_error."\nExiting");
}
else
try
{
$oMysqli = CMDBSource::GetMysqliInstance($sDBServer, $sDBUser, $sDBPwd, null, $bDBTlsEnabled, $sDBTlsCa, true);
if ($oMysqli->select_db($sDBName))
{
echo "Deleting database '$sDBName'\n";
@@ -236,6 +235,10 @@ if ($sMode == 'install')
echo "The database '$sDBName' does not seem to exist. Nothing to cleanup.\n";
}
}
catch (MySQLException $e)
{
die($e->getMessage()."\nExiting");
}
}
}
else
@@ -312,9 +315,9 @@ if ($bInstall)
}
else
{
$oMysqli = new mysqli($sDBServer, $sDBUser, $sDBPwd);
if (!$oMysqli->connect_errno)
try
{
$oMysqli = CMDBSource::GetMysqliInstance($sDBServer, $sDBUser, $sDBPwd, null, $bDBTlsEnabled, $sDBTlsCa, true);
if ($oMysqli->select_db($sDBName))
{
// Check the presence of a table to record information about the MTP (from the Designer)
@@ -357,6 +360,10 @@ if ($bInstall)
}
}
}
catch (MySQLException $e)
{
// Continue anyway
}
}
}
else

View File

@@ -82,7 +82,9 @@ class ObjectController extends AbstractController
{
throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class'));
}
if (!is_subclass_of($sClass, cmdbAbstractObject::class)) {
throw new SecurityException('The class "'.$sClass.'" is not a subclass of cmdbAbstractObject so it can\'t be created by the user');
}
// If the specified class has subclasses, ask the user an instance of which class to create
$aSubClasses = MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself
$aPossibleClasses = array();

View File

@@ -435,7 +435,7 @@ class EMailLaminas extends Email
// Add body content to as a new part
$oNewPart = new Part($sBody);
$oNewPart->encoding = Mime::ENCODING_8BIT;
$oNewPart->encoding = Mime::ENCODING_BASE64;
$oNewPart->type = $sMimeType;
$oNewPart->charset = 'UTF-8';
$oBody->addPart($oNewPart);
@@ -463,7 +463,7 @@ class EMailLaminas extends Email
}
$this->m_aData['parts'][] = array('text' => $sText, 'mimeType' => $sMimeType);
$oNewPart = new Part($sText);
$oNewPart->encoding = Mime::ENCODING_8BIT;
$oNewPart->encoding = Mime::ENCODING_BASE64;
$oNewPart->type = $sMimeType;
// setBody called only to refresh Content-Type to multipart/mixed

View File

@@ -893,7 +893,7 @@ JS
} else if ($oAttDef->IsExternalKey()) {
/** @var \AttributeExternalKey $oAttDef */
$aAttProperties['value_html'] = $oItem->Get($sAttCode.'_friendlyname');
$aAttProperties['value_html'] = utils::EscapeHtml($oItem->Get($sAttCode.'_friendlyname'));
// Checking if user can access object's external key
$sObjectUrl = ApplicationContext::MakeObjectUrl($oAttDef->GetTargetClass(), $oItem->Get($sAttCode));

View File

@@ -213,7 +213,7 @@ class ObjectRepository
foreach ($aComplementAttributeSpec[1] as $sAdditionalField) {
$aArguments[] = $oDbObject->Get($sAdditionalField);
}
$aData['additional_field'] = vsprintf($aComplementAttributeSpec[0], $aArguments);
$aData['additional_field'] = utils::VSprintf($aComplementAttributeSpec[0], $aArguments);
$sAdditionalFieldForHtml = utils::EscapeHtml($aData['additional_field']);
$aData['full_description'] = "{$sFriendlyNameForHtml}<br><i><small>{$sAdditionalFieldForHtml}</small></i>";
} else {

View File

@@ -775,7 +775,7 @@ try
$oP->add_comment('Objects updated: '.$oStatLog->Get('stats_nb_obj_updated').' ('.$oStatLog->Get('stats_nb_obj_updated_warnings')." warnings)");
$oP->add_comment('Objects update errors: '.$oStatLog->Get('stats_nb_obj_updated_errors'));
$oP->add_comment('Objects reconciled (updated): '.$oStatLog->Get('stats_nb_obj_new_updated').' ('.$oStatLog->Get('stats_nb_obj_new_updated_warnings').' warnings)');
$oP->add_comment('Objects reconciled (unchanged): '.$oStatLog->Get('stats_nb_obj_new_unchanged').' ('.$oStatLog->Get('stats_nb_obj_new_updated_warnings').' warnings)');
$oP->add_comment('Objects reconciled (unchanged): '.$oStatLog->Get('stats_nb_obj_new_unchanged').' ('.$oStatLog->Get('stats_nb_obj_new_unchanged_warnings').' warnings)');
$oP->add_comment('Objects reconciliation errors: '.$oStatLog->Get('stats_nb_replica_reconciled_errors'));
$oP->add_comment('Replica disappeared, no action taken: '.$oStatLog->Get('stats_nb_replica_disappeared_no_action'));
}

View File

@@ -2440,7 +2440,9 @@ class SynchroReplica extends DBObject implements iDisplay
// Really modified ?
if ($oDestObj->IsModified())
{
$oDestObj::SetCurrentChange($oChange);
if(method_exists(get_class($oDestObj), "SetCurrentChange")){
$oDestObj::SetCurrentChange($oChange);
}
$oDestObj->DBUpdate();
$bModified = true;
$oStatLog->AddTrace('Updated object - Values: {'.implode(', ', $aValueTrace).'}', $this);
@@ -2499,7 +2501,11 @@ class SynchroReplica extends DBObject implements iDisplay
$aValueTrace[] = "$sAttCode: $value";
}
}
$oDestObj::SetCurrentChange($oChange);
if(method_exists(get_class($oDestObj), "SetCurrentChange")){
//N°8413 - Make data synchro work on DBObject
$oDestObj::SetCurrentChange($oChange);
}
$iNew = $oDestObj->DBInsert();
$this->Set('dest_id', $oDestObj->GetKey());
@@ -2552,7 +2558,10 @@ class SynchroReplica extends DBObject implements iDisplay
$oDestObj->Set($sAttCode, $value);
}
$this->Set('info_last_modified', date(AttributeDateTime::GetSQLFormat()));
$oDestObj::SetCurrentChange($oChange);
if(method_exists(get_class($oDestObj), "SetCurrentChange")){
//N°8413 - Make data synchro work on DBObject
$oDestObj::SetCurrentChange($oChange);
}
$oDestObj->DBUpdate();
$oStatLog->AddTrace('Replica marked as obsolete', $this);
$oStatLog->Inc('stats_nb_obj_obsoleted');
@@ -2590,7 +2599,10 @@ class SynchroReplica extends DBObject implements iDisplay
$oCheckDeletionPlan = new DeletionPlan();
if ($oDestObj->CheckToDelete($oCheckDeletionPlan))
{
$oDestObj::SetCurrentChange($oChange);
if(method_exists(get_class($oDestObj), "SetCurrentChange")){
//N°8413 - Make data synchro work on DBObject
$oDestObj::SetCurrentChange($oChange);
}
$oDestObj->DBDelete();
$this->DBDelete();
$oStatLog->Inc('stats_nb_obj_deleted');
@@ -2636,7 +2648,7 @@ class SynchroReplica extends DBObject implements iDisplay
}
// $sExtAttCode is a valid attribute code
//
//
$sClass = $this->Get('base_class');
$oAttDef = MetaModel::GetAttributeDef($sClass, $sExtAttCode);
@@ -2745,7 +2757,7 @@ class SynchroReplica extends DBObject implements iDisplay
public function GetHilightClass()
{
// Possible return values are:
// HILIGHT_CLASS_CRITICAL, HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE
// HILIGHT_CLASS_CRITICAL, HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE
return HILIGHT_CLASS_NONE; // Not hilighted by default
}

View File

@@ -41,7 +41,7 @@ class DictionariesConsistencyAfterSetupTest extends ItopTestCase
],
'traduction that breaks expected nb of arguments' => [
'sTemplate' => 'toto %1$s titi %2$s',
'sExpectedTraduction' => 'ITOP::DICT:FORMAT:BROKEN:KEY - 1',
'sExpectedTraduction' => 'toto 1 titi %2$s',
],
'traduction ok' => [
'sTemplate' => 'toto %1$s titi',

View File

@@ -834,9 +834,10 @@ HTML,
'bad context_param' => [utils::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM, '%dssD,25_=%:+-', null],
'good element_identifier' => [utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER, 'AD05nb', 'AD05nb'],
'bad element_identifier' => [utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER, 'AD05nb+', 'AD05nb'],
'array' => [utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER, ['AD05nb+','apply_modify'], ['AD05nb','apply_modify']],
'good url' => [utils::ENUM_SANITIZATION_FILTER_URL, 'https://www.w3schools.com', 'https://www.w3schools.com'],
'bad url' => [utils::ENUM_SANITIZATION_FILTER_URL, 'https://www.w3schoo<EFBFBD><EFBFBD>ls.co<EFBFBD>m', null],
'url with injection' => [utils::ENUM_SANITIZATION_FILTER_URL, 'https://demo.combodo.com/simple/pages/UI.php?operation=full_text&text=<img zzz src=x onerror=alert(1) //>', null],
'bad url' => [utils::ENUM_SANITIZATION_FILTER_URL, 'https//www.w3schools.com', null],
'url with injection' => [utils::ENUM_SANITIZATION_FILTER_URL, 'https://demo.combodo.com/simple/pages/UI.php?operation=full_text&text=<img zzz src=x onerror=alert(1) //>', 'https://demo.combodo.com/simple/pages/UI.php?operation=full_text&text=<imgzzzsrc=xonerror=alert(1)//>'],
'raw_data' => ['raw_data', '<Test>\s😃😃😃', '<Test>\s😃😃😃'],
];
}
@@ -906,4 +907,94 @@ HTML,
$this->assertFalse(utils::GetDocumentFromSelfURL($sURL));
}
/**
* @dataProvider VSprintfProvider
*/
public function testVSprintf($sFormat, $aArgs, $sExpected)
{
$sTested = utils::VSprintf($sFormat, $aArgs, false);
$this->assertEquals($sExpected, $sTested);
}
public function VSprintfProvider()
{
return [
// Basic positional specifier tests
'Basic positional with enough args' => [
'Format: %1$s, %2$d, %3$s',
['Hello', 42, 'World'],
'Format: Hello, 42, World'
],
'Basic positional with args in different order' => [
'Format: %2$s, %1$d, %3$s',
[42, 'Hello', 'World'],
'Format: Hello, 42, World'
],
'Positional with reused specifiers' => [
'Format: %1$s, %2$d, %1$s again',
['Hello', 42],
'Format: Hello, 42, Hello again'
],
// Missing arguments tests
'Missing one positional arg' => [
'Format: %1$s, %2$d, %3$s',
['Hello', 42],
'Format: Hello, 42, %3$s'
],
'Missing multiple positional args' => [
'Format: %1$s, %2$s, %3$s, %4$s',
['Hello'],
'Format: Hello, %2$s, %3$s, %4$s'
],
'Missing first positional arg' => [
'Format: %1$s, %2$s, %3$s',
[],
'Format: %1$s, %2$s, %3$s'
],
// Edge cases
'Positional with larger numbers' => [
'Format: %2$s, %1$d, %3$s, %2$s again',
[123456, 'Hello', 'World'],
'Format: Hello, 123456, World, Hello again'
],
'Positional specifiers with non-sequential indexes' => [
'Format: %3$s then %1$s and %5$d',
['first', 'second', 'third', 'fourth', 42],
'Format: third then first and 42'
],
// More complex format specifiers
'Positional with format modifiers' => [
'Format: %1$\'*10s, %2$04d',
['Hello', 42],
'Format: *****Hello, 0042'
],
'Positional with various types' => [
'Format: String: %1$s, Integer: %2$d, Char: %3$c',
['Hello', 42, 65],
'Format: String: Hello, Integer: 42, Char: A'
],
// Testing with non-Latin characters
'Positional with UTF-8 characters' => [
'Format: %1$s %2$s %3$s',
['こんにちは', 'Здравствуйте', '你好'],
'Format: こんにちは Здравствуйте 你好'
],
// Mixed formats
'Mixed positional with complex specifiers' => [
'Format: %1$-10s | %2$+d',
['Hello', 42],
'Format: Hello | +42'
],
'Reused positional indexes with some missing' => [
'Format: %1$s %2$d %1$s %3$s %2$d',
['Hello', 42],
'Format: Hello 42 Hello %3$s 42'
]
];
}
}

View File

@@ -28,23 +28,23 @@ class RestServicesTest extends ItopDataTestCase
static::assertJsonStringEqualsJsonString($sExpectedJsonDataSanitized, $sOutputJson);
}
/**
* @return array[]
*/
public function providerTestSanitizeJsonInput(): array
{
return [
'core/check_credentials' => [
'{"operation": "core/check_credentials", "user": "admin", "password": "admin"}',
'{
/**
* @return array[]
*/
public function providerTestSanitizeJsonInput(): array
{
return [
'core/check_credentials' => [
'{"operation": "core/check_credentials", "user": "admin", "password": "admin"}',
'{
"operation": "core/check_credentials",
"user": "admin",
"password": "*****"
}'
],
'core/update' => [
'{"operation": "core/update", "comment": "Update user", "class": "UserLocal", "key": {"id":1}, "output_fields": "first_name, password", "fields": {"password" : "123456"}}',
'{
],
'core/update' => [
'{"operation": "core/update", "comment": "Update user", "class": "UserLocal", "key": {"id":1}, "output_fields": "first_name, password", "fields": {"password" : "123456"}}',
'{
"operation": "core/update",
"comment": "Update user",
"class": "UserLocal",
@@ -56,10 +56,10 @@ class RestServicesTest extends ItopDataTestCase
"password": "*****"
}
}'
],
'core/create' => [
'{"operation": "core/create", "comment": "Create user", "class": "UserLocal", "fields": {"first_name": "John", "last_name": "Doe", "email": "jd@example/com", "password" : "123456"}}',
'{
],
'core/create' => [
'{"operation": "core/create", "comment": "Create user", "class": "UserLocal", "fields": {"first_name": "John", "last_name": "Doe", "email": "jd@example/com", "password" : "123456"}}',
'{
"operation": "core/create",
"comment": "Create user",
"class": "UserLocal",
@@ -70,9 +70,9 @@ class RestServicesTest extends ItopDataTestCase
"password": "*****"
}
}'
],
];
}
],
];
}
/**
* @param $sOperation
@@ -93,33 +93,33 @@ class RestServicesTest extends ItopDataTestCase
static::assertJsonStringEqualsJsonString($sExpectedJsonDataSanitized, json_encode($oRestResultWithObject));
}
/**
* @return array[]
*/
public function providerTestSanitizeJsonOutput(): array
{
return [
/**
* @return array[]
*/
public function providerTestSanitizeJsonOutput(): array
{
return [
'core/update' => [
'core/update',
['comment' => 'Update user', 'class' => 'UserLocal', 'key' => ['login' => 'my_example'], 'output_fields' => 'password', 'fields' => ['password' => 'opkB!req57']],
'{"objects":{"UserLocal::-1":{"code":0,"message":"ok","class":"UserLocal","key":-1,"fields":{"login":"","password":"*****"}}},"code":0,"message":null}'
],
'core/create' => [
'core/create',
['comment' => 'Create user', 'class' => 'UserLocal', 'fields' => ['password' => 'Azertyuiiop*12', 'login' => 'toto', 'profile_list' => [1]]],
'{"objects":{"UserLocal::-1":{"code":0,"message":"ok","class":"UserLocal","key":-1,"fields":{"login":"","password":"*****"}}},"code":0,"message":null}'
],
'core/get' => [
'core/get',
['comment' => 'Get user', 'class' => 'UserLocal', 'key' => ['login' => 'my_example'], 'output_fields' => 'first_name, password'],
'{"objects":{"UserLocal::-1":{"code":0,"message":"ok","class":"UserLocal","key":-1,"fields":{"login":"","password":"*****"}}},"code":0,"message":null}'
],
'core/check_credentials' => [
'core/check_credentials',
['user' => 'admin', 'password' => 'admin'],
'{"objects":{"UserLocal::-1":{"code":0,"message":"ok","class":"UserLocal","key":-1,"fields":{"login":"","password":"*****"}}},"code":0,"message":null}'
],
];
}
'core/update' => [
'core/update',
['comment' => 'Update user', 'class' => 'UserLocal', 'key' => ['login' => 'my_example'], 'output_fields' => 'password', 'fields' => ['password' => 'opkB!req57']],
'{"objects":{"UserLocal::-1":{"code":0,"message":"ok","class":"UserLocal","key":-1,"fields":{"login":"","password":"*****"}}},"code":0,"message":null}'
],
'core/create' => [
'core/create',
['comment' => 'Create user', 'class' => 'UserLocal', 'fields' => ['password' => 'Azertyuiiop*12', 'login' => 'toto', 'profile_list' => [1]]],
'{"objects":{"UserLocal::-1":{"code":0,"message":"ok","class":"UserLocal","key":-1,"fields":{"login":"","password":"*****"}}},"code":0,"message":null}'
],
'core/get' => [
'core/get',
['comment' => 'Get user', 'class' => 'UserLocal', 'key' => ['login' => 'my_example'], 'output_fields' => 'first_name, password'],
'{"objects":{"UserLocal::-1":{"code":0,"message":"ok","class":"UserLocal","key":-1,"fields":{"login":"","password":"*****"}}},"code":0,"message":null}'
],
'core/check_credentials' => [
'core/check_credentials',
['user' => 'admin', 'password' => 'admin'],
'{"objects":{"UserLocal::-1":{"code":0,"message":"ok","class":"UserLocal","key":-1,"fields":{"login":"","password":"*****"}}},"code":0,"message":null}'
],
];
}
}

View File

@@ -0,0 +1,442 @@
<?php
/**
* Copyright (C) 2013-2024 Combodo SAS
* This file is part of iTop.
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Test\UnitTest\Synchro;
use CMDBObject;
use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase;
use DBObject;
use DBObjectSearch;
use DBObjectSet;
use DBSearch;
use EventWithTitleAsReconciliationKey;
use Exception;
use MetaModel;
use SynchroDataSource;
use UserLocal;
use utils;
/**
* Class DataSynchroTest
*
* @package Combodo\iTop\Test\UnitTest\Synchro
* @group dataSynchro
* @group defaultProfiles
*/
class DBObjectDataSynchroTest extends ItopCustomDatamodelTestCase
{
protected const AUTH_USER = 'DBObjectDataSynchroTest';
protected const AUTH_PWD = 'sdf234(-fgh;,dfgDFG';
const USE_TRANSACTION = false;
public function GetDatamodelDeltaAbsPath(): string
{
return __DIR__.'/add-dbobject-with-reconciliation-key.xml';
}
protected function setUp(): void
{
parent::setUp();
$oSearch = DBSearch::FromOQL('SELECT User WHERE login = "'.static::AUTH_USER.'"');
$oSet = new DBObjectSet($oSearch);
if ($oSet->Count() == 0)
{
$iProfileId = self::$aURP_Profiles['REST Services User'];
$oProfileSearch = DBSearch::FromOQL("SELECT URP_Profiles WHERE id = $iProfileId");
$oProfileSearch->AllowAllData();
$oProfileSet = new DBObjectSet($oProfileSearch);
$oAdminProfile = $oProfileSet->fetch();
$oUser = MetaModel::NewObject('UserLocal', array(
'login' => static::AUTH_USER,
'password' => static::AUTH_PWD,
'expiration' => UserLocal::EXPIRE_NEVER,
));
$oProfiles = $oUser->Get('profile_list');
$oProfiles->AddItem(MetaModel::NewObject('URP_UserProfile', array(
'profileid' => $oAdminProfile->GetKey()
)));
$oUser->Set('profile_list', $oProfiles);
$oUser->DBInsertNoReload();
}
}
protected function ExecSynchroImport($aParams, $bSynchroByHttp)
{
if (!$bSynchroByHttp) {
return utils::ExecITopScript('synchro/synchro_import.php', $aParams, static::AUTH_USER, static::AUTH_PWD);
}
$aParams['auth_user'] = static::AUTH_USER;
$aParams['auth_pwd'] = static::AUTH_PWD;
//$aParams['output'] = 'details';
$aParams['csvdata'] = file_get_contents($aParams['csvfile']);
$sUrl = \MetaModel::GetConfig()->Get('app_root_url').'/synchro/synchro_import.php?login_mode=form';
$sResult = utils::DoPostRequest($sUrl, $aParams, null, $aResponseHeaders, [
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => 0,
]);
// Read the status code from the last line
$aLines = explode("\n", trim(strip_tags($sResult)));
//$sLastLine = array_pop($aLines);
return array(0, $aLines);
}
/**
* Run a series of data synchronization through the REST API
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \CoreWarning
* @throws \MySQLException
* @throws \OQLException
*/
public function RunDataSynchroTest($aUserLoginUsecase)
{
$sDescription = $aUserLoginUsecase['desc'];
$sTargetClass = $aUserLoginUsecase['target_class'];
$aSourceProperties = $aUserLoginUsecase['source_properties'];
$aSourceData = $aUserLoginUsecase['source_data'];
$aTargetData = $aUserLoginUsecase['target_data'];
$aAttributes =$aUserLoginUsecase['attributes'];
$bSynchroByHttp = $aUserLoginUsecase['bSynchroByHttp'];
$sClass = $sTargetClass;
$aTargetAttributes = array_shift($aTargetData);
$aSourceAttributes = array_shift($aSourceData);
if (count($aSourceData) + 1 != count($aTargetData))
{
throw new Exception("Target data must contain exactly ".(count($aSourceData) + 1)." items, found ".count($aTargetData));
}
// Create the data source
//
$oDataSource = new SynchroDataSource();
$oDataSource->Set('name', 'Test data sync '.time());
$oDataSource->Set('description', 'unit test - created automatically');
$oDataSource->Set('status', 'production');
$oDataSource->Set('user_id', 0);
$oDataSource->Set('scope_class', $sClass);
foreach ($aSourceProperties as $sProperty => $value)
{
$oDataSource->Set($sProperty, $value);
}
$iDataSourceId = $oDataSource->DBInsert();
$oAttributeSet = $oDataSource->Get('attribute_list');
while ($oAttribute = $oAttributeSet->Fetch())
{
if (array_key_exists($oAttribute->Get('attcode'), $aAttributes))
{
$aAttribInfo = $aAttributes[$oAttribute->Get('attcode')];
if (array_key_exists('reconciliation_attcode', $aAttribInfo))
{
$oAttribute->Set('reconciliation_attcode', $aAttribInfo['reconciliation_attcode']);
}
$oAttribute->Set('update', $aAttribInfo['do_update']);
$oAttribute->Set('reconcile', $aAttribInfo['do_reconcile']);
}
else
{
$oAttribute->Set('update', false);
$oAttribute->Set('reconcile', false);
}
$oAttribute->DBUpdate();
}
// Prepare list of prefixes -> make sure objects are unique with regard to the reconciliation scheme
$aPrefixes = array(); // attcode => prefix
foreach($aSourceAttributes as $iDummy => $sAttCode)
{
$aPrefixes[$sAttCode] = ''; // init with something
}
foreach($aAttributes as $sAttCode => $aAttribInfo)
{
if (isset($aAttribInfo['automatic_prefix']) && $aAttribInfo['automatic_prefix'])
{
$aPrefixes[$sAttCode] = 'TEST_'.$iDataSourceId.'_';
}
}
// List existing objects (to be ignored in the analysis)
//
$oAllObjects = new DBObjectSet(new DBObjectSearch($sClass));
$aExisting = $oAllObjects->ToArray(true);
$sExistingIds = implode(', ', array_keys($aExisting));
// Create the initial object list
//
$aInitialTarget = $aTargetData[0];
foreach($aInitialTarget as $aObjFields)
{
$oNewTarget = MetaModel::NewObject($sClass);
foreach($aTargetAttributes as $iAtt => $sAttCode)
{
$oNewTarget->Set($sAttCode, $aPrefixes[$sAttCode].$aObjFields[$iAtt]);
}
$oNewTarget->DBInsertNoReload();
}
//add sleep to make sure expected objects will be found
usleep(10000);
foreach($aTargetData as $iRow => $aExpectedObjects)
{
// Check the status (while ignoring existing objects)
//
if (empty($sExistingIds))
{
$oObjects = new DBObjectSet(DBObjectSearch::FromOQL("SELECT $sClass"));
}
else
{
$oObjects = new DBObjectSet(DBObjectSearch::FromOQL("SELECT $sClass WHERE id NOT IN($sExistingIds)"));
}
$aFound = $oObjects->ToArray();
$aErrors_Unexpected = array();
foreach($aFound as $iObj => $oObj)
{
// Is this object in the expected objects list
$bFoundMatch = false;
foreach($aExpectedObjects as $iExp => $aValues)
{
$bDoesMatch = true;
foreach($aTargetAttributes as $iCol => $sAttCode)
{
if ($oObj->Get($sAttCode) != $aPrefixes[$sAttCode].$aValues[$iCol])
{
$bDoesMatch = false;
break;
}
}
if ($bDoesMatch)
{
$bFoundMatch = true;
unset($aExpectedObjects[$iExp]);
break;
}
}
if (!$bFoundMatch)
{
$aObjDesc = array();
foreach($aTargetAttributes as $iCol => $sAttCode)
{
$aObjDesc[$sAttCode] = $oObj->Get($sAttCode);
}
$aErrors_Unexpected[get_class($oObj).'::'.$oObj->GetKey()] = $aObjDesc;
}
}
// Display the current status
//
$aErrors = array();
if (count($aErrors_Unexpected) > 0) {
$aErrors[] = "Unexpected objects found in iTop DB after step $iRow (starting at 0):\n".print_r($aErrors_Unexpected, true);
}
if (count($aExpectedObjects) > 0) {
$aErrors[] = "Expected objects NOT found in iTop DB after step $iRow (starting at 0)\n".print_r($aExpectedObjects, true);
}
if (count($aErrors) > 0) {
$sAdditionalInfo = (isset($sResultsViewable)) ? $sResultsViewable : "";
static::fail(implode("\n", $aErrors) . "\n $sAdditionalInfo");
} else {
static::assertTrue(true);
}
// If not on the final row, run a data exchange sequence
//
if (array_key_exists($iRow, $aSourceData))
{
$aToBeLoaded = $aSourceData[$iRow];
// First line
$sCsvData = implode(';', $aSourceAttributes)."\n";
$sTextQualifier = '"';
foreach($aToBeLoaded as $aDataRow)
{
$aFinalData = array();
foreach($aDataRow as $iCol => $value)
{
$sAttCode = $aSourceAttributes[$iCol];
$sRawValue = $aPrefixes[$sAttCode].$value;
$sFrom = array("\r\n", $sTextQualifier);
$sTo = array("\n", $sTextQualifier.$sTextQualifier);
$sCSVValue = $sTextQualifier.str_replace($sFrom, $sTo, (string)$sRawValue).$sTextQualifier;
$aFinalData[] = $sCSVValue;
}
$sCsvData .= implode(';', $aFinalData)."\n";
}
$sCSVTmpFile = tempnam(sys_get_temp_dir(), "CSV");
file_put_contents($sCSVTmpFile, $sCsvData);
$aParams = array(
'csvfile' => $sCSVTmpFile,
'data_source_id' => $iDataSourceId,
'separator' => ';',
'simulate' => 0,
'output' => 'details',
);
list($iRetCode, $aOutputLines) = static::ExecSynchroImport($aParams, $bSynchroByHttp);
unlink($sCSVTmpFile);
// Report the load results
//
if (strlen($sCsvData) > 5000)
{
$sCsvDataViewable = 'INPUT TOO LONG TO BE DISPLAYED ('.strlen($sCsvData).")\n".substr($sCsvData, 0, 500)."\n... TO BE CONTINUED";
}
else
{
$sCsvDataViewable = $sCsvData;
}
echo "Input Data:\n";
echo $sCsvDataViewable;
echo "\n";
$sResultsViewable = '| '.implode("\n| ", $aOutputLines);
echo "Results:\n";
echo $sResultsViewable;
echo "\n";
if ($iRetCode != 0)
{
static::fail("Execution of synchro_import failing with code '$iRetCode', see error.log for more details");
}
if (stripos($sResultsViewable, 'exception') !== false)
{
self::fail('Encountered an Exception during the last import/synchro');
}
$aKeys = ["creation", "update", "deletion"];
foreach ($aKeys as $sKey){
$this->assertStringContainsString("$sKey errors: 0", $sResultsViewable, "step $iRow : below res should contain '$sKey errors: 0': " . $sResultsViewable);
}
//N°3805 : potential javascript returned like
/*
Please wait...
var aListJsFiles = [];
$(document).ready(function () {
setTimeout(function () {
}, 50);
});
*/
$sLastExpectedLine = "#Replica disappeared, no action taken: 0";
$aSplittedRes = explode($sLastExpectedLine, $sResultsViewable);
$this->assertNotFalse($aSplittedRes);
if (count($aSplittedRes)>1){
$sPotentialIssuesWithWebApplication = $aSplittedRes[1];
$this->assertEquals("", $sPotentialIssuesWithWebApplication, 'when failed it means data synchro result is polluted with some web application stuff like html or js');
}
}
}
return $oDataSource;
}
public function testDataSynchroByCli_DBObjectUseCase(){
/**
* <class id="Event" _delta="define">
* <!-- Generated by toolkit/export-class-to-meta.php -->
* <parent>DBObject</parent>
* <properties>
* <category>core/cmdb,view_in_gui</category>
* </properties>
* <fields>
* <field id="message" xsi:type="AttributeText"/>
* <field id="date" xsi:type="AttributeDateTime"/>
* <field id="userinfo" xsi:type="AttributeString"/>
* </fields>
* </class>
*/
$DBObjectClass = EventWithTitleAsReconciliationKey::class;
$oEventNotification = new EventWithTitleAsReconciliationKey();
$this->assertTrue(is_a($oEventNotification, DBObject::class));
$this->assertFalse(is_a($oEventNotification, CMDBObject::class));
foreach (['A', 'C'] as $sKey) {
$oEventA = new EventWithTitleAsReconciliationKey();
$oEventA->Set('title', "title_$sKey");
$oEventA->Set('message', "message_$sKey");
$oEventA->Set('userinfo', "userinfo_$sKey");
$oEventA->DBWrite();
}
$aDbObjectSyncroUsecase = [
'desc' => 'Load EventNotification (DBObject)',
'target_class' => $DBObjectClass,
'source_properties' => [
'full_load_periodicity' => 3600, // should be ignored in this case
'reconciliation_policy' => 'use_attributes',
'action_on_zero' => 'create',
'action_on_one' => 'update',
'action_on_multiple' => 'error',
'delete_policy' => 'delete',
'delete_policy_update' => '',
'delete_policy_retention' => 0,
],
'source_data' => [
['primary_key', 'title', 'message', 'date','userinfo'],
[
['A', 'title_A', 'message_A', 'userinfo_AAA'],
['B', 'title_B', 'message_B', 'userinfo_B'],
],
],
'target_data' => [
['title'], //columns
[
// Initial state
],
[
['title_A'], //expected values
['title_B'], //expected values
],
],
'attributes' => [
'title' => [
'do_reconcile' => true,
'do_update' => true,
'automatic_prefix' => true, // unique id (for unit testing)
],
'message' => [
'do_reconcile' => false,
'do_update' => false,
],
'userinfo' => [
'do_reconcile' => false,
'do_update' => true,
],
],
'bSynchroByHttp' => false
];
$this->RunDataSynchroTest($aDbObjectSyncroUsecase);
}
}

View File

@@ -15,13 +15,18 @@
namespace Combodo\iTop\Test\UnitTest\Synchro;
use CMDBObject;
use CMDBSource;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use DBObject;
use DBObjectSearch;
use DBObjectSet;
use DBSearch;
use Event;
use EventNotification;
use Exception;
use MetaModel;
use ReflectionClass;
use SynchroDataSource;
use UserLocal;
use utils;

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0">
<classes>
<class id="EventWithTitleAsReconciliationKey" _delta="define">
<parent>Event</parent>
<properties>
<category>grant_by_profile,bizmodel,searchable</category>
<abstract>false</abstract>
<key_type>autoincrement</key_type>
<db_table>remoteapplicationconnection2</db_table>
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<attributes>
<attribute id="title"/>
</attributes>
</naming>
<display_template/>
<reconciliation>
<attributes>
<attribute id="title"/>
</attributes>
</reconciliation>
</properties>
<fields>
<field id="title" xsi:type="AttributeString">
<sql>title</sql>
<is_null_allowed>false</is_null_allowed>
</field>
</fields>
<methods/>
<presentation/>
</class>
</classes>
</itop_design>

View File

@@ -227,7 +227,7 @@ try
/** @var iRestServiceProvider $oRS */
$oRS = $aOpToRestService[$sOperation]['service_provider'];
$sProvider = get_class($oRS);
if ($oRS instanceof iRestInputSanitizer) {
$sSanitizedJsonInput = $oRS->SanitizeJsonInput($sJsonString);
}
@@ -299,7 +299,20 @@ if (MetaModel::GetConfig()->Get('log_rest_service'))
$oLog->SetTrim('message', $sMessage);
$oLog->Set('code', $oResult->code);
$oResult->SanitizeContent();
$oLog->SetTrim('json_output', json_encode($oResult, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
$iUnescapeSlashAndUnicode = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
$sJsonOuputWithPrettyPrinting = json_encode($oResult, $iUnescapeSlashAndUnicode | JSON_PRETTY_PRINT);
$sJsonOutputWithoutPrettyPrinting = json_encode($oResult, $iUnescapeSlashAndUnicode);
!StringFitsInLogField( $sJsonOuputWithPrettyPrinting) ?
$oLog->SetTrim('json_output', $sJsonOutputWithoutPrettyPrinting) : // too long, we don't make it pretty
$oLog->SetTrim('json_output', $sJsonOuputWithPrettyPrinting);
$oLog->DBInsertNoReload();
}
/**
* @deprecated - will be removed in 3.3.0
*/
function StringFitsInLogField(string $sLog): bool
{
return mb_strlen($sLog) <= 16383; // hardcoded value, see N°8260
}