Merge branch '4.4'
* 4.4: sync phpunit script with master [HttpFoundation] allow additinal characters in not raw cookies [Console] Deprecate abbreviating hidden command names using Application->find() Do not include hidden commands in suggested alternatives [Messenger] Improve error message when routing to an invalid transport (closes #31613) [DependencyInjection] Fix wrong exception when service is synthetic [Security] add "anonymous: lazy" mode to firewalls
This commit is contained in:
commit
10be999069
@ -6,6 +6,11 @@ Cache
|
|||||||
|
|
||||||
* Added argument `$prefix` to `AdapterInterface::clear()`
|
* Added argument `$prefix` to `AdapterInterface::clear()`
|
||||||
|
|
||||||
|
Console
|
||||||
|
-------
|
||||||
|
|
||||||
|
* Deprecated finding hidden commands using an abbreviation, use the full name instead
|
||||||
|
|
||||||
Debug
|
Debug
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ Config
|
|||||||
Console
|
Console
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
* Removed support for finding hidden commands using an abbreviation, use the full name instead
|
||||||
* Removed the `setCrossingChar()` method in favor of the `setDefaultCrossingChar()` method in `TableStyle`.
|
* Removed the `setCrossingChar()` method in favor of the `setDefaultCrossingChar()` method in `TableStyle`.
|
||||||
* Removed the `setHorizontalBorderChar()` method in favor of the `setDefaultCrossingChars()` method in `TableStyle`.
|
* Removed the `setHorizontalBorderChar()` method in favor of the `setDefaultCrossingChars()` method in `TableStyle`.
|
||||||
* Removed the `getHorizontalBorderChar()` method in favor of the `getBorderChars()` method in `TableStyle`.
|
* Removed the `getHorizontalBorderChar()` method in favor of the `getBorderChars()` method in `TableStyle`.
|
||||||
|
3
phpunit
3
phpunit
@ -9,6 +9,9 @@ if (!file_exists(__DIR__.'/vendor/symfony/phpunit-bridge/bin/simple-phpunit')) {
|
|||||||
}
|
}
|
||||||
if (!getenv('SYMFONY_PHPUNIT_VERSION')) {
|
if (!getenv('SYMFONY_PHPUNIT_VERSION')) {
|
||||||
if (\PHP_VERSION_ID >= 70200) {
|
if (\PHP_VERSION_ID >= 70200) {
|
||||||
|
if (false === getenv('SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT') && false !== strpos(@file_get_contents(__DIR__.'/src/Symfony/Component/HttpKernel/Kernel.php'), 'const MAJOR_VERSION = 3;')) {
|
||||||
|
putenv('SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1');
|
||||||
|
}
|
||||||
putenv('SYMFONY_PHPUNIT_VERSION=8.3');
|
putenv('SYMFONY_PHPUNIT_VERSION=8.3');
|
||||||
} elseif (\PHP_VERSION_ID >= 70000) {
|
} elseif (\PHP_VERSION_ID >= 70000) {
|
||||||
putenv('SYMFONY_PHPUNIT_VERSION=6.5');
|
putenv('SYMFONY_PHPUNIT_VERSION=6.5');
|
||||||
|
@ -1609,6 +1609,16 @@ class FrameworkExtension extends Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$senderReferences = [];
|
||||||
|
// alias => service_id
|
||||||
|
foreach ($senderAliases as $alias => $serviceId) {
|
||||||
|
$senderReferences[$alias] = new Reference($serviceId);
|
||||||
|
}
|
||||||
|
// service_id => service_id
|
||||||
|
foreach ($senderAliases as $serviceId) {
|
||||||
|
$senderReferences[$serviceId] = new Reference($serviceId);
|
||||||
|
}
|
||||||
|
|
||||||
$messageToSendersMapping = [];
|
$messageToSendersMapping = [];
|
||||||
foreach ($config['routing'] as $message => $messageConfiguration) {
|
foreach ($config['routing'] as $message => $messageConfiguration) {
|
||||||
if ('*' !== $message && !class_exists($message) && !interface_exists($message, false)) {
|
if ('*' !== $message && !class_exists($message) && !interface_exists($message, false)) {
|
||||||
@ -1617,19 +1627,14 @@ class FrameworkExtension extends Extension
|
|||||||
|
|
||||||
// make sure senderAliases contains all senders
|
// make sure senderAliases contains all senders
|
||||||
foreach ($messageConfiguration['senders'] as $sender) {
|
foreach ($messageConfiguration['senders'] as $sender) {
|
||||||
if (!isset($senderAliases[$sender])) {
|
if (!isset($senderReferences[$sender])) {
|
||||||
$senderAliases[$sender] = $sender;
|
throw new LogicException(sprintf('Invalid Messenger routing configuration: the "%s" class is being routed to a sender called "%s". This is not a valid transport or service id.', $message, $sender));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$messageToSendersMapping[$message] = $messageConfiguration['senders'];
|
$messageToSendersMapping[$message] = $messageConfiguration['senders'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$senderReferences = [];
|
|
||||||
foreach ($senderAliases as $alias => $serviceId) {
|
|
||||||
$senderReferences[$alias] = new Reference($serviceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
$container->getDefinition('messenger.senders_locator')
|
$container->getDefinition('messenger.senders_locator')
|
||||||
->replaceArgument(0, $messageToSendersMapping)
|
->replaceArgument(0, $messageToSendersMapping)
|
||||||
->replaceArgument(1, ServiceLocatorTagPass::register($container, $senderReferences))
|
->replaceArgument(1, ServiceLocatorTagPass::register($container, $senderReferences))
|
||||||
|
@ -9,5 +9,10 @@ $container->loadFromExtension('framework', [
|
|||||||
FooMessage::class => ['sender.bar', 'sender.biz'],
|
FooMessage::class => ['sender.bar', 'sender.biz'],
|
||||||
BarMessage::class => 'sender.foo',
|
BarMessage::class => 'sender.foo',
|
||||||
],
|
],
|
||||||
|
'transports' => [
|
||||||
|
'sender.biz' => 'null://',
|
||||||
|
'sender.bar' => 'null://',
|
||||||
|
'sender.foo' => 'null://',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
@ -7,7 +7,7 @@ $container->loadFromExtension('framework', [
|
|||||||
'default_serializer' => 'messenger.transport.symfony_serializer',
|
'default_serializer' => 'messenger.transport.symfony_serializer',
|
||||||
],
|
],
|
||||||
'routing' => [
|
'routing' => [
|
||||||
'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage' => ['amqp', 'audit'],
|
'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage' => ['amqp', 'messenger.transport.audit'],
|
||||||
'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\SecondMessage' => [
|
'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\SecondMessage' => [
|
||||||
'senders' => ['amqp', 'audit'],
|
'senders' => ['amqp', 'audit'],
|
||||||
],
|
],
|
||||||
@ -15,6 +15,7 @@ $container->loadFromExtension('framework', [
|
|||||||
],
|
],
|
||||||
'transports' => [
|
'transports' => [
|
||||||
'amqp' => 'amqp://localhost/%2f/messages',
|
'amqp' => 'amqp://localhost/%2f/messages',
|
||||||
|
'audit' => 'null://',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$container->loadFromExtension('framework', [
|
||||||
|
'serializer' => true,
|
||||||
|
'messenger' => [
|
||||||
|
'serializer' => [
|
||||||
|
'default_serializer' => 'messenger.transport.symfony_serializer',
|
||||||
|
],
|
||||||
|
'routing' => [
|
||||||
|
'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage' => 'invalid',
|
||||||
|
],
|
||||||
|
'transports' => [
|
||||||
|
'amqp' => 'amqp://localhost/%2f/messages',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
@ -14,6 +14,9 @@
|
|||||||
<framework:routing message-class="Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\BarMessage">
|
<framework:routing message-class="Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\BarMessage">
|
||||||
<framework:sender service="sender.foo" />
|
<framework:sender service="sender.foo" />
|
||||||
</framework:routing>
|
</framework:routing>
|
||||||
|
<framework:transport name="sender.bar" dsn="null://" />
|
||||||
|
<framework:transport name="sender.biz" dsn="null://" />
|
||||||
|
<framework:transport name="sender.foo" dsn="null://" />
|
||||||
</framework:messenger>
|
</framework:messenger>
|
||||||
</framework:config>
|
</framework:config>
|
||||||
</container>
|
</container>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<framework:serializer default-serializer="messenger.transport.symfony_serializer" />
|
<framework:serializer default-serializer="messenger.transport.symfony_serializer" />
|
||||||
<framework:routing message-class="Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage">
|
<framework:routing message-class="Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage">
|
||||||
<framework:sender service="amqp" />
|
<framework:sender service="amqp" />
|
||||||
<framework:sender service="audit" />
|
<framework:sender service="messenger.transport.audit" />
|
||||||
</framework:routing>
|
</framework:routing>
|
||||||
<framework:routing message-class="Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\SecondMessage">
|
<framework:routing message-class="Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\SecondMessage">
|
||||||
<framework:sender service="amqp" />
|
<framework:sender service="amqp" />
|
||||||
@ -21,6 +21,7 @@
|
|||||||
<framework:sender service="amqp" />
|
<framework:sender service="amqp" />
|
||||||
</framework:routing>
|
</framework:routing>
|
||||||
<framework:transport name="amqp" dsn="amqp://localhost/%2f/messages" />
|
<framework:transport name="amqp" dsn="amqp://localhost/%2f/messages" />
|
||||||
|
<framework:transport name="audit" dsn="null://" />
|
||||||
</framework:messenger>
|
</framework:messenger>
|
||||||
</framework:config>
|
</framework:config>
|
||||||
</container>
|
</container>
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<container xmlns="http://symfony.com/schema/dic/services"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:framework="http://symfony.com/schema/dic/symfony"
|
||||||
|
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">
|
||||||
|
|
||||||
|
<framework:config>
|
||||||
|
<framework:serializer enabled="true" />
|
||||||
|
<framework:messenger>
|
||||||
|
<framework:serializer default-serializer="messenger.transport.symfony_serializer" />
|
||||||
|
<framework:routing message-class="Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage">
|
||||||
|
<framework:sender service="invalid" />
|
||||||
|
</framework:routing>
|
||||||
|
<framework:routing message-class="Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\SecondMessage">
|
||||||
|
<framework:sender service="amqp" />
|
||||||
|
<framework:sender service="audit" />
|
||||||
|
</framework:routing>
|
||||||
|
<framework:transport name="amqp" dsn="amqp://localhost/%2f/messages" />
|
||||||
|
</framework:messenger>
|
||||||
|
</framework:config>
|
||||||
|
</container>
|
@ -3,3 +3,7 @@ framework:
|
|||||||
routing:
|
routing:
|
||||||
'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\FooMessage': ['sender.bar', 'sender.biz']
|
'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\FooMessage': ['sender.bar', 'sender.biz']
|
||||||
'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\BarMessage': 'sender.foo'
|
'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\BarMessage': 'sender.foo'
|
||||||
|
transports:
|
||||||
|
sender.biz: 'null://'
|
||||||
|
sender.bar: 'null://'
|
||||||
|
sender.foo: 'null://'
|
||||||
|
@ -4,9 +4,10 @@ framework:
|
|||||||
serializer:
|
serializer:
|
||||||
default_serializer: messenger.transport.symfony_serializer
|
default_serializer: messenger.transport.symfony_serializer
|
||||||
routing:
|
routing:
|
||||||
'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage': [amqp, audit]
|
'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage': [amqp, messenger.transport.audit]
|
||||||
'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\SecondMessage':
|
'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\SecondMessage':
|
||||||
senders: [amqp, audit]
|
senders: [amqp, audit]
|
||||||
'*': amqp
|
'*': amqp
|
||||||
transports:
|
transports:
|
||||||
amqp: 'amqp://localhost/%2f/messages'
|
amqp: 'amqp://localhost/%2f/messages'
|
||||||
|
audit: 'null://'
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
framework:
|
||||||
|
serializer: true
|
||||||
|
messenger:
|
||||||
|
serializer:
|
||||||
|
default_serializer: messenger.transport.symfony_serializer
|
||||||
|
routing:
|
||||||
|
'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage': invalid
|
||||||
|
transports:
|
||||||
|
amqp: 'amqp://localhost/%2f/messages'
|
@ -570,8 +570,8 @@ abstract class FrameworkExtensionTest extends TestCase
|
|||||||
$container = $this->createContainerFromFile('messenger');
|
$container = $this->createContainerFromFile('messenger');
|
||||||
$this->assertTrue($container->hasAlias('messenger.default_bus'));
|
$this->assertTrue($container->hasAlias('messenger.default_bus'));
|
||||||
$this->assertTrue($container->getAlias('messenger.default_bus')->isPublic());
|
$this->assertTrue($container->getAlias('messenger.default_bus')->isPublic());
|
||||||
$this->assertFalse($container->hasDefinition('messenger.transport.amqp.factory'));
|
$this->assertTrue($container->hasDefinition('messenger.transport.amqp.factory'));
|
||||||
$this->assertFalse($container->hasDefinition('messenger.transport.redis.factory'));
|
$this->assertTrue($container->hasDefinition('messenger.transport.redis.factory'));
|
||||||
$this->assertTrue($container->hasDefinition('messenger.transport_factory'));
|
$this->assertTrue($container->hasDefinition('messenger.transport_factory'));
|
||||||
$this->assertSame(TransportFactory::class, $container->getDefinition('messenger.transport_factory')->getClass());
|
$this->assertSame(TransportFactory::class, $container->getDefinition('messenger.transport_factory')->getClass());
|
||||||
}
|
}
|
||||||
@ -614,14 +614,11 @@ abstract class FrameworkExtensionTest extends TestCase
|
|||||||
$senderLocatorDefinition = $container->getDefinition('messenger.senders_locator');
|
$senderLocatorDefinition = $container->getDefinition('messenger.senders_locator');
|
||||||
|
|
||||||
$sendersMapping = $senderLocatorDefinition->getArgument(0);
|
$sendersMapping = $senderLocatorDefinition->getArgument(0);
|
||||||
$this->assertEquals([
|
$this->assertEquals(['amqp', 'messenger.transport.audit'], $sendersMapping[DummyMessage::class]);
|
||||||
'amqp',
|
|
||||||
'audit',
|
|
||||||
], $sendersMapping[DummyMessage::class]);
|
|
||||||
$sendersLocator = $container->getDefinition((string) $senderLocatorDefinition->getArgument(1));
|
$sendersLocator = $container->getDefinition((string) $senderLocatorDefinition->getArgument(1));
|
||||||
$this->assertSame(['amqp', 'audit'], array_keys($sendersLocator->getArgument(0)));
|
$this->assertSame(['amqp', 'audit', 'messenger.transport.amqp', 'messenger.transport.audit'], array_keys($sendersLocator->getArgument(0)));
|
||||||
$this->assertEquals(new Reference('messenger.transport.amqp'), $sendersLocator->getArgument(0)['amqp']->getValues()[0]);
|
$this->assertEquals(new Reference('messenger.transport.amqp'), $sendersLocator->getArgument(0)['amqp']->getValues()[0]);
|
||||||
$this->assertEquals(new Reference('audit'), $sendersLocator->getArgument(0)['audit']->getValues()[0]);
|
$this->assertEquals(new Reference('messenger.transport.audit'), $sendersLocator->getArgument(0)['messenger.transport.audit']->getValues()[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMessengerTransportConfiguration()
|
public function testMessengerTransportConfiguration()
|
||||||
@ -676,6 +673,13 @@ abstract class FrameworkExtensionTest extends TestCase
|
|||||||
$this->createContainerFromFile('messenger_middleware_factory_erroneous_format');
|
$this->createContainerFromFile('messenger_middleware_factory_erroneous_format');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testMessengerInvalidTransportRouting()
|
||||||
|
{
|
||||||
|
$this->expectException('LogicException');
|
||||||
|
$this->expectExceptionMessage('Invalid Messenger routing configuration: the "Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage" class is being routed to a sender called "invalid". This is not a valid transport or service id.');
|
||||||
|
$this->createContainerFromFile('messenger_routing_invalid_transport');
|
||||||
|
}
|
||||||
|
|
||||||
public function testTranslator()
|
public function testTranslator()
|
||||||
{
|
{
|
||||||
$container = $this->createContainerFromFile('full');
|
$container = $this->createContainerFromFile('full');
|
||||||
|
@ -24,6 +24,7 @@ CHANGELOG
|
|||||||
4.3.0
|
4.3.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
* Added `anonymous: lazy` mode to firewalls to make them (not) start the session as late as possible
|
||||||
* Added new encoder types: `auto` (recommended), `native` and `sodium`
|
* Added new encoder types: `auto` (recommended), `native` and `sodium`
|
||||||
* The normalization of the cookie names configured in the `logout.delete_cookies`
|
* The normalization of the cookie names configured in the `logout.delete_cookies`
|
||||||
option is deprecated and will be disabled in Symfony 5.0. This affects to cookies
|
option is deprecated and will be disabled in Symfony 5.0. This affects to cookies
|
||||||
|
@ -17,6 +17,7 @@ use Symfony\Component\HttpFoundation\Request;
|
|||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
|
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
|
||||||
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
|
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
|
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
|
||||||
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
|
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
|
||||||
@ -107,7 +108,7 @@ class SecurityDataCollector extends DataCollector implements LateDataCollectorIn
|
|||||||
|
|
||||||
$logoutUrl = null;
|
$logoutUrl = null;
|
||||||
try {
|
try {
|
||||||
if (null !== $this->logoutUrlGenerator) {
|
if (null !== $this->logoutUrlGenerator && !$token instanceof AnonymousToken) {
|
||||||
$logoutUrl = $this->logoutUrlGenerator->getLogoutPath();
|
$logoutUrl = $this->logoutUrlGenerator->getLogoutPath();
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
@ -55,7 +55,12 @@ class AnonymousFactory implements SecurityFactoryInterface
|
|||||||
public function addConfiguration(NodeDefinition $builder)
|
public function addConfiguration(NodeDefinition $builder)
|
||||||
{
|
{
|
||||||
$builder
|
$builder
|
||||||
|
->beforeNormalization()
|
||||||
|
->ifTrue(function ($v) { return 'lazy' === $v; })
|
||||||
|
->then(function ($v) { return ['lazy' => true]; })
|
||||||
|
->end()
|
||||||
->children()
|
->children()
|
||||||
|
->booleanNode('lazy')->defaultFalse()->end()
|
||||||
->scalarNode('secret')->defaultNull()->end()
|
->scalarNode('secret')->defaultNull()->end()
|
||||||
->end()
|
->end()
|
||||||
;
|
;
|
||||||
|
@ -237,7 +237,8 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
|
|||||||
list($matcher, $listeners, $exceptionListener, $logoutListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId);
|
list($matcher, $listeners, $exceptionListener, $logoutListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId);
|
||||||
|
|
||||||
$contextId = 'security.firewall.map.context.'.$name;
|
$contextId = 'security.firewall.map.context.'.$name;
|
||||||
$context = $container->setDefinition($contextId, new ChildDefinition('security.firewall.context'));
|
$context = new ChildDefinition($firewall['stateless'] || empty($firewall['anonymous']['lazy']) ? 'security.firewall.context' : 'security.firewall.lazy_context');
|
||||||
|
$context = $container->setDefinition($contextId, $context);
|
||||||
$context
|
$context
|
||||||
->replaceArgument(0, new IteratorArgument($listeners))
|
->replaceArgument(0, new IteratorArgument($listeners))
|
||||||
->replaceArgument(1, $exceptionListener)
|
->replaceArgument(1, $exceptionListener)
|
||||||
@ -403,7 +404,9 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Access listener
|
// Access listener
|
||||||
|
if ($firewall['stateless'] || empty($firewall['anonymous']['lazy'])) {
|
||||||
$listeners[] = new Reference('security.access_listener');
|
$listeners[] = new Reference('security.access_listener');
|
||||||
|
}
|
||||||
|
|
||||||
// Exception listener
|
// Exception listener
|
||||||
$exceptionListener = new Reference($this->createExceptionListener($container, $firewall, $id, $configuredEntryPoint ?: $defaultEntryPoint, $firewall['stateless']));
|
$exceptionListener = new Reference($this->createExceptionListener($container, $firewall, $id, $configuredEntryPoint ?: $defaultEntryPoint, $firewall['stateless']));
|
||||||
|
@ -146,6 +146,16 @@
|
|||||||
<argument /> <!-- FirewallConfig -->
|
<argument /> <!-- FirewallConfig -->
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="security.firewall.lazy_context" class="Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext" abstract="true">
|
||||||
|
<argument type="collection" />
|
||||||
|
<argument type="service" id="security.exception_listener" />
|
||||||
|
<argument /> <!-- LogoutListener -->
|
||||||
|
<argument /> <!-- FirewallConfig -->
|
||||||
|
<argument type="service" id="security.access_listener" />
|
||||||
|
<argument type="service" id="security.untracked_token_storage" />
|
||||||
|
<argument type="service" id="security.access_map" />
|
||||||
|
</service>
|
||||||
|
|
||||||
<service id="security.firewall.config" class="Symfony\Bundle\SecurityBundle\Security\FirewallConfig" abstract="true">
|
<service id="security.firewall.config" class="Symfony\Bundle\SecurityBundle\Security\FirewallConfig" abstract="true">
|
||||||
<argument /> <!-- name -->
|
<argument /> <!-- name -->
|
||||||
<argument /> <!-- user_checker -->
|
<argument /> <!-- user_checker -->
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Bundle\SecurityBundle\Security;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter;
|
||||||
|
use Symfony\Component\Security\Core\Exception\LazyResponseException;
|
||||||
|
use Symfony\Component\Security\Http\AccessMapInterface;
|
||||||
|
use Symfony\Component\Security\Http\Event\LazyResponseEvent;
|
||||||
|
use Symfony\Component\Security\Http\Firewall\AccessListener;
|
||||||
|
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
|
||||||
|
use Symfony\Component\Security\Http\Firewall\LogoutListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily calls authentication listeners when actually required by the access listener.
|
||||||
|
*
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
class LazyFirewallContext extends FirewallContext
|
||||||
|
{
|
||||||
|
private $accessListener;
|
||||||
|
private $tokenStorage;
|
||||||
|
private $map;
|
||||||
|
|
||||||
|
public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener, ?LogoutListener $logoutListener, ?FirewallConfig $config, AccessListener $accessListener, TokenStorage $tokenStorage, AccessMapInterface $map)
|
||||||
|
{
|
||||||
|
parent::__construct($listeners, $exceptionListener, $logoutListener, $config);
|
||||||
|
|
||||||
|
$this->accessListener = $accessListener;
|
||||||
|
$this->tokenStorage = $tokenStorage;
|
||||||
|
$this->map = $map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getListeners(): iterable
|
||||||
|
{
|
||||||
|
return [$this];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(RequestEvent $event)
|
||||||
|
{
|
||||||
|
$this->tokenStorage->setInitializer(function () use ($event) {
|
||||||
|
$event = new LazyResponseEvent($event);
|
||||||
|
foreach (parent::getListeners() as $listener) {
|
||||||
|
if (\is_callable($listener)) {
|
||||||
|
$listener($event);
|
||||||
|
} else {
|
||||||
|
@trigger_error(sprintf('Calling the "%s::handle()" method from the firewall is deprecated since Symfony 4.3, implement "__invoke()" instead.', \get_class($listener)), E_USER_DEPRECATED);
|
||||||
|
$listener->handle($event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
[$attributes] = $this->map->getPatterns($event->getRequest());
|
||||||
|
|
||||||
|
if ($attributes && [AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY] !== $attributes) {
|
||||||
|
($this->accessListener)($event);
|
||||||
|
}
|
||||||
|
} catch (LazyResponseException $e) {
|
||||||
|
$event->setResponse($e->getResponse());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -59,6 +59,6 @@ class LocalizedController implements ContainerAwareInterface
|
|||||||
|
|
||||||
public function homepageAction()
|
public function homepageAction()
|
||||||
{
|
{
|
||||||
return new Response('<html><body>Homepage</body></html>');
|
return (new Response('<html><body>Homepage</body></html>'))->setPublic();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,6 +129,16 @@ class SecurityRoutingIntegrationTest extends AbstractWebTestCase
|
|||||||
$client->request('GET', '/unprotected_resource');
|
$client->request('GET', '/unprotected_resource');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testPublicHomepage()
|
||||||
|
{
|
||||||
|
$client = $this->createClient(['test_case' => 'StandardFormLogin', 'root_config' => 'config.yml']);
|
||||||
|
$client->request('GET', '/en/');
|
||||||
|
|
||||||
|
$this->assertEquals(200, $client->getResponse()->getStatusCode(), (string) $client->getResponse());
|
||||||
|
$this->assertTrue($client->getResponse()->headers->getCacheControlDirective('public'));
|
||||||
|
$this->assertSame(0, self::$container->get('session')->getUsageIndex());
|
||||||
|
}
|
||||||
|
|
||||||
private function assertAllowed($client, $path)
|
private function assertAllowed($client, $path)
|
||||||
{
|
{
|
||||||
$client->request('GET', $path);
|
$client->request('GET', $path);
|
||||||
|
@ -27,7 +27,7 @@ security:
|
|||||||
check_path: /login_check
|
check_path: /login_check
|
||||||
default_target_path: /profile
|
default_target_path: /profile
|
||||||
logout: ~
|
logout: ~
|
||||||
anonymous: ~
|
anonymous: lazy
|
||||||
|
|
||||||
# This firewall is here just to check its the logout functionality
|
# This firewall is here just to check its the logout functionality
|
||||||
second_area:
|
second_area:
|
||||||
@ -38,6 +38,7 @@ security:
|
|||||||
path: /second/logout
|
path: /second/logout
|
||||||
|
|
||||||
access_control:
|
access_control:
|
||||||
|
- { path: ^/en/$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
- { path: ^/unprotected_resource$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
- { path: ^/unprotected_resource$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
- { path: ^/secure-but-not-covered-by-access-control$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
- { path: ^/secure-but-not-covered-by-access-control$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
- { path: ^/secured-by-one-ip$, ip: 10.10.10.10, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
- { path: ^/secured-by-one-ip$, ip: 10.10.10.10, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
|
@ -536,6 +536,10 @@ class Application implements ResetInterface
|
|||||||
{
|
{
|
||||||
$namespaces = [];
|
$namespaces = [];
|
||||||
foreach ($this->all() as $command) {
|
foreach ($this->all() as $command) {
|
||||||
|
if ($command->isHidden()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
|
$namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
|
||||||
|
|
||||||
foreach ($command->getAliases() as $alias) {
|
foreach ($command->getAliases() as $alias) {
|
||||||
@ -629,6 +633,11 @@ class Application implements ResetInterface
|
|||||||
$message = sprintf('Command "%s" is not defined.', $name);
|
$message = sprintf('Command "%s" is not defined.', $name);
|
||||||
|
|
||||||
if ($alternatives = $this->findAlternatives($name, $allCommands)) {
|
if ($alternatives = $this->findAlternatives($name, $allCommands)) {
|
||||||
|
// remove hidden commands
|
||||||
|
$alternatives = array_filter($alternatives, function ($name) {
|
||||||
|
return !$this->get($name)->isHidden();
|
||||||
|
});
|
||||||
|
|
||||||
if (1 == \count($alternatives)) {
|
if (1 == \count($alternatives)) {
|
||||||
$message .= "\n\nDid you mean this?\n ";
|
$message .= "\n\nDid you mean this?\n ";
|
||||||
} else {
|
} else {
|
||||||
@ -637,7 +646,7 @@ class Application implements ResetInterface
|
|||||||
$message .= implode("\n ", $alternatives);
|
$message .= implode("\n ", $alternatives);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new CommandNotFoundException($message, $alternatives);
|
throw new CommandNotFoundException($message, array_values($alternatives));
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter out aliases for commands which are already on the list
|
// filter out aliases for commands which are already on the list
|
||||||
@ -658,20 +667,36 @@ class Application implements ResetInterface
|
|||||||
foreach ($abbrevs as $abbrev) {
|
foreach ($abbrevs as $abbrev) {
|
||||||
$maxLen = max(Helper::strlen($abbrev), $maxLen);
|
$maxLen = max(Helper::strlen($abbrev), $maxLen);
|
||||||
}
|
}
|
||||||
$abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen) {
|
$abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen, &$commands) {
|
||||||
if (!$commandList[$cmd] instanceof Command) {
|
if (!$commandList[$cmd] instanceof Command) {
|
||||||
return $cmd;
|
$commandList[$cmd] = $this->commandLoader->get($cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($commandList[$cmd]->isHidden()) {
|
||||||
|
unset($commands[array_search($cmd, $commands)]);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription();
|
$abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription();
|
||||||
|
|
||||||
return Helper::strlen($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;
|
return Helper::strlen($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;
|
||||||
}, array_values($commands));
|
}, array_values($commands));
|
||||||
$suggestions = $this->getAbbreviationSuggestions($abbrevs);
|
|
||||||
|
if (\count($commands) > 1) {
|
||||||
|
$suggestions = $this->getAbbreviationSuggestions(array_filter($abbrevs));
|
||||||
|
|
||||||
throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $name, $suggestions), array_values($commands));
|
throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $name, $suggestions), array_values($commands));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $this->get(reset($commands));
|
$command = $this->get(reset($commands));
|
||||||
|
|
||||||
|
if ($command->isHidden()) {
|
||||||
|
@trigger_error(sprintf('Command "%s" is hidden, finding it using an abbreviation is deprecated since Symfony 4.4, use its full name instead.', $command->getName()), E_USER_DEPRECATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $command;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,6 +16,7 @@ CHANGELOG
|
|||||||
4.4.0
|
4.4.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
* deprecated finding hidden commands using an abbreviation, use the full name instead
|
||||||
* added `Question::setTrimmable` default to true to allow the answer to be trimmed
|
* added `Question::setTrimmable` default to true to allow the answer to be trimmed
|
||||||
* added method `preventRedrawFasterThan()` and `forceRedrawSlowerThan()` on `ProgressBar`
|
* added method `preventRedrawFasterThan()` and `forceRedrawSlowerThan()` on `ProgressBar`
|
||||||
* `Application` implements `ResetInterface`
|
* `Application` implements `ResetInterface`
|
||||||
|
@ -75,6 +75,8 @@ class ApplicationTest extends TestCase
|
|||||||
require_once self::$fixturesPath.'/FooWithoutAliasCommand.php';
|
require_once self::$fixturesPath.'/FooWithoutAliasCommand.php';
|
||||||
require_once self::$fixturesPath.'/TestAmbiguousCommandRegistering.php';
|
require_once self::$fixturesPath.'/TestAmbiguousCommandRegistering.php';
|
||||||
require_once self::$fixturesPath.'/TestAmbiguousCommandRegistering2.php';
|
require_once self::$fixturesPath.'/TestAmbiguousCommandRegistering2.php';
|
||||||
|
require_once self::$fixturesPath.'/FooHiddenCommand.php';
|
||||||
|
require_once self::$fixturesPath.'/BarHiddenCommand.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function normalizeLineBreaks($text)
|
protected function normalizeLineBreaks($text)
|
||||||
@ -440,6 +442,16 @@ class ApplicationTest extends TestCase
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testFindWithAmbiguousAbbreviationsFindsCommandIfAlternativesAreHidden()
|
||||||
|
{
|
||||||
|
$application = new Application();
|
||||||
|
|
||||||
|
$application->add(new \FooCommand());
|
||||||
|
$application->add(new \FooHiddenCommand());
|
||||||
|
|
||||||
|
$this->assertInstanceOf('FooCommand', $application->find('foo:'));
|
||||||
|
}
|
||||||
|
|
||||||
public function testFindCommandEqualNamespace()
|
public function testFindCommandEqualNamespace()
|
||||||
{
|
{
|
||||||
$application = new Application();
|
$application = new Application();
|
||||||
@ -664,6 +676,7 @@ class ApplicationTest extends TestCase
|
|||||||
$application->add(new \Foo1Command());
|
$application->add(new \Foo1Command());
|
||||||
$application->add(new \Foo2Command());
|
$application->add(new \Foo2Command());
|
||||||
$application->add(new \Foo3Command());
|
$application->add(new \Foo3Command());
|
||||||
|
$application->add(new \FooHiddenCommand());
|
||||||
|
|
||||||
$expectedAlternatives = [
|
$expectedAlternatives = [
|
||||||
'afoobar',
|
'afoobar',
|
||||||
@ -706,6 +719,49 @@ class ApplicationTest extends TestCase
|
|||||||
$application->find('foo::bar');
|
$application->find('foo::bar');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testFindHiddenWithExactName()
|
||||||
|
{
|
||||||
|
$application = new Application();
|
||||||
|
$application->add(new \FooHiddenCommand());
|
||||||
|
|
||||||
|
$this->assertInstanceOf('FooHiddenCommand', $application->find('foo:hidden'));
|
||||||
|
$this->assertInstanceOf('FooHiddenCommand', $application->find('afoohidden'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group legacy
|
||||||
|
* @expectedDeprecation Command "%s:hidden" is hidden, finding it using an abbreviation is deprecated since Symfony 4.4, use its full name instead.
|
||||||
|
* @dataProvider provideAbbreviationsForHiddenCommands
|
||||||
|
*/
|
||||||
|
public function testFindHiddenWithAbbreviatedName($name)
|
||||||
|
{
|
||||||
|
$application = new Application();
|
||||||
|
|
||||||
|
$application->add(new \FooHiddenCommand());
|
||||||
|
$application->add(new \BarHiddenCommand());
|
||||||
|
|
||||||
|
$application->find($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideAbbreviationsForHiddenCommands()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
['foo:hidde'],
|
||||||
|
['afoohidd'],
|
||||||
|
['bar:hidde'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFindAmbiguousCommandsIfAllAlternativesAreHidden()
|
||||||
|
{
|
||||||
|
$application = new Application();
|
||||||
|
|
||||||
|
$application->add(new \FooCommand());
|
||||||
|
$application->add(new \FooHiddenCommand());
|
||||||
|
|
||||||
|
$this->assertInstanceOf('FooCommand', $application->find('foo:'));
|
||||||
|
}
|
||||||
|
|
||||||
public function testSetCatchExceptions()
|
public function testSetCatchExceptions()
|
||||||
{
|
{
|
||||||
$application = new Application();
|
$application = new Application();
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
class BarHiddenCommand extends Command
|
||||||
|
{
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setName('bar:hidden')
|
||||||
|
->setAliases(['abarhidden'])
|
||||||
|
->setHidden(true)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
class FooHiddenCommand extends Command
|
||||||
|
{
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setName('foo:hidden')
|
||||||
|
->setAliases(['afoohidden'])
|
||||||
|
->setHidden(true)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -110,6 +110,10 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
|
|||||||
*/
|
*/
|
||||||
protected function getConstructor(Definition $definition, bool $required)
|
protected function getConstructor(Definition $definition, bool $required)
|
||||||
{
|
{
|
||||||
|
if ($definition->isSynthetic()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (\is_string($factory = $definition->getFactory())) {
|
if (\is_string($factory = $definition->getFactory())) {
|
||||||
if (!\function_exists($factory)) {
|
if (!\function_exists($factory)) {
|
||||||
throw new RuntimeException(sprintf('Invalid service "%s": function "%s" does not exist.', $this->currentId, $factory));
|
throw new RuntimeException(sprintf('Invalid service "%s": function "%s" does not exist.', $this->currentId, $factory));
|
||||||
|
@ -568,12 +568,10 @@ class AutowirePassTest extends TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @exceptedExceptionMessage Invalid service "Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy": method "setLogger()" does not exist.
|
|
||||||
*/
|
|
||||||
public function testWithNonExistingSetterAndAutowiring()
|
public function testWithNonExistingSetterAndAutowiring()
|
||||||
{
|
{
|
||||||
$this->expectException(RuntimeException::class);
|
$this->expectException(RuntimeException::class);
|
||||||
|
$this->expectExceptionMessage('Invalid service "Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass": method "setLogger()" does not exist.');
|
||||||
$container = new ContainerBuilder();
|
$container = new ContainerBuilder();
|
||||||
|
|
||||||
$definition = $container->register(CaseSensitiveClass::class, CaseSensitiveClass::class)->setAutowired(true);
|
$definition = $container->register(CaseSensitiveClass::class, CaseSensitiveClass::class)->setAutowired(true);
|
||||||
|
@ -15,9 +15,11 @@ use PHPUnit\Framework\TestCase;
|
|||||||
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
|
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
|
||||||
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
||||||
use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredMethodsPass;
|
use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredMethodsPass;
|
||||||
|
use Symfony\Component\DependencyInjection\Compiler\DefinitionErrorExceptionPass;
|
||||||
use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass;
|
use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\Definition;
|
use Symfony\Component\DependencyInjection\Definition;
|
||||||
|
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||||
use Symfony\Component\DependencyInjection\Reference;
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass;
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass;
|
||||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy;
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy;
|
||||||
@ -131,4 +133,25 @@ class ResolveBindingsPassTest extends TestCase
|
|||||||
$pass = new ResolveBindingsPass();
|
$pass = new ResolveBindingsPass();
|
||||||
$pass->process($container);
|
$pass->process($container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSyntheticServiceWithBind()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$argument = new BoundArgument('bar');
|
||||||
|
|
||||||
|
$container->register('foo', 'stdClass')
|
||||||
|
->addArgument(new Reference('synthetic.service'));
|
||||||
|
|
||||||
|
$container->register('synthetic.service')
|
||||||
|
->setSynthetic(true)
|
||||||
|
->setBindings(['$apiKey' => $argument]);
|
||||||
|
|
||||||
|
$container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class)
|
||||||
|
->setBindings(['$apiKey' => $argument]);
|
||||||
|
|
||||||
|
(new ResolveBindingsPass())->process($container);
|
||||||
|
(new DefinitionErrorExceptionPass())->process($container);
|
||||||
|
|
||||||
|
$this->assertSame([1 => 'bar'], $container->getDefinition(NamedArgumentsDummy::class)->getArguments());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,10 @@ namespace Symfony\Component\HttpFoundation;
|
|||||||
*/
|
*/
|
||||||
class Cookie
|
class Cookie
|
||||||
{
|
{
|
||||||
|
const SAMESITE_NONE = 'none';
|
||||||
|
const SAMESITE_LAX = 'lax';
|
||||||
|
const SAMESITE_STRICT = 'strict';
|
||||||
|
|
||||||
protected $name;
|
protected $name;
|
||||||
protected $value;
|
protected $value;
|
||||||
protected $domain;
|
protected $domain;
|
||||||
@ -25,13 +29,14 @@ class Cookie
|
|||||||
protected $path;
|
protected $path;
|
||||||
protected $secure;
|
protected $secure;
|
||||||
protected $httpOnly;
|
protected $httpOnly;
|
||||||
|
|
||||||
private $raw;
|
private $raw;
|
||||||
private $sameSite;
|
private $sameSite;
|
||||||
private $secureDefault = false;
|
private $secureDefault = false;
|
||||||
|
|
||||||
const SAMESITE_NONE = 'none';
|
private static $reservedCharsList = "=,; \t\r\n\v\f";
|
||||||
const SAMESITE_LAX = 'lax';
|
private static $reservedCharsFrom = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"];
|
||||||
const SAMESITE_STRICT = 'strict';
|
private static $reservedCharsTo = ['%3D', '%2C', '%3B', '%20', '%09', '%0D', '%0A', '%0B', '%0C'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates cookie from raw header string.
|
* Creates cookie from raw header string.
|
||||||
@ -86,7 +91,7 @@ class Cookie
|
|||||||
public function __construct(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = 'lax')
|
public function __construct(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = 'lax')
|
||||||
{
|
{
|
||||||
// from PHP source code
|
// from PHP source code
|
||||||
if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
|
if ($raw && false !== strpbrk($name, self::$reservedCharsList)) {
|
||||||
throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
|
throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +139,13 @@ class Cookie
|
|||||||
*/
|
*/
|
||||||
public function __toString()
|
public function __toString()
|
||||||
{
|
{
|
||||||
$str = ($this->isRaw() ? $this->getName() : urlencode($this->getName())).'=';
|
if ($this->isRaw()) {
|
||||||
|
$str = $this->getName();
|
||||||
|
} else {
|
||||||
|
$str = str_replace(self::$reservedCharsFrom, self::$reservedCharsTo, $this->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
$str .= '=';
|
||||||
|
|
||||||
if ('' === (string) $this->getValue()) {
|
if ('' === (string) $this->getValue()) {
|
||||||
$str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0';
|
$str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0';
|
||||||
|
@ -335,7 +335,7 @@ class Response
|
|||||||
|
|
||||||
// cookies
|
// cookies
|
||||||
foreach ($this->headers->getCookies() as $cookie) {
|
foreach ($this->headers->getCookies() as $cookie) {
|
||||||
header('Set-Cookie: '.$cookie->getName().strstr($cookie, '='), false, $this->statusCode);
|
header('Set-Cookie: '.$cookie, false, $this->statusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// status
|
// status
|
||||||
|
@ -24,10 +24,9 @@ use Symfony\Component\HttpFoundation\Cookie;
|
|||||||
*/
|
*/
|
||||||
class CookieTest extends TestCase
|
class CookieTest extends TestCase
|
||||||
{
|
{
|
||||||
public function invalidNames()
|
public function namesWithSpecialCharacters()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[''],
|
|
||||||
[',MyName'],
|
[',MyName'],
|
||||||
[';MyName'],
|
[';MyName'],
|
||||||
[' MyName'],
|
[' MyName'],
|
||||||
@ -40,12 +39,26 @@ class CookieTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider invalidNames
|
* @dataProvider namesWithSpecialCharacters
|
||||||
*/
|
*/
|
||||||
public function testInstantiationThrowsExceptionIfCookieNameContainsInvalidCharacters($name)
|
public function testInstantiationThrowsExceptionIfRawCookieNameContainsSpecialCharacters($name)
|
||||||
{
|
{
|
||||||
$this->expectException('InvalidArgumentException');
|
$this->expectException('InvalidArgumentException');
|
||||||
Cookie::create($name);
|
Cookie::create($name, null, 0, null, null, null, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider namesWithSpecialCharacters
|
||||||
|
*/
|
||||||
|
public function testInstantiationSucceedNonRawCookieNameContainsSpecialCharacters($name)
|
||||||
|
{
|
||||||
|
$this->assertInstanceOf(Cookie::class, Cookie::create($name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInstantiationThrowsExceptionIfCookieNameIsEmpty()
|
||||||
|
{
|
||||||
|
$this->expectException('InvalidArgumentException');
|
||||||
|
Cookie::create('');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testInvalidExpiration()
|
public function testInvalidExpiration()
|
||||||
|
@ -4,7 +4,8 @@ Array
|
|||||||
[0] => Content-Type: text/plain; charset=utf-8
|
[0] => Content-Type: text/plain; charset=utf-8
|
||||||
[1] => Cache-Control: no-cache, private
|
[1] => Cache-Control: no-cache, private
|
||||||
[2] => Date: Sat, 12 Nov 1955 20:04:00 GMT
|
[2] => Date: Sat, 12 Nov 1955 20:04:00 GMT
|
||||||
[3] => Set-Cookie: ?*():@&+$/%#[]=%3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D; path=/
|
[3] => Set-Cookie: %3D%2C%3B%20%09%0D%0A%0B%0C=%3D%2C%3B%20%09%0D%0A%0B%0C; path=/
|
||||||
[4] => Set-Cookie: ?*():@&+$/%#[]=%3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D; path=/
|
[4] => Set-Cookie: ?*():@&+$/%#[]=%3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D; path=/
|
||||||
|
[5] => Set-Cookie: ?*():@&+$/%#[]=%3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D; path=/
|
||||||
)
|
)
|
||||||
shutdown
|
shutdown
|
||||||
|
@ -4,9 +4,12 @@ use Symfony\Component\HttpFoundation\Cookie;
|
|||||||
|
|
||||||
$r = require __DIR__.'/common.inc';
|
$r = require __DIR__.'/common.inc';
|
||||||
|
|
||||||
$str = '?*():@&+$/%#[]';
|
$str1 = "=,; \t\r\n\v\f";
|
||||||
|
$r->headers->setCookie(new Cookie($str1, $str1, 0, '', null, false, false, false, null));
|
||||||
|
|
||||||
$r->headers->setCookie(new Cookie($str, $str, 0, '', null, false, false, false, null));
|
$str2 = '?*():@&+$/%#[]';
|
||||||
|
|
||||||
|
$r->headers->setCookie(new Cookie($str2, $str2, 0, '', null, false, false, false, null));
|
||||||
$r->sendHeaders();
|
$r->sendHeaders();
|
||||||
|
|
||||||
setcookie($str, $str, 0, '/');
|
setcookie($str2, $str2, 0, '/');
|
||||||
|
@ -5,7 +5,7 @@ use Symfony\Component\HttpFoundation\Cookie;
|
|||||||
$r = require __DIR__.'/common.inc';
|
$r = require __DIR__.'/common.inc';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$r->headers->setCookie(Cookie::create('Hello + world', 'hodor'));
|
$r->headers->setCookie(new Cookie('Hello + world', 'hodor', 0, null, null, null, false, true));
|
||||||
} catch (\InvalidArgumentException $e) {
|
} catch (\InvalidArgumentException $e) {
|
||||||
echo $e->getMessage();
|
echo $e->getMessage();
|
||||||
}
|
}
|
||||||
|
@ -25,12 +25,18 @@ use Symfony\Contracts\Service\ResetInterface;
|
|||||||
class TokenStorage implements TokenStorageInterface, ResetInterface
|
class TokenStorage implements TokenStorageInterface, ResetInterface
|
||||||
{
|
{
|
||||||
private $token;
|
private $token;
|
||||||
|
private $initializer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function getToken()
|
public function getToken()
|
||||||
{
|
{
|
||||||
|
if ($initializer = $this->initializer) {
|
||||||
|
$this->initializer = null;
|
||||||
|
$initializer();
|
||||||
|
}
|
||||||
|
|
||||||
return $this->token;
|
return $this->token;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,9 +45,15 @@ class TokenStorage implements TokenStorageInterface, ResetInterface
|
|||||||
*/
|
*/
|
||||||
public function setToken(TokenInterface $token = null)
|
public function setToken(TokenInterface $token = null)
|
||||||
{
|
{
|
||||||
|
$this->initializer = null;
|
||||||
$this->token = $token;
|
$this->token = $token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setInitializer(?callable $initializer): void
|
||||||
|
{
|
||||||
|
$this->initializer = $initializer;
|
||||||
|
}
|
||||||
|
|
||||||
public function reset()
|
public function reset()
|
||||||
{
|
{
|
||||||
$this->setToken(null);
|
$this->setToken(null);
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Security\Core\Exception;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A signaling exception that wraps a lazily computed response.
|
||||||
|
*
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
class LazyResponseException extends \Exception implements ExceptionInterface
|
||||||
|
{
|
||||||
|
private $response;
|
||||||
|
|
||||||
|
public function __construct(Response $response)
|
||||||
|
{
|
||||||
|
$this->response = $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResponse(): Response
|
||||||
|
{
|
||||||
|
return $this->response;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Security\Http\Event;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||||
|
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||||
|
use Symfony\Component\Security\Core\Exception\LazyResponseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a lazily computed response in a signaling exception.
|
||||||
|
*
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
final class LazyResponseEvent extends RequestEvent
|
||||||
|
{
|
||||||
|
private $event;
|
||||||
|
|
||||||
|
public function __construct(parent $event)
|
||||||
|
{
|
||||||
|
$this->event = $event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setResponse(Response $response)
|
||||||
|
{
|
||||||
|
$this->stopPropagation();
|
||||||
|
$this->event->stopPropagation();
|
||||||
|
|
||||||
|
throw new LazyResponseException($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getKernel(): HttpKernelInterface
|
||||||
|
{
|
||||||
|
return $this->event->getKernel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getRequest(): Request
|
||||||
|
{
|
||||||
|
return $this->event->getRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getRequestType(): int
|
||||||
|
{
|
||||||
|
return $this->event->getRequestType();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isMasterRequest(): bool
|
||||||
|
{
|
||||||
|
return $this->event->isMasterRequest();
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,7 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
|||||||
use Symfony\Component\Security\Core\Exception\AccountStatusException;
|
use Symfony\Component\Security\Core\Exception\AccountStatusException;
|
||||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||||
use Symfony\Component\Security\Core\Exception\InsufficientAuthenticationException;
|
use Symfony\Component\Security\Core\Exception\InsufficientAuthenticationException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\LazyResponseException;
|
||||||
use Symfony\Component\Security\Core\Exception\LogoutException;
|
use Symfony\Component\Security\Core\Exception\LogoutException;
|
||||||
use Symfony\Component\Security\Core\Security;
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;
|
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;
|
||||||
@ -103,6 +104,12 @@ class ExceptionListener
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($exception instanceof LazyResponseException) {
|
||||||
|
$event->setResponse($exception->getResponse());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ($exception instanceof LogoutException) {
|
if ($exception instanceof LogoutException) {
|
||||||
$this->handleLogoutException($exception);
|
$this->handleLogoutException($exception);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user