<?php
namespace App\Security\Voter\UserRequest;
use App\DTO\UserRequest\StatusPostDTO;
use App\Entity\User;
use App\Manager\UserRightsManagerInterface;
use App\Security\Voter\UserRequest\UserOwnsRequestVoter;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use WebServiceCollectionBundle\Model\AtlasPce\UserRequestRequestModel;
class UserMayPostToStatus extends Voter
{
public const POST_STATUS_ATTR = 'POST_STATUS';
public const ROLES_REQUIREMENTS = [
'to_qualify' => [
UserRightsManagerInterface::ROLE_CLIENT,
UserRightsManagerInterface::ROLE_SUEZ
],
'to_specify' => [ UserRightsManagerInterface::ROLE_SUEZ ],
'to_plan' => [ UserRightsManagerInterface::ROLE_SUEZ ],
'planned' => [ UserRightsManagerInterface::ROLE_SUEZ ],
'ongoing' => [ UserRightsManagerInterface::ROLE_SUEZ ],
'done' => [ UserRightsManagerInterface::ROLE_SUEZ ],
'closed_rejected' => [ UserRightsManagerInterface::ROLE_ADMIN ],
'closed' => [ UserRightsManagerInterface::ROLE_ADMIN ],
];
/**
* @var AuthorizationCheckerInterface
*/
protected $authChecker;
/**
* @var LoggerInterface
*/
protected $logger;
/**
* @param AuthorizationCheckerInterface $authChecker
* @param LoggerInterface $logger
*/
public function __construct(
AuthorizationCheckerInterface $authChecker,
LoggerInterface $logger
) {
$this->authChecker = $authChecker;
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
public function supports($attribute, $subject)
{
$isPostStatus = static::POST_STATUS_ATTR === $attribute;
$subjectIsStatusPostDTO = $subject instanceof StatusPostDTO;
return $isPostStatus && $subjectIsStatusPostDTO;
}
/**
* {@inheritdoc}
*/
public function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
$user = $token->getUser();
switch ($attribute) {
case static::POST_STATUS_ATTR:
return $this->mayPost($subject, $user);
}
throw new \LogicException('This code should not be reached!');
}
/**
* Test if a user is allowed to view a request.
*
* @param StatusPostDTO $dto
* @param User $tokenAttributes
*
* @return bool
*/
protected function mayPost(StatusPostDTO $dto, User $user): bool
{
$status = $dto->getTargetStatus();
if (!array_key_exists($status, static::ROLES_REQUIREMENTS)) {
$this->logger->warning('[UserMayPostToStatus] Unknown status requirements: {status}.', [
'status' => $status,
]);
return false;
}
$requiredRoles = static::ROLES_REQUIREMENTS[$status];
$roles = $user->getRoles();
$hasRole = (bool) count(array_intersect($requiredRoles, $roles));
if (!$hasRole) {
$this->logger->info('[UserMayPostToStatus] User cannot modify status to {status}, because one of the following roles is required {roles}', [
'status' => $status,
'roles' => json_encode($requiredRoles),
]);
return false;
}
/*
Things get tricky here. There are two use cases:
1. the user is Suez, and then only the role check is needed. Suez user can act on every request just on a role-based checked.
2. the user is client, we need to check both the role, but also if the client is the creator or an admin to avoid one client posting status of another client.
*/
if (in_array(UserRightsManagerInterface::ROLE_SUEZ, $roles)) {
$this->logger->info('[UserMayPostToStatus] User can modify status to {status}, since only it only requires a Suez role to do so. Required roles: {roles}', [
'status' => $status,
'roles' => json_encode($requiredRoles),
]);
// Suez role is needed here, so we just need to check if the user has the role.
return true;
}
// Below this point, we must check both the role and the creator id.
$request = $dto->getRequest();
$userIsOwnerOrAdmin = $this->authChecker->isGranted(UserOwnsRequestVoter::USER_OWNS_REQUEST, $request);
if ($userIsOwnerOrAdmin) {
$this->logger->info('[UserMayPostToStatus] User can modify status to {status}, since user is either creator or admin.', [
'status' => $status,
'roles' => json_encode($requiredRoles),
]);
return true;
}
// No need to log anything more in this voter, UserOwnsRequestVoter will already have logged everything relevant.
return false;
}
}