bug #23638 [FrameworkBundle][Workflow] better errors when security deps are missing (xabbuh)
This PR was merged into the 3.3 branch.
Discussion
----------
[FrameworkBundle][Workflow] better errors when security deps are missing
| Q | A
| ------------- | ---
| Branch? | 3.3
| Bug fix? | yes
| New feature? | no
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | https://github.com/symfony/symfony/pull/23499#discussion_r128752026
| License | MIT
| Doc PR |
All the referenced services must either be explicitly configured or the SecurityBundle must be installed with some security related config being enabled. Otherwise, you will end up with a not so useful error message.
Commits
-------
56ee4aa
better errors when security deps are missing
This commit is contained in:
commit
f693fcd2ea
@ -0,0 +1,50 @@
|
|||||||
|
<?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\FrameworkBundle\DependencyInjection\Compiler;
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Exception\LogicException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
|
||||||
|
*/
|
||||||
|
class WorkflowGuardListenerPass implements CompilerPassInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function process(ContainerBuilder $container)
|
||||||
|
{
|
||||||
|
if (!$container->hasParameter('workflow.has_guard_listeners')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$container->getParameterBag()->remove('workflow.has_guard_listeners');
|
||||||
|
|
||||||
|
if (!$container->has('security.token_storage')) {
|
||||||
|
throw new LogicException('The "security.token_storage" service is needed to be able to use the workflow guard listener.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$container->has('security.authorization_checker')) {
|
||||||
|
throw new LogicException('The "security.authorization_checker" service is needed to be able to use the workflow guard listener.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$container->has('security.authentication.trust_resolver')) {
|
||||||
|
throw new LogicException('The "security.authentication.trust_resolver" service is needed to be able to use the workflow guard listener.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$container->has('security.role_hierarchy')) {
|
||||||
|
throw new LogicException('The "security.role_hierarchy" service is needed to be able to use the workflow guard listener.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -46,6 +46,7 @@ use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
|
|||||||
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
|
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
|
||||||
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
|
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
|
||||||
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
|
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Symfony\Component\Serializer\Encoder\CsvEncoder;
|
use Symfony\Component\Serializer\Encoder\CsvEncoder;
|
||||||
use Symfony\Component\Serializer\Encoder\DecoderInterface;
|
use Symfony\Component\Serializer\Encoder\DecoderInterface;
|
||||||
use Symfony\Component\Serializer\Encoder\EncoderInterface;
|
use Symfony\Component\Serializer\Encoder\EncoderInterface;
|
||||||
@ -597,6 +598,10 @@ class FrameworkExtension extends Extension
|
|||||||
throw new LogicException('Cannot guard workflows as the ExpressionLanguage component is not installed.');
|
throw new LogicException('Cannot guard workflows as the ExpressionLanguage component is not installed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!class_exists(Security::class)) {
|
||||||
|
throw new LogicException('Cannot guard workflows as the Security component is not installed.');
|
||||||
|
}
|
||||||
|
|
||||||
$eventName = sprintf('workflow.%s.guard.%s', $name, $transitionName);
|
$eventName = sprintf('workflow.%s.guard.%s', $name, $transitionName);
|
||||||
$guard->addTag('kernel.event_listener', array('event' => $eventName, 'method' => 'onTransition'));
|
$guard->addTag('kernel.event_listener', array('event' => $eventName, 'method' => 'onTransition'));
|
||||||
$configuration[$eventName] = $config['guard'];
|
$configuration[$eventName] = $config['guard'];
|
||||||
@ -612,6 +617,7 @@ class FrameworkExtension extends Extension
|
|||||||
));
|
));
|
||||||
|
|
||||||
$container->setDefinition(sprintf('%s.listener.guard', $workflowId), $guard);
|
$container->setDefinition(sprintf('%s.listener.guard', $workflowId), $guard);
|
||||||
|
$container->setParameter('workflow.has_guard_listeners', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ContainerBuilder
|
|||||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationExtractorPass;
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationExtractorPass;
|
||||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationDumperPass;
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationDumperPass;
|
||||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass;
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\WorkflowGuardListenerPass;
|
||||||
use Symfony\Component\Config\DependencyInjection\ConfigCachePass;
|
use Symfony\Component\Config\DependencyInjection\ConfigCachePass;
|
||||||
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
|
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
|
||||||
use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass;
|
use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass;
|
||||||
@ -109,6 +110,7 @@ class FrameworkBundle extends Bundle
|
|||||||
$this->addCompilerPassIfExists($container, ValidateWorkflowsPass::class);
|
$this->addCompilerPassIfExists($container, ValidateWorkflowsPass::class);
|
||||||
$container->addCompilerPass(new CachePoolClearerPass(), PassConfig::TYPE_AFTER_REMOVING);
|
$container->addCompilerPass(new CachePoolClearerPass(), PassConfig::TYPE_AFTER_REMOVING);
|
||||||
$this->addCompilerPassIfExists($container, FormPass::class);
|
$this->addCompilerPassIfExists($container, FormPass::class);
|
||||||
|
$container->addCompilerPass(new WorkflowGuardListenerPass());
|
||||||
|
|
||||||
if ($container->getParameter('kernel.debug')) {
|
if ($container->getParameter('kernel.debug')) {
|
||||||
$container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
|
$container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
|
||||||
|
@ -0,0 +1,127 @@
|
|||||||
|
<?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\FrameworkBundle\Tests\DependencyInjection\Compiler;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\WorkflowGuardListenerPass;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Exception\LogicException;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||||
|
use Symfony\Component\Security\Core\Role\RoleHierarchy;
|
||||||
|
use Symfony\Component\Workflow\EventListener\GuardListener;
|
||||||
|
|
||||||
|
class WorkflowGuardListenerPassTest extends TestCase
|
||||||
|
{
|
||||||
|
private $container;
|
||||||
|
private $compilerPass;
|
||||||
|
|
||||||
|
protected function setUp()
|
||||||
|
{
|
||||||
|
$this->container = new ContainerBuilder();
|
||||||
|
$this->container->register('foo.listener.guard', GuardListener::class);
|
||||||
|
$this->container->register('bar.listener.guard', GuardListener::class);
|
||||||
|
$this->compilerPass = new WorkflowGuardListenerPass();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testListenersAreNotRemovedIfParameterIsNotSet()
|
||||||
|
{
|
||||||
|
$this->compilerPass->process($this->container);
|
||||||
|
|
||||||
|
$this->assertTrue($this->container->hasDefinition('foo.listener.guard'));
|
||||||
|
$this->assertTrue($this->container->hasDefinition('bar.listener.guard'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testParameterIsRemovedWhenThePassIsProcessed()
|
||||||
|
{
|
||||||
|
$this->container->setParameter('workflow.has_guard_listeners', array('foo.listener.guard', 'bar.listener.guard'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->compilerPass->process($this->container);
|
||||||
|
} catch (LogicException $e) {
|
||||||
|
// Here, we are not interested in the exception handling. This is tested further down.
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertFalse($this->container->hasParameter('workflow.has_guard_listeners'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testListenersAreNotRemovedIfAllDependenciesArePresent()
|
||||||
|
{
|
||||||
|
$this->container->setParameter('workflow.has_guard_listeners', array('foo.listener.guard', 'bar.listener.guard'));
|
||||||
|
$this->container->register('security.token_storage', TokenStorageInterface::class);
|
||||||
|
$this->container->register('security.authorization_checker', AuthorizationCheckerInterface::class);
|
||||||
|
$this->container->register('security.authentication.trust_resolver', AuthenticationTrustResolverInterface::class);
|
||||||
|
$this->container->register('security.role_hierarchy', RoleHierarchy::class);
|
||||||
|
|
||||||
|
$this->compilerPass->process($this->container);
|
||||||
|
|
||||||
|
$this->assertTrue($this->container->hasDefinition('foo.listener.guard'));
|
||||||
|
$this->assertTrue($this->container->hasDefinition('bar.listener.guard'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\DependencyInjection\Exception\LogicException
|
||||||
|
* @expectedExceptionMessage The "security.token_storage" service is needed to be able to use the workflow guard listener.
|
||||||
|
*/
|
||||||
|
public function testListenersAreRemovedIfTheTokenStorageServiceIsNotPresent()
|
||||||
|
{
|
||||||
|
$this->container->setParameter('workflow.has_guard_listeners', array('foo.listener.guard', 'bar.listener.guard'));
|
||||||
|
$this->container->register('security.authorization_checker', AuthorizationCheckerInterface::class);
|
||||||
|
$this->container->register('security.authentication.trust_resolver', AuthenticationTrustResolverInterface::class);
|
||||||
|
$this->container->register('security.role_hierarchy', RoleHierarchy::class);
|
||||||
|
|
||||||
|
$this->compilerPass->process($this->container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\DependencyInjection\Exception\LogicException
|
||||||
|
* @expectedExceptionMessage The "security.authorization_checker" service is needed to be able to use the workflow guard listener.
|
||||||
|
*/
|
||||||
|
public function testListenersAreRemovedIfTheAuthorizationCheckerServiceIsNotPresent()
|
||||||
|
{
|
||||||
|
$this->container->setParameter('workflow.has_guard_listeners', array('foo.listener.guard', 'bar.listener.guard'));
|
||||||
|
$this->container->register('security.token_storage', TokenStorageInterface::class);
|
||||||
|
$this->container->register('security.authentication.trust_resolver', AuthenticationTrustResolverInterface::class);
|
||||||
|
$this->container->register('security.role_hierarchy', RoleHierarchy::class);
|
||||||
|
|
||||||
|
$this->compilerPass->process($this->container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\DependencyInjection\Exception\LogicException
|
||||||
|
* @expectedExceptionMessage The "security.authentication.trust_resolver" service is needed to be able to use the workflow guard listener.
|
||||||
|
*/
|
||||||
|
public function testListenersAreRemovedIfTheAuthenticationTrustResolverServiceIsNotPresent()
|
||||||
|
{
|
||||||
|
$this->container->setParameter('workflow.has_guard_listeners', array('foo.listener.guard', 'bar.listener.guard'));
|
||||||
|
$this->container->register('security.token_storage', TokenStorageInterface::class);
|
||||||
|
$this->container->register('security.authorization_checker', AuthorizationCheckerInterface::class);
|
||||||
|
$this->container->register('security.role_hierarchy', RoleHierarchy::class);
|
||||||
|
|
||||||
|
$this->compilerPass->process($this->container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\DependencyInjection\Exception\LogicException
|
||||||
|
* @expectedExceptionMessage The "security.role_hierarchy" service is needed to be able to use the workflow guard listener.
|
||||||
|
*/
|
||||||
|
public function testListenersAreRemovedIfTheRoleHierarchyServiceIsNotPresent()
|
||||||
|
{
|
||||||
|
$this->container->setParameter('workflow.has_guard_listeners', array('foo.listener.guard', 'bar.listener.guard'));
|
||||||
|
$this->container->register('security.token_storage', TokenStorageInterface::class);
|
||||||
|
$this->container->register('security.authorization_checker', AuthorizationCheckerInterface::class);
|
||||||
|
$this->container->register('security.authentication.trust_resolver', AuthenticationTrustResolverInterface::class);
|
||||||
|
|
||||||
|
$this->compilerPass->process($this->container);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user