Files
Twig/tests/Resources/LegacyCoreTest.php

300 lines
10 KiB
PHP

<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Tests\Resources;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
use Twig\Environment;
use Twig\Error\RuntimeError;
use Twig\Extension\SandboxExtension;
use Twig\Loader\ArrayLoader;
use Twig\Sandbox\SecurityNotAllowedPropertyError;
use Twig\Sandbox\SecurityPolicy;
use Twig\Sandbox\SourcePolicyInterface;
use Twig\Source;
use Twig\Template;
use Twig\TemplateWrapper;
require_once __DIR__.'/../../src/Resources/core.php';
/**
* Tests that the deprecated wrappers in src/Resources/core.php still enforce
* sandbox checks.
*
* @group legacy
*/
#[Group('legacy')]
class LegacyCoreTest extends TestCase
{
public function testTwigSortFilterEnforcesGlobalSandbox()
{
$env = $this->createSandboxedEnvironment(true);
$template = new LegacyCoreTestTemplate($env, 'index.twig');
$this->expectException(RuntimeError::class);
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
$template->callLegacySort(['b', 'a'], 'strnatcasecmp');
}
public function testTwigSortFilterRecoversSourceForSourcePolicy()
{
$env = $this->createSandboxedEnvironment(false, new class implements SourcePolicyInterface {
public function enableSandbox(Source $source): bool
{
return 'sandboxed.twig' === $source->getName();
}
});
$sandboxedTemplate = new LegacyCoreTestTemplate($env, 'sandboxed.twig');
$trustedTemplate = new LegacyCoreTestTemplate($env, 'trusted.twig');
// Trusted template: non-Closure callable is allowed (only a deprecation is triggered).
$this->assertSame(['a', 'b'], array_values($trustedTemplate->callLegacySort(['b', 'a'], 'strnatcasecmp')));
// Sandboxed template: non-Closure callable is rejected thanks to Source recovery.
$this->expectException(RuntimeError::class);
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
$sandboxedTemplate->callLegacySort(['b', 'a'], 'strnatcasecmp');
}
public function testTwigArrayFilterRecoversSourceForSourcePolicy()
{
$env = $this->createSandboxedEnvironment(false, new class implements SourcePolicyInterface {
public function enableSandbox(Source $source): bool
{
return 'sandboxed.twig' === $source->getName();
}
});
$template = new LegacyCoreTestTemplate($env, 'sandboxed.twig');
$this->expectException(RuntimeError::class);
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
iterator_to_array($template->callLegacyArrayFilter(['a', 'b'], 'is_string'));
}
public function testTwigArrayMapRecoversSourceForSourcePolicy()
{
$env = $this->createSandboxedEnvironment(false, new class implements SourcePolicyInterface {
public function enableSandbox(Source $source): bool
{
return 'sandboxed.twig' === $source->getName();
}
});
$template = new LegacyCoreTestTemplate($env, 'sandboxed.twig');
$this->expectException(RuntimeError::class);
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
$template->callLegacyArrayMap(['a', 'b'], 'strtoupper');
}
public function testTwigArrayColumnEnforcesSandbox()
{
$env = $this->createSandboxedEnvironment(true);
$template = new LegacyCoreTestTemplate($env, 'index.twig');
$this->expectException(SecurityNotAllowedPropertyError::class);
$template->callLegacyArrayColumn([new LegacyColumnObject()], 'bar');
}
public function testTwigArrayReduceRecoversSourceForSourcePolicy()
{
$env = $this->createSandboxedEnvironment(false, new class implements SourcePolicyInterface {
public function enableSandbox(Source $source): bool
{
return 'sandboxed.twig' === $source->getName();
}
});
$template = new LegacyCoreTestTemplate($env, 'sandboxed.twig');
$this->expectException(RuntimeError::class);
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
$template->callLegacyArrayReduce([1, 2], 'intval');
}
public function testTwigArraySomeEnforcesGlobalSandbox()
{
$env = $this->createSandboxedEnvironment(true);
$template = new LegacyCoreTestTemplate($env, 'index.twig');
$this->expectException(RuntimeError::class);
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
$template->callLegacyArraySome(['a', 'b'], 'is_string');
}
public function testTwigArraySomeRecoversSourceForSourcePolicy()
{
$env = $this->createSandboxedEnvironment(false, new class implements SourcePolicyInterface {
public function enableSandbox(Source $source): bool
{
return 'sandboxed.twig' === $source->getName();
}
});
$template = new LegacyCoreTestTemplate($env, 'sandboxed.twig');
$this->expectException(RuntimeError::class);
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
$template->callLegacyArraySome(['a', 'b'], 'is_string');
}
public function testTwigArrayEveryEnforcesGlobalSandbox()
{
$env = $this->createSandboxedEnvironment(true);
$template = new LegacyCoreTestTemplate($env, 'index.twig');
$this->expectException(RuntimeError::class);
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
$template->callLegacyArrayEvery(['a', 'b'], 'is_string');
}
public function testTwigArrayEveryRecoversSourceForSourcePolicy()
{
$env = $this->createSandboxedEnvironment(false, new class implements SourcePolicyInterface {
public function enableSandbox(Source $source): bool
{
return 'sandboxed.twig' === $source->getName();
}
});
$template = new LegacyCoreTestTemplate($env, 'sandboxed.twig');
$this->expectException(RuntimeError::class);
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
$template->callLegacyArrayEvery(['a', 'b'], 'is_string');
}
public function testTwigCheckArrowInSandboxEnforcesGlobalSandbox()
{
$env = $this->createSandboxedEnvironment(true);
$template = new LegacyCoreTestTemplate($env, 'index.twig');
$this->expectException(RuntimeError::class);
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
$template->callLegacyCheckArrowInSandbox('strlen', 'test', 'filter');
}
public function testTwigCheckArrowInSandboxRecoversSourceForSourcePolicy()
{
$env = $this->createSandboxedEnvironment(false, new class implements SourcePolicyInterface {
public function enableSandbox(Source $source): bool
{
return 'sandboxed.twig' === $source->getName();
}
});
$template = new LegacyCoreTestTemplate($env, 'sandboxed.twig');
$this->expectException(RuntimeError::class);
$this->expectExceptionMessageMatches('/must be a Closure in sandbox mode/');
$template->callLegacyCheckArrowInSandbox('strlen', 'test', 'filter');
}
public function testTwigCheckArrowInSandboxIsNoopWhenNotSandboxed()
{
$env = $this->createSandboxedEnvironment(false);
$template = new LegacyCoreTestTemplate($env, 'index.twig');
$template->callLegacyCheckArrowInSandbox('strlen', 'test', 'filter');
$this->expectNotToPerformAssertions();
}
private function createSandboxedEnvironment(bool $globallySandboxed, ?SourcePolicyInterface $sourcePolicy = null): Environment
{
$env = new Environment(new ArrayLoader([]), ['cache' => false, 'autoescape' => false]);
$env->addExtension(new SandboxExtension(new SecurityPolicy(), $globallySandboxed, $sourcePolicy));
return $env;
}
}
class LegacyCoreTestTemplate extends Template
{
public function __construct(Environment $env, private string $name)
{
parent::__construct($env);
}
public function callLegacySort($array, $arrow)
{
return twig_sort_filter($this->env, $array, $arrow);
}
public function callLegacyArrayFilter($array, $arrow)
{
return twig_array_filter($this->env, $array, $arrow);
}
public function callLegacyArrayMap($array, $arrow)
{
return twig_array_map($this->env, $array, $arrow);
}
public function callLegacyArrayReduce($array, $arrow, $initial = null)
{
return twig_array_reduce($this->env, $array, $arrow, $initial);
}
public function callLegacyArrayColumn($array, $name, $index = null)
{
return twig_array_column($this->env, $array, $name, $index);
}
public function callLegacyArraySome($array, $arrow)
{
return twig_array_some($this->env, $array, $arrow);
}
public function callLegacyArrayEvery($array, $arrow)
{
return twig_array_every($this->env, $array, $arrow);
}
public function callLegacyCheckArrowInSandbox($arrow, $thing, $type)
{
twig_check_arrow_in_sandbox($this->env, $arrow, $thing, $type);
}
public function getTemplateName(): string
{
return $this->name;
}
public function getDebugInfo(): array
{
return [];
}
public function getSourceContext(): Source
{
return new Source('', $this->name);
}
protected function doGetParent(array $context): bool|string|Template|TemplateWrapper
{
return false;
}
protected function doDisplay(array $context, array $blocks = []): iterable
{
return [];
}
}
class LegacyColumnObject
{
public $bar = 'bar';
}