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:
Grégoire Pineau 2017-08-04 10:02:36 +02:00
commit f693fcd2ea
4 changed files with 185 additions and 0 deletions

View File

@ -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.');
}
}
}

View File

@ -46,6 +46,7 @@ use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Encoder\CsvEncoder;
use Symfony\Component\Serializer\Encoder\DecoderInterface;
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.');
}
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);
$guard->addTag('kernel.event_listener', array('event' => $eventName, 'method' => 'onTransition'));
$configuration[$eventName] = $config['guard'];
@ -612,6 +617,7 @@ class FrameworkExtension extends Extension
));
$container->setDefinition(sprintf('%s.listener.guard', $workflowId), $guard);
$container->setParameter('workflow.has_guard_listeners', true);
}
}
}

View File

@ -28,6 +28,7 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ContainerBuilder
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationExtractorPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationDumperPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\WorkflowGuardListenerPass;
use Symfony\Component\Config\DependencyInjection\ConfigCachePass;
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass;
@ -109,6 +110,7 @@ class FrameworkBundle extends Bundle
$this->addCompilerPassIfExists($container, ValidateWorkflowsPass::class);
$container->addCompilerPass(new CachePoolClearerPass(), PassConfig::TYPE_AFTER_REMOVING);
$this->addCompilerPassIfExists($container, FormPass::class);
$container->addCompilerPass(new WorkflowGuardListenerPass());
if ($container->getParameter('kernel.debug')) {
$container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);

View File

@ -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);
}
}