fix(absences) : durcissement RGPD des données RH des utilisateurs
Suite à la revue de conformité du module absences.
Fuite corrigée : GET /api/users et /api/users/{id} n'avaient aucun contrôle
d'accès alors que le groupe user:list exposait les données RH/familiales
(date d'embauche, contrat, soldes de CP, rôles…). Tout utilisateur authentifié
pouvait donc lire ces informations sur tous ses collègues.
- chaque champ RH (isEmployee, hireDate, endDate, contractType, workTimeRatio,
annualLeaveDays, referencePeriodStart, initialLeaveBalance) ainsi que roles
est désormais exposé via #[ApiProperty(security: "is_granted('ROLE_ADMIN') or
object == user")] : visible uniquement par un admin ou par l'utilisateur
lui-même. id et username restent publics (sélecteurs d'assigné, avatars).
Minimisation : suppression de familySituation et nbChildren, collectés et
exposés (form RH, API, outil MCP) mais utilisés par aucun calcul.
- entité User + enum FamilySituation + migration de drop des colonnes
- Serializer MCP, update-user (MCP), EmployeeDrawer, DTO, fixtures, i18n
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,6 @@ use App\Entity\ZimbraConfiguration;
|
||||
use App\Enum\AbsenceStatus;
|
||||
use App\Enum\AbsenceType;
|
||||
use App\Enum\ContractType;
|
||||
use App\Enum\FamilySituation;
|
||||
use App\Enum\RecurrenceType;
|
||||
use App\Enum\StatusCategory;
|
||||
use DateTimeImmutable;
|
||||
@@ -670,20 +669,15 @@ class AppFixtures extends Fixture
|
||||
$admin->setIsEmployee(true);
|
||||
$admin->setHireDate(new DateTimeImmutable('2020-01-15'));
|
||||
$admin->setContractType(ContractType::Cdi);
|
||||
$admin->setFamilySituation(FamilySituation::Married);
|
||||
$admin->setNbChildren(2);
|
||||
|
||||
$userAlice->setIsEmployee(true);
|
||||
$userAlice->setHireDate(new DateTimeImmutable('2022-09-01'));
|
||||
$userAlice->setContractType(ContractType::Cdi);
|
||||
$userAlice->setFamilySituation(FamilySituation::Single);
|
||||
|
||||
$userBob->setIsEmployee(true);
|
||||
$userBob->setHireDate(new DateTimeImmutable('2023-03-10'));
|
||||
$userBob->setContractType(ContractType::Cdd);
|
||||
$userBob->setWorkTimeRatio(0.8);
|
||||
$userBob->setFamilySituation(FamilySituation::Pacsed);
|
||||
$userBob->setNbChildren(1);
|
||||
|
||||
// Paid-leave balances for the current reference period (June 1st → May 31st)
|
||||
$cpPeriod = '2025-2026';
|
||||
|
||||
+11
-34
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
@@ -11,7 +12,6 @@ use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use App\Enum\ContractType;
|
||||
use App\Enum\FamilySituation;
|
||||
use App\Repository\UserRepository;
|
||||
use App\State\MeProvider;
|
||||
use App\State\UserPasswordHasherProcessor;
|
||||
@@ -57,6 +57,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
|
||||
/** @var list<string> */
|
||||
#[ORM\Column]
|
||||
#[ApiProperty(security: "is_granted('ROLE_ADMIN') or object == user")]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private array $roles = [];
|
||||
|
||||
@@ -76,54 +77,54 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $avatarFileName = null;
|
||||
|
||||
// --- HR / absence management fields ---
|
||||
// --- HR / absence management fields (readable only by an admin or the user themselves) ---
|
||||
|
||||
/** Whether this user is an employee subject to absence management. */
|
||||
#[ORM\Column]
|
||||
#[ApiProperty(security: "is_granted('ROLE_ADMIN') or object == user")]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private bool $isEmployee = false;
|
||||
|
||||
/** Hiring date — start of paid-leave acquisition. */
|
||||
#[ORM\Column(type: Types::DATE_IMMUTABLE, nullable: true)]
|
||||
#[ApiProperty(security: "is_granted('ROLE_ADMIN') or object == user")]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private ?DateTimeImmutable $hireDate = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATE_IMMUTABLE, nullable: true)]
|
||||
#[ApiProperty(security: "is_granted('ROLE_ADMIN') or object == user")]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private ?DateTimeImmutable $endDate = null;
|
||||
|
||||
#[ORM\Column(type: Types::STRING, length: 16, nullable: true, enumType: ContractType::class)]
|
||||
#[ApiProperty(security: "is_granted('ROLE_ADMIN') or object == user")]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private ?ContractType $contractType = null;
|
||||
|
||||
/** Work-time ratio: 1.0 = full time, 0.8 = 4 days out of 5. */
|
||||
#[ORM\Column(type: Types::FLOAT)]
|
||||
#[ApiProperty(security: "is_granted('ROLE_ADMIN') or object == user")]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private float $workTimeRatio = 1.0;
|
||||
|
||||
/** Yearly paid-leave entitlement in worked days (default 25 = jours ouvrés). */
|
||||
#[ORM\Column(type: Types::FLOAT)]
|
||||
#[ApiProperty(security: "is_granted('ROLE_ADMIN') or object == user")]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private float $annualLeaveDays = 25.0;
|
||||
|
||||
/** Reference period start as MM-DD (default 06-01, 1st of June). */
|
||||
#[ORM\Column(length: 5)]
|
||||
#[ApiProperty(security: "is_granted('ROLE_ADMIN') or object == user")]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private string $referencePeriodStart = '06-01';
|
||||
|
||||
/** Paid-leave already acquired when the module is rolled out. */
|
||||
#[ORM\Column(type: Types::FLOAT)]
|
||||
#[ApiProperty(security: "is_granted('ROLE_ADMIN') or object == user")]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private float $initialLeaveBalance = 0.0;
|
||||
|
||||
#[ORM\Column(type: Types::STRING, length: 16, nullable: true, enumType: FamilySituation::class)]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private ?FamilySituation $familySituation = null;
|
||||
|
||||
#[ORM\Column]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private int $nbChildren = 0;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->createdAt = new DateTimeImmutable();
|
||||
@@ -338,28 +339,4 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFamilySituation(): ?FamilySituation
|
||||
{
|
||||
return $this->familySituation;
|
||||
}
|
||||
|
||||
public function setFamilySituation(?FamilySituation $familySituation): static
|
||||
{
|
||||
$this->familySituation = $familySituation;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNbChildren(): int
|
||||
{
|
||||
return $this->nbChildren;
|
||||
}
|
||||
|
||||
public function setNbChildren(int $nbChildren): static
|
||||
{
|
||||
$this->nbChildren = $nbChildren;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enum;
|
||||
|
||||
enum FamilySituation: string
|
||||
{
|
||||
case Single = 'CELIBATAIRE';
|
||||
case Married = 'MARIE';
|
||||
case Pacsed = 'PACSE';
|
||||
case Divorced = 'DIVORCE';
|
||||
case Widowed = 'VEUF';
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Single => 'Célibataire',
|
||||
self::Married => 'Marié(e)',
|
||||
self::Pacsed => 'Pacsé(e)',
|
||||
self::Divorced => 'Divorcé(e)',
|
||||
self::Widowed => 'Veuf(ve)',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||
namespace App\Mcp\Tool\Reference;
|
||||
|
||||
use App\Enum\ContractType;
|
||||
use App\Enum\FamilySituation;
|
||||
use App\Mcp\Tool\Serializer;
|
||||
use App\Repository\UserRepository;
|
||||
use DateTimeImmutable;
|
||||
@@ -17,7 +16,7 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
#[McpTool(name: 'update-user', description: 'Update a user HR/profile fields (admin). Does NOT change password or roles. contractType = CDI|CDD|STAGE|ALTERNANCE|AUTRE. familySituation = CELIBATAIRE|MARIE|PACSE|DIVORCE|VEUF. hireDate/endDate as YYYY-MM-DD. referencePeriodStart as MM-DD (e.g. 06-01).')]
|
||||
#[McpTool(name: 'update-user', description: 'Update a user HR/profile fields (admin). Does NOT change password or roles. contractType = CDI|CDD|STAGE|ALTERNANCE|AUTRE. hireDate/endDate as YYYY-MM-DD. referencePeriodStart as MM-DD (e.g. 06-01).')]
|
||||
class UpdateUserTool
|
||||
{
|
||||
public function __construct(
|
||||
@@ -36,8 +35,6 @@ class UpdateUserTool
|
||||
?float $annualLeaveDays = null,
|
||||
?string $referencePeriodStart = null,
|
||||
?float $initialLeaveBalance = null,
|
||||
?string $familySituation = null,
|
||||
?int $nbChildren = null,
|
||||
): string {
|
||||
if (!$this->security->isGranted('ROLE_ADMIN')) {
|
||||
throw new AccessDeniedException('Access denied: ROLE_ADMIN required.');
|
||||
@@ -75,15 +72,6 @@ class UpdateUserTool
|
||||
if (null !== $initialLeaveBalance) {
|
||||
$user->setInitialLeaveBalance($initialLeaveBalance);
|
||||
}
|
||||
if (null !== $familySituation) {
|
||||
$user->setFamilySituation(
|
||||
FamilySituation::tryFrom($familySituation)
|
||||
?? throw new InvalidArgumentException(sprintf('Unknown family situation "%s".', $familySituation)),
|
||||
);
|
||||
}
|
||||
if (null !== $nbChildren) {
|
||||
$user->setNbChildren($nbChildren);
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
|
||||
@@ -389,8 +389,6 @@ final class Serializer
|
||||
'annualLeaveDays' => $u->getAnnualLeaveDays(),
|
||||
'referencePeriodStart' => $u->getReferencePeriodStart(),
|
||||
'initialLeaveBalance' => $u->getInitialLeaveBalance(),
|
||||
'familySituation' => $u->getFamilySituation()?->value,
|
||||
'nbChildren' => $u->getNbChildren(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user