1<?php 2 3/* 4 * This file is part of Twig. 5 * 6 * (c) Fabien Potencier 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Twig\Sandbox; 13 14use Twig\Markup; 15use Twig\Template; 16 17/** 18 * Represents a security policy which need to be enforced when sandbox mode is enabled. 19 * 20 * @author Fabien Potencier <fabien@symfony.com> 21 */ 22final class SecurityPolicy implements SecurityPolicyInterface 23{ 24 private $allowedTags; 25 private $allowedFilters; 26 private $allowedMethods; 27 private $allowedProperties; 28 private $allowedFunctions; 29 private bool $strict = false; 30 31 public function __construct(array $allowedTags = [], array $allowedFilters = [], array $allowedMethods = [], array $allowedProperties = [], array $allowedFunctions = []) 32 { 33 $this->allowedTags = $allowedTags; 34 $this->allowedFilters = $allowedFilters; 35 $this->setAllowedMethods($allowedMethods); 36 $this->allowedProperties = $allowedProperties; 37 $this->allowedFunctions = $allowedFunctions; 38 } 39 40 public function setAllowedTags(array $tags): void 41 { 42 $this->allowedTags = $tags; 43 } 44 45 public function setAllowedFilters(array $filters): void 46 { 47 $this->allowedFilters = $filters; 48 } 49 50 public function setAllowedMethods(array $methods): void 51 { 52 $this->allowedMethods = []; 53 foreach ($methods as $class => $m) { 54 $this->allowedMethods[$class] = array_map('strtolower', \is_array($m) ? $m : [$m]); 55 } 56 } 57 58 public function setAllowedProperties(array $properties): void 59 { 60 $this->allowedProperties = $properties; 61 } 62 63 public function setAllowedFunctions(array $functions): void 64 { 65 $this->allowedFunctions = $functions; 66 } 67 68 /** 69 * Toggles strict mode. 70 * 71 * In strict mode, the tags and functions that are historically always allowed in a 72 * sandbox (the ``extends`` and ``use`` tags, the ``parent``, ``block``, and 73 * ``attribute`` functions) are no longer implicitly allowed and must be added to the 74 * relevant allow-list to be usable. Use this flag in 3.x to opt-in to the forthcoming 75 * 4.0 behavior and silence the related deprecations. 76 */ 77 public function setStrict(bool $strict): void 78 { 79 $this->strict = $strict; 80 } 81 82 public function checkSecurity($tags, $filters, $functions): void 83 { 84 foreach ($tags as $tag) { 85 if (!\in_array($tag, $this->allowedTags, true)) { 86 if (!$this->strict && 'extends' === $tag) { 87 trigger_deprecation('twig/twig', '3.12', 'The "extends" tag is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed (or enable strict mode on the security policy to opt-in to the 4.0 behavior now).'); 88 } elseif (!$this->strict && 'use' === $tag) { 89 trigger_deprecation('twig/twig', '3.12', 'The "use" tag is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed (or enable strict mode on the security policy to opt-in to the 4.0 behavior now).'); 90 } else { 91 throw new SecurityNotAllowedTagError(\sprintf('Tag "%s" is not allowed.', $tag), $tag); 92 } 93 } 94 } 95 96 foreach ($filters as $filter) { 97 if (!\in_array($filter, $this->allowedFilters, true)) { 98 throw new SecurityNotAllowedFilterError(\sprintf('Filter "%s" is not allowed.', $filter), $filter); 99 } 100 } 101 102 foreach ($functions as $function) { 103 if (!\in_array($function, $this->allowedFunctions, true)) { 104 if (!$this->strict && 'parent' === $function) { 105 trigger_deprecation('twig/twig', '3.27', 'The "parent" function is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed (or enable strict mode on the security policy to opt-in to the 4.0 behavior now).'); 106 } elseif (!$this->strict && 'block' === $function) { 107 trigger_deprecation('twig/twig', '3.27', 'The "block" function is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed (or enable strict mode on the security policy to opt-in to the 4.0 behavior now).'); 108 } elseif (!$this->strict && 'attribute' === $function) { 109 trigger_deprecation('twig/twig', '3.27', 'The "attribute" function is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed (or enable strict mode on the security policy to opt-in to the 4.0 behavior now).'); 110 } else { 111 throw new SecurityNotAllowedFunctionError(\sprintf('Function "%s" is not allowed.', $function), $function); 112 } 113 } 114 } 115 } 116 117 public function checkMethodAllowed($obj, $method): void 118 { 119 if ($obj instanceof Template || $obj instanceof Markup) { 120 return; 121 } 122 123 $allowed = false; 124 $method = strtolower($method); 125 foreach ($this->allowedMethods as $class => $methods) { 126 if ($obj instanceof $class && \in_array($method, $methods, true)) { 127 $allowed = true; 128 break; 129 } 130 } 131 132 if (!$allowed) { 133 $class = $obj::class; 134 throw new SecurityNotAllowedMethodError(\sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, $class), $class, $method); 135 } 136 } 137 138 public function checkPropertyAllowed($obj, $property): void 139 { 140 $allowed = false; 141 foreach ($this->allowedProperties as $class => $properties) { 142 if ($obj instanceof $class && \in_array($property, \is_array($properties) ? $properties : [$properties], true)) { 143 $allowed = true; 144 break; 145 } 146 } 147 148 if (!$allowed) { 149 $class = $obj::class; 150 throw new SecurityNotAllowedPropertyError(\sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, $class), $class, $property); 151 } 152 } 153} 154