Skip to content

Commit ab6e3ea

Browse files
committed
MAGETWO-97961: Fixed incorrect email template rendering
1 parent d9259f4 commit ab6e3ea

File tree

2 files changed

+122
-11
lines changed

2 files changed

+122
-11
lines changed

lib/internal/Magento/Framework/Filter/Template.php

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
*/
1010
namespace Magento\Framework\Filter;
1111

12+
use Magento\Framework\Model\AbstractExtensibleModel;
13+
use Magento\Framework\Model\AbstractModel;
14+
1215
class Template implements \Zend_Filter_Interface
1316
{
1417
/**
@@ -53,6 +56,19 @@ class Template implements \Zend_Filter_Interface
5356
*/
5457
protected $string;
5558

59+
/**
60+
* @var string[]
61+
*/
62+
private $restrictedMethods = [
63+
'addafterfiltercallback',
64+
'getresourcecollection',
65+
'load',
66+
'save',
67+
'getcollection',
68+
'getresource',
69+
'getconfig',
70+
];
71+
5672
/**
5773
* @param \Magento\Framework\Stdlib\StringUtils $string
5874
* @param array $variables
@@ -298,6 +314,46 @@ protected function getParameters($value)
298314
return $params;
299315
}
300316

317+
/**
318+
* Validate method call initiated in a template.
319+
*
320+
* Deny calls for methods that may disrupt template processing.
321+
*
322+
* @param object $object
323+
* @param string $method
324+
* @return void
325+
* @throws \InvalidArgumentException
326+
*/
327+
private function validateVariableMethodCall($object, string $method)
328+
{
329+
if ($object === $this) {
330+
if (in_array(mb_strtolower($method), $this->restrictedMethods)) {
331+
throw new \InvalidArgumentException("Method $method cannot be called from template.");
332+
}
333+
}
334+
}
335+
336+
/**
337+
* Check allowed methods for data objects.
338+
*
339+
* Deny calls for methods that may disrupt template processing.
340+
*
341+
* @param object $object
342+
* @param string $method
343+
* @return bool
344+
* @throws \InvalidArgumentException
345+
*/
346+
private function isAllowedDataObjectMethod($object, string $method)
347+
{
348+
if ($object instanceof AbstractExtensibleModel || $object instanceof AbstractModel) {
349+
if (in_array(mb_strtolower($method), $this->restrictedMethods)) {
350+
throw new \InvalidArgumentException("Method $method cannot be called from template.");
351+
}
352+
}
353+
354+
return true;
355+
}
356+
301357
/**
302358
* Return variable value for var construction
303359
*
@@ -322,7 +378,7 @@ protected function getVariable($value, $default = '{no_value_defined}')
322378
isset($stackVars[$i - 1]['variable'])
323379
&& $stackVars[$i - 1]['variable'] instanceof \Magento\Framework\DataObject
324380
) {
325-
// If object calling methods or getting properties
381+
// If data object calling methods or getting properties
326382
if ($stackVars[$i]['type'] == 'property') {
327383
$caller = 'get' . $this->string->upperCaseWords($stackVars[$i]['name'], '_', '');
328384
$stackVars[$i]['variable'] = method_exists(
@@ -332,19 +388,33 @@ protected function getVariable($value, $default = '{no_value_defined}')
332388
$stackVars[$i]['name']
333389
);
334390
} elseif ($stackVars[$i]['type'] == 'method') {
335-
// Calling of object method
336-
if (
337-
method_exists($stackVars[$i - 1]['variable'], $stackVars[$i]['name'])
338-
|| substr($stackVars[$i]['name'], 0, 3) == 'get'
391+
// Calling of data object method
392+
if (method_exists($stackVars[$i - 1]['variable'], $stackVars[$i]['name'])
393+
|| substr($stackVars[$i]['name'], 0, 3) == 'get'
339394
) {
340395
$stackVars[$i]['args'] = $this->getStackArgs($stackVars[$i]['args']);
341-
$stackVars[$i]['variable'] = call_user_func_array(
342-
[$stackVars[$i - 1]['variable'], $stackVars[$i]['name']],
343-
$stackVars[$i]['args']
344-
);
396+
if ($this->isAllowedDataObjectMethod($stackVars[$i - 1]['variable'], $stackVars[$i]['name'])) {
397+
$stackVars[$i]['variable'] = call_user_func_array(
398+
[$stackVars[$i - 1]['variable'], $stackVars[$i]['name']],
399+
$stackVars[$i]['args']
400+
);
401+
}
345402
}
346403
}
347404
$last = $i;
405+
} elseif (isset($stackVars[$i - 1]['variable'])
406+
&& is_object($stackVars[$i - 1]['variable'])
407+
&& $stackVars[$i]['type'] == 'method'
408+
) {
409+
// Calling object methods
410+
$object = $stackVars[$i - 1]['variable'];
411+
$method = $stackVars[$i]['name'];
412+
if (method_exists($object, $method)) {
413+
$args = $this->getStackArgs($stackVars[$i]['args']);
414+
$this->validateVariableMethodCall($object, $method);
415+
$stackVars[$i]['variable'] = call_user_func_array([$object, $method], $args);
416+
}
417+
$last = $i;
348418
}
349419
}
350420

lib/internal/Magento/Framework/Filter/Test/Unit/TemplateTest.php

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,26 @@
66

77
namespace Magento\Framework\Filter\Test\Unit;
88

9+
use Magento\Framework\Filter\Template;
10+
use Magento\Store\Model\Store;
11+
912
class TemplateTest extends \PHPUnit_Framework_TestCase
1013
{
1114
/**
12-
* @var \Magento\Framework\Filter\Template
15+
* @var Template
1316
*/
1417
private $templateFilter;
1518

19+
/**
20+
* @var Store
21+
*/
22+
private $store;
23+
1624
protected function setUp()
1725
{
1826
$objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
19-
$this->templateFilter = $objectManager->getObject('Magento\Framework\Filter\Template');
27+
$this->templateFilter = $objectManager->getObject(Template::class);
28+
$this->store = $objectManager->getObject(Store::class);
2029
}
2130

2231
public function testFilter()
@@ -191,4 +200,36 @@ public function varDirectiveDataProvider()
191200
],
192201
];
193202
}
203+
204+
/**
205+
* Test filtering disallowed methods calls.
206+
*
207+
* @param string $method
208+
* @dataProvider disallowedMethods
209+
* @expectedException \InvalidArgumentException
210+
*
211+
* @return void
212+
*/
213+
public function testDisallowedMethods(string $method)
214+
{
215+
$this->templateFilter->setVariables(['store' => $this->store]);
216+
$this->templateFilter->filter('{{var store.' . $method . '()}}');
217+
}
218+
219+
/**
220+
* Data for testDisallowedMethods method.
221+
*
222+
* @return array
223+
*/
224+
public function disallowedMethods()
225+
{
226+
return [
227+
['getResourceCollection'],
228+
['load'],
229+
['save'],
230+
['getCollection'],
231+
['getResource'],
232+
['getConfig'],
233+
];
234+
}
194235
}

0 commit comments

Comments
 (0)