MDL-80203 courseformat: Fix some typos and PHPDoc
[moodle.git] / lib / tests / core_renderer_template_exploit_test.php
blob992fa1f95bc346da8b181acee5648546315b117b
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 namespace core;
19 /**
20 * Unit tests for core renderer render template exploit.
22 * @package core
23 * @category test
24 * @copyright 2019 Ryan Wyllie <ryan@moodle.com>
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 class core_renderer_template_exploit_test extends \advanced_testcase {
28 /**
29 * Test cases to confirm that blacklisted helpers are stripped from the source
30 * text by the helper before being passed to other another helper. This prevents
31 * nested calls to helpers.
33 public function get_template_testcases() {
34 // Different helper implementations to test various combinations of nested
35 // calls to render the templates.
36 $norender = function($text) {
37 return $text;
39 $singlerender = function($text, $helper) {
40 return $helper->render($text);
42 $recursiverender = function($text, $helper) {
43 $result = $helper->render($text);
45 while (strpos($result, '{{') != false) {
46 $result = $helper->render($result);
49 return $result;
52 return [
53 'nested JS helper' => [
54 'templates' => [
55 'test' => '{{#testpix}} core, move, {{#js}} some nasty JS {{/js}}{{/testpix}}',
57 'torender' => 'test',
58 'context' => [],
59 'helpers' => [
60 'testpix' => $singlerender
62 'js' => 'some nasty JS',
63 'expected' => 'core, move,',
64 'include' => false
66 'other nested helper' => [
67 'templates' => [
68 'test' => '{{#testpix}} core, move, {{#test1}} some text {{/test1}}{{/testpix}}',
70 'torender' => 'test',
71 'context' => [],
72 'helpers' => [
73 'testpix' => $singlerender,
74 'test1' => $norender,
76 'js' => 'some nasty JS',
77 'expected' => 'core, move, some text',
78 'include' => false
80 'double nested helper' => [
81 'templates' => [
82 'test' => '{{#testpix}} core, move, {{#test1}} some text {{#js}} some nasty JS {{/js}} {{/test1}}{{/testpix}}',
84 'torender' => 'test',
85 'context' => [],
86 'helpers' => [
87 'testpix' => $singlerender,
88 'test1' => $norender,
90 'js' => 'some nasty JS',
91 'expected' => 'core, move, some text {{}}',
92 'include' => false
94 'js helper not nested' => [
95 'templates' => [
96 'test' => '{{#testpix}} core, move, some text {{/testpix}}{{#js}} some nasty JS {{/js}}',
98 'torender' => 'test',
99 'context' => [],
100 'helpers' => [
101 'testpix' => $singlerender
103 'js' => 'some nasty JS',
104 'expected' => 'core, move, some text',
105 'include' => true
107 'js in context not in helper' => [
108 'templates' => [
109 'test' => '{{#testpix}} core, move, {{/testpix}}{{hack}}',
111 'torender' => 'test',
112 'context' => [
113 'hack' => '{{#js}} some nasty JS {{/js}}'
115 'helpers' => [
116 'testpix' => $singlerender
118 'js' => 'some nasty JS',
119 'expected' => 'core, move, {{#js}} some nasty JS {{/js}}',
120 'include' => false
122 'js in context' => [
123 'templates' => [
124 'test' => '{{#testpix}} core, move, {{hack}}{{/testpix}}',
126 'torender' => 'test',
127 'context' => [
128 'hack' => '{{#js}} some nasty JS {{/js}}'
130 'helpers' => [
131 'testpix' => $singlerender
133 'js' => 'some nasty JS',
134 'expected' => 'core, move, {{}}',
135 'include' => false
137 'js in context double depth with single render' => [
138 'templates' => [
139 'test' => '{{#testpix}} core, move, {{first}}{{/testpix}}',
141 'torender' => 'test',
142 'context' => [
143 'first' => '{{second}}',
144 'second' => '{{#js}} some nasty JS {{/js}}'
146 'helpers' => [
147 'testpix' => $singlerender
149 'js' => 'some nasty JS',
150 'expected' => 'core, move, {{second}}',
151 'include' => false
153 'js in context double depth with recursive render' => [
154 'templates' => [
155 'test' => '{{#testpix}} core, move, {{first}}{{/testpix}}',
157 'torender' => 'test',
158 'context' => [
159 'first' => '{{second}}',
160 'second' => '{{#js}} some nasty JS {{/js}}'
162 'helpers' => [
163 'testpix' => $recursiverender
165 'js' => 'some nasty JS',
166 'expected' => 'core, move,',
167 'include' => false
169 'partial' => [
170 'templates' => [
171 'test' => '{{#testpix}} core, move, blah{{/testpix}}, {{> test2}}',
172 'test2' => 'some content',
174 'torender' => 'test',
175 'context' => [],
176 'helpers' => [
177 'testpix' => $recursiverender
179 'js' => 'some nasty JS',
180 'expected' => 'core, move, blah, some content',
181 'include' => false
183 'partial nested' => [
184 'templates' => [
185 'test' => '{{#testpix}} core, move, {{> test2}}{{/testpix}}',
186 'test2' => 'some content',
188 'torender' => 'test',
189 'context' => [],
190 'helpers' => [
191 'testpix' => $recursiverender
193 'js' => 'some nasty JS',
194 'expected' => 'core, move, some content',
195 'include' => false
197 'partial with js' => [
198 'templates' => [
199 'test' => '{{#testpix}} core, move, blah{{/testpix}}, {{> test2}}',
200 'test2' => '{{#js}} some nasty JS {{/js}}',
202 'torender' => 'test',
203 'context' => [],
204 'helpers' => [
205 'testpix' => $recursiverender
207 'js' => 'some nasty JS',
208 'expected' => 'core, move, blah,',
209 'include' => true
211 'partial nested with js' => [
212 'templates' => [
213 'test' => '{{#testpix}} core, move, {{> test2}}{{/testpix}}',
214 'test2' => '{{#js}} some nasty JS {{/js}}',
216 'torender' => 'test',
217 'context' => [],
218 'helpers' => [
219 'testpix' => $recursiverender
221 'js' => 'some nasty JS',
222 'expected' => 'core, move,',
223 'include' => false
225 'partial with js from context' => [
226 'templates' => [
227 'test' => '{{#testpix}} core, move, blah{{/testpix}}, {{{foo}}}',
228 'test2' => '{{#js}} some nasty JS {{/js}}',
230 'torender' => 'test',
231 'context' => [
232 'foo' => '{{> test2}}'
234 'helpers' => [
235 'testpix' => $recursiverender
237 'js' => 'some nasty JS',
238 'expected' => 'core, move, blah, {{> test2}}',
239 'include' => false
241 'partial nested with js from context recursive render' => [
242 'templates' => [
243 'test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}',
244 'test2' => '{{#js}} some nasty JS {{/js}}',
246 'torender' => 'test',
247 'context' => [
248 'foo' => '{{> test2}}'
250 'helpers' => [
251 'testpix' => $recursiverender
253 'js' => 'some nasty JS',
254 'expected' => 'core, move,',
255 'include' => false
257 'partial nested with js from context single render' => [
258 'templates' => [
259 'test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}',
260 'test2' => '{{#js}} some nasty JS {{/js}}',
262 'torender' => 'test',
263 'context' => [
264 'foo' => '{{> test2}}'
266 'helpers' => [
267 'testpix' => $singlerender
269 'js' => 'some nasty JS',
270 'expected' => 'core, move, {{&gt; test2}}',
271 'include' => false
273 'partial double nested with js from context recursive render' => [
274 'templates' => [
275 'test' => '{{#testpix}} core, move, {{foo}}{{/testpix}}',
276 'test2' => '{{#js}} some nasty JS {{/js}}',
278 'torender' => 'test',
279 'context' => [
280 'foo' => '{{bar}}',
281 'bar' => '{{> test2}}'
283 'helpers' => [
284 'testpix' => $recursiverender
286 'js' => 'some nasty JS',
287 'expected' => 'core, move,',
288 'include' => false
290 'array context depth 1' => [
291 'templates' => [
292 'test' => '{{#items}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/items}}'
294 'torender' => 'test',
295 'context' => [
296 'items' => [
297 'legit',
298 '{{#js}}some nasty JS{{/js}}'
301 'helpers' => [
302 'testpix' => $recursiverender
304 'js' => 'some nasty JS',
305 'expected' => 'core, move, legit core, move,',
306 'include' => false
308 'array context depth 2' => [
309 'templates' => [
310 'test' => '{{#items}}{{#subitems}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/subitems}}{{/items}}'
312 'torender' => 'test',
313 'context' => [
314 'items' => [
316 'subitems' => [
317 'legit',
318 '{{#js}}some nasty JS{{/js}}'
323 'helpers' => [
324 'testpix' => $recursiverender
326 'js' => 'some nasty JS',
327 'expected' => 'core, move, legit core, move,',
328 'include' => false
330 'object context depth 1' => [
331 'templates' => [
332 'test' => '{{#items}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/items}}'
334 'torender' => 'test',
335 'context' => (object) [
336 'items' => [
337 'legit',
338 '{{#js}}some nasty JS{{/js}}'
341 'helpers' => [
342 'testpix' => $recursiverender
344 'js' => 'some nasty JS',
345 'expected' => 'core, move, legit core, move,',
346 'include' => false
348 'object context depth 2' => [
349 'templates' => [
350 'test' => '{{#items}}{{#subitems}}{{#testpix}} core, move, {{.}}{{/testpix}}{{/subitems}}{{/items}}'
352 'torender' => 'test',
353 'context' => (object) [
354 'items' => [
355 (object) [
356 'subitems' => [
357 'legit',
358 '{{#js}}some nasty JS{{/js}}'
363 'helpers' => [
364 'testpix' => $recursiverender
366 'js' => 'some nasty JS',
367 'expected' => 'core, move, legit core, move,',
368 'include' => false
370 'change delimeters' => [
371 'templates' => [
372 'test' => '{{#testpix}} core, move, {{{foo}}}{{/testpix}}'
374 'torender' => 'test',
375 'context' => [
376 'foo' => '{{=<% %>=}} <%#js%>some nasty JS,<%/js%>'
378 'helpers' => [
379 'testpix' => $recursiverender
381 'js' => 'some nasty JS',
382 'expected' => 'core, move,',
383 'include' => false
389 * Test that the mustache_helper_collection class correctly strips
390 * @dataProvider get_template_testcases()
391 * @param array $templates The template to add
392 * @param string $torender The name of the template to render
393 * @param array $context The template context
394 * @param array $helpers Mustache helpers to add
395 * @param string $js The JS string from the template
396 * @param string $expected The expected output of the string after stripping JS
397 * @param bool $include If the JS should be added to the page or not
399 public function test_core_mustache_engine_strips_js_helper(
400 $templates,
401 $torender,
402 $context,
403 $helpers,
404 $js,
405 $expected,
406 $include
408 $page = new \moodle_page();
409 $renderer = $page->get_renderer('core');
411 // Get the mustache engine from the renderer.
412 $reflection = new \ReflectionMethod($renderer, 'get_mustache');
413 $engine = $reflection->invoke($renderer);
415 // Swap the loader out with an array loader so that we can set some
416 // inline templates for testing.
417 $loader = new \Mustache_Loader_ArrayLoader([]);
418 $engine->setLoader($loader);
420 // Add our test helpers.
421 $helpercollection = $engine->getHelpers();
422 foreach ($helpers as $name => $function) {
423 $helpercollection->add($name, $function);
426 // Add our test template to be rendered.
427 foreach ($templates as $name => $template) {
428 $loader->setTemplate($name, $template);
431 // Confirm that the rendered template matches what we expect.
432 $this->assertEquals($expected, trim($engine->render($torender, $context)));
434 if ($include) {
435 // Confirm that the JS was added to the page.
436 $this->assertStringContainsString($js, $page->requires->get_end_code());
437 } else {
438 // Confirm that the JS wasn't added to the page.
439 $this->assertStringNotContainsString($js, $page->requires->get_end_code());