From 4696332a46bf9ab172fb5ea85a99e21033e7b74f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 16 Sep 2024 14:16:42 +0200 Subject: [PATCH] Create a voter for applying all transitions on all workflow's steps This voter checks that the related entity's centers is reachable by the user. --- .../EntityWorkflowTransitionVoter.php | 88 +++++++++++ .../EntityWorkflowTransitionVoterTest.php | 144 ++++++++++++++++++ .../translations/messages.fr.yml | 3 + 3 files changed, 235 insertions(+) create mode 100644 src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowTransitionVoter.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Authorization/EntityWorkflowTransitionVoterTest.php diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowTransitionVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowTransitionVoter.php new file mode 100644 index 000000000..811c45f64 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowTransitionVoter.php @@ -0,0 +1,88 @@ + [ + self::APPLY_ALL_TRANSITIONS, + ], + ]; + } + + protected function supports(string $attribute, $subject): bool + { + return self::APPLY_ALL_TRANSITIONS === $attribute && $subject instanceof EntityWorkflowStep; + } + + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + { + /** @var EntityWorkflowStep $subject */ + $entityWorkflow = $subject->getEntityWorkflow(); + + if (!$this->accessDecisionManager->decide($token, [EntityWorkflowVoter::SEE], $entityWorkflow)) { + return false; + } + + $handler = $this->workflowManager->getHandler($entityWorkflow); + $entity = $handler->getRelatedEntity($entityWorkflow); + + if (null === $entity) { + return false; + } + + $centers = $this->centerResolverManager->resolveCenters($entity); + $reachableCenters = $this->authorizationHelper->getReachableCenters(self::APPLY_ALL_TRANSITIONS); + + foreach ($centers as $center) { + if (in_array($center, $reachableCenters, true)) { + return true; + } + } + + return false; + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Authorization/EntityWorkflowTransitionVoterTest.php b/src/Bundle/ChillMainBundle/Tests/Authorization/EntityWorkflowTransitionVoterTest.php new file mode 100644 index 000000000..035d4cf58 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Authorization/EntityWorkflowTransitionVoterTest.php @@ -0,0 +1,144 @@ +prophesize(EntityWorkflowHandlerInterface::class); + $handler->getRelatedEntity($entityWorkflow)->willReturn($object); + + $entityWorkflowManager = $this->prophesize(EntityWorkflowManager::class); + $entityWorkflowManager->getHandler($entityWorkflow)->willReturn($handler); + + $centerResolver = $this->prophesize(CenterResolverManagerInterface::class); + $centerResolver->resolveCenters($object)->willReturn([$center, new Center()]); + + $autorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class); + $autorizationHelper->getReachableCenters('CHILL_MAIN_WORKFLOW_APPLY_ALL_TRANSITION') + ->willReturn([$center, new Center()]); + + $token = new UsernamePasswordToken($user, 'default', $user->getRoles()); + + $accessDecision = $this->prophesize(AccessDecisionManagerInterface::class); + $accessDecision->decide($token, [EntityWorkflowVoter::SEE], $entityWorkflow) + ->willReturn(true)->shouldBeCalled(); + + $voter = new EntityWorkflowTransitionVoter( + $entityWorkflowManager->reveal(), + $autorizationHelper->reveal(), + $centerResolver->reveal(), + $accessDecision->reveal(), + ); + + self::assertEquals(Voter::ACCESS_GRANTED, $voter->vote($token, $entityWorkflow->getCurrentStep(), ['CHILL_MAIN_WORKFLOW_APPLY_ALL_TRANSITION'])); + } + + public function testVoteOnAttributeCenterNotReachable(): void + { + $entityWorkflow = new EntityWorkflow(); + $object = new \stdClass(); + $user = new User(); + + $handler = $this->prophesize(EntityWorkflowHandlerInterface::class); + $handler->getRelatedEntity($entityWorkflow)->willReturn($object); + + $entityWorkflowManager = $this->prophesize(EntityWorkflowManager::class); + $entityWorkflowManager->getHandler($entityWorkflow)->willReturn($handler); + + $centerResolver = $this->prophesize(CenterResolverManagerInterface::class); + $centerResolver->resolveCenters($object)->willReturn([new Center()]); + + $autorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class); + $autorizationHelper->getReachableCenters('CHILL_MAIN_WORKFLOW_APPLY_ALL_TRANSITION') + ->willReturn([new Center()]); + + $token = new UsernamePasswordToken($user, 'default', $user->getRoles()); + + $accessDecision = $this->prophesize(AccessDecisionManagerInterface::class); + $accessDecision->decide($token, [EntityWorkflowVoter::SEE], $entityWorkflow) + ->willReturn(true)->shouldBeCalled(); + + $voter = new EntityWorkflowTransitionVoter( + $entityWorkflowManager->reveal(), + $autorizationHelper->reveal(), + $centerResolver->reveal(), + $accessDecision->reveal(), + ); + + self::assertEquals(Voter::ACCESS_DENIED, $voter->vote($token, $entityWorkflow->getCurrentStep(), ['CHILL_MAIN_WORKFLOW_APPLY_ALL_TRANSITION'])); + } + + public function testVoteNotOnSupportedAttribute(): void + { + $entityWorkflow = new EntityWorkflow(); + $object = new \stdClass(); + $user = new User(); + + $handler = $this->prophesize(EntityWorkflowHandlerInterface::class); + $handler->getRelatedEntity($entityWorkflow)->willReturn($object); + + $entityWorkflowManager = $this->prophesize(EntityWorkflowManager::class); + $entityWorkflowManager->getHandler($entityWorkflow)->willReturn($handler); + + $centerResolver = $this->prophesize(CenterResolverManagerInterface::class); + $centerResolver->resolveCenters($object)->willReturn([new Center()]); + + $autorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class); + $autorizationHelper->getReachableCenters('CHILL_MAIN_WORKFLOW_APPLY_ALL_TRANSITION') + ->willReturn([new Center()]); + + $token = new UsernamePasswordToken($user, 'default', $user->getRoles()); + + $accessDecision = $this->prophesize(AccessDecisionManagerInterface::class); + $accessDecision->decide($token, [EntityWorkflowVoter::SEE], $entityWorkflow) + ->willReturn(true); + + $voter = new EntityWorkflowTransitionVoter( + $entityWorkflowManager->reveal(), + $autorizationHelper->reveal(), + $centerResolver->reveal(), + $accessDecision->reveal(), + ); + + self::assertEquals(Voter::ACCESS_ABSTAIN, $voter->vote($token, new \stdClass(), ['CHILL_MAIN_WORKFLOW_APPLY_ALL_TRANSITION'])); + self::assertEquals(Voter::ACCESS_ABSTAIN, $voter->vote($token, $entityWorkflow->getCurrentStep(), ['SOMETHING_ELSE'])); + } +} diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 003dcbee5..495b9a3bd 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -532,6 +532,8 @@ workflow: On hold: En attente Automated transition: Transition automatique waiting_for_signature: En attente de signature + Permissions: Workflows (suivi de décision) + signature_zone: title: Signatures électroniques @@ -551,6 +553,7 @@ workflow: Subscribe final: Recevoir une notification à l'étape finale Subscribe all steps: Recevoir une notification à chaque étape +CHILL_MAIN_WORKFLOW_APPLY_ALL_TRANSITION: Appliquer les transitions sur tous les workflows notification: Notification: Notification