<?php
namespace Platform\SecurityBundle\Service\Voters;
use Cms\CoreBundle\Util\Doctrine\EntityManager;
use Platform\SecurityBundle\Entity\Identity\Account;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
/**
* Handles the checking of permissions to see if a person is able to impersonate users.
*
* Class ImpersonateVoter
* @package Platform\SecurityBundle\Service\Voters
*/
final class ImpersonateVoter implements VoterInterface
{
/**
* Link to current requests.
*
* @var RequestStack
*/
public RequestStack $requestStack;
/**
* Will need to do DB work, mainly loading impersonated user.
*
* @var EntityManager;
*/
public EntityManager $em;
/**
* @param RequestStack $requestStack
* @param EntityManager $em
*/
public function __construct(RequestStack $requestStack, EntityManager $em)
{
$this->requestStack = $requestStack;
$this->em = $em;
}
/**
* {@inheritdoc}
*/
public function vote(
TokenInterface $token,
$subject,
array $attributes
): int
{
// TODO: prevent double impersonation? maybe ensure the token isn't already an impersonated token...
// do not do anything if not legit check
if (count($attributes) !== 1 || $attributes[0] !== 'ROLE_ALLOWED_TO_SWITCH') {
return VoterInterface::ACCESS_ABSTAIN;
}
// get the current user from the token and ensure proper type
$user = $token->getUser();
if ( ! $user instanceof Account) {
return VoterInterface::ACCESS_ABSTAIN;
}
// NOTE:
// from here, we need to either grant or deny access
// we have ensured that we are trying to do something with impersonation, so a decision needs made!
// make sure this account is able to impersonate to begin with
if ( ! $user->getSpecialPermissions()->canImpersonate()) {
return VoterInterface::ACCESS_DENIED;
}
// get the account we are trying to impersonate
$impersonated = $this->getImpersonatedAccount();
// ensure that there is a real account to switch to
if ( ! $impersonated instanceof Account) {
return VoterInterface::ACCESS_DENIED;
}
// if a user who is being impersonated is a superuser and the one requesting impersonation is not, must deny
if ($impersonated->getSpecialPermissions()->isSuperUser() && ! $user->getSpecialPermissions()->isSuperUser()) {
return VoterInterface::ACCESS_DENIED;
}
// if we are impersonating an internal user, and we are not, we must deny
if ($impersonated->isInternal() && ! $user->isInternal()) {
return VoterInterface::ACCESS_DENIED;
}
// make sure user can't impersonate themselves
if ($user->getId() === $impersonated->getId()) {
return VoterInterface::ACCESS_DENIED;
}
// we have run all the checks we need to run, so grant the impersonation action
return VoterInterface::ACCESS_GRANTED;
}
/**
* Tries to load impersonated account.
*
* @return Account|null
*/
private function getImpersonatedAccount(): ?Account
{
// make sure we have the things we need to try and pull the id from the request
// TODO: should this use the master request instead?
if ( ! $this->requestStack->getCurrentRequest()) {
return null;
}
// obtain the id of the user we are trying to switch to
$id = $this->requestStack->getCurrentRequest()->get('_switch_user');
if ( ! $id) {
return null;
}
// attempt lookup in the database
return $this->em->getRepository(Account::class)->find($id);
}
}