diff --git a/sources/Application/Helper/CKEditorHelper.php b/sources/Application/Helper/CKEditorHelper.php index 30023a0d5..7110f430a 100644 --- a/sources/Application/Helper/CKEditorHelper.php +++ b/sources/Application/Helper/CKEditorHelper.php @@ -7,6 +7,7 @@ use Combodo\iTop\Application\WebPage\WebPage; use Combodo\iTop\Renderer\BlockRenderer; use Combodo\iTop\Renderer\RenderingOutput; use Dict; +use DOMSanitizer; use Exception; use ExceptionLog; use UserRights; @@ -36,11 +37,12 @@ class CKEditorHelper * * @return array */ - static public function GetCkeditorConfiguration(bool $bWithMentions, ?string $sInitialValue, array $aOverloadConfiguration = []) : array + public static function GetCkeditorConfiguration(bool $bWithMentions, ?string $sInitialValue, array $aOverloadConfiguration = []) : array { // Extract language from user preferences $sLanguageCountry = trim(UserRights::GetUserLanguage()); $sLanguage = strtolower(explode(' ', $sLanguageCountry)[0]); + $aSanitizerConfiguration = self::GetDOMSanitizerForCKEditor(); // configuration $aConfiguration = array( @@ -52,6 +54,7 @@ class CKEditorHelper 'objectShortcut' => [ 'buttonLabel' => Dict::S('UI:ObjectShortcutInsert') ], + 'htmlSupport' => $aSanitizerConfiguration, ); // Mentions @@ -75,7 +78,7 @@ class CKEditorHelper * @return array|array[] * @throws \Exception */ - static private function GetMentionConfiguration() : array + private static function GetMentionConfiguration() : array { // initialize feeds $aMentionConfiguration = ['feeds' => []]; @@ -277,4 +280,75 @@ HTML; return $aJSRelPaths; } + + /** + * @param \DOMSanitizer|null $oSanitizer + * + * @return array|array[] + * @throws \ConfigException + * @throws \CoreException + */ + public static function GetDOMSanitizerForCKEditor(DOMSanitizer $oSanitizer = null) : array + { + if($oSanitizer === null) { + /* @var $oSanitizer DOMSanitizer */ + $sSanitizerClass = utils::GetConfig()->Get('html_sanitizer'); + $oSanitizer = new $sSanitizerClass(); + } + + $aWhitelist = [ + 'allow' => [], + 'disallow' => [] + ]; + + // Build the allow list + foreach ($oSanitizer->GetTagsWhiteList() as $sTag => $aAttributes) { + $aAllowedItem = [ + 'name' => $sTag, + 'attributes' => [], + 'classes' => false, + 'styles' => false + ]; + + foreach ($aAttributes as $aAttr) { + if ($aAttr === 'style') { + $aAllowedItem['styles'] = array_fill_keys($oSanitizer->GetStylesWhiteList(), true); + } elseif ($aAttr === 'class') { + $aAllowedItem['classes'] = true; + } elseif (isset($oSanitizer->GetAttrsWhiteList()[$aAttr])) { + $aAllowedItem['attributes'][$aAttr] = [ + 'pattern' => $oSanitizer->GetAttrsWhiteList()[$aAttr] + ]; + } else { + $aAllowedItem['attributes'][$aAttr] = true; + } + } + + if (empty($aAllowedItem['attributes'])) { + $aAllowedItem['attributes'] = false; + } + + $aWhitelist['allow'][] = $aAllowedItem; + } + + // Build the disallow list + foreach ($oSanitizer->GetTagsBlackList() as $sTag) { + $aDisallowedItem = [ + 'name' => $sTag, + 'attributes' => [], + ]; + + foreach ($oSanitizer->GetAttrsBlackList() as $aAttr) { + $aDisallowedItem['attributes'][$aAttr] = true; + } + + if (empty($aDisallowedItem['attributes'])) { + $aDisallowedItem['attributes'] = true; + } + + $aWhitelist['disallow'][] = $aDisallowedItem; + } + + return $aWhitelist; + } } \ No newline at end of file diff --git a/tests/php-unit-tests/unitary-tests/sources/Application/Helper/CKEditorHelperTest.php b/tests/php-unit-tests/unitary-tests/sources/Application/Helper/CKEditorHelperTest.php index 52f88ff9e..22b805f50 100644 --- a/tests/php-unit-tests/unitary-tests/sources/Application/Helper/CKEditorHelperTest.php +++ b/tests/php-unit-tests/unitary-tests/sources/Application/Helper/CKEditorHelperTest.php @@ -32,4 +32,318 @@ class CKEditorHelperTest extends ItopTestCase 'GetJSFilesRelPathsForCKEditor' => ['GetJSFilesRelPathsForCKEditor'], ]; } + + /** + * @dataProvider DOMSanitizerForCKEditorProvider + * + * @param $aSanitizerConfiguration + * @param $aExpectedCKEditorConfiguration + * + * @return void + * @throws \ConfigException + * @throws \CoreException + */ + public function testDOMSanitizerForCKEditor($aSanitizerConfiguration, $aExpectedCKEditorConfiguration) + { + $oSanitizer = new TestDOMSanitizer(); + $oSanitizer->SetTagsWhiteList($aSanitizerConfiguration['tagsWhiteList']); + $oSanitizer->SetAttrsWhiteList($aSanitizerConfiguration['attrsWhiteList']); + $oSanitizer->SetStylesWhiteList($aSanitizerConfiguration['stylesWhiteList']); + $oSanitizer->SetTagsBlackList($aSanitizerConfiguration['tagsBlackList']); + $oSanitizer->SetAttrsBlackList($aSanitizerConfiguration['attrsBlackList']); + + $aCKEditorConfiguration = CKEditorHelper::GetDOMSanitizerForCKEditor($oSanitizer); + $this->assertEquals($aExpectedCKEditorConfiguration, $aCKEditorConfiguration); + } + + public function DOMSanitizerForCKEditorProvider(): array + { + return [ + 'Allow list small dataset' => [ + [ + 'tagsWhiteList' => [ + 'html' => array(), + 'p' => array('style', 'class'), + 'a' => array('href', 'name'), + ], + 'attrsWhiteList' => [ + 'href' => '/^(https:)/i' + ], + 'stylesWhiteList' => [ + 'color', + 'font-size' + ], + 'tagsBlackList' => [], + 'attrsBlackList' => [], + ], + [ + 'allow' => [ + [ + 'name' => 'html', + 'attributes' => false, + 'classes' => false, + 'styles' => false + ], + [ + 'name' => 'p', + 'attributes' => false, + 'classes' => true, + 'styles' => [ + 'color' => true, + 'font-size' => true + ] + ], + [ + 'name' => 'a', + 'attributes' => [ + 'href' => [ + 'pattern' => '/^(https:)/i' + ], + 'name' => true + ], + 'classes' => false, + 'styles' => false + ] + ], + 'disallow' => [] + ] + ], + 'Allow list medium dataset' => [ + [ + 'tagsWhiteList' => [ + 'h1' => array('style', 'class'), + 'h2' => array('style', 'class'), + 'h3' => array('style', 'class'), + 'h4' => array('style', 'class'), + 'table' => array('style', 'class', 'width', 'summary', 'align', 'border', 'cellpadding', 'cellspacing', 'style'), + 'tr' => array('style', 'class', 'align', 'valign', 'bgcolor', 'style'), + 'ul' => array(), + 'ol' => array(), + ], + 'attrsWhiteList' => [ + 'href' => '/^(https:)/i', + 'src' => '/^(https:)/i', + 'width' => '/^([0-9]+(px|em|%)?)$/i', + 'height' => '/^([0-9]+(px|em|%)?)$/i', + 'align' => '/^(left|right|center|justify)$/i', + 'valign' => '/^(top|middle|bottom)$/i', + 'bgcolor' => '/^#[0-9a-f]{6}$/i', + ], + 'stylesWhiteList' => [ + 'color', + 'float', + 'font', + 'font-family', + 'font-size', + 'font-style', + 'height', + 'margin', + 'padding', + 'text-align', + 'vertical-align', + 'width', + 'white-space', + ], + 'tagsBlackList' => [], + 'attrsBlackList' => [], + ], + [ + 'allow' => [ + [ + 'name' => 'h1', + 'attributes' => false, + 'classes' => true, + 'styles' => [ + 'color' => true, + 'font' => true, + 'font-family' => true, + 'font-size' => true, + 'font-style' => true, + 'height' => true, + 'margin' => true, + 'padding' => true, + 'text-align' => true, + 'vertical-align' => true, + 'width' => true, + 'white-space' => true, + 'float' => true + ] + ], + [ + 'name' => 'h2', + 'attributes' => false, + 'classes' => true, + 'styles' => [ + 'color' => true, + 'font' => true, + 'font-family' => true, + 'font-size' => true, + 'font-style' => true, + 'height' => true, + 'margin' => true, + 'padding' => true, + 'text-align' => true, + 'vertical-align' => true, + 'width' => true, + 'white-space' => true, + 'float' => true + ] + ], + [ + 'name' => 'h3', + 'attributes' => false, + 'classes' => true, + 'styles' => [ + 'color' => true, + 'font' => true, + 'font-family' => true, + 'font-size' => true, + 'font-style' => true, + 'height' => true, + 'margin' => true, + 'padding' => true, + 'text-align' => true, + 'vertical-align' => true, + 'width' => true, + 'white-space' => true, + 'float' => true + ] + ], + [ + 'name' => 'h4', + 'attributes' => false, + 'classes' => true, + 'styles' => [ + 'color' => true, + 'font' => true, + 'font-family' => true, + 'font-size' => true, + 'font-style' => true, + 'height' => true, + 'margin' => true, + 'padding' => true, + 'text-align' => true, + 'vertical-align' => true, + 'width' => true, + 'white-space' => true, + 'float' => true + ] + ], + [ + 'name' => 'table', + 'attributes' => [ + 'width' => [ + 'pattern' => '/^([0-9]+(px|em|%)?)$/i' + ], + 'summary' => true, + 'align' => [ + 'pattern' => '/^(left|right|center|justify)$/i' + ], + 'border' => true, + 'cellpadding' => true, + 'cellspacing' => true, + ], + 'classes' => true, + 'styles' => [ + 'color' => true, + 'font' => true, + 'font-family' => true, + 'font-size' => true, + 'font-style' => true, + 'height' => true, + 'margin' => true, + 'padding' => true, + 'text-align' => true, + 'vertical-align' => true, + 'width' => true, + 'white-space' => true, + 'float' => true + ] + ], + [ + 'name' => 'tr', + 'attributes' => [ + 'align' => [ + 'pattern' => '/^(left|right|center|justify)$/i' + ], + 'valign' => [ + 'pattern' => '/^(top|middle|bottom)$/i' + ], + 'bgcolor' => [ + 'pattern' => '/^#[0-9a-f]{6}$/i' + ] + ], + 'classes' => true, + 'styles' => [ + 'color' => true, + 'font' => true, + 'font-family' => true, + 'font-size' => true, + 'font-style' => true, + 'height' => true, + 'margin' => true, + 'padding' => true, + 'text-align' => true, + 'vertical-align' => true, + 'width' => true, + 'white-space' => true, + 'float' => true + ] + ], + [ + 'name' => 'ul', + 'attributes' => false, + 'classes' => false, + 'styles' => false + ], + [ + 'name' => 'ol', + 'attributes' => false, + 'classes' => false, + 'styles' => false + ] + ], + 'disallow' => [] + ] + ], + 'Disallow list small dataset' => [ + [ + 'tagsWhiteList' => [], + 'attrsWhiteList' => [], + 'stylesWhiteList' => [], + 'tagsBlackList' => [ + 'html', + 'p', + 'a', + ], + 'attrsBlackList' => [ + 'href', + ], + ], + [ + 'allow' => [], + 'disallow' => [ + [ + 'name' => 'html', + 'attributes' => [ + 'href' => true + ], + ], + [ + 'name' => 'p', + 'attributes' => [ + 'href' => true + ], + ], + [ + 'name' => 'a', + 'attributes' => [ + 'href' => true + ], + ] + ] + ] + ] + ]; + } } diff --git a/tests/php-unit-tests/unitary-tests/sources/Application/Helper/TestDOMSanitizer.php b/tests/php-unit-tests/unitary-tests/sources/Application/Helper/TestDOMSanitizer.php new file mode 100644 index 000000000..b8d494f4b --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/sources/Application/Helper/TestDOMSanitizer.php @@ -0,0 +1,76 @@ +