chill-bundles/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowTransitionVoter.php
Julien Fastré cf2fe1bba7
Add guards and tests for entity workflow transitions
Introduced EntityWorkflowGuardUnsignedTransition to block transitions with pending signatures. Implemented a new center resolver and added comprehensive unit tests for verifying transition rules and permissions.
2024-09-24 11:08:22 +02:00

90 lines
3.0 KiB
PHP

<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Security\Authorization;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Workflow\EntityWorkflowManager;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
/**
* A voter class that determines if a user has permission to apply all transitions
* in a workflow based on their roles and the centers they have access to.
*/
final class EntityWorkflowTransitionVoter extends Voter implements ProvideRoleHierarchyInterface
{
final public const APPLY_ALL_TRANSITIONS = 'CHILL_MAIN_WORKFLOW_APPLY_ALL_TRANSITION';
public function __construct(
private readonly EntityWorkflowManager $workflowManager,
private readonly AuthorizationHelperForCurrentUserInterface $authorizationHelper,
private readonly CenterResolverManagerInterface $centerResolverManager,
private readonly AccessDecisionManagerInterface $accessDecisionManager,
) {}
public function getRoles(): array
{
return [self::APPLY_ALL_TRANSITIONS];
}
public function getRolesWithoutScope(): array
{
return [self::APPLY_ALL_TRANSITIONS];
}
public function getRolesWithHierarchy(): array
{
return [
'workflow.Permissions' => [
self::APPLY_ALL_TRANSITIONS,
],
];
}
protected function supports(string $attribute, $subject): bool
{
return self::APPLY_ALL_TRANSITIONS === $attribute && ($subject instanceof EntityWorkflowStep || $subject instanceof EntityWorkflow);
}
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
/** @var EntityWorkflowStep|EntityWorkflow $subject */
$entityWorkflow = $subject instanceof EntityWorkflowStep ? $subject->getEntityWorkflow() : $subject;
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;
}
}