Skip to content

Commit ff9ba11

Browse files
committed
Merge remote-tracking branch 'remotes/origin/MC-15311' into cms-team-1-delivery
2 parents af6595e + c85d3d1 commit ff9ba11

File tree

11 files changed

+140
-3
lines changed

11 files changed

+140
-3
lines changed

app/code/Magento/PageBuilder/Plugin/Filter/TemplatePlugin.php

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
namespace Magento\PageBuilder\Plugin\Filter;
99

10+
use Magento\Store\Model\Store;
11+
1012
/**
1113
* Plugin to the template filter to process any background images added by Page Builder
1214
*/
@@ -105,6 +107,32 @@ public function afterFilter(\Magento\Framework\Filter\Template $subject, string
105107
return $result;
106108
}
107109

110+
/**
111+
* Determine if custom variable directive's return value needs to be escaped and do so if true
112+
*
113+
* @param \Magento\Framework\Filter\Template $subject
114+
* @param \Closure $proceed
115+
* @param string[] $construction
116+
* @return string
117+
*/
118+
public function aroundCustomvarDirective(
119+
\Magento\Framework\Filter\Template $subject,
120+
\Closure $proceed,
121+
$construction
122+
) {
123+
// Determine the need to escape the return value of observed method.
124+
// Admin context requires store ID of 0; in that context return value should be escaped
125+
$shouldEscape = $subject->getStoreId() !== null && (int) $subject->getStoreId() === Store::DEFAULT_STORE_ID;
126+
127+
if (!$shouldEscape) {
128+
return $proceed($construction);
129+
}
130+
131+
$result = $proceed($construction);
132+
133+
return htmlspecialchars($result);
134+
}
135+
108136
/**
109137
* Create a DOM document from a given string
110138
*
@@ -180,8 +208,27 @@ private function generateDecodedHtmlPlaceholderMappingInDocument(\DOMDocument $d
180208
continue;
181209
}
182210

211+
// clone html code content type to save reference to its attributes/outerHTML, which we are not going to
212+
// decode
213+
$clonedHtmlContentTypeNode = clone $htmlContentTypeNode;
214+
215+
// clear inner contents of cloned node for replacement later with $decodedInnerHtml using sprintf;
216+
// we want to retain html content type node and avoid doing any manipulation on it
217+
$clonedHtmlContentTypeNode->nodeValue = '%s';
218+
219+
// remove potentially harmful attributes on html content type node itself
220+
while ($htmlContentTypeNode->attributes->length) {
221+
$htmlContentTypeNode->removeAttribute($htmlContentTypeNode->attributes->item(0)->name);
222+
}
223+
224+
// decode outerHTML safely
183225
$preDecodedOuterHtml = $document->saveHTML($htmlContentTypeNode);
184-
$decodedOuterHtml = html_entity_decode($preDecodedOuterHtml);
226+
227+
// clear empty <div> wrapper around outerHTML to replace with $clonedHtmlContentTypeNode
228+
$decodedInnerHtml = preg_replace('#^<[^>]*>|</[^>]*>$#', '', html_entity_decode($preDecodedOuterHtml));
229+
230+
// Use $clonedHtmlContentTypeNode's placeholder to inject decoded inner html
231+
$decodedOuterHtml = sprintf($document->saveHTML($clonedHtmlContentTypeNode), $decodedInnerHtml);
185232

186233
// generate unique node name element to replace with decoded html contents at end of processing;
187234
// goal is to create a document as few times as possible to prevent inadvertent parsing of contents as html

app/code/Magento/PageBuilder/Test/Mftf/Data/AdvancedData.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,20 @@
304304
<data key="fieldName">css_classes</data>
305305
<data key="value">first-class second-class third-class</data>
306306
</entity>
307+
<entity name="PageBuilderAdvancedCssClassesProperty_Invalid_GreaterThan" type="pagebuilder_advanced_css_classes_property">
308+
<data key="name">CSS Classes</data>
309+
<data key="section">advanced</data>
310+
<data key="fieldName">css_classes</data>
311+
<data key="value">first&gt;class second-class third-class</data>
312+
<data key="errorMessage">Please enter a valid CSS class.</data>
313+
</entity>
314+
<entity name="PageBuilderAdvancedCssClassesProperty_Invalid_LessThan" type="pagebuilder_advanced_css_classes_property">
315+
<data key="name">CSS Classes</data>
316+
<data key="section">advanced</data>
317+
<data key="fieldName">css_classes</data>
318+
<data key="value">first-class second-class third&lt;class</data>
319+
<data key="errorMessage">Please enter a valid CSS class.</data>
320+
</entity>
307321
<!-- Text Color -->
308322
<entity name="PageBuilderAdvancedTextColorProperty" type="pagebuilder_advanced_color_property">
309323
<data key="name">Text Color</data>

app/code/Magento/PageBuilder/Test/Mftf/Test/AdminPageBuilderAdvancedConfigurationTest.xml

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1629,8 +1629,6 @@
16291629
<actionGroup ref="dragContentTypeToStage" stepKey="dragRowIntoStage2">
16301630
<argument name="contentType" value="PageBuilderRowContentType"/>
16311631
</actionGroup>
1632-
<!-- Add CSS Classes: Empty -->
1633-
<comment userInput="Add CSS Classes: Empty" stepKey="commentAddCSSClassesEmpty"/>
16341632
<actionGroup ref="expandPageBuilderPanelMenuSection" stepKey="expandPageBuilderPanelMenuSection">
16351633
<argument name="contentType" value="PageBuilderTextContentType"/>
16361634
</actionGroup>
@@ -1648,6 +1646,24 @@
16481646
<actionGroup ref="addTextToTinyMCE" stepKey="enterText1">
16491647
<argument name="property" value="PageBuilderTextProperty"/>
16501648
</actionGroup>
1649+
<!-- Add CSS Classes: Invalid Greater Than -->
1650+
<comment userInput="Add CSS Classes: Invalid Greater Than" stepKey="commentAddCSSClassesInvalidGreaterThan"/>
1651+
<actionGroup ref="fillSlideOutPanelFieldAndExpectToSeeErrorInFieldset" stepKey="enterGreaterThanSymbol">
1652+
<argument name="property" value="PageBuilderAdvancedCssClassesProperty_Invalid_GreaterThan"/>
1653+
</actionGroup>
1654+
<actionGroup ref="saveEditPanelAndValidateFieldError" stepKey="validateErrorGreaterThan">
1655+
<argument name="property" value="PageBuilderAdvancedCssClassesProperty_Invalid_GreaterThan"/>
1656+
</actionGroup>
1657+
<!-- Add CSS Classes: Invalid Less Than -->
1658+
<comment userInput="Add CSS Classes: Invalid Less Than" stepKey="commentAddCSSClassesInvalidLessThan"/>
1659+
<actionGroup ref="fillSlideOutPanelFieldAndExpectToSeeErrorInFieldset" stepKey="enterLessThanSymbol">
1660+
<argument name="property" value="PageBuilderAdvancedCssClassesProperty_Invalid_LessThan"/>
1661+
</actionGroup>
1662+
<actionGroup ref="saveEditPanelAndValidateFieldError" stepKey="validateErrorLessThan">
1663+
<argument name="property" value="PageBuilderAdvancedCssClassesProperty_Invalid_LessThan"/>
1664+
</actionGroup>
1665+
<!-- Add CSS Classes: Empty -->
1666+
<comment userInput="Add CSS Classes: Empty" stepKey="commentAddCSSClassesEmpty"/>
16511667
<actionGroup ref="clearSlideOutPanelFieldGeneral" stepKey="clearCSSClasses">
16521668
<argument name="property" value="PageBuilderAdvancedCssClassesDefaultProperty"/>
16531669
</actionGroup>

app/code/Magento/PageBuilder/view/adminhtml/ui_component/pagebuilder_base_form.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@
184184
<dataType>text</dataType>
185185
<label translate="true">CSS Classes</label>
186186
<notice translate="true">Space separated list of classes.</notice>
187+
<validation>
188+
<rule name="validate-css-class" xsi:type="boolean">true</rule>
189+
</validation>
187190
</settings>
188191
</field>
189192
<field name="margins_and_padding" sortOrder="70" formElement="input" component="Magento_PageBuilder/js/form/element/margins-and-padding">

app/code/Magento/PageBuilder/view/adminhtml/web/js/form/element/validator-rules-mixin.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ define([
5252
return (/<a[\s]+([^>]+)>|<a>|<\/a>/igm).test(str);
5353
}
5454

55+
/**
56+
* Validate that string is a proper css-class
57+
* @param {String} str
58+
* @return {Boolean}
59+
*/
60+
function validateCssClass(str) {
61+
return (/^[a-zA-Z _\-\d]+$/i).test(str);
62+
}
63+
5564
/**
5665
* Validate message field and url field anchor tag is used exclusively by one field
5766
* @param {String} message
@@ -147,6 +156,18 @@ define([
147156
$.mage.__('Please enter a valid video URL.')
148157
);
149158

159+
validator.addRule(
160+
'validate-css-class',
161+
function (value) {
162+
if (utils.isEmptyNoTrim(value)) {
163+
return true;
164+
}
165+
166+
return validateCssClass(value);
167+
},
168+
$.mage.__('Please enter a valid CSS class.')
169+
);
170+
150171
validator.addRule(
151172
'required-entry',
152173
function (value) {

dev/tests/integration/testsuite/Magento/PageBuilder/Plugin/Filter/TemplatePluginTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace Magento\PageBuilder\Plugin\Filter;
1010

11+
use Magento\Store\Model\Store;
1112
use Magento\Widget\Model\Template\Filter as TemplateFilter;
1213
use Magento\TestFramework\Helper\Bootstrap;
1314

@@ -30,13 +31,18 @@ protected function setUp()
3031
{
3132
$this->objectManager = Bootstrap::getObjectManager();
3233
$this->templateFilter = $this->objectManager->get(TemplateFilter::class);
34+
35+
// set store id to 0 to recognize that escaping is required in custom variable
36+
$this->templateFilter->setStoreId(Store::DEFAULT_STORE_ID);
3337
}
3438

3539
/**
3640
* @param string $preFiltered
3741
* @param string $postFiltered
3842
* @param string $preFilteredBasename
3943
* @dataProvider filterDataProvider
44+
* @magentoDataFixture Magento/PageBuilder/_files/custom_variable_xss.php
45+
* @magentoDbIsolation enabled
4046
*/
4147
public function testFiltering(string $preFiltered, string $postFiltered, string $preFilteredBasename)
4248
{
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
8+
9+
/** @var \Magento\Variable\Model\ResourceModel\Variable $variableResource */
10+
$variableResource = $this->objectManager->get(\Magento\Variable\Model\ResourceModel\Variable::class);
11+
12+
/** @var \Magento\Variable\Model\Variable $variable */
13+
$variable = $this->objectManager->get(\Magento\Variable\Model\Variable::class);
14+
15+
$variable->setData([
16+
'code' => 'xssVariable',
17+
'name' => 'xssVariable',
18+
'html_value' => '<img src=x onerror="alert(0)">',
19+
'plain_value' => '<img src=x onerror="alert(0)">',
20+
]);
21+
22+
$variableResource->save($variable);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
&lt;img src=x onerror=&quot;alert(0)&quot;&gt;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{customVar code=xssVariable}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div data-content-type="html" class="&gt;'&gt;&quot;&gt;&lt;img src=x onerror=alert(0)&gt;" data-decoded="true">
2+
Nothing to see here
3+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div data-content-type="html" class="&gt;'&gt;&quot;&gt;&lt;img src=x onerror=alert(0)&gt;">
2+
Nothing to see here
3+
</div>

0 commit comments

Comments
 (0)