| Numéro du ticket | Titre du ticket | |------------------|-----------------| | #322 | Page horaire | ## Description de la PR [#322] Page horaire ## Modification du .env ## Check list - [ ] Pas de régression - [ ] TU/TI/TF rédigée - [ ] TU/TI/TF OK - [ ] CHANGELOG modifié Reviewed-on: #4 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #4.
This commit is contained in:
99
src/Security/EmployeeScopeService.php
Normal file
99
src/Security/EmployeeScopeService.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Security;
|
||||
|
||||
use App\Entity\Employee;
|
||||
use App\Entity\User;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
class EmployeeScopeService
|
||||
{
|
||||
public const string SITE_ACCESS_ROLE = 'SITE_ACCESS';
|
||||
|
||||
/**
|
||||
* Règle métier centrale d'accès à un employé.
|
||||
* - Admin : accès global
|
||||
* - Self : uniquement son employé lié
|
||||
* - Site : uniquement les employés des sites autorisés.
|
||||
*/
|
||||
public function canAccessEmployee(User $user, Employee $employee): bool
|
||||
{
|
||||
if (in_array('ROLE_ADMIN', $user->getRoles(), true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array('ROLE_SELF', $user->getRoles(), true)) {
|
||||
return $user->getEmployee()?->getId() === $employee->getId();
|
||||
}
|
||||
|
||||
$employeeSiteId = $employee->getSite()?->getId();
|
||||
if (!$employeeSiteId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($employeeSiteId, $this->getAllowedSiteIds($user), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la liste des sites accessibles via user_site_roles.
|
||||
*
|
||||
* @return list<int>
|
||||
*/
|
||||
public function getAllowedSiteIds(User $user): array
|
||||
{
|
||||
$siteIds = [];
|
||||
|
||||
foreach ($user->getSiteRoles() as $siteRole) {
|
||||
if (self::SITE_ACCESS_ROLE !== $siteRole->getRole()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$siteId = $siteRole->getSite()?->getId();
|
||||
if ($siteId) {
|
||||
$siteIds[] = $siteId;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique($siteIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applique le scope directement sur un QueryBuilder Doctrine.
|
||||
* Cette méthode est utilisée pour filtrer les collections SQL
|
||||
* avant sérialisation (plus sûr et plus performant).
|
||||
*/
|
||||
public function applyEmployeeScope(QueryBuilder $qb, string $employeeAlias, string $paramPrefix, User $user): void
|
||||
{
|
||||
if (in_array('ROLE_ADMIN', $user->getRoles(), true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array('ROLE_SELF', $user->getRoles(), true)) {
|
||||
$employeeId = $user->getEmployee()?->getId();
|
||||
if (!$employeeId) {
|
||||
$qb->andWhere('1 = 0');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$qb->andWhere(sprintf('%s.id = :%s_employee_id', $employeeAlias, $paramPrefix))
|
||||
->setParameter(sprintf('%s_employee_id', $paramPrefix), $employeeId)
|
||||
;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$siteIds = $this->getAllowedSiteIds($user);
|
||||
if ([] === $siteIds) {
|
||||
$qb->andWhere('1 = 0');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$qb->andWhere(sprintf('%s.site IN (:%s_site_ids)', $employeeAlias, $paramPrefix))
|
||||
->setParameter(sprintf('%s_site_ids', $paramPrefix), $siteIds)
|
||||
;
|
||||
}
|
||||
}
|
||||
48
src/Security/Voter/AbsenceVoter.php
Normal file
48
src/Security/Voter/AbsenceVoter.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Security\Voter;
|
||||
|
||||
use App\Entity\Absence;
|
||||
use App\Entity\User;
|
||||
use App\Security\EmployeeScopeService;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
|
||||
final class AbsenceVoter extends Voter
|
||||
{
|
||||
public const string VIEW = 'ABSENCE_VIEW';
|
||||
public const string EDIT = 'ABSENCE_EDIT';
|
||||
|
||||
public function __construct(
|
||||
private readonly Security $security,
|
||||
private readonly EmployeeScopeService $employeeScopeService,
|
||||
) {}
|
||||
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
return in_array($attribute, [self::VIEW, self::EDIT], true) && $subject instanceof Absence;
|
||||
}
|
||||
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
|
||||
{
|
||||
$user = $this->security->getUser();
|
||||
if (!$user instanceof User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$subject instanceof Absence) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$employee = $subject->getEmployee();
|
||||
if (null === $employee) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->employeeScopeService->canAccessEmployee($user, $employee);
|
||||
}
|
||||
}
|
||||
50
src/Security/Voter/WorkHourVoter.php
Normal file
50
src/Security/Voter/WorkHourVoter.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Security\Voter;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Entity\WorkHour;
|
||||
use App\Security\EmployeeScopeService;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
|
||||
class WorkHourVoter extends Voter
|
||||
{
|
||||
public const string VIEW = 'WORK_HOUR_VIEW';
|
||||
public const string EDIT = 'WORK_HOUR_EDIT';
|
||||
|
||||
public function __construct(
|
||||
private readonly Security $security,
|
||||
private readonly EmployeeScopeService $employeeScopeService,
|
||||
) {}
|
||||
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
return in_array($attribute, [self::VIEW, self::EDIT], true) && $subject instanceof WorkHour;
|
||||
}
|
||||
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
|
||||
{
|
||||
// On ne traite que des utilisateurs applicatifs authentifiés.
|
||||
$user = $this->security->getUser();
|
||||
if (!$user instanceof User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$subject instanceof WorkHour) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$employee = $subject->getEmployee();
|
||||
if (null === $employee) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Délégation de la règle au service de scope unique (évite la duplication).
|
||||
return $this->employeeScopeService->canAccessEmployee($user, $employee);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user