From 9499bc291cc9c18ed59f9c9339d4dd2816d793d9 Mon Sep 17 00:00:00 2001 From: Alain Flaus Date: Thu, 13 Jul 2017 12:20:18 +0200 Subject: [PATCH 1/2] [Workflow] Added guard 'is_valid()' method support --- .../FrameworkExtension.php | 1 + src/Symfony/Component/Workflow/CHANGELOG.md | 1 + .../EventListener/ExpressionLanguage.php | 14 +++++++++++++ .../Workflow/EventListener/GuardListener.php | 7 ++++++- .../Workflow/Exception/RuntimeException.php | 21 +++++++++++++++++++ 5 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Workflow/Exception/RuntimeException.php diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 90673719814b5..568bb0913ebc7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -752,6 +752,7 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ new Reference('security.authorization_checker'), new Reference('security.authentication.trust_resolver'), new Reference('security.role_hierarchy'), + new Reference('validator', ContainerInterface::NULL_ON_INVALID_REFERENCE), )); $container->setDefinition(sprintf('%s.listener.guard', $workflowId), $guard); diff --git a/src/Symfony/Component/Workflow/CHANGELOG.md b/src/Symfony/Component/Workflow/CHANGELOG.md index 02c04f6dc08f6..328420c6f1e1f 100644 --- a/src/Symfony/Component/Workflow/CHANGELOG.md +++ b/src/Symfony/Component/Workflow/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 3.4.0 ----- + * Add guard `is_valid()` method support * Added support for `Event::getWorkflowName()` for "announce" events. * Added `workflow.completed` events which are fired after a transition is completed. diff --git a/src/Symfony/Component/Workflow/EventListener/ExpressionLanguage.php b/src/Symfony/Component/Workflow/EventListener/ExpressionLanguage.php index 46d5d7520c0fb..df7e21a92855c 100644 --- a/src/Symfony/Component/Workflow/EventListener/ExpressionLanguage.php +++ b/src/Symfony/Component/Workflow/EventListener/ExpressionLanguage.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Workflow\EventListener; use Symfony\Component\Security\Core\Authorization\ExpressionLanguage as BaseExpressionLanguage; +use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Component\Workflow\Exception\RuntimeException; /** * Adds some function to the default Symfony Security ExpressionLanguage. @@ -29,5 +31,17 @@ protected function registerFunctions() }, function (array $variables, $attributes, $object = null) { return $variables['auth_checker']->isGranted($attributes, $object); }); + + $this->register('is_valid', function ($object = 'null', $groups = 'null') { + return sprintf('0 === count($validator->validate(%s, null, %s))', $object, $groups); + }, function (array $variables, $object = null, $groups = null) { + if (!$variables['validator'] instanceof ValidatorInterface) { + throw new RuntimeException('Validator not defined, did you install the component?'); + } + + $errors = $variables['validator']->validate($object, null, $groups); + + return 0 === count($errors); + }); } } diff --git a/src/Symfony/Component/Workflow/EventListener/GuardListener.php b/src/Symfony/Component/Workflow/EventListener/GuardListener.php index 20ba04c007fc2..1fa082ac4d1c0 100644 --- a/src/Symfony/Component/Workflow/EventListener/GuardListener.php +++ b/src/Symfony/Component/Workflow/EventListener/GuardListener.php @@ -15,6 +15,7 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; +use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Workflow\Event\GuardEvent; /** @@ -28,8 +29,9 @@ class GuardListener private $authenticationChecker; private $trustResolver; private $roleHierarchy; + private $validator; - public function __construct($configuration, ExpressionLanguage $expressionLanguage, TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authenticationChecker, AuthenticationTrustResolverInterface $trustResolver, RoleHierarchyInterface $roleHierarchy = null) + public function __construct($configuration, ExpressionLanguage $expressionLanguage, TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authenticationChecker, AuthenticationTrustResolverInterface $trustResolver, RoleHierarchyInterface $roleHierarchy = null, ValidatorInterface $validator = null) { $this->configuration = $configuration; $this->expressionLanguage = $expressionLanguage; @@ -37,6 +39,7 @@ public function __construct($configuration, ExpressionLanguage $expressionLangua $this->authenticationChecker = $authenticationChecker; $this->trustResolver = $trustResolver; $this->roleHierarchy = $roleHierarchy; + $this->validator = $validator; } public function onTransition(GuardEvent $event, $eventName) @@ -72,6 +75,8 @@ private function getVariables(GuardEvent $event) 'auth_checker' => $this->authenticationChecker, // needed for the is_* expression function 'trust_resolver' => $this->trustResolver, + // needed for the is_valid expression function + 'validator' => $this->validator, ); return $variables; diff --git a/src/Symfony/Component/Workflow/Exception/RuntimeException.php b/src/Symfony/Component/Workflow/Exception/RuntimeException.php new file mode 100644 index 0000000000000..7e9caf1bf5b1f --- /dev/null +++ b/src/Symfony/Component/Workflow/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Exception; + +/** + * Base RuntimeException for the Workflow component. + * + * @author Alain Flaus + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} From 06d81987145eb9136683ae715925d193bfbc2c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Fri, 18 Aug 2017 16:07:04 +0200 Subject: [PATCH 2/2] [Workflow] Added tests for the is_valid() guard expression --- .../Compiler/WorkflowGuardListenerPass.php | 26 +++--- .../FrameworkExtension.php | 1 + .../WorkflowGuardListenerPassTest.php | 45 +++------- src/Symfony/Component/Workflow/CHANGELOG.md | 2 +- .../EventListener/ExpressionLanguage.php | 2 +- .../Tests/EventListener/GuardListenerTest.php | 90 +++++++++++++------ src/Symfony/Component/Workflow/composer.json | 3 +- 7 files changed, 95 insertions(+), 74 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php index 349d78cfc4fed..c5498adc4728d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php @@ -17,6 +17,7 @@ /** * @author Christian Flothmann + * @author Grégoire Pineau */ class WorkflowGuardListenerPass implements CompilerPassInterface { @@ -31,20 +32,17 @@ public function process(ContainerBuilder $container) $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.'); + $servicesNeeded = array( + 'security.token_storage', + 'security.authorization_checker', + 'security.authentication.trust_resolver', + 'security.role_hierarchy', + ); + + foreach ($servicesNeeded as $service) { + if (!$container->has($service)) { + throw new LogicException(sprintf('The "%s" service is needed to be able to use the workflow guard listener.', $service)); + } } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 568bb0913ebc7..9bdbb373bb0f9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -85,6 +85,7 @@ use Symfony\Component\Translation\Translator; use Symfony\Component\Validator\ConstraintValidatorInterface; use Symfony\Component\Validator\ObjectInitializerInterface; +use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\WebLink\HttpHeaderSerializer; use Symfony\Component\Workflow; use Symfony\Component\Yaml\Command\LintCommand as BaseYamlLintCommand; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/WorkflowGuardListenerPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/WorkflowGuardListenerPassTest.php index fead9e28f26d0..44c87b188fa17 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/WorkflowGuardListenerPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/WorkflowGuardListenerPassTest.php @@ -14,12 +14,11 @@ 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; +use Symfony\Component\Validator\Validator\ValidatorInterface; class WorkflowGuardListenerPassTest extends TestCase { @@ -29,53 +28,37 @@ class WorkflowGuardListenerPassTest extends TestCase 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() + public function testNoExeptionIfParameterIsNotSet() { $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() + public function testNoExeptionIfAllDependenciesArePresent() { - $this->container->setParameter('workflow.has_guard_listeners', array('foo.listener.guard', 'bar.listener.guard')); + $this->container->setParameter('workflow.has_guard_listeners', true); $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->container->register('validator', ValidatorInterface::class); $this->compilerPass->process($this->container); - $this->assertTrue($this->container->hasDefinition('foo.listener.guard')); - $this->assertTrue($this->container->hasDefinition('bar.listener.guard')); + $this->assertFalse($this->container->hasParameter('workflow.has_guard_listeners')); } /** * @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() + public function testExceptionIfTheTokenStorageServiceIsNotPresent() { - $this->container->setParameter('workflow.has_guard_listeners', array('foo.listener.guard', 'bar.listener.guard')); + $this->container->setParameter('workflow.has_guard_listeners', true); $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); @@ -87,9 +70,9 @@ public function testListenersAreRemovedIfTheTokenStorageServiceIsNotPresent() * @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() + public function testExceptionIfTheAuthorizationCheckerServiceIsNotPresent() { - $this->container->setParameter('workflow.has_guard_listeners', array('foo.listener.guard', 'bar.listener.guard')); + $this->container->setParameter('workflow.has_guard_listeners', true); $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); @@ -101,9 +84,9 @@ public function testListenersAreRemovedIfTheAuthorizationCheckerServiceIsNotPres * @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() + public function testExceptionIfTheAuthenticationTrustResolverServiceIsNotPresent() { - $this->container->setParameter('workflow.has_guard_listeners', array('foo.listener.guard', 'bar.listener.guard')); + $this->container->setParameter('workflow.has_guard_listeners', true); $this->container->register('security.token_storage', TokenStorageInterface::class); $this->container->register('security.authorization_checker', AuthorizationCheckerInterface::class); $this->container->register('security.role_hierarchy', RoleHierarchy::class); @@ -115,9 +98,9 @@ public function testListenersAreRemovedIfTheAuthenticationTrustResolverServiceIs * @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() + public function testExceptionIfTheRoleHierarchyServiceIsNotPresent() { - $this->container->setParameter('workflow.has_guard_listeners', array('foo.listener.guard', 'bar.listener.guard')); + $this->container->setParameter('workflow.has_guard_listeners', true); $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); diff --git a/src/Symfony/Component/Workflow/CHANGELOG.md b/src/Symfony/Component/Workflow/CHANGELOG.md index 328420c6f1e1f..abbf3238011c1 100644 --- a/src/Symfony/Component/Workflow/CHANGELOG.md +++ b/src/Symfony/Component/Workflow/CHANGELOG.md @@ -4,7 +4,7 @@ CHANGELOG 3.4.0 ----- - * Add guard `is_valid()` method support + * Added guard `is_valid()` method support. * Added support for `Event::getWorkflowName()` for "announce" events. * Added `workflow.completed` events which are fired after a transition is completed. diff --git a/src/Symfony/Component/Workflow/EventListener/ExpressionLanguage.php b/src/Symfony/Component/Workflow/EventListener/ExpressionLanguage.php index df7e21a92855c..e1bcc7997d317 100644 --- a/src/Symfony/Component/Workflow/EventListener/ExpressionLanguage.php +++ b/src/Symfony/Component/Workflow/EventListener/ExpressionLanguage.php @@ -36,7 +36,7 @@ protected function registerFunctions() return sprintf('0 === count($validator->validate(%s, null, %s))', $object, $groups); }, function (array $variables, $object = null, $groups = null) { if (!$variables['validator'] instanceof ValidatorInterface) { - throw new RuntimeException('Validator not defined, did you install the component?'); + throw new RuntimeException('"is_valid" cannot be used as the Validator component is not installed.'); } $errors = $variables['validator']->validate($object, null, $groups); diff --git a/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php b/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php index b46ee9092c573..f532639ae09c5 100644 --- a/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php +++ b/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php @@ -8,6 +8,7 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Role\Role; +use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Workflow\EventListener\ExpressionLanguage; use Symfony\Component\Workflow\EventListener\GuardListener; use Symfony\Component\Workflow\Event\GuardEvent; @@ -16,59 +17,85 @@ class GuardListenerTest extends TestCase { - private $tokenStorage; + private $authenticationChecker; + private $validator; private $listener; protected function setUp() { $configuration = array( - 'event_name_a' => 'true', - 'event_name_b' => 'false', + 'test_is_granted' => 'is_granted("something")', + 'test_is_valid' => 'is_valid(subject)', ); - $expressionLanguage = new ExpressionLanguage(); - $this->tokenStorage = $this->getMockBuilder(TokenStorageInterface::class)->getMock(); - $authenticationChecker = $this->getMockBuilder(AuthorizationCheckerInterface::class)->getMock(); + $token = $this->getMockBuilder(TokenInterface::class)->getMock(); + $token->expects($this->any())->method('getRoles')->willReturn(array(new Role('ROLE_USER'))); + $tokenStorage = $this->getMockBuilder(TokenStorageInterface::class)->getMock(); + $tokenStorage->expects($this->any())->method('getToken')->willReturn($token); + $this->authenticationChecker = $this->getMockBuilder(AuthorizationCheckerInterface::class)->getMock(); $trustResolver = $this->getMockBuilder(AuthenticationTrustResolverInterface::class)->getMock(); - - $this->listener = new GuardListener($configuration, $expressionLanguage, $this->tokenStorage, $authenticationChecker, $trustResolver); + $this->validator = $this->getMockBuilder(ValidatorInterface::class)->getMock(); + $this->listener = new GuardListener($configuration, $expressionLanguage, $tokenStorage, $this->authenticationChecker, $trustResolver, null, $this->validator); } protected function tearDown() { + $this->authenticationChecker = null; + $this->validator = null; $this->listener = null; } public function testWithNotSupportedEvent() { $event = $this->createEvent(); - $this->configureTokenStorage(false); + $this->configureAuthenticationChecker(false); + $this->configureValidator(false); $this->listener->onTransition($event, 'not supported'); $this->assertFalse($event->isBlocked()); } - public function testWithSupportedEventAndReject() + public function testWithSecuritySupportedEventAndReject() { $event = $this->createEvent(); - $this->configureTokenStorage(true); + $this->configureAuthenticationChecker(true, false); - $this->listener->onTransition($event, 'event_name_a'); + $this->listener->onTransition($event, 'test_is_granted'); + + $this->assertTrue($event->isBlocked()); + } + + public function testWithSecuritySupportedEventAndAccept() + { + $event = $this->createEvent(); + $this->configureAuthenticationChecker(true, true); + + $this->listener->onTransition($event, 'test_is_granted'); $this->assertFalse($event->isBlocked()); } - public function testWithSupportedEventAndAccept() + public function testWithValidatorSupportedEventAndReject() { $event = $this->createEvent(); - $this->configureTokenStorage(true); + $this->configureValidator(true, false); - $this->listener->onTransition($event, 'event_name_b'); + $this->listener->onTransition($event, 'test_is_valid'); $this->assertTrue($event->isBlocked()); } + public function testWithValidatorSupportedEventAndAccept() + { + $event = $this->createEvent(); + $this->configureValidator(true, true); + + $this->listener->onTransition($event, 'test_is_valid'); + + $this->assertFalse($event->isBlocked()); + } + private function createEvent() { $subject = new \stdClass(); @@ -78,28 +105,39 @@ private function createEvent() return new GuardEvent($subject, $subject->marking, $transition); } - private function configureTokenStorage($hasUser) + private function configureAuthenticationChecker($isUsed, $granted = true) { - if (!$hasUser) { - $this->tokenStorage + if (!$isUsed) { + $this->authenticationChecker ->expects($this->never()) - ->method('getToken') + ->method('isGranted') ; return; } - $token = $this->getMockBuilder(TokenInterface::class)->getMock(); - $token + $this->authenticationChecker ->expects($this->once()) - ->method('getRoles') - ->willReturn(array(new Role('ROLE_ADMIN'))) + ->method('isGranted') + ->willReturn($granted) ; + } + + private function configureValidator($isUsed, $valid = true) + { + if (!$isUsed) { + $this->validator + ->expects($this->never()) + ->method('validate') + ; + + return; + } - $this->tokenStorage + $this->validator ->expects($this->once()) - ->method('getToken') - ->willReturn($token) + ->method('validate') + ->willReturn($valid ? array() : array('a violation')) ; } } diff --git a/src/Symfony/Component/Workflow/composer.json b/src/Symfony/Component/Workflow/composer.json index 9a472dec60e34..1ff206983b822 100644 --- a/src/Symfony/Component/Workflow/composer.json +++ b/src/Symfony/Component/Workflow/composer.json @@ -28,7 +28,8 @@ "symfony/dependency-injection": "~2.8|~3.0|~4.0", "symfony/event-dispatcher": "~2.1|~3.0|~4.0", "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/security-core": "~2.8|~3.0|~4.0" + "symfony/security-core": "~2.8|~3.0|~4.0", + "symfony/validator": "~2.8|~3.4|~4.0" }, "autoload": { "psr-4": { "Symfony\\Component\\Workflow\\": "" } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy