<?php
//------------------------------------------------------------------------------
// src/Security/MissionShareDemandVoter.php
//------------------------------------------------------------------------------
namespace App\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Doctrine\Persistence\ManagerRegistry;
use App\Entity\Access;
use App\Entity\Config\Config;
use App\Entity\Config\Module;
use App\Entity\HR\AccessFunction;
use App\Entity\Mission\Mission;
use App\Entity\Mission\MissionShareDemand;
use App\Entity\Platform\SocietyGroup\SocietyGroupFavorite;
use App\Entity\Security\Acl;
use App\Entity\Security\AclPermission;
use App\Services\LogTools;
use App\Services\Config\ModuleTools;
class MissionShareDemandVoter extends Voter
{
//--------------------------------------------------------------------------------
// is_granted constants
// Limit Commissions to JCAF SocietyGroup for now
const USE_MISSION_SHARE_COMMISSIONS = "use_mission_share_commissions";
// The ones below apply to the demands belonging to the current society group
const ANNUL_SHARE = "annul_share_mission_demand";
// The ones below apply to the demands shared with the current society group
const LISTING = "list_shared_mission_demands";
const VIEW = "view_shared_mission_demand";
const ACCEPT = "accept_shared_mission_demand";
const REFUSE = "refuse_shared_mission_demand";
const ACCEPT_OR_REFUSE = "accept_or_refuse_shared_mission_demand";
const REFUSE_ACCEPTED = "refuse_accepted_shared_mission_demand";
const IS_GRANTED_CONSTANTS = array(
self::USE_MISSION_SHARE_COMMISSIONS,
self::ANNUL_SHARE,
self::LISTING,
self::VIEW,
self::ACCEPT,
self::REFUSE,
self::ACCEPT_OR_REFUSE,
self::REFUSE_ACCEPTED,
);
//--------------------------------------------------------------------------------
// acl constants
// The ones below apply to the demands shared with the current society group
const ACL_PERM_LIST = 'mission_shared_list';
const ACL_PERM_ACCEPT_REFUSE = 'mission_shared_accept_refuse';
//--------------------------------------------------------------------------------
public function __construct(AccessDecisionManagerInterface $accessDecisionManager, ManagerRegistry $doctrine, ModuleTools $moduleTools, LogTools $logTools)
{
$this->accessDecisionManager = $accessDecisionManager;
$this->em = $doctrine->getManager();
$this->moduleTools = $moduleTools;
$this->logTools = $logTools;
$this->aclRepository = $this->em->getRepository(Acl::class);
$this->aclPermissionRepository = $this->em->getRepository(AclPermission::class);
}
// Plan.io Task #4453 [See AccessVoter for details]
public function supportsAttribute(string $attribute): bool
{
return in_array($attribute, self::IS_GRANTED_CONSTANTS, true);
}
protected function supports(string $attribute, $subject): bool
{
// if the attribute isn't one we support, return false
if (!in_array($attribute, self::IS_GRANTED_CONSTANTS))
{
return false;
}
// only vote on MissionShareDemand objects inside this voter
if ($subject !== null && !$subject instanceof MissionShareDemand)
{
return false;
}
return true;
}
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user instanceof Access)
{
// the user must be logged in; if not, deny access
return false;
}
// The user must have a function; if not deny access
$function = $user->getFunction();
if ($function === null) return false;
// Plan.io Task #3710 : Get current group
$currentGroup = $user->getSocietyGroup();
if ($currentGroup === null)
return false;
$this->currentGroup = $currentGroup;
// This one also needs clients or clients light
if (
$this->moduleTools->isInactiveByCode($currentGroup, Module::MODULE_CLIENT) &&
$this->moduleTools->isInactiveByCode($currentGroup, Module::MODULE_CLIENT_LIGHT)
)
{
return false;
}
// you know $subject is a MissionShareDemand object, thanks to supports
/** @var MissionShareDemand $missionShareDemand */
$missionShareDemand = $subject;
switch ($attribute)
{
case self::USE_MISSION_SHARE_COMMISSIONS:
return $this->canUseMissionShareCommissions();
case self::ANNUL_SHARE:
return $this->canAnnulShare($missionShareDemand, $user, $function, $token);
case self::LISTING:
return $this->canList($user, $function);
case self::VIEW:
return $this->canView($missionShareDemand, $user, $function);
case self::ACCEPT:
return $this->canAcceptOrRefuse($missionShareDemand, $user, $function);
case self::REFUSE:
return $this->canAcceptOrRefuse($missionShareDemand, $user, $function);
case self::REFUSE_ACCEPTED:
{
return $this->canRefuseAccepted($missionShareDemand, $user, $function);
}
case self::ACCEPT_OR_REFUSE:
return $this->canAcceptOrRefuse($missionShareDemand, $user, $function);
}
throw new \LogicException('This code should not be reached!');
}
private function canUseMissionShareCommissions()
{
if ($this->currentGroup->isJcaf())
{
return true;
}
return false;
}
private function canAnnulShare(MissionShareDemand $missionShareDemand, Access $user, AccessFunction $function, $token)
{
// This requires the sharing module to be activated
if ($this->moduleTools->isInactiveByCode($this->currentGroup, Module::MODULE_MISSION_PLUS))
{
return false;
}
// Check current group affectation
// A society group can only annul the demands it has created
$missionShareDemandGroup = $missionShareDemand->getEmitter();
if ($missionShareDemandGroup === null)
return false;
if (!$this->currentGroup->equals($missionShareDemandGroup))
return false;
// Cannot annul a demand that is accepted / expired / refused / annulled
// Always check expiration first
if ($missionShareDemand->isExpired())
{
return false;
}
if ($missionShareDemand->isAccepted())
{
return false;
}
if ($missionShareDemand->isRefused())
{
return false;
}
if ($missionShareDemand->isAnnulled())
{
return false;
}
// All looks good
// Apply standard sharing conditions
return $this->accessDecisionManager->decide($token, ['share_mission'], $missionShareDemand->getMission());
}
private function canList(Access $user, AccessFunction $function)
{
// Module activated ?
// At least one, not necessarly the plus one
// MODULE_MISSION_PLUS is required only if we want to share missions,
// not accept / refuse missions shared with us
if (
$this->moduleTools->isInactiveByCode($this->currentGroup, Module::MODULE_MISSION) &&
$this->moduleTools->isInactiveByCode($this->currentGroup, Module::MODULE_MISSION_LIGHT) &&
$this->moduleTools->isInactiveByCode($this->currentGroup, Module::MODULE_MISSION_PLUS)
)
{
return false;
}
// Task plan.io #4096
// Check has favorite
$societyGroupFavorites = $this->em->getRepository(SocietyGroupFavorite::class)
->findBy(array('favorite' => $user->getSocietyGroup(),));
if (sizeof($societyGroupFavorites) < 1)
{
return false;
}
// Get Acl_Permission
$aclPerm = $this->aclPermissionRepository->findOneByName(self::ACL_PERM_LIST);
if ($aclPerm === null) return false;
// Get Acl
$acl = $this->aclRepository->findOneBy(array(
'function' => $function,
'permission' => $aclPerm
));
if ($acl === null) return false;
// Since only one acl type can exist
// we can return the result of the acl_permission
return $acl->getValue();
}
// Can View a MissionShareDemand that has been shared with my society group
private function canView(MissionShareDemand $missionShareDemand, Access $user, AccessFunction $function)
{
// Module activated ?
// At least one, not necessarly the plus one
// MODULE_MISSION_PLUS is required only if we want to share missions,
// not accept / refuse missions shared with us
if (
$this->moduleTools->isInactiveByCode($this->currentGroup, Module::MODULE_MISSION) &&
$this->moduleTools->isInactiveByCode($this->currentGroup, Module::MODULE_MISSION_LIGHT) &&
$this->moduleTools->isInactiveByCode($this->currentGroup, Module::MODULE_MISSION_PLUS)
)
{
return false;
}
$mission = $missionShareDemand->getMission();
if ($mission === null)
{
return false;
}
// CurrentGroup should be the receiver
$receiver = $missionShareDemand->getReceiver();
if ($receiver === null)
{
return false;
}
if (!$receiver->equals($this->currentGroup))
{
return false;
}
// The demand should be either accepted or in the default status (not accepted)
// Refused / Annulled / Expired => Die hard
if ($missionShareDemand->isExpired())
{
return false;
}
if ($missionShareDemand->isRefused())
{
return false;
}
if ($missionShareDemand->isAnnulled())
{
return false;
}
// If we are here, it means the status is ok (Default or Accepted),
// so simply check if the user has the required permissions to list shared demands
// Get Acl_Permission
$aclPerm = $this->aclPermissionRepository->findOneByName(self::ACL_PERM_LIST);
if ($aclPerm === null) return false;
// Get Acl
$acl = $this->aclRepository->findOneBy(array(
'function' => $function,
'permission' => $aclPerm
));
if ($acl === null) return false;
// Since only one acl type can exist
// we can return the result of the acl_permission
return $acl->getValue();
}
private function canAcceptOrRefuse(MissionShareDemand $missionShareDemand, Access $user, AccessFunction $function)
{
// Module activated ?
// At least one, not necessarly the plus one
// MODULE_MISSION_PLUS is required only if we want to share missions,
// not accept / refuse missions shared with us
if (
$this->moduleTools->isInactiveByCode($this->currentGroup, Module::MODULE_MISSION) &&
$this->moduleTools->isInactiveByCode($this->currentGroup, Module::MODULE_MISSION_LIGHT) &&
$this->moduleTools->isInactiveByCode($this->currentGroup, Module::MODULE_MISSION_PLUS)
)
{
return false;
}
$mission = $missionShareDemand->getMission();
if ($mission === null)
{
return false;
}
// CurrentGroup should be the receiver
$receiver = $missionShareDemand->getReceiver();
if ($receiver === null)
{
return false;
}
if (!$receiver->equals($this->currentGroup))
{
return false;
}
// The demand should be in the default status
// Accepted / Refused / Annulled / Expired => Die hard
if ($missionShareDemand->isExpired())
{
return false;
}
if ($missionShareDemand->isAccepted())
{
return false;
}
if ($missionShareDemand->isRefused())
{
return false;
}
if ($missionShareDemand->isAnnulled())
{
return false;
}
// If we are here, it means the status is ok (Default),
// so simply check if the user has the required permissions to list shared demands
// Get Acl_Permission
$aclPerm = $this->aclPermissionRepository->findOneByName(self::ACL_PERM_ACCEPT_REFUSE);
if ($aclPerm === null) return false;
// Get Acl
$acl = $this->aclRepository->findOneBy(array(
'function' => $function,
'permission' => $aclPerm
));
if ($acl === null) return false;
// Since only one acl type can exist
// we can return the result of the acl_permission
return $acl->getValue();
}
private function canRefuseAccepted(MissionShareDemand $missionShareDemand, Access $user, AccessFunction $function)
{
// Module activated ?
// At least one, not necessarly the plus one
// MODULE_MISSION_PLUS is required only if we want to share missions,
// not accept / refuse missions shared with us
if (
$this->moduleTools->isInactiveByCode($this->currentGroup, Module::MODULE_MISSION) &&
$this->moduleTools->isInactiveByCode($this->currentGroup, Module::MODULE_MISSION_LIGHT) &&
$this->moduleTools->isInactiveByCode($this->currentGroup, Module::MODULE_MISSION_PLUS)
)
{
return false;
}
$mission = $missionShareDemand->getMission();
if ($mission === null)
{
return false;
}
// Plan.io Task #3304
// Deny refusing a mission for which the owner was paid
if ($mission->ownerWasPaid())
{
return false;
}
// CurrentGroup should be the receiver
$receiver = $missionShareDemand->getReceiver();
if ($receiver === null)
{
return false;
}
if (!$receiver->equals($this->currentGroup))
{
return false;
}
// If we are here, it means the status is ok (Accepted),
// so simply check if the user has the required permissions to list shared demands
// Get Acl_Permission
$aclPerm = $this->aclPermissionRepository->findOneByName(self::ACL_PERM_ACCEPT_REFUSE);
if ($aclPerm === null) return false;
// Get Acl
$acl = $this->aclRepository->findOneBy(array(
'function' => $function,
'permission' => $aclPerm
));
if ($acl === null) return false;
// Since only one acl type can exist
// we can return the result of the acl_permission
// The demand should be Accepted status
if ($missionShareDemand->isAccepted())
{
return $acl->getValue();
}
// We are doing the status check here because we want to avoid the following case :
// Demand is accepted, but is has also expired
// If we check Expired before checking Acl,
// an expired but accepted demand will return false
// when it should return true
// Wtf we are doing here ? :D
return false;
}
}