mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-21 19:34:12 +01:00
Compare commits
36 Commits
3.2.1
...
feature/73
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
891a7bcbdd | ||
|
|
60e54e6160 | ||
|
|
9a895a7fbd | ||
|
|
f5011bb200 | ||
|
|
29c75f626b | ||
|
|
0562563cbb | ||
|
|
40068bd913 | ||
|
|
874a5fd2ce | ||
|
|
1ec139782e | ||
|
|
056dce4d78 | ||
|
|
063bb9680e | ||
|
|
9b1395db03 | ||
|
|
8fd9eb6a84 | ||
|
|
1142bf327c | ||
|
|
278496eaf6 | ||
|
|
77ba0b398f | ||
|
|
04ca7bf603 | ||
|
|
8f8ac46f55 | ||
|
|
07b904ee1b | ||
|
|
5aee7f4722 | ||
|
|
292701b71c | ||
|
|
533b57ab99 | ||
|
|
be8d348b25 | ||
|
|
6c7a98fe3d | ||
|
|
ec2203229b | ||
|
|
2f699d355a | ||
|
|
da4457f5b4 | ||
|
|
97848cea4f | ||
|
|
2ccd883c9a | ||
|
|
eabd68ff77 | ||
|
|
2b493787b1 | ||
|
|
d74c850621 | ||
|
|
4e575e17a3 | ||
|
|
94d6eca0c1 | ||
|
|
355da8ec0a | ||
|
|
5f006c45db |
@@ -93,6 +93,12 @@ gitGraph
|
||||
checkout support/3.2
|
||||
commit id: "2024-06-25" tag: "3.2.0-beta1" type: REVERSE
|
||||
commit id: "2024-08-07" tag: "3.2.0"
|
||||
checkout support/2.7
|
||||
commit id: "2025-02-25" tag: "2.7.12"
|
||||
checkout support/3.1
|
||||
commit id: "2025-02-25 " tag: "3.1.3"
|
||||
checkout support/3.2
|
||||
commit id: "2025-02-25 " tag: "3.2.1"
|
||||
```
|
||||
|
||||
To learn more, check the [iTop community versions history on the official wiki](https://www.itophub.io/wiki/page?id=latest:release:start).
|
||||
|
||||
@@ -106,6 +106,7 @@ We would like to give a special thank you 🤗 to the people from the community
|
||||
- Raenker, Martin
|
||||
- Roháč, Richard (a.k.a [@RohacRichard](https://github.com/RohacRichard))
|
||||
- Rosenke, Stephan
|
||||
- Rossi, Tommaso (a.k.a [@tomrss](https://www.github.com/tomrss))
|
||||
- Rudner, Björn (a.k.a [@rudnerbjoern](https://github.com/rudnerbjoern))
|
||||
- Šafránek, Jaroslav (a.k.a [jkcinik](https://sourceforge.net/u/jkcinik/profile/) on SourceForge)
|
||||
- Seki, Shoji
|
||||
@@ -115,6 +116,7 @@ We would like to give a special thank you 🤗 to the people from the community
|
||||
- Tarjányi, Csaba (a.k.a [@tacsaby](https://github.com/tacsaby))
|
||||
- Tulio, Marco
|
||||
- Turrubiates, Miguel
|
||||
- Vlk, Karel (a.k.a [@vlk-charles](https://www.github.com/vlk-charles))
|
||||
|
||||
### Aliases
|
||||
|
||||
|
||||
@@ -1711,6 +1711,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.
|
||||
*
|
||||
@@ -1802,6 +1807,14 @@ class RestResult
|
||||
* @api
|
||||
*/
|
||||
public $message;
|
||||
|
||||
/**
|
||||
* Sanitize the content of this result to hide sensitive information
|
||||
*/
|
||||
public function SanitizeContent()
|
||||
{
|
||||
// The default implementation does nothing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -90,6 +90,27 @@ class UILinksWidgetDirect
|
||||
return ConsoleBlockRenderer::RenderBlockTemplateInPage($oPage, $oBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param $oValue
|
||||
* @param $aArgs
|
||||
* @param $sFormPrefix
|
||||
* @param $oCurrentObj
|
||||
* @return BlockIndirectLinkSetEditTable
|
||||
* @throws ArchivedObjectException
|
||||
* @throws ConfigException
|
||||
* @throws CoreException
|
||||
* @throws CoreUnexpectedValue
|
||||
* @since 3.2
|
||||
*/
|
||||
public function GetBlock(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj)
|
||||
{
|
||||
$oBlock = new BlockDirectLinkSetEditTable($this, $this->sInputid);
|
||||
$oBlock->InitTable($oPage, $oValue, $sFormPrefix, $oCurrentObj);
|
||||
|
||||
return $oBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param string $sProposedRealClass
|
||||
|
||||
@@ -148,6 +148,27 @@ class UILinksWidget
|
||||
return ConsoleBlockRenderer::RenderBlockTemplateInPage($oPage, $oBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param $oValue
|
||||
* @param $aArgs
|
||||
* @param $sFormPrefix
|
||||
* @param $oCurrentObj
|
||||
* @return BlockIndirectLinkSetEditTable
|
||||
* @throws ArchivedObjectException
|
||||
* @throws ConfigException
|
||||
* @throws CoreException
|
||||
* @throws CoreUnexpectedValue
|
||||
* @since 3.2
|
||||
*/
|
||||
public function GetBlock(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj): BlockIndirectLinkSetEditTable
|
||||
{
|
||||
$oBlock = new BlockIndirectLinkSetEditTable($this);
|
||||
$oBlock->InitTable($oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $this->m_aTableConfig);
|
||||
|
||||
return $oBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObject $oCurrentObj
|
||||
|
||||
@@ -419,11 +419,26 @@ class utils
|
||||
* @since 2.7.7, 3.0.2, 3.1.0 N°4899 - new 'url' filter
|
||||
* @since 2.7.10 N°6606 use the utils::ENUM_SANITIZATION_* const
|
||||
* @since 2.7.10 N°6606 new case for ENUM_SANITIZATION_FILTER_PHP_CLASS
|
||||
* @since 3.2.1-1 N°8242 Allow value to be an array for every filter
|
||||
*
|
||||
* @link https://www.php.net/manual/en/filter.filters.sanitize.php PHP sanitization filters
|
||||
*/
|
||||
protected static function Sanitize_Internal($value, $sSanitizationFilter)
|
||||
{
|
||||
if (is_array($value))
|
||||
{
|
||||
$retValue = array();
|
||||
foreach ($value as $key => $val)
|
||||
{
|
||||
$retValue[$key] = self::Sanitize_Internal($val, $sSanitizationFilter); // recursively check arrays
|
||||
if ($retValue[$key] === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return $retValue;
|
||||
}
|
||||
|
||||
switch ($sSanitizationFilter)
|
||||
{
|
||||
case static::ENUM_SANITIZATION_FILTER_INTEGER:
|
||||
@@ -454,52 +469,36 @@ class utils
|
||||
case static::ENUM_SANITIZATION_FILTER_PARAMETER:
|
||||
case static::ENUM_SANITIZATION_FILTER_FIELD_NAME:
|
||||
case static::ENUM_SANITIZATION_FILTER_TRANSACTION_ID:
|
||||
if (is_array($value))
|
||||
switch ($sSanitizationFilter)
|
||||
{
|
||||
$retValue = array();
|
||||
foreach ($value as $key => $val)
|
||||
{
|
||||
$retValue[$key] = self::Sanitize_Internal($val, $sSanitizationFilter); // recursively check arrays
|
||||
if ($retValue[$key] === false)
|
||||
{
|
||||
$retValue = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch ($sSanitizationFilter)
|
||||
{
|
||||
case static::ENUM_SANITIZATION_FILTER_TRANSACTION_ID:
|
||||
// Same as parameter type but keep the dot character
|
||||
// transaction_id, the dot is mostly for Windows servers when using file storage as the tokens are named *.tmp
|
||||
// - See N°1835
|
||||
// - Note: It must be included at the regexp beginning otherwise you'll get an invalid character error
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[\. A-Za-z0-9_=-]*$/')));
|
||||
break;
|
||||
case static::ENUM_SANITIZATION_FILTER_TRANSACTION_ID:
|
||||
// Same as parameter type but keep the dot character
|
||||
// transaction_id, the dot is mostly for Windows servers when using file storage as the tokens are named *.tmp
|
||||
// - See N°1835
|
||||
// - Note: It must be included at the regexp beginning otherwise you'll get an invalid character error
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[\. A-Za-z0-9_=-]*$/')));
|
||||
break;
|
||||
|
||||
case static::ENUM_SANITIZATION_FILTER_ROUTE:
|
||||
case static::ENUM_SANITIZATION_FILTER_OPERATION:
|
||||
// - Routes should be of the "controller_namespace_code.controller_method_name" form
|
||||
// - Operations should be allowed to be namespaced as well even though then don't have dedicated controller yet
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[\.A-Za-z0-9_-]*$/')));
|
||||
break;
|
||||
case static::ENUM_SANITIZATION_FILTER_ROUTE:
|
||||
case static::ENUM_SANITIZATION_FILTER_OPERATION:
|
||||
// - Routes should be of the "controller_namespace_code.controller_method_name" form
|
||||
// - Operations should be allowed to be namespaced as well even though then don't have dedicated controller yet
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[\.A-Za-z0-9_-]*$/')));
|
||||
break;
|
||||
|
||||
case static::ENUM_SANITIZATION_FILTER_PARAMETER:
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[ A-Za-z0-9_=-]*$/'))); // the '=', '%3D, '%2B', '%2F'
|
||||
// Characters are used in serialized filters (starting 2.5, only the url encoded versions are presents, but the "=" is kept for BC)
|
||||
break;
|
||||
case static::ENUM_SANITIZATION_FILTER_PARAMETER:
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[ A-Za-z0-9_=-]*$/'))); // the '=', '%3D, '%2B', '%2F'
|
||||
// Characters are used in serialized filters (starting 2.5, only the url encoded versions are presents, but the "=" is kept for BC)
|
||||
break;
|
||||
|
||||
case static::ENUM_SANITIZATION_FILTER_FIELD_NAME:
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[A-Za-z0-9_]+(->[A-Za-z0-9_]+)*$/'))); // att_code or att_code->name or AttCode->Name or AttCode->Key2->Name
|
||||
break;
|
||||
case static::ENUM_SANITIZATION_FILTER_FIELD_NAME:
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[A-Za-z0-9_]+(->[A-Za-z0-9_]+)*$/'))); // att_code or att_code->name or AttCode->Name or AttCode->Key2->Name
|
||||
break;
|
||||
|
||||
case static::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM:
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[ A-Za-z0-9_=%:+-]*$/')));
|
||||
break;
|
||||
case static::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM:
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[ A-Za-z0-9_=%:+-]*$/')));
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ abstract class AttributeDefinition
|
||||
|
||||
protected $aCSSClasses;
|
||||
|
||||
public function GetType()
|
||||
public function GetType()
|
||||
{
|
||||
return Dict::S('Core:'.get_class($this));
|
||||
}
|
||||
@@ -4193,7 +4193,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)
|
||||
@@ -4270,7 +4270,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;
|
||||
|
||||
protected function GetSQLCol($bFullSpec = false)
|
||||
{
|
||||
@@ -4440,7 +4440,7 @@ class AttributeText extends AttributeString
|
||||
{
|
||||
// Is there a way to know the current limitation for mysql?
|
||||
// See mysql_field_len()
|
||||
return 65535;
|
||||
return 16383; // number of characters (that can be 1-4 bytes long), not of bytes
|
||||
}
|
||||
|
||||
public static function RenderWikiHtml($sText, $bWikiOnly = false)
|
||||
@@ -10004,7 +10004,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)
|
||||
|
||||
@@ -760,10 +760,10 @@ abstract class DBObject implements iDisplay
|
||||
*/
|
||||
public function SetTrim($sAttCode, $sValue)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$iMaxSize = $oAttDef->GetMaxSize();
|
||||
$sLength = mb_strlen($sValue);
|
||||
if ($iMaxSize && ($sLength > $iMaxSize)) {
|
||||
if (!$this->StringFitsInField($sAttCode, $sValue)) {
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$iMaxSize = $oAttDef->GetMaxSize();
|
||||
$sLength = mb_strlen($sValue);
|
||||
$sMessage = " -truncated ($sLength chars)";
|
||||
$sValue = mb_substr($sValue, 0, $iMaxSize - mb_strlen($sMessage)).$sMessage;
|
||||
}
|
||||
@@ -818,6 +818,24 @@ abstract class DBObject implements iDisplay
|
||||
$oKPI->ComputeStatsForExtension($this, 'AfterDelete');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sAttCode
|
||||
* @param string $sValue
|
||||
*
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*
|
||||
* @Since 3.2.2
|
||||
*/
|
||||
public function StringFitsInField(string $sAttCode, string $sValue): bool
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$iMaxSize = $oAttDef->GetMaxSize();
|
||||
$sLength = mb_strlen($sValue);
|
||||
|
||||
return !($iMaxSize && ($sLength > $iMaxSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute (and optionally start) the StopWatches deadlines
|
||||
*
|
||||
|
||||
@@ -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] = '*****';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1399,55 +1399,56 @@ class ObjectFormManager extends FormManager
|
||||
$this->aFieldsAtts = array();
|
||||
$this->aExtraData = array();
|
||||
$aFieldsDMOnlyAttCodes = array();
|
||||
switch ($this->aFormProperties['type']) {
|
||||
case 'custom_list':
|
||||
case 'static':
|
||||
foreach ($this->aFormProperties['fields'] as $sAttCode => $aOptions) {
|
||||
// When in a transition and no flags are specified for the field, we will retrieve its flags from DM later
|
||||
if ($this->IsTransitionForm() && empty($aOptions)) {
|
||||
$aFieldsDMOnlyAttCodes[] = $sAttCode;
|
||||
continue;
|
||||
}
|
||||
if (array_key_exists('type', $this->aFormProperties)) {
|
||||
switch ($this->aFormProperties['type']) {
|
||||
case 'custom_list':
|
||||
case 'static':
|
||||
foreach ($this->aFormProperties['fields'] as $sAttCode => $aOptions) {
|
||||
// When in a transition and no flags are specified for the field, we will retrieve its flags from DM later
|
||||
if ($this->IsTransitionForm() && empty($aOptions)) {
|
||||
$aFieldsDMOnlyAttCodes[] = $sAttCode;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise we proceed as usual
|
||||
$iFieldFlags = OPT_ATT_NORMAL;
|
||||
// Checking if field should be slave
|
||||
if (isset($aOptions['slave']) && ($aOptions['slave'] === true)) {
|
||||
$iFieldFlags = $iFieldFlags | OPT_ATT_SLAVE;
|
||||
// Otherwise we proceed as usual
|
||||
$iFieldFlags = OPT_ATT_NORMAL;
|
||||
// Checking if field should be slave
|
||||
if (isset($aOptions['slave']) && ($aOptions['slave'] === true)) {
|
||||
$iFieldFlags = $iFieldFlags | OPT_ATT_SLAVE;
|
||||
}
|
||||
// Checking if field should be must_change
|
||||
if (isset($aOptions['must_change']) && ($aOptions['must_change'] === true)) {
|
||||
$iFieldFlags = $iFieldFlags | OPT_ATT_MUSTCHANGE;
|
||||
}
|
||||
// Checking if field should be must prompt
|
||||
if (isset($aOptions['must_prompt']) && ($aOptions['must_prompt'] === true)) {
|
||||
$iFieldFlags = $iFieldFlags | OPT_ATT_MUSTPROMPT;
|
||||
}
|
||||
// Checking if field should be hidden
|
||||
if (isset($aOptions['hidden']) && ($aOptions['hidden'] === true)) {
|
||||
$iFieldFlags = $iFieldFlags | OPT_ATT_HIDDEN;
|
||||
}
|
||||
// Checking if field should be readonly
|
||||
if (isset($aOptions['read_only']) && ($aOptions['read_only'] === true)) {
|
||||
$iFieldFlags = $iFieldFlags | OPT_ATT_READONLY;
|
||||
}
|
||||
// Checking if field should be mandatory
|
||||
if (isset($aOptions['mandatory']) && ($aOptions['mandatory'] === true)) {
|
||||
$iFieldFlags = $iFieldFlags | OPT_ATT_MANDATORY;
|
||||
}
|
||||
// Finally, adding the attribute and its flags
|
||||
$this->aFieldsAtts[$sAttCode] = $iFieldFlags;
|
||||
}
|
||||
// Checking if field should be must_change
|
||||
if (isset($aOptions['must_change']) && ($aOptions['must_change'] === true)) {
|
||||
$iFieldFlags = $iFieldFlags | OPT_ATT_MUSTCHANGE;
|
||||
}
|
||||
// Checking if field should be must prompt
|
||||
if (isset($aOptions['must_prompt']) && ($aOptions['must_prompt'] === true)) {
|
||||
$iFieldFlags = $iFieldFlags | OPT_ATT_MUSTPROMPT;
|
||||
}
|
||||
// Checking if field should be hidden
|
||||
if (isset($aOptions['hidden']) && ($aOptions['hidden'] === true)) {
|
||||
$iFieldFlags = $iFieldFlags | OPT_ATT_HIDDEN;
|
||||
}
|
||||
// Checking if field should be readonly
|
||||
if (isset($aOptions['read_only']) && ($aOptions['read_only'] === true)) {
|
||||
$iFieldFlags = $iFieldFlags | OPT_ATT_READONLY;
|
||||
}
|
||||
// Checking if field should be mandatory
|
||||
if (isset($aOptions['mandatory']) && ($aOptions['mandatory'] === true)) {
|
||||
$iFieldFlags = $iFieldFlags | OPT_ATT_MANDATORY;
|
||||
}
|
||||
// Finally, adding the attribute and its flags
|
||||
$this->aFieldsAtts[$sAttCode] = $iFieldFlags;
|
||||
}
|
||||
break;
|
||||
break;
|
||||
|
||||
case 'zlist':
|
||||
foreach (MetaModel::FlattenZList(MetaModel::GetZListItems($sObjectClass, $this->aFormProperties['fields'])) as $sAttCode) {
|
||||
$this->aFieldsAtts[$sAttCode] = OPT_ATT_NORMAL;
|
||||
}
|
||||
break;
|
||||
case 'zlist':
|
||||
foreach (MetaModel::FlattenZList(MetaModel::GetZListItems($sObjectClass, $this->aFormProperties['fields'])) as $sAttCode) {
|
||||
$this->aFieldsAtts[$sAttCode] = OPT_ATT_NORMAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->aFormProperties['layout'] !== null) {
|
||||
if (isset($this->aFormProperties['layout'])) {
|
||||
$oXPath = new DOMXPath($this->oHtmlDocument);
|
||||
/** @var \DOMElement $oFieldNode */
|
||||
foreach ($oXPath->query('//div[contains(@class, "form_field")][@data-field-id]') as $oFieldNode) {
|
||||
@@ -1510,7 +1511,7 @@ class ObjectFormManager extends FormManager
|
||||
// Also, retrieving mandatory attributes from metamodel to be able to complete the form with them if necessary
|
||||
//
|
||||
// Note: When in a transition, we don't do this for fields that should be set from DM
|
||||
if ($this->aFormProperties['type'] !== 'static') {
|
||||
if (array_key_exists('type', $this->aFormProperties) && $this->aFormProperties['type'] !== 'static') {
|
||||
if ($this->IsTransitionForm()) {
|
||||
$aDatamodelAttCodes = $this->oObject->GetTransitionAttributes($this->aFormProperties['stimulus_code']);
|
||||
}
|
||||
@@ -1616,7 +1617,7 @@ class ObjectFormManager extends FormManager
|
||||
}
|
||||
|
||||
$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
|
||||
if ($this->aFormProperties['layout']['type'] === 'twig') {
|
||||
if ($this->oFormHandlerHelper !== null) {
|
||||
|
||||
@@ -1560,6 +1560,7 @@ return array(
|
||||
'Sabberworm\\CSS\\Value\\URL' => $vendorDir . '/sabberworm/php-css-parser/src/Value/URL.php',
|
||||
'Sabberworm\\CSS\\Value\\Value' => $vendorDir . '/sabberworm/php-css-parser/src/Value/Value.php',
|
||||
'Sabberworm\\CSS\\Value\\ValueList' => $vendorDir . '/sabberworm/php-css-parser/src/Value/ValueList.php',
|
||||
'SanitizeTrait' => $baseDir . '/core/restservices.class.inc.php',
|
||||
'ScalarExpression' => $baseDir . '/core/oql/expression.class.inc.php',
|
||||
'ScalarOqlExpression' => $baseDir . '/core/oql/oqlquery.class.inc.php',
|
||||
'ScssPhp\\ScssPhp\\Base\\Range' => $vendorDir . '/scssphp/scssphp/src/Base/Range.php',
|
||||
@@ -3201,6 +3202,7 @@ return array(
|
||||
'iPreferencesExtension' => $baseDir . '/application/applicationextension.inc.php',
|
||||
'iProcess' => $baseDir . '/core/backgroundprocess.inc.php',
|
||||
'iQueryModifier' => $baseDir . '/core/querymodifier.class.inc.php',
|
||||
'iRestInputSanitizer' => $baseDir . '/application/applicationextension.inc.php',
|
||||
'iRestServiceProvider' => $baseDir . '/application/applicationextension.inc.php',
|
||||
'iScheduledProcess' => $baseDir . '/core/backgroundprocess.inc.php',
|
||||
'iSelfRegister' => $baseDir . '/core/userrights.class.inc.php',
|
||||
@@ -3228,5 +3230,5 @@ return array(
|
||||
'privUITransactionFile' => $baseDir . '/application/transaction.class.inc.php',
|
||||
'privUITransactionSession' => $baseDir . '/application/transaction.class.inc.php',
|
||||
'utils' => $baseDir . '/application/utils.inc.php',
|
||||
'©' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php',
|
||||
'<EFBFBD>' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php',
|
||||
);
|
||||
|
||||
@@ -1950,6 +1950,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'Sabberworm\\CSS\\Value\\URL' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Value/URL.php',
|
||||
'Sabberworm\\CSS\\Value\\Value' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Value/Value.php',
|
||||
'Sabberworm\\CSS\\Value\\ValueList' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Value/ValueList.php',
|
||||
'SanitizeTrait' => __DIR__ . '/../..' . '/core/restservices.class.inc.php',
|
||||
'ScalarExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php',
|
||||
'ScalarOqlExpression' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php',
|
||||
'ScssPhp\\ScssPhp\\Base\\Range' => __DIR__ . '/..' . '/scssphp/scssphp/src/Base/Range.php',
|
||||
@@ -3591,6 +3592,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'iPreferencesExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
|
||||
'iProcess' => __DIR__ . '/../..' . '/core/backgroundprocess.inc.php',
|
||||
'iQueryModifier' => __DIR__ . '/../..' . '/core/querymodifier.class.inc.php',
|
||||
'iRestInputSanitizer' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
|
||||
'iRestServiceProvider' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
|
||||
'iScheduledProcess' => __DIR__ . '/../..' . '/core/backgroundprocess.inc.php',
|
||||
'iSelfRegister' => __DIR__ . '/../..' . '/core/userrights.class.inc.php',
|
||||
@@ -3618,7 +3620,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'privUITransactionFile' => __DIR__ . '/../..' . '/application/transaction.class.inc.php',
|
||||
'privUITransactionSession' => __DIR__ . '/../..' . '/application/transaction.class.inc.php',
|
||||
'utils' => __DIR__ . '/../..' . '/application/utils.inc.php',
|
||||
'©' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php',
|
||||
'<EFBFBD>' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
|
||||
@@ -143,6 +143,8 @@ $sDBUser = $aDBXmlSettings['user'];
|
||||
$sDBPwd = $aDBXmlSettings['pwd'];
|
||||
$sDBName = $aDBXmlSettings['name'];
|
||||
$sDBPrefix = $aDBXmlSettings['prefix'];
|
||||
$bDBTlsEnabled = $aDBXmlSettings['db_tls_enabled'];
|
||||
$sDBTlsCa = $aDBXmlSettings['db_tls_ca'];
|
||||
|
||||
if ($sMode == 'install')
|
||||
{
|
||||
@@ -219,13 +221,10 @@ if ($sMode == 'install')
|
||||
die("Cleanup not implemented for a partial database (prefix= '$sDBPrefix')\nExiting.");
|
||||
}
|
||||
|
||||
$oMysqli = new mysqli($sDBServer, $sDBUser, $sDBPwd);
|
||||
if ($oMysqli->connect_errno)
|
||||
{
|
||||
die("Cannot connect to the MySQL server (".$oMysqli->connect_errno . ") ".$oMysqli->connect_error."\nExiting");
|
||||
}
|
||||
else
|
||||
try
|
||||
{
|
||||
$oMysqli = CMDBSource::GetMysqliInstance($sDBServer, $sDBUser, $sDBPwd, null, $bDBTlsEnabled, $sDBTlsCa, true);
|
||||
|
||||
if ($oMysqli->select_db($sDBName))
|
||||
{
|
||||
echo "Deleting database '$sDBName'\n";
|
||||
@@ -236,6 +235,10 @@ if ($sMode == 'install')
|
||||
echo "The database '$sDBName' does not seem to exist. Nothing to cleanup.\n";
|
||||
}
|
||||
}
|
||||
catch (MySQLException $e)
|
||||
{
|
||||
die($e->getMessage()."\nExiting");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -312,9 +315,9 @@ if ($bInstall)
|
||||
}
|
||||
else
|
||||
{
|
||||
$oMysqli = new mysqli($sDBServer, $sDBUser, $sDBPwd);
|
||||
if (!$oMysqli->connect_errno)
|
||||
try
|
||||
{
|
||||
$oMysqli = CMDBSource::GetMysqliInstance($sDBServer, $sDBUser, $sDBPwd, null, $bDBTlsEnabled, $sDBTlsCa, true);
|
||||
if ($oMysqli->select_db($sDBName))
|
||||
{
|
||||
// Check the presence of a table to record information about the MTP (from the Designer)
|
||||
@@ -357,6 +360,10 @@ if ($bInstall)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (MySQLException $e)
|
||||
{
|
||||
// Continue anyway
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Combodo\iTop\Test\UnitTest;
|
||||
use CMDBSource;
|
||||
use DeprecatedCallsLog;
|
||||
use MySQLTransactionNotClosedException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use ReflectionMethod;
|
||||
use SetupUtils;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
@@ -23,7 +23,7 @@ use const DEBUG_BACKTRACE_IGNORE_ARGS;
|
||||
*
|
||||
* @since 3.0.4 3.1.1 3.2.0 N°6658 move some setUp/tearDown code to the corresponding methods *BeforeClass to speed up tests process time.
|
||||
*/
|
||||
abstract class ItopTestCase extends TestCase
|
||||
abstract class ItopTestCase extends KernelTestCase
|
||||
{
|
||||
public const TEST_LOG_DIR = 'test';
|
||||
|
||||
|
||||
@@ -834,6 +834,7 @@ HTML,
|
||||
'bad context_param' => [utils::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM, '%dssD,25_=%:+-', null],
|
||||
'good element_identifier' => [utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER, 'AD05nb', 'AD05nb'],
|
||||
'bad element_identifier' => [utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER, 'AD05nb+', 'AD05nb'],
|
||||
'array' => [utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER, ['AD05nb+','apply_modify'], ['AD05nb','apply_modify']],
|
||||
'good url' => [utils::ENUM_SANITIZATION_FILTER_URL, 'https://www.w3schools.com', 'https://www.w3schools.com'],
|
||||
'bad url' => [utils::ENUM_SANITIZATION_FILTER_URL, 'https://www.w3schoo<6F><6F>ls.co<63>m', null],
|
||||
'url with injection' => [utils::ENUM_SANITIZATION_FILTER_URL, 'https://demo.combodo.com/simple/pages/UI.php?operation=full_text&text=<img zzz src=x onerror=alert(1) //>', null],
|
||||
|
||||
@@ -2,11 +2,19 @@
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use ArchivedObjectException;
|
||||
use AttributeDate;
|
||||
use AttributeDateTime;
|
||||
use Change;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use CoreCannotSaveObjectException;
|
||||
use CoreException;
|
||||
use CoreUnexpectedValue;
|
||||
use CoreWarning;
|
||||
use EventRestService;
|
||||
use MetaModel;
|
||||
use MySQLException;
|
||||
use OQLException;
|
||||
use UserRequest;
|
||||
|
||||
class AttributeDefinitionTest extends ItopDataTestCase {
|
||||
@@ -343,4 +351,23 @@ PHP
|
||||
return $oAttribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CoreException
|
||||
* @throws CoreUnexpectedValue
|
||||
* @throws OQLException
|
||||
* @throws ArchivedObjectException
|
||||
* @throws CoreCannotSaveObjectException
|
||||
* @throws CoreWarning
|
||||
* @throws MySQLException
|
||||
*/
|
||||
public function testTrimLogOnAttributeText()
|
||||
{
|
||||
// will throw MySQLException if GetMaxSize() of AttributeText is incorrect (should be number of bytes, not of characters)
|
||||
$oLog = new EventRestService();
|
||||
$sLongString = json_encode(array_fill(0, 5000, 'é😃 '),
|
||||
JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
$oLog->SetTrim('json_input', $sLongString);
|
||||
static::assertNotEquals($oLog->Get('json_input'), $sLongString);
|
||||
static::assertStringContainsString('truncated', $oLog->Get('json_input'));
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
use Attachment;
|
||||
use AttributeDateTime;
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use EventRestService;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use CoreException;
|
||||
use DateTime;
|
||||
@@ -1415,4 +1416,22 @@ class DBObjectTest extends ItopDataTestCase
|
||||
$this->assertEquals("2024-01-15 09:45:00", $oObject->Get('end_date'), 'SetComputedDate +2 weeks on a WorkOrder DateTimeAttribute');
|
||||
|
||||
}
|
||||
|
||||
public function testStringFitsInField()
|
||||
{
|
||||
//🎁 character is 4 bytes long
|
||||
$sTooLongText = str_repeat('🎁', 17000);
|
||||
$oLog = new EventRestService();
|
||||
$this->assertFalse($oLog->StringFitsInField('json_output', $sTooLongText));
|
||||
|
||||
$sCorrectLengthText = str_repeat('🎁', 16383);
|
||||
$this->assertTrue($oLog->StringFitsInField('json_output', $sCorrectLengthText));
|
||||
|
||||
|
||||
$sCorrectLengthString = str_repeat('🎁', 255);
|
||||
$this->assertTrue($oLog->StringFitsInField('operation', $sCorrectLengthString));
|
||||
|
||||
$sTooLongString = str_repeat('🎁', 256);
|
||||
$this->assertFalse($oLog->StringFitsInField('operation', $sTooLongString));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,167 @@
|
||||
<?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();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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}'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -227,7 +227,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);
|
||||
}
|
||||
@@ -252,6 +256,7 @@ catch(Exception $e)
|
||||
//
|
||||
$sResponse = json_encode($oResult);
|
||||
|
||||
|
||||
if ($sResponse === false)
|
||||
{
|
||||
$oJsonIssue = new RestResult();
|
||||
@@ -283,7 +288,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;
|
||||
@@ -293,7 +298,13 @@ if (MetaModel::GetConfig()->Get('log_rest_service'))
|
||||
}
|
||||
$oLog->SetTrim('message', $sMessage);
|
||||
$oLog->Set('code', $oResult->code);
|
||||
$oLog->SetTrim('json_output', $sResponse);
|
||||
$oResult->SanitizeContent();
|
||||
$iUnescapeSlashAndUnicode = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
|
||||
$sJsonOuputWithPrettyPrinting = json_encode($oResult, $iUnescapeSlashAndUnicode | JSON_PRETTY_PRINT);
|
||||
$sJsonOutputWithoutPrettyPrinting = json_encode($oResult, $iUnescapeSlashAndUnicode);
|
||||
!$oLog->StringFitsInField('json_output', $sJsonOuputWithPrettyPrinting) ?
|
||||
$oLog->SetTrim('json_output', $sJsonOutputWithoutPrettyPrinting) : // too long, we don't make it pretty
|
||||
$oLog->SetTrim('json_output', $sJsonOuputWithPrettyPrinting);
|
||||
|
||||
$oLog->DBInsertNoReload();
|
||||
}
|
||||
Reference in New Issue
Block a user