Skip to content

[FrameworkBundle] Allow default action in configuration #57653

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: 7.4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\Form\Form;
use Symfony\Component\HtmlSanitizer\HtmlSanitizerAction;
use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpFoundation\Cookie;
Expand Down Expand Up @@ -2382,6 +2383,10 @@ private function addHtmlSanitizerSection(ArrayNodeDefinition $rootNode, callable
->fixXmlConfig('with_attribute_sanitizer')
->fixXmlConfig('without_attribute_sanitizer')
->children()
->enumNode('default_action')
->info('Defines how the sanitizer must behave by default.')
->values(array_map(static fn (HtmlSanitizerAction $action): string => $action->value, HtmlSanitizerAction::cases()))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
->values(array_map(static fn (HtmlSanitizerAction $action): string => $action->value, HtmlSanitizerAction::cases()))
->values(array_column(HtmlSanitizerAction::cases(), 'value'))

Copy link
Member

@nicolas-grekas nicolas-grekas Jul 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but this won't work if the component is not installed, isn't it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes good point. Just came here to say #57686 might allow this to be cleaner, but indeed if the enum class is missing what do we do?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok so now that #57686 is merged, I would say this could probably be changed to:

->enumNode('default_action')->enumFqcn(HtmlSanitizerAction::class)->end()

The question of what to do when !class_exists(HtmlSanitizerAction::class).. I would say make the enum node conditional, and only declare it if the class exists, so that if html-sanitizer is outdated and you try to configure a default_action the config will error with an unknown option default_action. For better DX we could also if the enum class is not there define it as a dummy null value but throw with a message about html-sanitizer bieng outdated if any value is passed in.

->end()
->booleanNode('allow_safe_elements')
->info('Allows "safe" elements and attributes.')
->defaultFalse()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
use Symfony\Component\Form\FormTypeGuesserInterface;
use Symfony\Component\Form\FormTypeInterface;
use Symfony\Component\HtmlSanitizer\HtmlSanitizer;
use Symfony\Component\HtmlSanitizer\HtmlSanitizerAction;
use Symfony\Component\HtmlSanitizer\HtmlSanitizerConfig;
use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface;
use Symfony\Component\HttpClient\Messenger\PingWebhookMessageHandler;
Expand Down Expand Up @@ -3006,6 +3007,17 @@ private function registerHtmlSanitizerConfiguration(array $config, ContainerBuil
$def = $container->register($configId, HtmlSanitizerConfig::class);

// Base
if ($sanitizerConfig['default_action'] ?? false) {
if (!class_exists(HtmlSanitizerAction::class)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is dead code because of the way the config is done at the moment: if there is a value, it must be one of the allowed cases, which are derived from the enum

throw new LogicException(\sprintf(
'Default action requires the HtmlSanitizer component to be installed in version >=7.2 (%s currently installed). Try running "composer require symfony/html-sanitizer".',
InstalledVersions::getVersion('symfony/html-sanitizer')
));
}

$def->addMethodCall('defaultAction', [HtmlSanitizerAction::from($sanitizerConfig['default_action'])], true);
}

if ($sanitizerConfig['allow_safe_elements']) {
$def->addMethodCall('allowSafeElements', [], true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,7 @@
<xsd:attribute name="allow-relative-links" type="xsd:boolean" />
<xsd:attribute name="allow-relative-medias" type="xsd:boolean" />
<xsd:attribute name="max-input-length" type="xsd:positiveInteger" />
<xsd:attribute name="default-action" type="xsd:string" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we restrict the possible values to the ones allowed by the enum?

</xsd:complexType>

<xsd:complexType name="element-option">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
'html_sanitizer' => [
'sanitizers' => [
'custom' => [
'default_action' => 'allow',
'allow_safe_elements' => true,
'allow_static_elements' => true,
'allow_elements' => [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

$container->loadFromExtension('framework', [
'annotations' => false,
'http_method_override' => false,
'handle_all_throwables' => true,
'php_errors' => ['log' => true],
'html_sanitizer' => [
'sanitizers' => [
'custom' => [
'allow_safe_elements' => true,
'allow_static_elements' => true,
'allow_elements' => [
'iframe' => 'src',
'custom-tag' => ['data-attr', 'data-attr-1'],
'custom-tag-2' => '*',
],
'block_elements' => ['section'],
'drop_elements' => ['video'],
'allow_attributes' => [
'src' => ['iframe'],
'data-attr' => '*',
],
'drop_attributes' => [
'data-attr' => ['custom-tag'],
'data-attr-1' => [],
'data-attr-2' => '*',
],
'force_attributes' => [
'a' => ['rel' => 'noopener noreferrer'],
'h1' => ['class' => 'bp4-heading'],
],
'force_https_urls' => true,
'allowed_link_schemes' => ['http', 'https', 'mailto'],
'allowed_link_hosts' => ['symfony.com'],
'allow_relative_links' => true,
'allowed_media_schemes' => ['http', 'https', 'data'],
'allowed_media_hosts' => ['symfony.com'],
'allow_relative_medias' => true,
'with_attribute_sanitizers' => [
'App\\Sanitizer\\CustomAttributeSanitizer',
],
'without_attribute_sanitizers' => [
'App\\Sanitizer\\OtherCustomAttributeSanitizer',
],
],
'all.sanitizer' => null,
],
],
]);
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<php-errors log="true" />
<html-sanitizer>
<sanitizer name="custom"
default-action="allow"
allow-safe-elements="true"
allow-static-elements="true"
force-https-urls="true"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">

<config xmlns="http://symfony.com/schema/dic/symfony" http-method-override="false" handle-all-throwables="true">
<annotations enabled="false" />
<php-errors log="true" />
<html-sanitizer>
<sanitizer name="custom"
allow-safe-elements="true"
allow-static-elements="true"
force-https-urls="true"
allow-relative-links="true"
allow-relative-medias="true"
>
<allow-element name="iframe">
<attribute>src</attribute>
</allow-element>
<allow-element name="custom-tag">
<attribute>data-attr</attribute>
<attribute>data-attr-1</attribute>
</allow-element>
<allow-element name="custom-tag-2">
<attribute>*</attribute>
</allow-element>
<block-element>section</block-element>
<drop-element>video</drop-element>
<allow-attribute name="src">
<element>iframe</element>
</allow-attribute>
<allow-attribute name="data-attr">
<element>*</element>
</allow-attribute>
<drop-attribute name="data-attr">
<element>custom-tag</element>
</drop-attribute>
<drop-attribute name="data-attr-1" />
<drop-attribute name="data-attr-2">
<element>*</element>
</drop-attribute>
<force-attribute name="a">
<attribute name="rel">noopener noreferrer</attribute>
</force-attribute>
<force-attribute name="h1">
<attribute name="class">bp4-heading</attribute>
</force-attribute>
<allowed-link-scheme>http</allowed-link-scheme>
<allowed-link-scheme>https</allowed-link-scheme>
<allowed-link-scheme>mailto</allowed-link-scheme>
<allowed-link-host>symfony.com</allowed-link-host>
<allowed-media-scheme>http</allowed-media-scheme>
<allowed-media-scheme>https</allowed-media-scheme>
<allowed-media-scheme>data</allowed-media-scheme>
<allowed-media-host>symfony.com</allowed-media-host>
<with-attribute-sanitizer>App\Sanitizer\CustomAttributeSanitizer</with-attribute-sanitizer>
<without-attribute-sanitizer>App\Sanitizer\OtherCustomAttributeSanitizer</without-attribute-sanitizer>
</sanitizer>

<sanitizer name="all.sanitizer"/>
</html-sanitizer>
</config>
</container>
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ framework:
html_sanitizer:
sanitizers:
custom:
default_action: 'allow'
allow_safe_elements: true
allow_static_elements: true
allow_elements:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
framework:
annotations: false
http_method_override: false
handle_all_throwables: true
php_errors:
log: true
html_sanitizer:
sanitizers:
custom:
allow_safe_elements: true
allow_static_elements: true
allow_elements:
iframe: 'src'
custom-tag: ['data-attr', 'data-attr-1']
custom-tag-2: '*'
block_elements:
- section
drop_elements:
- video
allow_attributes:
src: ['iframe']
data-attr: '*'
drop_attributes:
data-attr: [custom-tag]
data-attr-1: []
data-attr-2: '*'
force_attributes:
a:
rel: noopener noreferrer
h1:
class: bp4-heading
force_https_urls: true
allowed_link_schemes: ['http', 'https', 'mailto']
allowed_link_hosts: ['symfony.com']
allow_relative_links: true
allowed_media_schemes: ['http', 'https', 'data']
allowed_media_hosts: ['symfony.com']
allow_relative_medias: true
with_attribute_sanitizers:
- App\Sanitizer\CustomAttributeSanitizer
without_attribute_sanitizers:
- App\Sanitizer\OtherCustomAttributeSanitizer

all.sanitizer: null
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
use Symfony\Component\Finder\Finder;
use Symfony\Component\Form\Form;
use Symfony\Component\HtmlSanitizer\HtmlSanitizer;
use Symfony\Component\HtmlSanitizer\HtmlSanitizerAction;
use Symfony\Component\HtmlSanitizer\HtmlSanitizerConfig;
use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface;
use Symfony\Component\HttpClient\MockHttpClient;
Expand Down Expand Up @@ -2219,8 +2220,74 @@ public function testLocaleSwitcherServiceRegistered()
$this->assertNotContains('translation.locale_switcher', $localeAwareServices);
}

public function testHtmlSanitizerBefore72()
{
if (class_exists(HtmlSanitizerAction::class)) {
$this->markTestSkipped('HtmlSanitizer version is <7.2');
}

$container = $this->createContainerFromFile('html_sanitizer_without_default_action');

// html_sanitizer service
$this->assertSame(HtmlSanitizer::class, $container->getDefinition('html_sanitizer.sanitizer.custom')->getClass());
$this->assertCount(1, $args = $container->getDefinition('html_sanitizer.sanitizer.custom')->getArguments());
$this->assertSame('html_sanitizer.config.custom', (string) $args[0]);

// config
$this->assertTrue($container->hasDefinition('html_sanitizer.config.custom'), '->registerHtmlSanitizerConfiguration() loads custom sanitizer');
$this->assertSame(HtmlSanitizerConfig::class, $container->getDefinition('html_sanitizer.config.custom')->getClass());
$this->assertCount(23, $calls = $container->getDefinition('html_sanitizer.config.custom')->getMethodCalls());
$this->assertSame(
[
['allowSafeElements', [], true],
['allowStaticElements', [], true],
['allowElement', ['iframe', 'src'], true],
['allowElement', ['custom-tag', ['data-attr', 'data-attr-1']], true],
['allowElement', ['custom-tag-2', '*'], true],
['blockElement', ['section'], true],
['dropElement', ['video'], true],
['allowAttribute', ['src', $this instanceof XmlFrameworkExtensionTest ? 'iframe' : ['iframe']], true],
['allowAttribute', ['data-attr', '*'], true],
['dropAttribute', ['data-attr', $this instanceof XmlFrameworkExtensionTest ? 'custom-tag' : ['custom-tag']], true],
['dropAttribute', ['data-attr-1', []], true],
['dropAttribute', ['data-attr-2', '*'], true],
['forceAttribute', ['a', 'rel', 'noopener noreferrer'], true],
['forceAttribute', ['h1', 'class', 'bp4-heading'], true],
['forceHttpsUrls', [true], true],
['allowLinkSchemes', [['http', 'https', 'mailto']], true],
['allowLinkHosts', [['symfony.com']], true],
['allowRelativeLinks', [true], true],
['allowMediaSchemes', [['http', 'https', 'data']], true],
['allowMediaHosts', [['symfony.com']], true],
['allowRelativeMedias', [true], true],
['withAttributeSanitizer', ['@App\\Sanitizer\\CustomAttributeSanitizer'], true],
['withoutAttributeSanitizer', ['@App\\Sanitizer\\OtherCustomAttributeSanitizer'], true],
],

// Convert references to their names for easier assertion
array_map(
static function ($call) {
foreach ($call[1] as $k => $arg) {
$call[1][$k] = $arg instanceof Reference ? '@'.$arg : $arg;
}

return $call;
},
$calls
)
);

// Named alias
$this->assertSame('html_sanitizer.sanitizer.all.sanitizer', (string) $container->getAlias(HtmlSanitizerInterface::class.' $allSanitizer'));
$this->assertFalse($container->hasAlias(HtmlSanitizerInterface::class.' $default'));
}

public function testHtmlSanitizer()
{
if (!class_exists(HtmlSanitizerAction::class)) {
$this->markTestSkipped('HtmlSanitizer version must be >=7.2');
}

$container = $this->createContainerFromFile('html_sanitizer');

// html_sanitizer service
Expand All @@ -2231,9 +2298,10 @@ public function testHtmlSanitizer()
// config
$this->assertTrue($container->hasDefinition('html_sanitizer.config.custom'), '->registerHtmlSanitizerConfiguration() loads custom sanitizer');
$this->assertSame(HtmlSanitizerConfig::class, $container->getDefinition('html_sanitizer.config.custom')->getClass());
$this->assertCount(23, $calls = $container->getDefinition('html_sanitizer.config.custom')->getMethodCalls());
$this->assertCount(24, $calls = $container->getDefinition('html_sanitizer.config.custom')->getMethodCalls());
$this->assertSame(
[
['defaultAction', [HtmlSanitizerAction::Allow], true],
['allowSafeElements', [], true],
['allowStaticElements', [], true],
['allowElement', ['iframe', 'src'], true],
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy