141 lines
4.5 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\TaskBundle\Security\Authorization;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\TaskBundle\Entity\AbstractTask;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use function in_array;
final class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
{
public const CREATE_COURSE = 'CHILL_TASK_TASK_CREATE_FOR_COURSE';
public const CREATE_PERSON = 'CHILL_TASK_TASK_CREATE_FOR_PERSON';
public const DELETE = 'CHILL_TASK_TASK_DELETE';
public const ROLES = [
self::CREATE_COURSE,
self::CREATE_PERSON,
self::DELETE,
self::SHOW,
self::UPDATE,
];
public const SHOW = 'CHILL_TASK_TASK_SHOW';
public const UPDATE = 'CHILL_TASK_TASK_UPDATE';
private VoterHelperInterface $voter;
public function __construct(
private AccessDecisionManagerInterface $accessDecisionManager,
private EventDispatcherInterface $eventDispatcher,
private LoggerInterface $logger,
VoterHelperFactoryInterface $voterFactory
) {
$this->voter = $voterFactory
->generate(AbstractTask::class)
->addCheckFor(AbstractTask::class, self::ROLES)
->addCheckFor(Person::class, [self::SHOW, self::CREATE_PERSON])
->addCheckFor(AccompanyingPeriod::class, [self::SHOW, self::CREATE_COURSE])
->addCheckFor(null, [self::SHOW])
->build();
}
public function getRoles(): array
{
return self::ROLES;
}
public function getRolesWithHierarchy(): array
{
return [
'Task' => self::ROLES,
];
}
public function getRolesWithoutScope(): array
{
return [];
}
public function supports($attribute, $subject)
{
return $this->voter->supports($attribute, $subject);
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
if (!$token->getUser() instanceof User) {
return false;
}
$event = new AuthorizationEvent($subject, $attribute, $token);
$this->eventDispatcher->dispatch(AuthorizationEvent::VOTE, $event);
if ($event->hasVote()) {
$this->logger->debug('The TaskVoter is overriding by '
. AuthorizationEvent::VOTE, [
'vote' => $event->getVote(),
'task_id' => $subject->getId(),
]);
return $event->getVote();
}
// do pre-flight check, relying on other decision manager
// those pre-flight check concern associated entities
if ($subject instanceof AbstractTask) {
// a user can always see his own tasks
if ($subject->getAssignee() === $token->getUser()) {
return true;
}
if (null !== $person = $subject->getPerson()) {
if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) {
return false;
}
} elseif (null !== $period = $subject->getCourse()) {
if (!$this->accessDecisionManager->decide($token, [AccompanyingPeriodVoter::SEE], $period)) {
return false;
}
}
}
if ($subject instanceof AccompanyingPeriod) {
if (AccompanyingPeriod::STEP_CLOSED === $subject->getStep()) {
if (in_array($attribute, [self::UPDATE, self::CREATE_COURSE, self::DELETE], true)) {
return false;
}
}
}
// do regular check.
return $this->voter->voteOnAttribute($attribute, $subject, $token);
}
}