<?php
namespace App\Security\Voter;
use App\Entity\Role;
use App\Entity\User;
use App\Service\UserService;
use LogicException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Security;
class UserVoter extends Voter
{
public const VIEW_USER = 'view_user';
public const EDIT_USER = 'edit_user';
public const LIST_USERS = 'list_users';
public const CREATE_USER = 'create_user';
public const CHANGE_PASS = 'change_pass';
public const LOCK_USER = 'lock_user';
public const UNLOCK_USER = 'unlock_user';
public const DELETE_USER = 'delete_user';
/** @var UserService $userService */
private $userService;
/**
* UserVoter constructor.
* @param UserService $userService
*/
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
/**
* @inheritDoc
*/
protected function supports($attribute, $subject): bool
{
// if the attribute isn't one we support, return false
if (
!in_array($attribute, [
self::VIEW_USER,
self::EDIT_USER,
self::LIST_USERS,
self::CREATE_USER,
self::CHANGE_PASS,
self::LOCK_USER,
self::UNLOCK_USER,
self::DELETE_USER
])
) {
return false;
}
// only vote on User objects inside this voter
if (!$subject instanceof User) {
return false;
}
return true;
}
/**
* @inheritDoc
*/
protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user instanceof User) {
// the user must be logged in; if not, deny access
return false;
}
// you know $subject is a User object, thanks to supports
/** @var User $userSubject */
$userSubject = $subject;
switch ($attribute) {
case self::CREATE_USER:
return $this->canCreate($user);
case self::VIEW_USER:
return $this->canView($userSubject, $user);
case self::EDIT_USER:
return $this->canEdit($userSubject, $user);
case self::LIST_USERS:
return $this->canList($user);
case self::CHANGE_PASS:
return $this->canChangePass($userSubject, $user);
case self::LOCK_USER:
return $this->canLock($userSubject, $user);
case self::UNLOCK_USER:
return $this->canUnlock($userSubject, $user);
case self::DELETE_USER:
return $this->canDelete($userSubject, $user);
}
throw new LogicException('This code should not be reached!');
}
/**
* @param User $userSubject
* @param User $user
* @return bool
*/
private function canView(User $userSubject, User $user): bool
{
// if they can edit, they can view
if ($this->canEdit($userSubject, $user)) {
return true;
}
return false;
}
/**
* @param User $user
* @return bool
*/
private function canCreate(User $user): bool
{
return $this->userService->isAdmin($user);
}
/**
* @param User $userSubject
* @param User $user
* @return bool
*/
private function canEdit(User $userSubject, User $user): bool
{
if ($user === $userSubject) {
// this assumes that the data object has a getId() method
// to get the entity of the user who owns this data object
return true;
}
if ($userSubject->isSuperAdmin()) {
return false;
}
return $this->canByRoles($userSubject, $user);
}
/**
* @param User $user
* @return bool
*/
private function canList(User $user): bool
{
return $this->userService->isAdmin($user);
}
/**
* @param User $userSubject
* @param User $user
* @return bool
*/
private function canChangePass(User $userSubject, User $user): bool
{
return $user === $userSubject;
}
/**
* @param User $userSubject
* @param User $user
* @return bool
*/
private function canUnlock(User $userSubject, User $user): bool
{
if ($this->canLock($userSubject, $user)) {
return true;
}
return false;
}
/**
* @param User $userSubject
* @param User $user
* @return bool
*/
private function canDelete(User $userSubject, User $user): bool
{
if ($this->canLock($userSubject, $user)) {
return true;
}
return false;
}
/**
* @param User $userSubject
* @param User $user
* @return bool
*/
private function canLock(User $userSubject, User $user): bool
{
if ($userSubject->isSuperAdmin() || $user === $userSubject) {
return false;
}
return $this->canByRoles($userSubject, $user);
}
/**
* @param User $userSubject
* @param User $user
* @return bool
*/
private function canByRoles(User $userSubject, User $user): bool
{
// logged user is admin of userSubject
/** @var Role $role */
foreach ($user->getRolesAsObject() as $role) {
if ($role->getChildren()->count()) {
return $this->userService->isSubordinate($user, $userSubject);
}
}
return false;
}
}