Base de travail V7.8 reprise du répertoire de travail
Commit technique : reproduit à l'identique l'état non commité de la copie de travail (sécurisation V7.8, CSP à nonce, Session, migration des droits) afin que le travail d'ergonomie qui suit soit isolable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,14 @@ namespace App\Core;
|
||||
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* Contrôle des rôles et des périmètres.
|
||||
*
|
||||
* Aucune règle de droit n'est écrite ici : les pages autorisées, les
|
||||
* permissions fonctionnelles et les périmètres de visibilité sont lus dans les
|
||||
* vues SQL créées par database/migration_v7_8_securite_acces.sql. Cette classe
|
||||
* ne fait que poser la question à la base et appliquer la réponse.
|
||||
*/
|
||||
final class Access
|
||||
{
|
||||
public const ROLE_AGENT = 'AGENT';
|
||||
@@ -14,11 +22,15 @@ final class Access
|
||||
|
||||
private const SESSION_KEY = 'pta_access';
|
||||
|
||||
/** @var array<string, array<string, bool>> */
|
||||
private array $permissionCache = [];
|
||||
|
||||
/** @var array<string, array<int, string>> */
|
||||
private array $pageCache = [];
|
||||
|
||||
public function __construct(private PDO $pdo)
|
||||
{
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
session_start();
|
||||
}
|
||||
Session::start();
|
||||
}
|
||||
|
||||
public function isConfigured(): bool
|
||||
@@ -85,13 +97,16 @@ final class Access
|
||||
$selection['structure_id'] = $structureId;
|
||||
}
|
||||
|
||||
$_SESSION[self::SESSION_KEY] = $selection;
|
||||
session_regenerate_id(true);
|
||||
$_SESSION[self::SESSION_KEY] = $selection;
|
||||
// Le jeton CSRF suit le cycle de vie de la session : un changement de
|
||||
// rôle invalide le jeton précédent.
|
||||
unset($_SESSION['csrf_token']);
|
||||
}
|
||||
|
||||
public function clear(): void
|
||||
{
|
||||
unset($_SESSION[self::SESSION_KEY]);
|
||||
unset($_SESSION[self::SESSION_KEY], $_SESSION['csrf_token']);
|
||||
session_regenerate_id(true);
|
||||
}
|
||||
|
||||
@@ -128,39 +143,61 @@ final class Access
|
||||
return $profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permissions fonctionnelles lues dans v_acces_role_permission.
|
||||
* Une permission absente de la base vaut « refusé ».
|
||||
*
|
||||
* @return array<string, bool>
|
||||
*/
|
||||
public function permissions(): array
|
||||
{
|
||||
$role = $this->role();
|
||||
$service = $role === self::ROLE_SERVICE;
|
||||
$responsable = $role === self::ROLE_RESPONSABLE;
|
||||
if ($role === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'read_only' => $role === self::ROLE_AGENT,
|
||||
'can_edit_draft' => $service || $responsable,
|
||||
'can_edit_cross_structure' => $service,
|
||||
'can_validate' => $service,
|
||||
'can_reopen' => $service || $responsable,
|
||||
'can_use_templates' => $service,
|
||||
'can_copy_full_week' => $service,
|
||||
'can_view_pending' => $service,
|
||||
'can_manage_coverage' => $service,
|
||||
'can_manage_agents' => $service,
|
||||
'can_manage_structures' => $service,
|
||||
'can_manage_vacations' => $service,
|
||||
'can_manage_teams' => $service,
|
||||
'can_manage_pta' => $service,
|
||||
'can_forecast_next_year' => $service,
|
||||
];
|
||||
if (!isset($this->permissionCache[$role])) {
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT permission, autorise FROM v_acces_role_permission WHERE role = :role'
|
||||
);
|
||||
$stmt->execute(['role' => $role]);
|
||||
|
||||
$permissions = [];
|
||||
foreach ($stmt->fetchAll() as $row) {
|
||||
$permissions[(string) $row['permission']] = (bool) $row['autorise'];
|
||||
}
|
||||
$this->permissionCache[$role] = $permissions;
|
||||
}
|
||||
|
||||
return $this->permissionCache[$role];
|
||||
}
|
||||
|
||||
public function hasPermission(string $permission): bool
|
||||
{
|
||||
return $this->permissions()[$permission] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pages autorisées lues dans v_acces_role_page.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function allowedPages(): array
|
||||
{
|
||||
return match ($this->role()) {
|
||||
self::ROLE_AGENT => ['agents', 'structures'],
|
||||
self::ROLE_RESPONSABLE => ['planning', 'structures', 'agents', 'coverage'],
|
||||
self::ROLE_SERVICE => ['planning', 'pending', 'coverage', 'structures', 'agents', 'annual', 'pta', 'teams', 'vacations', 'administration'],
|
||||
default => [],
|
||||
};
|
||||
$role = $this->role();
|
||||
if ($role === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!isset($this->pageCache[$role])) {
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT page FROM v_acces_role_page WHERE role = :role ORDER BY ordre_affichage, page'
|
||||
);
|
||||
$stmt->execute(['role' => $role]);
|
||||
$this->pageCache[$role] = array_map('strval', $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||
}
|
||||
|
||||
return $this->pageCache[$role];
|
||||
}
|
||||
|
||||
public function canAccessPage(string $page): bool
|
||||
@@ -248,10 +285,7 @@ final class Access
|
||||
return;
|
||||
}
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT p.id_structure
|
||||
FROM creneau_horaire c
|
||||
INNER JOIN planning p ON p.id_planning = c.id_planning
|
||||
WHERE c.id_creneau = :id LIMIT 1'
|
||||
'SELECT id_structure FROM v_acces_perimetre_creneau WHERE id_creneau = :id LIMIT 1'
|
||||
);
|
||||
$stmt->execute(['id' => $entryId]);
|
||||
$structureId = $stmt->fetchColumn();
|
||||
@@ -267,7 +301,9 @@ final class Access
|
||||
if ($this->role() === self::ROLE_SERVICE) {
|
||||
return;
|
||||
}
|
||||
$stmt = $this->pdo->prepare('SELECT id_structure FROM planning WHERE id_planning = :id LIMIT 1');
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT id_structure FROM v_acces_perimetre_planning WHERE id_planning = :id LIMIT 1'
|
||||
);
|
||||
$stmt->execute(['id' => $planningId]);
|
||||
$structureId = $stmt->fetchColumn();
|
||||
if ($structureId === false) {
|
||||
@@ -293,14 +329,14 @@ final class Access
|
||||
|
||||
private function activeAgentExists(int $agentId): bool
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT 1 FROM agent WHERE id_agent = :id AND actif = TRUE LIMIT 1');
|
||||
$stmt = $this->pdo->prepare('SELECT 1 FROM v_acces_identite_agent WHERE id_agent = :id LIMIT 1');
|
||||
$stmt->execute(['id' => $agentId]);
|
||||
return $stmt->fetchColumn() !== false;
|
||||
}
|
||||
|
||||
private function activeStructureExists(int $structureId): bool
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT 1 FROM structure WHERE id_structure = :id AND actif = TRUE LIMIT 1');
|
||||
$stmt = $this->pdo->prepare('SELECT 1 FROM v_acces_identite_structure WHERE id_structure = :id LIMIT 1');
|
||||
$stmt->execute(['id' => $structureId]);
|
||||
return $stmt->fetchColumn() !== false;
|
||||
}
|
||||
@@ -312,11 +348,9 @@ final class Access
|
||||
return null;
|
||||
}
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT a.id_agent, a.matricule, a.nom, a.prenom, a.id_structure,
|
||||
s.nom AS structure_nom
|
||||
FROM agent a
|
||||
LEFT JOIN structure s ON s.id_structure = a.id_structure
|
||||
WHERE a.id_agent = :id AND a.actif = TRUE LIMIT 1'
|
||||
'SELECT id_agent, matricule, nom, prenom, id_structure, structure_nom
|
||||
FROM v_acces_identite_agent
|
||||
WHERE id_agent = :id LIMIT 1'
|
||||
);
|
||||
$stmt->execute(['id' => $agentId]);
|
||||
return $stmt->fetch() ?: null;
|
||||
@@ -330,8 +364,8 @@ final class Access
|
||||
}
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT id_structure, code, nom
|
||||
FROM structure
|
||||
WHERE id_structure = :id AND actif = TRUE LIMIT 1'
|
||||
FROM v_acces_identite_structure
|
||||
WHERE id_structure = :id LIMIT 1'
|
||||
);
|
||||
$stmt->execute(['id' => $structureId]);
|
||||
return $stmt->fetch() ?: null;
|
||||
@@ -344,25 +378,11 @@ final class Access
|
||||
return false;
|
||||
}
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT 1
|
||||
FROM agent a
|
||||
WHERE a.id_agent = :agent_id
|
||||
AND a.actif = TRUE
|
||||
AND (
|
||||
a.id_structure = :structure_id_default
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM planning p
|
||||
WHERE p.id_agent = a.id_agent
|
||||
AND p.id_structure = :structure_id_planning
|
||||
)
|
||||
)
|
||||
'SELECT 1 FROM v_acces_agent_par_structure
|
||||
WHERE id_agent = :agent_id AND id_structure_autorisee = :structure_id
|
||||
LIMIT 1'
|
||||
);
|
||||
$stmt->execute([
|
||||
'agent_id' => $agentId,
|
||||
'structure_id_default' => $structureId,
|
||||
'structure_id_planning' => $structureId,
|
||||
]);
|
||||
$stmt->execute(['agent_id' => $agentId, 'structure_id' => $structureId]);
|
||||
return $stmt->fetchColumn() !== false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user