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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user