From 79621e8ab720c539c00fa17da0c9fe27ff795619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 12 Nov 2024 18:14:30 +0100 Subject: [PATCH] Add guard to block signatures on related entities without objects Implemented a guard to prevent signatures on related entities that lack a stored object. It also includes corresponding tests and added translation for the error message in French. --- ...dEntityWithoutAnyStoredObjectGuardTest.php | 135 ++++++++++++++++++ .../Workflow/EntityWorkflowManager.php | 19 +++ ...latedEntityWithoutAnyStoredObjectGuard.php | 57 ++++++++ .../WorkflowNotificationHandler.php | 2 +- .../translations/messages.fr.yml | 1 + 5 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/BlockSignatureOnRelatedEntityWithoutAnyStoredObjectGuardTest.php create mode 100644 src/Bundle/ChillMainBundle/Workflow/EventSubscriber/BlockSignatureOnRelatedEntityWithoutAnyStoredObjectGuard.php diff --git a/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/BlockSignatureOnRelatedEntityWithoutAnyStoredObjectGuardTest.php b/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/BlockSignatureOnRelatedEntityWithoutAnyStoredObjectGuardTest.php new file mode 100644 index 000000000..8f11dfd0b --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/BlockSignatureOnRelatedEntityWithoutAnyStoredObjectGuardTest.php @@ -0,0 +1,135 @@ +setWorkflowName('dummy')->setRelatedEntityClass('with_stored_object'); + + $registry = $this->buildRegistry(); + $workflow = $registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); + + $list = $workflow->buildTransitionBlockerList($entityWorkflow, 'to_signature'); + + self::assertCount(0, $list); + } + + public function testBlockForSignatureOnRelatedEntityWithoutStoredObject(): void + { + $entityWorkflow = new EntityWorkflow(); + $entityWorkflow->setWorkflowName('dummy')->setRelatedEntityClass('no_stored_object'); + + $registry = $this->buildRegistry(); + $workflow = $registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); + + $list = $workflow->buildTransitionBlockerList($entityWorkflow, 'to_signature'); + + self::assertCount(1, $list); + self::assertTrue($list->has('e8e28caa-a106-11ef-97e8-f3919e8b5c8a')); + } + + public function testAllowedForNoSignatureOnRelatedEntityWithoutStoredObject(): void + { + $entityWorkflow = new EntityWorkflow(); + $entityWorkflow->setWorkflowName('dummy')->setRelatedEntityClass('no_stored_object'); + + $registry = $this->buildRegistry(); + $workflow = $registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); + + $list = $workflow->buildTransitionBlockerList($entityWorkflow, 'to_no_signature'); + + self::assertTrue($list->isEmpty()); + } + + public function testAllowedForNoSignatureOnRelatedEntityWithStoredObject(): void + { + $entityWorkflow = new EntityWorkflow(); + $entityWorkflow->setWorkflowName('dummy')->setRelatedEntityClass('no_stored_object'); + + $registry = $this->buildRegistry(); + $workflow = $registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); + + $list = $workflow->buildTransitionBlockerList($entityWorkflow, 'to_no_signature'); + + self::assertTrue($list->isEmpty()); + } + + private function buildRegistry(): Registry + { + $definitionBuilder = new DefinitionBuilder(); + $definitionBuilder + ->addPlaces(['initial', 'signature', 'no_signature']) + ->addTransition( + new Transition('to_signature', 'initial', 'signature') + ) + ->addTransition( + new Transition('to_no_signature', 'initial', 'no_signature') + ) + ->setMetadataStore( + new InMemoryMetadataStore( + placesMetadata: ['signature' => ['isSignature' => ['person']]] + ) + ); + + $workflow = new Workflow($definitionBuilder->build(), new EntityWorkflowMarkingStore(), $eventDispatcher = new EventDispatcher(), name: 'dummy'); + $registry = new Registry(); + $registry->addWorkflow($workflow, new class () implements WorkflowSupportStrategyInterface { + public function supports(WorkflowInterface $workflow, object $subject): bool + { + return true; + } + }); + + $entityWorkflowManager = $this->prophesize(EntityWorkflowManager::class); + $entityWorkflowManager->canAssociateStoredObject(Argument::type(EntityWorkflow::class))->will( + function ($args): bool { + /** @var EntityWorkflow $entityWorkflow */ + $entityWorkflow = $args[0]; + + return 'with_stored_object' === $entityWorkflow->getRelatedEntityClass(); + } + ); + $eventSubscriber = new BlockSignatureOnRelatedEntityWithoutAnyStoredObjectGuard( + $entityWorkflowManager->reveal() + ); + + $eventDispatcher->addSubscriber($eventSubscriber); + + return $registry; + } +} diff --git a/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowManager.php b/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowManager.php index 474370127..bfe41f559 100644 --- a/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowManager.php +++ b/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowManager.php @@ -61,6 +61,25 @@ class EntityWorkflowManager return null; } + /** + * Return true if the entityWorkflow may associate a stored object. + * + * Take care that, for various reasons, the method @see{getAssociatedStoredObject} may return null if, for + * various reasons, the associated stored object may not be retrieved. + * + * @return bool return true if the entityWorkflow may associate a stored object + */ + public function canAssociateStoredObject(EntityWorkflow $entityWorkflow): bool + { + foreach ($this->handlers as $handler) { + if ($handler instanceof EntityWorkflowWithStoredObjectHandlerInterface && $handler->supports($entityWorkflow)) { + return true; + } + } + + return false; + } + /** * @return list */ diff --git a/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/BlockSignatureOnRelatedEntityWithoutAnyStoredObjectGuard.php b/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/BlockSignatureOnRelatedEntityWithoutAnyStoredObjectGuard.php new file mode 100644 index 000000000..c94046c0a --- /dev/null +++ b/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/BlockSignatureOnRelatedEntityWithoutAnyStoredObjectGuard.php @@ -0,0 +1,57 @@ + [ + ['blockSignatureIfNoStoredObject', 0], + ], + ]; + } + + public function blockSignatureIfNoStoredObject(GuardEvent $event): void + { + $entityWorkflow = $event->getSubject(); + + if (!$entityWorkflow instanceof EntityWorkflow) { + return; + } + + $metadataStore = $event->getWorkflow()->getMetadataStore(); + + foreach ($event->getTransition()->getTos() as $to) { + $placeMetadata = $metadataStore->getPlaceMetadata($to); + if ([] !== ($placeMetadata['isSignature'] ?? [])) { + if (!$this->entityWorkflowManager->canAssociateStoredObject($entityWorkflow)) { + $event->addTransitionBlocker( + new TransitionBlocker( + 'workflow.May not associate a document', + 'e8e28caa-a106-11ef-97e8-f3919e8b5c8a' + ) + ); + } + } + } + } +} diff --git a/src/Bundle/ChillMainBundle/Workflow/Notification/WorkflowNotificationHandler.php b/src/Bundle/ChillMainBundle/Workflow/Notification/WorkflowNotificationHandler.php index 3d4dd9c8c..99cc163ed 100644 --- a/src/Bundle/ChillMainBundle/Workflow/Notification/WorkflowNotificationHandler.php +++ b/src/Bundle/ChillMainBundle/Workflow/Notification/WorkflowNotificationHandler.php @@ -33,7 +33,7 @@ class WorkflowNotificationHandler implements NotificationHandlerInterface return [ 'entity_workflow' => $entityWorkflow, - 'handler' => null !== $entityWorkflow ? $this->entityWorkflowManager->getHandler($entityWorkflow): null, + 'handler' => null !== $entityWorkflow ? $this->entityWorkflowManager->getHandler($entityWorkflow) : null, 'notificationCc' => $this->isNotificationCc($notification), ]; } diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 07a2bebc3..5344147d6 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -569,6 +569,7 @@ workflow: sent_through_secured_link: Envoi par lien sécurisé public_views_by_ip: Visualisation par adresse IP deleted_title: Workflow supprimé + May not associate a document: Le workflow ne concerne pas un document public_link: expired_link_title: Lien expiré