diff --git a/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php b/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php index 3fac09f48..2705b8c4d 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php @@ -91,26 +91,34 @@ class ObjectFormManager extends FormManager * - formmode : view|edit|create * - values for parent * + * @param bool $bTrustContent if false then won't allow TWIG content + * * @inheritDoc * @throws \Exception + * @throws \SecurityException if twig content is present and $bTrustContent is false */ - static function FromJSON($sJson) + public static function FromJSON($sJson, $bTrustContent = false) { - if (is_array($sJson)) - { + if (is_array($sJson)) { $aJson = $sJson; - } - else - { + } else { $aJson = json_decode($sJson, true); } + if (false === $bTrustContent) { + /** @noinspection NestedPositiveIfStatementsInspection */ + if (isset($aJson['formproperties']['layout']['type']) && ($aJson['formproperties']['layout']['type'] === 'twig')) { + // There will be an IssueLog above in the hierarchy due to the exception, but we are logging here so that we can output the JSON data ! + IssueLog::Error('Portal received a query with forbidden twig content!', \LogChannels::PORTAL, ['formmanager_data' => $aJson]); + throw new \SecurityException('Twig content not allowed in this context!'); + } + } + /** @var \Combodo\iTop\Portal\Form\ObjectFormManager $oFormManager */ $oFormManager = parent::FromJSON($sJson); // Retrieving object to edit - if (!isset($aJson['formobject_class'])) - { + if (!isset($aJson['formobject_class'])) { throw new Exception('Object class must be defined in order to generate the form'); } $sObjectClass = $aJson['formobject_class']; @@ -151,14 +159,44 @@ class ObjectFormManager extends FormManager } // Retrieving callback urls - if (!isset($aJson['formcallbacks'])) - { + if (!isset($aJson['formcallbacks'])) { // TODO } return $oFormManager; } + /** + * @param string $sPostedFormManagerData received data from the browser + * @param array $aOriginalFormProperties data generated server side + * + * @return bool true if the data are identical + * + * @since 2.7.6 3.0.0 N°4384 check formmanager_data + */ + public static function CanTrustFormLayoutContent($sPostedFormManagerData, $aOriginalFormProperties) + { + $aPostedFormManagerData = json_decode($sPostedFormManagerData, true); + $sPostedFormLayoutType = (isset($aPostedFormManagerData['formproperties']['layout']['type'])) ? $aPostedFormManagerData['formproperties']['layout']['type'] : ''; + + if ($sPostedFormLayoutType === 'xhtml') { + return true; + } + + // we need to parse the content so that autoclose tags are returned correctly (`
` => `
`) + $oHtmlDocument = new \DOMDocument(); + + $sPostedFormLayoutContent = (isset($aPostedFormManagerData['formproperties']['layout']['content'])) ? $aPostedFormManagerData['formproperties']['layout']['content'] : ''; + $oHtmlDocument->loadXML(''.$sPostedFormLayoutContent.''); + $sPostedFormLayoutRendered = $oHtmlDocument->saveHTML(); + + $sOriginalFormLayoutContent = (isset($aOriginalFormProperties['layout']['content'])) ? $aOriginalFormProperties['layout']['content'] : ''; + $oHtmlDocument->loadXML(''.$sOriginalFormLayoutContent.''); + $sOriginalFormLayoutContentRendered = $oHtmlDocument->saveHTML(); + + return ($sPostedFormLayoutRendered === $sOriginalFormLayoutContentRendered); + } + /** * * @return \Symfony\Component\DependencyInjection\ContainerInterface diff --git a/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php b/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php index d114b1b05..ff4fcbd49 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php @@ -108,10 +108,10 @@ class ObjectFormHandlerHelper /** * @param \Symfony\Component\HttpFoundation\Request $oRequest - * @param string $sMode - * @param string $sObjectClass - * @param string $sObjectId - * @param string $aFormProperties + * @param string $sMode + * @param string $sObjectClass + * @param string $sObjectId + * @param array $aFormProperties * * @return array * @@ -127,9 +127,10 @@ class ObjectFormHandlerHelper $bModal = ($oRequest->isXmlHttpRequest() && empty($sOperation)); // - Retrieve form properties + $aOriginalFormProperties = ApplicationHelper::GetLoadedFormFromClass($this->aCombodoPortalInstanceConf['forms'], $sObjectClass, $sMode); if ($aFormProperties === null) { - $aFormProperties = ApplicationHelper::GetLoadedFormFromClass($this->aCombodoPortalInstanceConf['forms'], $sObjectClass, $sMode); + $aFormProperties = $aOriginalFormProperties; } // - Create and @@ -281,27 +282,24 @@ class ObjectFormHandlerHelper $oFormManager->Build(); $aFormData['hidden_fields'] = $oFormManager->GetHiddenFieldsId(); - // Check the number of editable fields + // Check the number of editable fields $aFormData['editable_fields_count'] = $oFormManager->GetForm()->GetEditableFieldCount(); - } - else - { + } else { // Update / Submit / Cancel + /** @var \Combodo\iTop\Portal\Form\ObjectFormManager $sFormManagerClass */ $sFormManagerClass = $this->oRequestManipulator->ReadParam('formmanager_class', '', FILTER_UNSAFE_RAW); $sFormManagerData = $this->oRequestManipulator->ReadParam('formmanager_data', '', FILTER_UNSAFE_RAW); - if (empty($sFormManagerClass) || empty($sFormManagerData)) - { + if (empty($sFormManagerClass) || empty($sFormManagerData)) { IssueLog::Error(__METHOD__.' at line '.__LINE__.' : Parameters formmanager_class and formamanager_data must be defined.'); throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, 'Parameters formmanager_class and formmanager_data must be defined.'); } - /** @var \Combodo\iTop\Portal\Form\ObjectFormManager $oFormManager */ - $oFormManager = $sFormManagerClass::FromJSON($sFormManagerData); + $bTrustContent = $sFormManagerClass::CanTrustFormLayoutContent($sFormManagerData, $aOriginalFormProperties); + $oFormManager = $sFormManagerClass::FromJSON($sFormManagerData, $bTrustContent); $oFormManager->SetContainer($this->oContainer); // Applying action rules if present - if (($oFormManager->GetActionRulesToken() !== null) && ($oFormManager->GetActionRulesToken() !== '')) - { + if (($oFormManager->GetActionRulesToken() !== null) && ($oFormManager->GetActionRulesToken() !== '')) { $aActionRules = ContextManipulatorHelper::DecodeRulesToken($oFormManager->GetActionRulesToken()); $oObj = $oFormManager->GetObject(); $this->oContextManipulator->PrepareObject($aActionRules, $oObj); diff --git a/sources/form/formmanager.class.inc.php b/sources/form/formmanager.class.inc.php index feeb2359e..4f20d1f4c 100644 --- a/sources/form/formmanager.class.inc.php +++ b/sources/form/formmanager.class.inc.php @@ -40,7 +40,8 @@ abstract class FormManager * - formrenderer_class : The class of the FormRenderer to use in the FormManager * - formrenderer_endpoint : The endpoint of the renderer * - * @param string $sJson + * @param string|string[] $sJson + * * @return $this */ static function FromJSON($sJson)