diff --git a/src/Bundle/ChillDocStoreBundle/Service/WorkflowStoredObjectPermissionHelper.php b/src/Bundle/ChillDocStoreBundle/Service/WorkflowStoredObjectPermissionHelper.php index 545325121..971371bea 100644 --- a/src/Bundle/ChillDocStoreBundle/Service/WorkflowStoredObjectPermissionHelper.php +++ b/src/Bundle/ChillDocStoreBundle/Service/WorkflowStoredObjectPermissionHelper.php @@ -14,28 +14,41 @@ namespace Chill\DocStoreBundle\Service; use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum; use Chill\MainBundle\Workflow\EntityWorkflowManager; use Symfony\Component\Security\Core\Security; +use Symfony\Component\Workflow\Registry; class WorkflowStoredObjectPermissionHelper { - public function __construct(private readonly Security $security, private readonly EntityWorkflowManager $entityWorkflowManager) {} + public function __construct( + private readonly Security $security, + private readonly EntityWorkflowManager $entityWorkflowManager, + private readonly Registry $registry, + ) {} public function notBlockedByWorkflow(object $entity): bool { - $workflows = $this->entityWorkflowManager->findByRelatedEntity($entity); + $entityWorkflows = $this->entityWorkflowManager->findByRelatedEntity($entity); $currentUser = $this->security->getUser(); - foreach ($workflows as $workflow) { - if ($workflow->isFinal()) { - return false; - } + foreach ($entityWorkflows as $entityWorkflow) { + if ($entityWorkflow->isFinal()) { - if (!$workflow->getCurrentStep()->getAllDestUser()->contains($currentUser)) { - return false; + $workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); + $marking = $workflow->getMarkingStore()->getMarking($entityWorkflow); + foreach ($marking->getPlaces() as $place => $active) { + $metadata = $workflow->getMetadataStore()->getPlaceMetadata($place); + if ($metadata['isFinalPositive'] ?? true) { + return false; + } + } + } else { + if (!$entityWorkflow->getCurrentStep()->getAllDestUser()->contains($currentUser)) { + return false; + } } // as soon as there is one signatured applyied, we are not able to // edit the document any more - foreach ($workflow->getSteps() as $step) { + foreach ($entityWorkflow->getSteps() as $step) { foreach ($step->getSignatures() as $signature) { if (EntityWorkflowSignatureStateEnum::SIGNED === $signature->getState()) { return false; diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Service/WorkflowStoredObjectPermissionHelperTest.php b/src/Bundle/ChillDocStoreBundle/Tests/Service/WorkflowStoredObjectPermissionHelperTest.php index e87ea4653..8f693f44e 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/Service/WorkflowStoredObjectPermissionHelperTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/Service/WorkflowStoredObjectPermissionHelperTest.php @@ -17,12 +17,19 @@ use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum; use Chill\MainBundle\Entity\Workflow\EntityWorkflowStepSignature; use Chill\MainBundle\Workflow\EntityWorkflowManager; +use Chill\MainBundle\Workflow\EntityWorkflowMarkingStore; use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO; use Chill\PersonBundle\Entity\Person; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Component\Security\Core\Security; +use Symfony\Component\Workflow\DefinitionBuilder; +use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore; +use Symfony\Component\Workflow\Registry; +use Symfony\Component\Workflow\SupportStrategy\WorkflowSupportStrategyInterface; +use Symfony\Component\Workflow\Workflow; +use Symfony\Component\Workflow\WorkflowInterface; /** * @internal @@ -38,6 +45,8 @@ class WorkflowStoredObjectPermissionHelperTest extends TestCase */ public function testNotBlockByWorkflow(EntityWorkflow $entityWorkflow, User $user, bool $expected, string $message): void { + // all entities must have this workflow name, so we are ok to set it here + $entityWorkflow->setWorkflowName('dummy'); $object = new \stdClass(); $helper = $this->buildHelper($object, $entityWorkflow, $user); @@ -52,7 +61,7 @@ class WorkflowStoredObjectPermissionHelperTest extends TestCase $entityWorkflowManager = $this->prophesize(EntityWorkflowManager::class); $entityWorkflowManager->findByRelatedEntity(Argument::type('object'))->willReturn([$entityWorkflow]); - return new WorkflowStoredObjectPermissionHelper($security->reveal(), $entityWorkflowManager->reveal()); + return new WorkflowStoredObjectPermissionHelper($security->reveal(), $entityWorkflowManager->reveal(), $this->buildRegistry()); } public static function provideDataNotBlockByWorkflow(): iterable @@ -73,10 +82,18 @@ class WorkflowStoredObjectPermissionHelperTest extends TestCase $entityWorkflow = new EntityWorkflow(); $dto = new WorkflowTransitionContextDTO($entityWorkflow); $dto->futureDestUsers[] = $user = new User(); - $entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), $user); + $entityWorkflow->setStep('final_positive', $dto, 'to_final_positive', new \DateTimeImmutable(), $user); $entityWorkflow->getCurrentStep()->setIsFinal(true); - yield [$entityWorkflow, $user, false, 'blocked because the step is final']; + yield [$entityWorkflow, $user, false, 'blocked because the step is final, and final positive']; + + $entityWorkflow = new EntityWorkflow(); + $dto = new WorkflowTransitionContextDTO($entityWorkflow); + $dto->futureDestUsers[] = $user = new User(); + $entityWorkflow->setStep('final_negative', $dto, 'to_final_negative', new \DateTimeImmutable(), $user); + $entityWorkflow->getCurrentStep()->setIsFinal(true); + + yield [$entityWorkflow, $user, true, 'allowed because the step is final, and final negative']; $entityWorkflow = new EntityWorkflow(); $dto = new WorkflowTransitionContextDTO($entityWorkflow); @@ -97,5 +114,48 @@ class WorkflowStoredObjectPermissionHelperTest extends TestCase yield [$entityWorkflow, $user, false, 'blocked, a signature is present and signed']; + $entityWorkflow = new EntityWorkflow(); + $dto = new WorkflowTransitionContextDTO($entityWorkflow); + $dto->futureDestUsers[] = $user = new User(); + $entityWorkflow->setStep('final_negative', $dto, 'to_final_negative', new \DateTimeImmutable(), $user); + $step = $entityWorkflow->getCurrentStep(); + $signature = new EntityWorkflowStepSignature($step, new Person()); + $signature->setState(EntityWorkflowSignatureStateEnum::SIGNED); + + yield [$entityWorkflow, $user, false, 'blocked, a signature is present and signed, although the workflow is final negative']; + + } + + private static function buildRegistry(): Registry + { + $builder = new DefinitionBuilder(); + $builder + ->setInitialPlaces(['initial']) + ->addPlaces(['initial', 'test', 'final_positive', 'final_negative']) + ->setMetadataStore( + new InMemoryMetadataStore( + placesMetadata: [ + 'final_positive' => [ + 'isFinal' => true, + 'isFinalPositive' => true, + ], + 'final_negative' => [ + 'isFinal' => true, + 'isFinalPositive' => false, + ], + ] + ) + ); + + $workflow = new Workflow($builder->build(), new EntityWorkflowMarkingStore(), name: 'dummy'); + $registry = new Registry(); + $registry->addWorkflow($workflow, new class () implements WorkflowSupportStrategyInterface { + public function supports(WorkflowInterface $workflow, object $subject): bool + { + return true; + } + }); + + return $registry; } }