[FrameworkBundle] fixed guard event names for transitions

This commit is contained in:
Grégoire Pineau 2018-11-08 14:02:15 +01:00
parent fb88bfc79a
commit 83dc473dd6
5 changed files with 73 additions and 61 deletions

View File

@ -602,15 +602,44 @@ class FrameworkExtension extends Extension
@trigger_error(sprintf('The "type" option of the "framework.workflows.%s" configuration entry must be defined since Symfony 3.3. The default value will be "state_machine" in Symfony 4.0.', $name), E_USER_DEPRECATED);
}
$type = $workflow['type'];
$workflowId = sprintf('%s.%s', $type, $name);
// Create transitions
$transitions = array();
$guardsConfiguration = array();
// Global transition counter per workflow
$transitionCounter = 0;
foreach ($workflow['transitions'] as $transition) {
if ('workflow' === $type) {
$transitions[] = new Definition(Workflow\Transition::class, array($transition['name'], $transition['from'], $transition['to']));
$transitionDefinition = new Definition(Workflow\Transition::class, array($transition['name'], $transition['from'], $transition['to']));
$transitionDefinition->setPublic(false);
$transitionId = sprintf('%s.transition.%s', $workflowId, $transitionCounter++);
$container->setDefinition($transitionId, $transitionDefinition);
$transitions[] = new Reference($transitionId);
if (isset($transition['guard'])) {
$configuration = new Definition(Workflow\EventListener\GuardExpression::class);
$configuration->addArgument(new Reference($transitionId));
$configuration->addArgument($transition['guard']);
$configuration->setPublic(false);
$eventName = sprintf('workflow.%s.guard.%s', $name, $transition['name']);
$guardsConfiguration[$eventName][] = $configuration;
}
} elseif ('state_machine' === $type) {
foreach ($transition['from'] as $from) {
foreach ($transition['to'] as $to) {
$transitions[] = new Definition(Workflow\Transition::class, array($transition['name'], $from, $to));
$transitionDefinition = new Definition(Workflow\Transition::class, array($transition['name'], $from, $to));
$transitionDefinition->setPublic(false);
$transitionId = sprintf('%s.transition.%s', $workflowId, $transitionCounter++);
$container->setDefinition($transitionId, $transitionDefinition);
$transitions[] = new Reference($transitionId);
if (isset($transition['guard'])) {
$configuration = new Definition(Workflow\EventListener\GuardExpression::class);
$configuration->addArgument(new Reference($transitionId));
$configuration->addArgument($transition['guard']);
$configuration->setPublic(false);
$eventName = sprintf('workflow.%s.guard.%s', $name, $transition['name']);
$guardsConfiguration[$eventName][] = $configuration;
}
}
}
}
@ -641,7 +670,6 @@ class FrameworkExtension extends Extension
}
// Create Workflow
$workflowId = sprintf('%s.%s', $type, $name);
$workflowDefinition = new ChildDefinition(sprintf('%s.abstract', $type));
$workflowDefinition->replaceArgument(0, new Reference(sprintf('%s.definition', $workflowId)));
if (isset($markingStoreDefinition)) {
@ -677,16 +705,7 @@ class FrameworkExtension extends Extension
}
// Add Guard Listener
$guard = new Definition(Workflow\EventListener\GuardListener::class);
$guard->setPrivate(true);
$configuration = array();
foreach ($workflow['transitions'] as $config) {
$transitionName = $config['name'];
if (!isset($config['guard'])) {
continue;
}
if ($guardsConfiguration) {
if (!class_exists(ExpressionLanguage::class)) {
throw new LogicException('Cannot guard workflows as the ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".');
}
@ -695,13 +714,11 @@ class FrameworkExtension extends Extension
throw new LogicException('Cannot guard workflows as the Security component is not installed. Try running "composer require symfony/security".');
}
$eventName = sprintf('workflow.%s.guard.%s', $name, $transitionName);
$guard->addTag('kernel.event_listener', array('event' => $eventName, 'method' => 'onTransition'));
$configuration[$eventName] = $config['guard'];
}
if ($configuration) {
$guard = new Definition(Workflow\EventListener\GuardListener::class);
$guard->setPrivate(true);
$guard->setArguments(array(
$configuration,
$guardsConfiguration,
new Reference('workflow.security.expression_language'),
new Reference('security.token_storage'),
new Reference('security.authorization_checker'),
@ -709,6 +726,9 @@ class FrameworkExtension extends Extension
new Reference('security.role_hierarchy'),
new Reference('validator', ContainerInterface::NULL_ON_INVALID_REFERENCE),
));
foreach ($guardsConfiguration as $eventName => $config) {
$guard->addTag('kernel.event_listener', array('event' => $eventName, 'method' => 'onTransition'));
}
$container->setDefinition(sprintf('%s.listener.guard', $workflowId), $guard);
$container->setParameter('workflow.has_guard_listeners', true);

View File

@ -13,12 +13,12 @@
<framework:argument>a</framework:argument>
</framework:marking-store>
<framework:support>Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest</framework:support>
<framework:place name="draft" />
<framework:place name="wait_for_journalist" />
<framework:place name="approved_by_journalist" />
<framework:place name="wait_for_spellchecker" />
<framework:place name="approved_by_spellchecker" />
<framework:place name="published" />
<framework:place>draft</framework:place>
<framework:place>wait_for_journalist</framework:place>
<framework:place>approved_by_journalist</framework:place>
<framework:place>wait_for_spellchecker</framework:place>
<framework:place>approved_by_spellchecker</framework:place>
<framework:place>published</framework:place>
<framework:transition name="request_review">
<framework:from>draft</framework:from>
<framework:to>wait_for_journalist</framework:to>

View File

@ -19,19 +19,22 @@ class GuardExpression
private $expression;
public function getTransition(): Transition
{
return $this->transition;
}
public function getExpression(): string
{
return $this->expression;
}
public function __construct(Transition $transition, string $expression)
/**
* @param string $expression
*/
public function __construct(Transition $transition, $expression)
{
$this->transition = $transition;
$this->expression = $expression;
}
public function getTransition()
{
return $this->transition;
}
public function getExpression()
{
return $this->expression;
}
}

View File

@ -18,7 +18,6 @@ use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Workflow\Event\GuardEvent;
use Symfony\Component\Workflow\Exception\InvalidTokenConfigurationException;
use Symfony\Component\Workflow\TransitionBlocker;
/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
@ -63,16 +62,15 @@ class GuardListener
}
}
private function validateGuardExpression(GuardEvent $event, string $expression)
private function validateGuardExpression(GuardEvent $event, $expression)
{
if (!$this->expressionLanguage->evaluate($expression, $this->getVariables($event))) {
$blocker = TransitionBlocker::createBlockedByExpressionGuardListener($expression);
$event->addTransitionBlocker($blocker);
$event->setBlocked(true);
}
}
// code should be sync with Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter
private function getVariables(GuardEvent $event): array
private function getVariables(GuardEvent $event)
{
$token = $this->tokenStorage->getToken();

View File

@ -21,16 +21,16 @@ class GuardListenerTest extends TestCase
private $authenticationChecker;
private $validator;
private $listener;
private $transition;
private $configuration;
protected function setUp()
{
$configuration = array(
$this->configuration = array(
'test_is_granted' => 'is_granted("something")',
'test_is_valid' => 'is_valid(subject)',
'test_expression' => array(
new GuardExpression($this->getTransition(true), '!is_valid(subject)'),
new GuardExpression($this->getTransition(true), 'is_valid(subject)'),
new GuardExpression(new Transition('name', 'from', 'to'), '!is_valid(subject)'),
new GuardExpression(new Transition('name', 'from', 'to'), 'is_valid(subject)'),
),
);
$expressionLanguage = new ExpressionLanguage();
@ -41,7 +41,7 @@ class GuardListenerTest extends TestCase
$this->authenticationChecker = $this->getMockBuilder(AuthorizationCheckerInterface::class)->getMock();
$trustResolver = $this->getMockBuilder(AuthenticationTrustResolverInterface::class)->getMock();
$this->validator = $this->getMockBuilder(ValidatorInterface::class)->getMock();
$this->listener = new GuardListener($configuration, $expressionLanguage, $tokenStorage, $this->authenticationChecker, $trustResolver, null, $this->validator);
$this->listener = new GuardListener($this->configuration, $expressionLanguage, $tokenStorage, $this->authenticationChecker, $trustResolver, null, $this->validator);
}
protected function tearDown()
@ -104,8 +104,8 @@ class GuardListenerTest extends TestCase
public function testWithGuardExpressionWithNotSupportedTransition()
{
$event = $this->createEvent(true);
$this->configureValidator(false, false);
$event = $this->createEvent();
$this->configureValidator(false);
$this->listener->onTransition($event, 'test_expression');
$this->assertFalse($event->isBlocked());
@ -113,7 +113,7 @@ class GuardListenerTest extends TestCase
public function testWithGuardExpressionWithSupportedTransition()
{
$event = $this->createEvent();
$event = $this->createEvent($this->configuration['test_expression'][1]->getTransition());
$this->configureValidator(true, true);
$this->listener->onTransition($event, 'test_expression');
@ -122,18 +122,18 @@ class GuardListenerTest extends TestCase
public function testGuardExpressionBlocks()
{
$event = $this->createEvent();
$event = $this->createEvent($this->configuration['test_expression'][1]->getTransition());
$this->configureValidator(true, false);
$this->listener->onTransition($event, 'test_expression');
$this->assertTrue($event->isBlocked());
}
private function createEvent($newTransition = false)
private function createEvent(Transition $transition = null)
{
$subject = new \stdClass();
$subject->marking = new Marking();
$transition = $this->getTransition($newTransition);
$transition = $transition ?: new Transition('name', 'from', 'to');
return new GuardEvent($subject, $subject->marking, $transition);
}
@ -173,13 +173,4 @@ class GuardListenerTest extends TestCase
->willReturn($valid ? array() : array('a violation'))
;
}
private function getTransition($new = false)
{
if ($new || !$this->transition) {
$this->transition = new Transition('name', 'from', 'to');
}
return $this->transition;
}
}