Skip to content

Commit b606a44

Browse files
committed
Merge branch 'release/5.0.3' into v5
2 parents 6f63c43 + f7c3a96 commit b606a44

File tree

10 files changed

+384
-252
lines changed

10 files changed

+384
-252
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@
22

33
All notable changes to this project will be documented in this file.
44

5-
## 5.0.2 -2025.02.17
5+
## 5.0.3 - 2025.06.08
6+
### Added
7+
* Add an example `config/blacklist-sandbox.php` and `config/whitelist-sandbox.php` files for user-customizable Twig sandbox environments
8+
* Add `SecurityPolicy::createFromFile()` to create a new Twig sandbox from a config file in the `config/` directory
9+
10+
### Changed
11+
* Cleaned up the `BlacklistSecurityPolicy` to no longer blacklist innocuous tags/filters/functions
12+
13+
## 5.0.2 - 2025.02.17
614
### Added
715
* Craft Twig Sandbox no longer automatically handles exceptions when rendering sandbox templates. Instead, you can decide whether to handle the exception yourself, or pass it along to the `sandboxErrorHandler` for display in the browser/console
816

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,42 @@ If you want all properties or methods to be able to be accessed on a given objec
224224
],
225225
```
226226

227+
### SecurityPolicy from a config file
228+
229+
Often you'll want to provide a sane Twig sandbox, but also allow your users to add or remove from the policy as they see fit.
230+
231+
To make this easy to do, there is a `SecurityPolicy::createFromFile()` helper method to create a sandbox security policy from a config file:
232+
```php
233+
public static function createFromFile(string $filePath, ?string $alias = null): BaseSecurityPolicy
234+
```
235+
236+
You pass it in a `$filePath`, and it will look for a file of that name (with `.php` added to the end of it) in the `craft/config/` directory. If no file is found, it will then also try to resolve the optional `$alias` and look for the file in that directory.
237+
238+
If the file still is not found, it will return a default `BlacklistSecurityPolicy`.
239+
240+
The config file is a standard [Yii2 Object Configuration file](https://www.yiiframework.com/doc/guide/2.0/en/concept-configurations).
241+
242+
Example files you can copy & rename exists in the `craft-twig-standbox` codebase in `src/config/`, as `blacklist-sandbox.php` and `whitelist-sandbox-php`.
243+
244+
These are the default files that are used to create the respective security policies when you allocate a new `BlacklistSecurityPolicy` or `WhitelistSecurityPolicy`, and pass in no object configuration.
245+
246+
So for a practical example, the author of the SEOmatic plugin would copy the `config/blacklist-sandbox.php` file to that plugin's `src/` directory as `seomatic-sandbox.php`, and put in any customizations that they might want there.
247+
248+
Then they could direct their users to copy the `seomatic-sandbox.php` file to their `craft/config/` directory if they wanted to make any customizations to it.
249+
250+
Then to create the sandbox view in the plugin, they would do:
251+
252+
```php
253+
use nystudio107\crafttwigsandbox\helpers\SecurityPolicy;
254+
255+
$securityPolicy = SecurityPolicy::createFromFile('seomatic-sandbox', '@nystudio107/seomatic');
256+
$sandboxView = new SandboxView(['securityPolicy' => $securityPolicy]);
257+
```
258+
259+
This will cause it to create the sandbox from the `seomatic-sandbox.php` file in the `craft/config/` directory (if it exists), and if it does not exist, it will load the config file from the `seomatic-sandbox.php` in the `@nystudio107/seomatic` directory (which points to the plugin's source).
260+
261+
Craft automatically creates a namespaced alias for each plugin.
262+
227263
### Custom SecurityPolicy
228264

229265
You can also create your own custom `SecurityPolicy` to use, it just needs to conform to the Twig [`SecurityPolicyInterface`](https://github.com/twigphp/Twig/blob/3.x/src/Sandbox/SecurityPolicyInterface.php):

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "nystudio107/craft-twig-sandbox",
33
"description": "Allows you to easily create a sandboxed Twig environment where you can control what tags, filters, functions, and object methods/properties are allowed",
4-
"version": "5.0.2",
4+
"version": "5.0.3",
55
"keywords": [
66
"craft",
77
"cms",

src/config/blacklist-sandbox.php

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<?php
2+
3+
/**
4+
* SecurityPolicy config.php
5+
*
6+
* This file exists only as a template for a sandbox configuration.
7+
* It does nothing on its own.
8+
*
9+
* Don't edit this file, instead copy it to 'craft/config' as 'xxxx-sandbox.php'
10+
* and make your changes there to override default settings.
11+
*
12+
* The idea is that this allows for a user-editable config file so that users
13+
* can customize the Twig sandbox that your application uses.
14+
*/
15+
16+
use nystudio107\crafttwigsandbox\twig\BlacklistSecurityPolicy;
17+
18+
return [
19+
'class' => BlacklistSecurityPolicy::class,
20+
'twigTags' => [
21+
'autoescape',
22+
'block',
23+
'deprecated',
24+
'do',
25+
'embed',
26+
'extends',
27+
'flush',
28+
'from',
29+
'import',
30+
'include',
31+
'macro',
32+
'sandbox',
33+
'use',
34+
'verbatim',
35+
'cache',
36+
'css',
37+
'dd',
38+
'dump',
39+
'exit',
40+
'header',
41+
'hook',
42+
'html',
43+
'js',
44+
'namespace',
45+
'nav',
46+
'paginate',
47+
'redirect',
48+
'requireAdmin',
49+
'requireEdition',
50+
'requireGuest',
51+
'requireLogin',
52+
'requirePermission',
53+
'script',
54+
'tag',
55+
],
56+
'twigFilters' => [
57+
'convert_encoding',
58+
'data_uri',
59+
'filter',
60+
'inky_to_html',
61+
'inline_css',
62+
'map',
63+
'merge',
64+
'reduce',
65+
'sort',
66+
'spaceless',
67+
'url_encode',
68+
'append',
69+
'attr',
70+
'base64_decode',
71+
'base64_encode',
72+
'column',
73+
'encenc',
74+
'filesize',
75+
'filter',
76+
'hash',
77+
'json_encode',
78+
'json_decode',
79+
'multisort',
80+
'namespace',
81+
'ns',
82+
'namespaceAttributes',
83+
'namespaceInputId',
84+
'namespaceInputName',
85+
'parseAttr',
86+
'parseRefs',
87+
'prepend',
88+
'removeClass',
89+
'where',
90+
],
91+
'twigFunctions' => [
92+
'attribute',
93+
'block',
94+
'constant',
95+
'cycle',
96+
'dump',
97+
'html_classes',
98+
'parent',
99+
'source',
100+
'template_from_string',
101+
'actionInput',
102+
'alias',
103+
'beginBody',
104+
'block',
105+
'canCreateDrafts',
106+
'canDelete',
107+
'canDeleteForSite',
108+
'canDuplicate',
109+
'canSave',
110+
'canView',
111+
'ceil',
112+
'className',
113+
'clone',
114+
'combine',
115+
'configure',
116+
'constant',
117+
'create',
118+
'csrfInput',
119+
'dump',
120+
'endBody',
121+
'expression',
122+
'failMessageInput',
123+
'getenv',
124+
'gql',
125+
'head',
126+
'hiddenInput',
127+
'input',
128+
'parseBooleanEnv',
129+
'parseEnv',
130+
'plugin',
131+
'redirectInput',
132+
'renderObjectTemplate',
133+
'source',
134+
'successMessageInput',
135+
],
136+
'twigMethods' => [
137+
],
138+
'twigProperties' => [
139+
],
140+
];

src/config/whitelist-sandbox.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
/**
4+
* SecurityPolicy config.php
5+
*
6+
* This file exists only as a template for a sandbox configuration.
7+
* It does nothing on its own.
8+
*
9+
* Don't edit this file, instead copy it to 'craft/config' as 'xxxx-sandbox.php'
10+
* and make your changes there to override default settings.
11+
*
12+
* The idea is that this allows for a user-editable config file so that users
13+
* can customize the Twig sandbox that your application uses.
14+
*/
15+
16+
use nystudio107\crafttwigsandbox\twig\WhitelistSecurityPolicy;
17+
18+
return [
19+
'class' => WhitelistSecurityPolicy::class,
20+
'twigTags' => [
21+
'for',
22+
'if',
23+
'set',
24+
],
25+
'twigFilters' => [
26+
'capitalize',
27+
'date',
28+
'escape',
29+
'first',
30+
'join',
31+
'keys',
32+
'last',
33+
'length',
34+
'lower',
35+
'markdown',
36+
'nl2br',
37+
'number_format',
38+
'raw',
39+
'replace',
40+
'sort',
41+
'split',
42+
'striptags',
43+
'title',
44+
'trim',
45+
'upper',
46+
'camel',
47+
'contains',
48+
'currency',
49+
'date',
50+
'datetime',
51+
'id',
52+
'index',
53+
'indexOf',
54+
'kebab',
55+
'lcfirst',
56+
'length',
57+
'markdown',
58+
'md',
59+
'merge',
60+
'money',
61+
'pascal',
62+
'percentage',
63+
'purify',
64+
'snake',
65+
'time',
66+
'timestamp',
67+
'translate',
68+
't',
69+
'ucfirst',
70+
'ucwords',
71+
],
72+
'twigFunctions' => [
73+
'date',
74+
'max',
75+
'min',
76+
'random',
77+
'range',
78+
'collect',
79+
],
80+
'twigMethods' => [
81+
],
82+
'twigProperties' => [
83+
],
84+
];

src/helpers/SecurityPolicy.php

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
namespace nystudio107\crafttwigsandbox\helpers;
4+
5+
use Craft;
6+
use craft\helpers\ArrayHelper;
7+
use craft\helpers\StringHelper;
8+
use nystudio107\crafttwigsandbox\twig\BaseSecurityPolicy;
9+
use nystudio107\seomatic\Seomatic;
10+
use function is_array;
11+
12+
class SecurityPolicy
13+
{
14+
// Static Methods
15+
// =========================================================================
16+
17+
public static function createFromFile(string $filePath, ?string $alias = null): BaseSecurityPolicy
18+
{
19+
$config = self::getConfigFromFile($filePath, $alias);
20+
21+
return Craft::createObject($config);
22+
}
23+
24+
/**
25+
* Loads a config file from, trying @craft/config first, then falling back on
26+
* the provided $alias, if any
27+
*
28+
* @param string $filePath
29+
* @param string|null $alias
30+
*
31+
* @return array
32+
*/
33+
public static function getConfigFromFile(string $filePath, ?string $alias = null): array
34+
{
35+
// Try craft/config first
36+
$path = self::getConfigFilePath('@config', $filePath);
37+
if (!file_exists($path)) {
38+
if (!$alias) {
39+
return [];
40+
}
41+
// Now the additional alias config
42+
$path = self::getConfigFilePath($alias, $filePath);
43+
if (!file_exists($path)) {
44+
return [];
45+
}
46+
}
47+
48+
if (!is_array($config = @include $path)) {
49+
return [];
50+
}
51+
52+
// If it's not a multi-environment config, return the whole thing
53+
if (!array_key_exists('*', $config)) {
54+
return $config;
55+
}
56+
57+
$mergedConfig = [];
58+
/** @var array $config */
59+
foreach ($config as $env => $envConfig) {
60+
if ($env === '*' || StringHelper::contains(Seomatic::$environment, $env)) {
61+
$mergedConfig = ArrayHelper::merge($mergedConfig, $envConfig);
62+
}
63+
}
64+
65+
return $mergedConfig;
66+
}
67+
68+
// Private Methods
69+
// =========================================================================
70+
71+
/**
72+
* Return a path from an alias and a partial path
73+
*
74+
* @param string $alias
75+
* @param string $filePath
76+
*
77+
* @return string
78+
*/
79+
private static function getConfigFilePath(string $alias, string $filePath): string
80+
{
81+
$path = DIRECTORY_SEPARATOR . ltrim($filePath, DIRECTORY_SEPARATOR);
82+
$path = Craft::getAlias($alias)
83+
. DIRECTORY_SEPARATOR
84+
. str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path)
85+
. '.php';
86+
87+
return $path;
88+
}
89+
}

0 commit comments

Comments
 (0)