mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-22 10:08:45 +02:00
Compare commits
16 Commits
3.2.1
...
support/3.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
056dce4d78 | ||
|
|
063bb9680e | ||
|
|
1142bf327c | ||
|
|
278496eaf6 | ||
|
|
77ba0b398f | ||
|
|
04ca7bf603 | ||
|
|
8f8ac46f55 | ||
|
|
07b904ee1b | ||
|
|
be8d348b25 | ||
|
|
6c7a98fe3d | ||
|
|
ec2203229b | ||
|
|
da4457f5b4 | ||
|
|
97848cea4f | ||
|
|
94d6eca0c1 | ||
|
|
355da8ec0a | ||
|
|
5f006c45db |
@@ -1715,6 +1715,11 @@ interface iRestServiceProvider
|
|||||||
public function ExecOperation($sVersion, $sVerb, $aParams);
|
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.
|
* Minimal REST response structure. Derive this structure to add response data and error codes.
|
||||||
*
|
*
|
||||||
@@ -1806,6 +1811,14 @@ class RestResult
|
|||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
public $message;
|
public $message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize the content of this result to hide sensitive information
|
||||||
|
*/
|
||||||
|
public function SanitizeContent()
|
||||||
|
{
|
||||||
|
// The default implementation does nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ abstract class AttributeDefinition
|
|||||||
|
|
||||||
protected $aCSSClasses;
|
protected $aCSSClasses;
|
||||||
|
|
||||||
public function GetType()
|
public function GetType()
|
||||||
{
|
{
|
||||||
return Dict::S('Core:'.get_class($this));
|
return Dict::S('Core:'.get_class($this));
|
||||||
}
|
}
|
||||||
@@ -4164,7 +4164,7 @@ class AttributeFinalClass extends AttributeString
|
|||||||
*/
|
*/
|
||||||
class AttributePassword extends AttributeString implements iAttributeNoGroupBy
|
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)
|
* 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
|
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 $sKey = null; // Encryption key used for all encrypted fields
|
||||||
static $sLibrary = null; // Encryption library 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
|
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)
|
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ class ObjectResult
|
|||||||
* @var string
|
* @var string
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
|
use SanitizeTrait;
|
||||||
|
|
||||||
public $message;
|
public $message;
|
||||||
/**
|
/**
|
||||||
* @var mixed|null
|
* @var mixed|null
|
||||||
@@ -156,6 +158,19 @@ class ObjectResult
|
|||||||
{
|
{
|
||||||
$this->fields[$sAttCode] = $this->MakeResultValue($oObject, $sAttCode, $bExtendedOutput);
|
$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();
|
$sObjKey = get_class($oObject).'::'.$oObject->GetKey();
|
||||||
$this->objects[$sObjKey] = $oObjRes;
|
$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
|
* @package Core
|
||||||
*/
|
*/
|
||||||
class CoreServices implements iRestServiceProvider
|
class CoreServices implements iRestServiceProvider, iRestInputSanitizer
|
||||||
{
|
{
|
||||||
/**
|
use SanitizeTrait;
|
||||||
|
/**
|
||||||
* Enumerate services delivered by this class
|
* Enumerate services delivered by this class
|
||||||
*
|
*
|
||||||
* @param string $sVersion The version (e.g. 1.0) supported by the services
|
* @param string $sVersion The version (e.g. 1.0) supported by the services
|
||||||
@@ -528,18 +554,18 @@ class CoreServices implements iRestServiceProvider
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!$bExtendedOutput && RestUtils::GetOptionalParam($aParams, 'output_fields', '*') != '*')
|
if (!$bExtendedOutput && RestUtils::GetOptionalParam($aParams, 'output_fields', '*') != '*')
|
||||||
{
|
{
|
||||||
$aFields = $aShowFields[$sClass];
|
$aFields = $aShowFields[$sClass];
|
||||||
//Id is not a valid attribute to optimize
|
//Id is not a valid attribute to optimize
|
||||||
if (in_array('id', $aFields))
|
if (in_array('id', $aFields))
|
||||||
{
|
{
|
||||||
unset($aFields[array_search('id', $aFields)]);
|
unset($aFields[array_search('id', $aFields)]);
|
||||||
}
|
}
|
||||||
$aAttToLoad = array($oObjectSet->GetClassAlias() => $aFields);
|
$aAttToLoad = array($oObjectSet->GetClassAlias() => $aFields);
|
||||||
$oObjectSet->OptimizeColumnLoad($aAttToLoad);
|
$oObjectSet->OptimizeColumnLoad($aAttToLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
while ($oObject = $oObjectSet->Fetch())
|
while ($oObject = $oObjectSet->Fetch())
|
||||||
{
|
{
|
||||||
$oResult->AddObject(0, '', $oObject, $aShowFields, $bExtendedOutput);
|
$oResult->AddObject(0, '', $oObject, $aShowFields, $bExtendedOutput);
|
||||||
@@ -737,6 +763,33 @@ class CoreServices implements iRestServiceProvider
|
|||||||
return $oResult;
|
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
|
* Helper for object deletion
|
||||||
*/
|
*/
|
||||||
@@ -875,3 +928,50 @@ class CoreServices implements iRestServiceProvider
|
|||||||
return $iLimit * max(0, $iPage - 1);
|
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] = '*****';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1339,55 +1339,56 @@ class ObjectFormManager extends FormManager
|
|||||||
$this->aFieldsAtts = array();
|
$this->aFieldsAtts = array();
|
||||||
$this->aExtraData = array();
|
$this->aExtraData = array();
|
||||||
$aFieldsDMOnlyAttCodes = array();
|
$aFieldsDMOnlyAttCodes = array();
|
||||||
switch ($this->aFormProperties['type']) {
|
if (array_key_exists('type', $this->aFormProperties)) {
|
||||||
case 'custom_list':
|
switch ($this->aFormProperties['type']) {
|
||||||
case 'static':
|
case 'custom_list':
|
||||||
foreach ($this->aFormProperties['fields'] as $sAttCode => $aOptions) {
|
case 'static':
|
||||||
// When in a transition and no flags are specified for the field, we will retrieve its flags from DM later
|
foreach ($this->aFormProperties['fields'] as $sAttCode => $aOptions) {
|
||||||
if ($this->IsTransitionForm() && empty($aOptions)) {
|
// When in a transition and no flags are specified for the field, we will retrieve its flags from DM later
|
||||||
$aFieldsDMOnlyAttCodes[] = $sAttCode;
|
if ($this->IsTransitionForm() && empty($aOptions)) {
|
||||||
continue;
|
$aFieldsDMOnlyAttCodes[] = $sAttCode;
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise we proceed as usual
|
// Otherwise we proceed as usual
|
||||||
$iFieldFlags = OPT_ATT_NORMAL;
|
$iFieldFlags = OPT_ATT_NORMAL;
|
||||||
// Checking if field should be slave
|
// Checking if field should be slave
|
||||||
if (isset($aOptions['slave']) && ($aOptions['slave'] === true)) {
|
if (isset($aOptions['slave']) && ($aOptions['slave'] === true)) {
|
||||||
$iFieldFlags = $iFieldFlags | OPT_ATT_SLAVE;
|
$iFieldFlags = $iFieldFlags | OPT_ATT_SLAVE;
|
||||||
|
}
|
||||||
|
// Checking if field should be must_change
|
||||||
|
if (isset($aOptions['must_change']) && ($aOptions['must_change'] === true)) {
|
||||||
|
$iFieldFlags = $iFieldFlags | OPT_ATT_MUSTCHANGE;
|
||||||
|
}
|
||||||
|
// Checking if field should be must prompt
|
||||||
|
if (isset($aOptions['must_prompt']) && ($aOptions['must_prompt'] === true)) {
|
||||||
|
$iFieldFlags = $iFieldFlags | OPT_ATT_MUSTPROMPT;
|
||||||
|
}
|
||||||
|
// Checking if field should be hidden
|
||||||
|
if (isset($aOptions['hidden']) && ($aOptions['hidden'] === true)) {
|
||||||
|
$iFieldFlags = $iFieldFlags | OPT_ATT_HIDDEN;
|
||||||
|
}
|
||||||
|
// Checking if field should be readonly
|
||||||
|
if (isset($aOptions['read_only']) && ($aOptions['read_only'] === true)) {
|
||||||
|
$iFieldFlags = $iFieldFlags | OPT_ATT_READONLY;
|
||||||
|
}
|
||||||
|
// Checking if field should be mandatory
|
||||||
|
if (isset($aOptions['mandatory']) && ($aOptions['mandatory'] === true)) {
|
||||||
|
$iFieldFlags = $iFieldFlags | OPT_ATT_MANDATORY;
|
||||||
|
}
|
||||||
|
// Finally, adding the attribute and its flags
|
||||||
|
$this->aFieldsAtts[$sAttCode] = $iFieldFlags;
|
||||||
}
|
}
|
||||||
// Checking if field should be must_change
|
break;
|
||||||
if (isset($aOptions['must_change']) && ($aOptions['must_change'] === true)) {
|
|
||||||
$iFieldFlags = $iFieldFlags | OPT_ATT_MUSTCHANGE;
|
|
||||||
}
|
|
||||||
// Checking if field should be must prompt
|
|
||||||
if (isset($aOptions['must_prompt']) && ($aOptions['must_prompt'] === true)) {
|
|
||||||
$iFieldFlags = $iFieldFlags | OPT_ATT_MUSTPROMPT;
|
|
||||||
}
|
|
||||||
// Checking if field should be hidden
|
|
||||||
if (isset($aOptions['hidden']) && ($aOptions['hidden'] === true)) {
|
|
||||||
$iFieldFlags = $iFieldFlags | OPT_ATT_HIDDEN;
|
|
||||||
}
|
|
||||||
// Checking if field should be readonly
|
|
||||||
if (isset($aOptions['read_only']) && ($aOptions['read_only'] === true)) {
|
|
||||||
$iFieldFlags = $iFieldFlags | OPT_ATT_READONLY;
|
|
||||||
}
|
|
||||||
// Checking if field should be mandatory
|
|
||||||
if (isset($aOptions['mandatory']) && ($aOptions['mandatory'] === true)) {
|
|
||||||
$iFieldFlags = $iFieldFlags | OPT_ATT_MANDATORY;
|
|
||||||
}
|
|
||||||
// Finally, adding the attribute and its flags
|
|
||||||
$this->aFieldsAtts[$sAttCode] = $iFieldFlags;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'zlist':
|
case 'zlist':
|
||||||
foreach (MetaModel::FlattenZList(MetaModel::GetZListItems($sObjectClass, $this->aFormProperties['fields'])) as $sAttCode) {
|
foreach (MetaModel::FlattenZList(MetaModel::GetZListItems($sObjectClass, $this->aFormProperties['fields'])) as $sAttCode) {
|
||||||
$this->aFieldsAtts[$sAttCode] = OPT_ATT_NORMAL;
|
$this->aFieldsAtts[$sAttCode] = OPT_ATT_NORMAL;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (isset($this->aFormProperties['layout'])) {
|
||||||
if ($this->aFormProperties['layout'] !== null) {
|
|
||||||
$oXPath = new DOMXPath($this->oHtmlDocument);
|
$oXPath = new DOMXPath($this->oHtmlDocument);
|
||||||
/** @var \DOMElement $oFieldNode */
|
/** @var \DOMElement $oFieldNode */
|
||||||
foreach ($oXPath->query('//div[contains(@class, "form_field")][@data-field-id]') as $oFieldNode) {
|
foreach ($oXPath->query('//div[contains(@class, "form_field")][@data-field-id]') as $oFieldNode) {
|
||||||
@@ -1446,7 +1447,7 @@ class ObjectFormManager extends FormManager
|
|||||||
// Also, retrieving mandatory attributes from metamodel to be able to complete the form with them if necessary
|
// Also, retrieving mandatory attributes from metamodel to be able to complete the form with them if necessary
|
||||||
//
|
//
|
||||||
// Note: When in a transition, we don't do this for fields that should be set from DM
|
// Note: When in a transition, we don't do this for fields that should be set from DM
|
||||||
if ($this->aFormProperties['type'] !== 'static') {
|
if (array_key_exists('type', $this->aFormProperties) && $this->aFormProperties['type'] !== 'static') {
|
||||||
if ($this->IsTransitionForm()) {
|
if ($this->IsTransitionForm()) {
|
||||||
$aDatamodelAttCodes = $this->oObject->GetTransitionAttributes($this->aFormProperties['stimulus_code']);
|
$aDatamodelAttCodes = $this->oObject->GetTransitionAttributes($this->aFormProperties['stimulus_code']);
|
||||||
}
|
}
|
||||||
@@ -1552,7 +1553,7 @@ class ObjectFormManager extends FormManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->oHtmlDocument = new DOMDocument();
|
$this->oHtmlDocument = new DOMDocument();
|
||||||
if ($this->aFormProperties['layout'] !== null) {
|
if (isset($this->aFormProperties['layout'])) {
|
||||||
// Checking if we need to render the template from twig to html in order to parse the fields
|
// Checking if we need to render the template from twig to html in order to parse the fields
|
||||||
if ($this->aFormProperties['layout']['type'] === 'twig') {
|
if ($this->aFormProperties['layout']['type'] === 'twig') {
|
||||||
if ($this->oFormHandlerHelper !== null) {
|
if ($this->oFormHandlerHelper !== null) {
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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}'
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -224,7 +224,11 @@ try
|
|||||||
/** @var iRestServiceProvider $oRS */
|
/** @var iRestServiceProvider $oRS */
|
||||||
$oRS = $aOpToRestService[$sOperation]['service_provider'];
|
$oRS = $aOpToRestService[$sOperation]['service_provider'];
|
||||||
$sProvider = get_class($oRS);
|
$sProvider = get_class($oRS);
|
||||||
|
|
||||||
|
if ($oRS instanceof iRestInputSanitizer) {
|
||||||
|
$sSanitizedJsonInput = $oRS->SanitizeJsonInput($sJsonString);
|
||||||
|
}
|
||||||
|
|
||||||
CMDBObject::SetTrackOrigin('webservice-rest');
|
CMDBObject::SetTrackOrigin('webservice-rest');
|
||||||
$oResult = $oRS->ExecOperation($sVersion, $sOperation, $aJsonData);
|
$oResult = $oRS->ExecOperation($sVersion, $sOperation, $aJsonData);
|
||||||
}
|
}
|
||||||
@@ -249,6 +253,7 @@ catch(Exception $e)
|
|||||||
//
|
//
|
||||||
$sResponse = json_encode($oResult);
|
$sResponse = json_encode($oResult);
|
||||||
|
|
||||||
|
|
||||||
if ($sResponse === false)
|
if ($sResponse === false)
|
||||||
{
|
{
|
||||||
$oJsonIssue = new RestResult();
|
$oJsonIssue = new RestResult();
|
||||||
@@ -280,7 +285,7 @@ if (MetaModel::GetConfig()->Get('log_rest_service'))
|
|||||||
$oLog->SetTrim('userinfo', UserRights::GetUser());
|
$oLog->SetTrim('userinfo', UserRights::GetUser());
|
||||||
$oLog->Set('version', $sVersion);
|
$oLog->Set('version', $sVersion);
|
||||||
$oLog->Set('operation', $sOperation);
|
$oLog->Set('operation', $sOperation);
|
||||||
$oLog->SetTrim('json_input', $sJsonString);
|
$oLog->SetTrim('json_input', $sSanitizedJsonInput ?? $sJsonString);
|
||||||
|
|
||||||
$oLog->Set('provider', $sProvider);
|
$oLog->Set('provider', $sProvider);
|
||||||
$sMessage = $oResult->message;
|
$sMessage = $oResult->message;
|
||||||
@@ -290,7 +295,8 @@ if (MetaModel::GetConfig()->Get('log_rest_service'))
|
|||||||
}
|
}
|
||||||
$oLog->SetTrim('message', $sMessage);
|
$oLog->SetTrim('message', $sMessage);
|
||||||
$oLog->Set('code', $oResult->code);
|
$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();
|
$oLog->DBInsertNoReload();
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user