[FrameworkBundle] fixed guard event names for transitions
This commit is contained in:
parent
6006448997
commit
fb88bfc79a
@ -300,6 +300,7 @@
|
|||||||
<xsd:sequence>
|
<xsd:sequence>
|
||||||
<xsd:element name="from" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
|
<xsd:element name="from" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
|
||||||
<xsd:element name="to" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
|
<xsd:element name="to" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
|
||||||
|
<xsd:element name="guard" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest;
|
||||||
|
|
||||||
|
$container->loadFromExtension('framework', array(
|
||||||
|
'workflows' => array(
|
||||||
|
'article' => array(
|
||||||
|
'type' => 'workflow',
|
||||||
|
'marking_store' => array(
|
||||||
|
'type' => 'multiple_state',
|
||||||
|
),
|
||||||
|
'supports' => array(
|
||||||
|
FrameworkExtensionTest::class,
|
||||||
|
),
|
||||||
|
'initial_place' => 'draft',
|
||||||
|
'places' => array(
|
||||||
|
'draft',
|
||||||
|
'wait_for_journalist',
|
||||||
|
'approved_by_journalist',
|
||||||
|
'wait_for_spellchecker',
|
||||||
|
'approved_by_spellchecker',
|
||||||
|
'published',
|
||||||
|
),
|
||||||
|
'transitions' => array(
|
||||||
|
'request_review' => array(
|
||||||
|
'from' => 'draft',
|
||||||
|
'to' => array('wait_for_journalist', 'wait_for_spellchecker'),
|
||||||
|
),
|
||||||
|
'journalist_approval' => array(
|
||||||
|
'from' => 'wait_for_journalist',
|
||||||
|
'to' => 'approved_by_journalist',
|
||||||
|
),
|
||||||
|
'spellchecker_approval' => array(
|
||||||
|
'from' => 'wait_for_spellchecker',
|
||||||
|
'to' => 'approved_by_spellchecker',
|
||||||
|
),
|
||||||
|
'publish' => array(
|
||||||
|
'from' => array('approved_by_journalist', 'approved_by_spellchecker'),
|
||||||
|
'to' => 'published',
|
||||||
|
'guard' => '!!true',
|
||||||
|
),
|
||||||
|
'publish_editor_in_chief' => array(
|
||||||
|
'name' => 'publish',
|
||||||
|
'from' => 'draft',
|
||||||
|
'to' => 'published',
|
||||||
|
'guard' => '!!false',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<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 http://symfony.com/schema/dic/services/services-1.0.xsd
|
||||||
|
http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
|
||||||
|
|
||||||
|
<framework:config>
|
||||||
|
<framework:workflow name="article" type="workflow" initial-place="draft">
|
||||||
|
<framework:marking-store type="multiple_state">
|
||||||
|
<framework:argument>a</framework:argument>
|
||||||
|
<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:transition name="request_review">
|
||||||
|
<framework:from>draft</framework:from>
|
||||||
|
<framework:to>wait_for_journalist</framework:to>
|
||||||
|
<framework:to>wait_for_spellchecker</framework:to>
|
||||||
|
</framework:transition>
|
||||||
|
<framework:transition name="journalist_approval">
|
||||||
|
<framework:from>wait_for_journalist</framework:from>
|
||||||
|
<framework:to>approved_by_journalist</framework:to>
|
||||||
|
</framework:transition>
|
||||||
|
<framework:transition name="spellchecker_approval">
|
||||||
|
<framework:from>wait_for_spellchecker</framework:from>
|
||||||
|
<framework:to>approved_by_spellchecker</framework:to>
|
||||||
|
</framework:transition>
|
||||||
|
<framework:transition name="publish">
|
||||||
|
<framework:from>approved_by_journalist</framework:from>
|
||||||
|
<framework:from>approved_by_spellchecker</framework:from>
|
||||||
|
<framework:to>published</framework:to>
|
||||||
|
<framework:guard>!!true</framework:guard>
|
||||||
|
</framework:transition>
|
||||||
|
<framework:transition name="publish">
|
||||||
|
<framework:from>draft</framework:from>
|
||||||
|
<framework:to>published</framework:to>
|
||||||
|
<framework:guard>!!false</framework:guard>
|
||||||
|
</framework:transition>
|
||||||
|
</framework:workflow>
|
||||||
|
</framework:config>
|
||||||
|
</container>
|
@ -0,0 +1,35 @@
|
|||||||
|
framework:
|
||||||
|
workflows:
|
||||||
|
article:
|
||||||
|
type: workflow
|
||||||
|
marking_store:
|
||||||
|
type: multiple_state
|
||||||
|
supports:
|
||||||
|
- Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest
|
||||||
|
initial_place: draft
|
||||||
|
places:
|
||||||
|
- draft
|
||||||
|
- wait_for_journalist
|
||||||
|
- approved_by_journalist
|
||||||
|
- wait_for_spellchecker
|
||||||
|
- approved_by_spellchecker
|
||||||
|
- published
|
||||||
|
transitions:
|
||||||
|
request_review:
|
||||||
|
from: [draft]
|
||||||
|
to: [wait_for_journalist, wait_for_spellchecker]
|
||||||
|
journalist_approval:
|
||||||
|
from: [wait_for_journalist]
|
||||||
|
to: [approved_by_journalist]
|
||||||
|
spellchecker_approval:
|
||||||
|
from: [wait_for_spellchecker]
|
||||||
|
to: [approved_by_spellchecker]
|
||||||
|
publish:
|
||||||
|
from: [approved_by_journalist, approved_by_spellchecker]
|
||||||
|
to: [published]
|
||||||
|
guard: "!!true"
|
||||||
|
publish_editor_in_chief:
|
||||||
|
name: publish
|
||||||
|
from: [draft]
|
||||||
|
to: [published]
|
||||||
|
guard: "!!false"
|
@ -302,14 +302,84 @@ abstract class FrameworkExtensionTest extends TestCase
|
|||||||
|
|
||||||
$this->assertCount(5, $transitions);
|
$this->assertCount(5, $transitions);
|
||||||
|
|
||||||
$this->assertSame('request_review', $transitions[0]->getArgument(0));
|
$this->assertSame('workflow.article.transition.0', (string) $transitions[0]);
|
||||||
$this->assertSame('journalist_approval', $transitions[1]->getArgument(0));
|
$this->assertSame(array(
|
||||||
$this->assertSame('spellchecker_approval', $transitions[2]->getArgument(0));
|
'request_review',
|
||||||
$this->assertSame('publish', $transitions[3]->getArgument(0));
|
array(
|
||||||
$this->assertSame('publish', $transitions[4]->getArgument(0));
|
'draft',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'wait_for_journalist', 'wait_for_spellchecker',
|
||||||
|
),
|
||||||
|
), $container->getDefinition($transitions[0])->getArguments());
|
||||||
|
|
||||||
$this->assertSame(array('approved_by_journalist', 'approved_by_spellchecker'), $transitions[3]->getArgument(1));
|
$this->assertSame('workflow.article.transition.1', (string) $transitions[1]);
|
||||||
$this->assertSame(array('draft'), $transitions[4]->getArgument(1));
|
$this->assertSame(array(
|
||||||
|
'journalist_approval',
|
||||||
|
array(
|
||||||
|
'wait_for_journalist',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'approved_by_journalist',
|
||||||
|
),
|
||||||
|
), $container->getDefinition($transitions[1])->getArguments());
|
||||||
|
|
||||||
|
$this->assertSame('workflow.article.transition.2', (string) $transitions[2]);
|
||||||
|
$this->assertSame(array(
|
||||||
|
'spellchecker_approval',
|
||||||
|
array(
|
||||||
|
'wait_for_spellchecker',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'approved_by_spellchecker',
|
||||||
|
),
|
||||||
|
), $container->getDefinition($transitions[2])->getArguments());
|
||||||
|
|
||||||
|
$this->assertSame('workflow.article.transition.3', (string) $transitions[3]);
|
||||||
|
$this->assertSame(array(
|
||||||
|
'publish',
|
||||||
|
array(
|
||||||
|
'approved_by_journalist',
|
||||||
|
'approved_by_spellchecker',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'published',
|
||||||
|
),
|
||||||
|
), $container->getDefinition($transitions[3])->getArguments());
|
||||||
|
|
||||||
|
$this->assertSame('workflow.article.transition.4', (string) $transitions[4]);
|
||||||
|
$this->assertSame(array(
|
||||||
|
'publish',
|
||||||
|
array(
|
||||||
|
'draft',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'published',
|
||||||
|
),
|
||||||
|
), $container->getDefinition($transitions[4])->getArguments());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGuardExpressions()
|
||||||
|
{
|
||||||
|
$container = $this->createContainerFromFile('workflow_with_guard_expression');
|
||||||
|
|
||||||
|
$this->assertTrue($container->hasDefinition('workflow.article.listener.guard'), 'Workflow guard listener is registered as a service');
|
||||||
|
$this->assertTrue($container->hasParameter('workflow.has_guard_listeners'), 'Workflow guard listeners parameter exists');
|
||||||
|
$this->assertTrue(true === $container->getParameter('workflow.has_guard_listeners'), 'Workflow guard listeners parameter is enabled');
|
||||||
|
$guardDefinition = $container->getDefinition('workflow.article.listener.guard');
|
||||||
|
$this->assertSame(array(
|
||||||
|
array(
|
||||||
|
'event' => 'workflow.article.guard.publish',
|
||||||
|
'method' => 'onTransition',
|
||||||
|
),
|
||||||
|
), $guardDefinition->getTag('kernel.event_listener'));
|
||||||
|
$guardsConfiguration = $guardDefinition->getArgument(0);
|
||||||
|
$this->assertTrue(1 === \count($guardsConfiguration), 'Workflow guard configuration contains one element per transition name');
|
||||||
|
$transitionGuardExpressions = $guardsConfiguration['workflow.article.guard.publish'];
|
||||||
|
$this->assertSame('workflow.article.transition.3', (string) $transitionGuardExpressions[0]->getArgument(0));
|
||||||
|
$this->assertSame('!!true', $transitionGuardExpressions[0]->getArgument(1));
|
||||||
|
$this->assertSame('workflow.article.transition.4', (string) $transitionGuardExpressions[1]->getArgument(0));
|
||||||
|
$this->assertSame('!!false', $transitionGuardExpressions[1]->getArgument(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testWorkflowServicesCanBeEnabled()
|
public function testWorkflowServicesCanBeEnabled()
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
<?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\Workflow\EventListener;
|
||||||
|
|
||||||
|
use Symfony\Component\Workflow\Transition;
|
||||||
|
|
||||||
|
class GuardExpression
|
||||||
|
{
|
||||||
|
private $transition;
|
||||||
|
|
||||||
|
private $expression;
|
||||||
|
|
||||||
|
public function getTransition(): Transition
|
||||||
|
{
|
||||||
|
return $this->transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExpression(): string
|
||||||
|
{
|
||||||
|
return $this->expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct(Transition $transition, string $expression)
|
||||||
|
{
|
||||||
|
$this->transition = $transition;
|
||||||
|
$this->expression = $expression;
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@ use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
|
|||||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||||
use Symfony\Component\Workflow\Event\GuardEvent;
|
use Symfony\Component\Workflow\Event\GuardEvent;
|
||||||
use Symfony\Component\Workflow\Exception\InvalidTokenConfigurationException;
|
use Symfony\Component\Workflow\Exception\InvalidTokenConfigurationException;
|
||||||
|
use Symfony\Component\Workflow\TransitionBlocker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||||
@ -32,7 +33,7 @@ class GuardListener
|
|||||||
private $roleHierarchy;
|
private $roleHierarchy;
|
||||||
private $validator;
|
private $validator;
|
||||||
|
|
||||||
public function __construct($configuration, ExpressionLanguage $expressionLanguage, TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authenticationChecker, AuthenticationTrustResolverInterface $trustResolver, RoleHierarchyInterface $roleHierarchy = null, ValidatorInterface $validator = null)
|
public function __construct(array $configuration, ExpressionLanguage $expressionLanguage, TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authenticationChecker, AuthenticationTrustResolverInterface $trustResolver, RoleHierarchyInterface $roleHierarchy = null, ValidatorInterface $validator = null)
|
||||||
{
|
{
|
||||||
$this->configuration = $configuration;
|
$this->configuration = $configuration;
|
||||||
$this->expressionLanguage = $expressionLanguage;
|
$this->expressionLanguage = $expressionLanguage;
|
||||||
@ -49,13 +50,29 @@ class GuardListener
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->expressionLanguage->evaluate($this->configuration[$eventName], $this->getVariables($event))) {
|
$eventConfiguration = (array) $this->configuration[$eventName];
|
||||||
$event->setBlocked(true);
|
foreach ($eventConfiguration as $guard) {
|
||||||
|
if ($guard instanceof GuardExpression) {
|
||||||
|
if ($guard->getTransition() !== $event->getTransition()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$this->validateGuardExpression($event, $guard->getExpression());
|
||||||
|
} else {
|
||||||
|
$this->validateGuardExpression($event, $guard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validateGuardExpression(GuardEvent $event, string $expression)
|
||||||
|
{
|
||||||
|
if (!$this->expressionLanguage->evaluate($expression, $this->getVariables($event))) {
|
||||||
|
$blocker = TransitionBlocker::createBlockedByExpressionGuardListener($expression);
|
||||||
|
$event->addTransitionBlocker($blocker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// code should be sync with Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter
|
// code should be sync with Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter
|
||||||
private function getVariables(GuardEvent $event)
|
private function getVariables(GuardEvent $event): array
|
||||||
{
|
{
|
||||||
$token = $this->tokenStorage->getToken();
|
$token = $this->tokenStorage->getToken();
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ use Symfony\Component\Security\Core\Role\Role;
|
|||||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||||
use Symfony\Component\Workflow\Event\GuardEvent;
|
use Symfony\Component\Workflow\Event\GuardEvent;
|
||||||
use Symfony\Component\Workflow\EventListener\ExpressionLanguage;
|
use Symfony\Component\Workflow\EventListener\ExpressionLanguage;
|
||||||
|
use Symfony\Component\Workflow\EventListener\GuardExpression;
|
||||||
use Symfony\Component\Workflow\EventListener\GuardListener;
|
use Symfony\Component\Workflow\EventListener\GuardListener;
|
||||||
use Symfony\Component\Workflow\Marking;
|
use Symfony\Component\Workflow\Marking;
|
||||||
use Symfony\Component\Workflow\Transition;
|
use Symfony\Component\Workflow\Transition;
|
||||||
@ -20,12 +21,17 @@ class GuardListenerTest extends TestCase
|
|||||||
private $authenticationChecker;
|
private $authenticationChecker;
|
||||||
private $validator;
|
private $validator;
|
||||||
private $listener;
|
private $listener;
|
||||||
|
private $transition;
|
||||||
|
|
||||||
protected function setUp()
|
protected function setUp()
|
||||||
{
|
{
|
||||||
$configuration = array(
|
$configuration = array(
|
||||||
'test_is_granted' => 'is_granted("something")',
|
'test_is_granted' => 'is_granted("something")',
|
||||||
'test_is_valid' => 'is_valid(subject)',
|
'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)'),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
$expressionLanguage = new ExpressionLanguage();
|
$expressionLanguage = new ExpressionLanguage();
|
||||||
$token = $this->getMockBuilder(TokenInterface::class)->getMock();
|
$token = $this->getMockBuilder(TokenInterface::class)->getMock();
|
||||||
@ -96,11 +102,38 @@ class GuardListenerTest extends TestCase
|
|||||||
$this->assertFalse($event->isBlocked());
|
$this->assertFalse($event->isBlocked());
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createEvent()
|
public function testWithGuardExpressionWithNotSupportedTransition()
|
||||||
|
{
|
||||||
|
$event = $this->createEvent(true);
|
||||||
|
$this->configureValidator(false, false);
|
||||||
|
$this->listener->onTransition($event, 'test_expression');
|
||||||
|
|
||||||
|
$this->assertFalse($event->isBlocked());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWithGuardExpressionWithSupportedTransition()
|
||||||
|
{
|
||||||
|
$event = $this->createEvent();
|
||||||
|
$this->configureValidator(true, true);
|
||||||
|
$this->listener->onTransition($event, 'test_expression');
|
||||||
|
|
||||||
|
$this->assertFalse($event->isBlocked());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGuardExpressionBlocks()
|
||||||
|
{
|
||||||
|
$event = $this->createEvent();
|
||||||
|
$this->configureValidator(true, false);
|
||||||
|
$this->listener->onTransition($event, 'test_expression');
|
||||||
|
|
||||||
|
$this->assertTrue($event->isBlocked());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createEvent($newTransition = false)
|
||||||
{
|
{
|
||||||
$subject = new \stdClass();
|
$subject = new \stdClass();
|
||||||
$subject->marking = new Marking();
|
$subject->marking = new Marking();
|
||||||
$transition = new Transition('name', 'from', 'to');
|
$transition = $this->getTransition($newTransition);
|
||||||
|
|
||||||
return new GuardEvent($subject, $subject->marking, $transition);
|
return new GuardEvent($subject, $subject->marking, $transition);
|
||||||
}
|
}
|
||||||
@ -140,4 +173,13 @@ class GuardListenerTest extends TestCase
|
|||||||
->willReturn($valid ? array() : array('a violation'))
|
->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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user