diff --git a/src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php b/src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php index e3cc9c408..3d7b95c61 100644 --- a/src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php +++ b/src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Workflow; use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument; +use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter; use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface; use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; @@ -36,6 +37,13 @@ class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandler $this->translator = $translator; } + public function getDeletionRoles(): array + { + return [ + AccompanyingCourseDocumentVoter::DELETE, + ]; + } + public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array { $course = $this->getRelatedEntity($entityWorkflow) @@ -66,6 +74,18 @@ class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandler return $this->repository->find($entityWorkflow->getRelatedEntityId()); } + /** + * @param AccompanyingCourseDocument $object + * + * @return array[] + */ + public function getRelatedObjects(object $object): array + { + return [ + ['entityClass' => AccompanyingCourseDocument::class, 'entityId' => $object->getId()], + ]; + } + public function getRoleShow(EntityWorkflow $entityWorkflow): ?string { return null; @@ -84,6 +104,11 @@ class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandler ]; } + public function isObjectSupported(object $object): bool + { + return $object instanceof AccompanyingCourseDocument; + } + public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool { return $entityWorkflow->getRelatedEntityClass() === AccompanyingCourseDocument::class; diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php index 5ad7348ee..09668f0e3 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php @@ -256,6 +256,13 @@ class ChillMainExtension extends Extension implements 'channels' => ['chill'], ]); + $container->prependExtensionConfig('security', [ + 'access_decision_manager' => [ + 'strategy' => 'unanimous', + 'allow_if_all_abstain' => false, + ], + ]); + //add crud api $this->prependCruds($container); } diff --git a/src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowRepository.php b/src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowRepository.php index ab58801fc..a73da74b0 100644 --- a/src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/Workflow/EntityWorkflowRepository.php @@ -55,6 +55,30 @@ class EntityWorkflowRepository implements ObjectRepository return (int) $qb->getQuery()->getSingleScalarResult(); } + public function countRelatedWorkflows(array $relateds): int + { + $qb = $this->repository->createQueryBuilder('w'); + + $orX = $qb->expr()->orX(); + $i = 0; + + foreach ($relateds as $related) { + $orX->add( + $qb->expr()->andX( + $qb->expr()->eq('w.relatedEntityClass', ':entity_class_' . $i), + $qb->expr()->eq('w.relatedEntityId', ':entity_id_' . $i) + ) + ); + $qb + ->setParameter('entity_class_' . $i, $related['entityClass']) + ->setParameter('entity_id_' . $i, $related['entityId']); + ++$i; + } + $qb->where($orX); + + return $qb->select('COUNT(w)')->getQuery()->getSingleScalarResult(); + } + public function find($id): ?EntityWorkflow { return $this->repository->find($id); diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/WorkflowEntityDeletionVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/WorkflowEntityDeletionVoter.php new file mode 100644 index 000000000..079c43c3b --- /dev/null +++ b/src/Bundle/ChillMainBundle/Security/Authorization/WorkflowEntityDeletionVoter.php @@ -0,0 +1,65 @@ +handlers = $handlers; + $this->entityWorkflowRepository = $entityWorkflowRepository; + } + + protected function supports($attribute, $subject) + { + if (!is_object($subject)) { + return false; + } + + foreach ($this->handlers as $handler) { + if ($handler->isObjectSupported($subject) + && in_array($attribute, $handler->getDeletionRoles($subject), true)) { + return true; + } + } + + return false; + } + + protected function voteOnAttribute($attribute, $subject, TokenInterface $token) + { + foreach ($this->handlers as $handler) { + if ($handler->isObjectSupported($subject)) { + return 0 === $this->entityWorkflowRepository->countRelatedWorkflows( + $handler->getRelatedObjects($subject) + ); + } + } + + throw new RuntimeException('no handlers found'); + } +} diff --git a/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowHandlerInterface.php b/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowHandlerInterface.php index 93ba4351e..b63546742 100644 --- a/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowHandlerInterface.php +++ b/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowHandlerInterface.php @@ -15,12 +15,19 @@ use Chill\MainBundle\Entity\Workflow\EntityWorkflow; interface EntityWorkflowHandlerInterface { + /** + * @return array|string[] + */ + public function getDeletionRoles(): array; + public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array; public function getEntityTitle(EntityWorkflow $entityWorkflow, array $options = []): string; public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?object; + public function getRelatedObjects(object $object): array; + /** * Return a string representing the role required for seeing the workflow. * @@ -33,6 +40,8 @@ interface EntityWorkflowHandlerInterface public function getTemplateData(EntityWorkflow $entityWorkflow, array $options = []): array; + public function isObjectSupported(object $object): bool; + public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool; public function supportsFreeze(EntityWorkflow $entityWorkflow, array $options = []): bool; diff --git a/src/Bundle/ChillMainBundle/config/services/security.yaml b/src/Bundle/ChillMainBundle/config/services/security.yaml index b884a92fc..3347871e3 100644 --- a/src/Bundle/ChillMainBundle/config/services/security.yaml +++ b/src/Bundle/ChillMainBundle/config/services/security.yaml @@ -75,3 +75,9 @@ services: $locker: '@Chill\MainBundle\Security\PasswordRecover\PasswordRecoverLocker' tags: - { name: security.voter } + + Chill\MainBundle\Security\Authorization\WorkflowEntityDeletionVoter: + autoconfigure: true + autowire: true + arguments: + $handlers: !tagged_iterator chill_main.workflow_handler diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_item.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_item.html.twig index 5af04c843..d7e4f4e96 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_item.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_item.html.twig @@ -130,11 +130,13 @@ href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}" > -
  • - -
  • + {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_DELETE', w) %} +
  • + +
  • + {% endif %} {% endif %} diff --git a/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkVoter.php b/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkVoter.php index 7193c524e..24df2bbc5 100644 --- a/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkVoter.php +++ b/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodWorkVoter.php @@ -24,6 +24,8 @@ class AccompanyingPeriodWorkVoter extends Voter { public const CREATE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_CREATE'; + public const DELETE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_DELETE'; + public const SEE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_SEE'; public const UPDATE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE'; @@ -60,6 +62,7 @@ class AccompanyingPeriodWorkVoter extends Voter case self::CREATE: case self::UPDATE: + case self::DELETE: return $this->security->isGranted(AccompanyingPeriodVoter::EDIT, $subject->getAccompanyingPeriod()); default: @@ -86,6 +89,6 @@ class AccompanyingPeriodWorkVoter extends Voter private function getRoles(): array { - return [self::SEE, self::CREATE, self::UPDATE]; + return [self::SEE, self::CREATE, self::UPDATE, self::DELETE]; } } diff --git a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php index eac460ee5..1e02d0465 100644 --- a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php +++ b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php @@ -37,6 +37,13 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW $this->translator = $translator; } + public function getDeletionRoles(): array + { + return [ + '_', + ]; + } + public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array { $doc = $this->getRelatedEntity($entityWorkflow); @@ -63,6 +70,18 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW return $this->repository->find($entityWorkflow->getRelatedEntityId()); } + /** + * @param AccompanyingPeriodWorkEvaluationDocument $object + * + * @return array[] + */ + public function getRelatedObjects(object $object): array + { + return [ + ['entityClass' => AccompanyingPeriodWorkEvaluationDocument::class, $object->getId()], + ]; + } + public function getRoleShow(EntityWorkflow $entityWorkflow): ?string { return AccompanyingPeriodWorkEvaluationDocumentVoter::SEE; @@ -84,6 +103,11 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW ]; } + public function isObjectSupported(object $object): bool + { + return $object instanceof AccompanyingPeriodWorkEvaluationDocument; + } + public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool { return $entityWorkflow->getRelatedEntityClass() === AccompanyingPeriodWorkEvaluationDocument::class; diff --git a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationWorkflowHandler.php b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationWorkflowHandler.php index 5af305ac5..7bbf54eef 100644 --- a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationWorkflowHandler.php +++ b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationWorkflowHandler.php @@ -15,6 +15,7 @@ use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; +use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument; use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationRepository; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkEvaluationVoter; use Symfony\Contracts\Translation\TranslatorInterface; @@ -37,6 +38,11 @@ class AccompanyingPeriodWorkEvaluationWorkflowHandler implements EntityWorkflowH $this->translator = $translator; } + public function getDeletionRoles(): array + { + return ['_']; + } + public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array { $evaluation = $this->getRelatedEntity($entityWorkflow); @@ -61,6 +67,20 @@ class AccompanyingPeriodWorkEvaluationWorkflowHandler implements EntityWorkflowH return $this->repository->find($entityWorkflow->getRelatedEntityId()); } + /** + * @param AccompanyingPeriodWorkEvaluation $object + */ + public function getRelatedObjects(object $object): array + { + $relateds[] = ['entityClass' => AccompanyingPeriodWorkEvaluation::class, 'entityId' => $object->getId()]; + + foreach ($object->getDocuments() as $doc) { + $relateds[] = ['entityClass' => AccompanyingPeriodWorkEvaluationDocument::class, 'entityId' => $doc->getId()]; + } + + return $relateds; + } + public function getRoleShow(EntityWorkflow $entityWorkflow): ?string { return AccompanyingPeriodWorkEvaluationVoter::SEE; @@ -79,6 +99,11 @@ class AccompanyingPeriodWorkEvaluationWorkflowHandler implements EntityWorkflowH ]; } + public function isObjectSupported(object $object): bool + { + return $object instanceof AccompanyingPeriodWorkEvaluation; + } + public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool { return $entityWorkflow->getRelatedEntityClass() === AccompanyingPeriodWorkEvaluation::class; diff --git a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkWorkflowHandler.php b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkWorkflowHandler.php index 1385cb841..90e7b4e46 100644 --- a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkWorkflowHandler.php +++ b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkWorkflowHandler.php @@ -15,7 +15,10 @@ use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; +use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; +use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument; use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository; +use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter; use Symfony\Contracts\Translation\TranslatorInterface; class AccompanyingPeriodWorkWorkflowHandler implements EntityWorkflowHandlerInterface @@ -36,6 +39,11 @@ class AccompanyingPeriodWorkWorkflowHandler implements EntityWorkflowHandlerInte $this->translator = $translator; } + public function getDeletionRoles(): array + { + return [AccompanyingPeriodWorkVoter::DELETE]; + } + public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array { return [ @@ -58,6 +66,24 @@ class AccompanyingPeriodWorkWorkflowHandler implements EntityWorkflowHandlerInte return $this->repository->find($entityWorkflow->getRelatedEntityId()); } + /** + * @param AccompanyingPeriodWork $object + */ + public function getRelatedObjects(object $object): array + { + $relateds[] = ['entityClass' => AccompanyingPeriodWork::class, 'entityId' => $object->getId()]; + + foreach ($object->getAccompanyingPeriodWorkEvaluations() as $evaluation) { + $relateds[] = ['entityClass' => AccompanyingPeriodWorkEvaluation::class, 'entityId' => $evaluation->getId()]; + + foreach ($evaluation->getDocuments() as $doc) { + $relateds[] = ['entityClass' => AccompanyingPeriodWorkEvaluationDocument::class, 'entityId' => $doc->getId()]; + } + } + + return $relateds; + } + public function getRoleShow(EntityWorkflow $entityWorkflow): ?string { return null; @@ -76,6 +102,11 @@ class AccompanyingPeriodWorkWorkflowHandler implements EntityWorkflowHandlerInte ]; } + public function isObjectSupported(object $object): bool + { + return $object instanceof AccompanyingPeriodWork; + } + public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool { return $entityWorkflow->getRelatedEntityClass() === AccompanyingPeriodWork::class;