From 50871117d92b830eb2d269dea8e0f711765e252f Mon Sep 17 00:00:00 2001 From: Ronan Lenouvel Date: Sat, 17 Oct 2026 17:40:09 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20feat(OperationsAccess):=20l'exp?= =?UTF-8?q?loitation=20et=20les=20mesures=20se=20lisent=20pour=20tout=20ad?= =?UTF-8?q?ministrateur=20actif=20(#375)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Metrics/OperationsAccess.php | 22 ++++++++++++++++ tests/Metrics/OperationsAccessTest.php | 36 ++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 src/Metrics/OperationsAccess.php create mode 100644 tests/Metrics/OperationsAccessTest.php diff --git a/src/Metrics/OperationsAccess.php b/src/Metrics/OperationsAccess.php new file mode 100644 index 00000000..e89342df --- /dev/null +++ b/src/Metrics/OperationsAccess.php @@ -0,0 +1,22 @@ +isActive() && $user->role() === UserRole::Admin; + } +} diff --git a/tests/Metrics/OperationsAccessTest.php b/tests/Metrics/OperationsAccessTest.php new file mode 100644 index 00000000..f35b9662 --- /dev/null +++ b/tests/Metrics/OperationsAccessTest.php @@ -0,0 +1,36 @@ +allows($this->user(UserRole::Admin))); + } + + #[Test] + public function testAMusicianADisabledAdministratorAndAVisitorAreRefused(): void + { + $access = new OperationsAccess(); + + self::assertFalse($access->allows($this->user(UserRole::Musicien))); + self::assertFalse($access->allows($this->user(UserRole::Admin, false))); + self::assertFalse($access->allows(null)); + } +} From 55c8dd4b1a3e293dc3cb721c54c5925a86259b83 Mon Sep 17 00:00:00 2001 From: Ronan Lenouvel Date: Sat, 17 Oct 2026 17:40:09 +0200 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9C=A8=20feat(Ops):=20pages=20et=20ongle?= =?UTF-8?q?t=20Ops=20ouverts=20=C3=A0=20tout=20administrateur=20actif,=20r?= =?UTF-8?q?estauration=20r=C3=A9serv=C3=A9e=20au=20propri=C3=A9taire=20(#3?= =?UTF-8?q?75)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/services.php | 7 ++-- .../Controller/MetricsPageController.php | 8 ++--- .../Controller/OpsPageController.php | 8 ++--- src/Operations/View/OpsLinkRenderer.php | 13 +++++--- templates/admin/ops/_header.php | 3 +- templates/partials/admin-tabs.php | 2 +- .../Controller/MetricsPageControllerTest.php | 25 ++++++++------- .../Controller/OpsPageControllerTest.php | 29 +++++++++++------ tests/Operations/View/OpsLinkRendererTest.php | 32 +++++++++++++------ 9 files changed, 79 insertions(+), 48 deletions(-) diff --git a/config/services.php b/config/services.php index d7ba0947..ac553d92 100644 --- a/config/services.php +++ b/config/services.php @@ -129,6 +129,7 @@ use App\Operations\Production\ReleaseInventory; use App\Operations\View\OpsLinkRenderer; use App\Metrics\MetricsAccess; +use App\Metrics\OperationsAccess; use App\Metrics\Report\HealthReportBuilder; use App\Metrics\Report\MetricsReaderInterface; use App\Metrics\Report\MysqlMetricsReader; @@ -349,7 +350,7 @@ return new MetricsPageController( $c->get(TemplateRendererInterface::class), $c->get(AuthGuard::class), - new MetricsAccess((string) $config['metrics']['viewer_email']), + new OperationsAccess(), new HealthReportBuilder($c->get(MetricsReaderInterface::class), $thresholds, $c->get(ClockInterface::class), $timezone), new SecurityReportBuilder( $c->get(MetricsReaderInterface::class), @@ -520,7 +521,7 @@ Transport::fromDsn($config['mailer']['dsn']), )); - $container->set(TemplateRendererInterface::class, fn ($c) => new OpsLinkRenderer(new PhpTemplateRenderer(__DIR__ . '/../templates'), $c->get(AuthGuard::class), new MetricsAccess((string) $config['metrics']['viewer_email']))); + $container->set(TemplateRendererInterface::class, fn ($c) => new OpsLinkRenderer(new PhpTemplateRenderer(__DIR__ . '/../templates'), $c->get(AuthGuard::class), new OperationsAccess(), new MetricsAccess((string) $config['metrics']['viewer_email']))); $container->set(PageController::class, fn ($c) => new PageController( $c->get(TemplateRendererInterface::class), @@ -1408,7 +1409,7 @@ $container->set(OpsPageController::class, fn ($c) => new OpsPageController( $c->get(TemplateRendererInterface::class), $c->get(AuthGuard::class), - new MetricsAccess((string) $config['metrics']['viewer_email']), + new OperationsAccess(), $c->get(BackupHealthBuilder::class), $c->get(ProtectionHealthBuilder::class), $c->get(ProductionReportBuilder::class), diff --git a/src/Metrics/Controller/MetricsPageController.php b/src/Metrics/Controller/MetricsPageController.php index a3adc76f..4b8c1dec 100644 --- a/src/Metrics/Controller/MetricsPageController.php +++ b/src/Metrics/Controller/MetricsPageController.php @@ -7,7 +7,7 @@ use App\Http\ErrorPage; use App\Http\Request; use App\Http\Response; -use App\Metrics\MetricsAccess; +use App\Metrics\OperationsAccess; use App\Metrics\Report\HealthReportBuilder; use App\Metrics\Report\Load\LoadReportBuilder; use App\Metrics\Report\MetricsPeriod; @@ -16,15 +16,15 @@ use App\View\TemplateRendererInterface; /** - * Tableau de bord des mesures, page « Santé et e-mails » (#196). Réservé à UN compte (MetricsAccess) : pour tous les autres, - * y compris les autres administrateurs et les visiteurs non connectés, la page n'existe pas (404 uniforme, aucun indice). + * Tableau de bord des mesures, page « Santé et e-mails » (#196). Ouvert à tout administrateur ACTIF (OperationsAccess, décision du + * propriétaire du 2026-10-17) : pour tous les autres (visiteur, musicien, administrateur désactivé), la page n'existe pas (404 uniforme, aucun indice). */ final class MetricsPageController { public function __construct( private readonly TemplateRendererInterface $renderer, private readonly AuthGuard $authGuard, - private readonly MetricsAccess $access, + private readonly OperationsAccess $access, private readonly HealthReportBuilder $reports, private readonly SecurityReportBuilder $securityReports, private readonly LoadReportBuilder $loadReports, diff --git a/src/Operations/Controller/OpsPageController.php b/src/Operations/Controller/OpsPageController.php index 5d1ebce8..31e6bd30 100644 --- a/src/Operations/Controller/OpsPageController.php +++ b/src/Operations/Controller/OpsPageController.php @@ -8,7 +8,7 @@ use App\Http\Request; use App\Http\Response; use App\Metrics\Chart\SvgChart; -use App\Metrics\MetricsAccess; +use App\Metrics\OperationsAccess; use App\Operations\Backup\BackupHealthBuilder; use App\Operations\Backup\ProtectionHealthBuilder; use App\Operations\Production\ProductionReportBuilder; @@ -16,8 +16,8 @@ use App\View\TemplateRendererInterface; /** - * Exploitation (#375) : état de la production, en lecture seule. Réservé au même UNIQUE compte que les mesures (MetricsAccess) : - * pour tous les autres, y compris un autre administrateur, un visiteur ou une adresse non configurée, la page n'existe pas + * Exploitation (#375) : état de la production, en lecture seule. Ouvert à tout administrateur ACTIF (OperationsAccess, décision du + * propriétaire du 2026-10-17) : pour tous les autres, un visiteur, un musicien ou un administrateur désactivé, la page n'existe pas * (404 uniforme, aucun indice) et aucun lecteur n'est interrogé avant ce contrôle. */ final class OpsPageController @@ -25,7 +25,7 @@ final class OpsPageController public function __construct( private readonly TemplateRendererInterface $renderer, private readonly AuthGuard $authGuard, - private readonly MetricsAccess $access, + private readonly OperationsAccess $access, private readonly BackupHealthBuilder $backups, private readonly ProtectionHealthBuilder $protection, private readonly ProductionReportBuilder $production, diff --git a/src/Operations/View/OpsLinkRenderer.php b/src/Operations/View/OpsLinkRenderer.php index b860a6f4..d761f887 100644 --- a/src/Operations/View/OpsLinkRenderer.php +++ b/src/Operations/View/OpsLinkRenderer.php @@ -6,12 +6,14 @@ use App\Account\Entity\UserRole; use App\Metrics\MetricsAccess; +use App\Metrics\OperationsAccess; use App\Security\AuthGuard; use App\View\TemplateRendererInterface; /** - * Ajoute à chaque gabarit d'administrateur l'indicateur `showOpsLink` (#400) : le lien « Ops » de la barre du bas n'est montré qu'au - * propriétaire (MetricsAccess), pas aux autres administrateurs, sans que chaque contrôleur ait à le calculer. Le compte n'est interrogé + * Ajoute à chaque gabarit d'administrateur deux indicateurs, sans que chaque contrôleur ait à les calculer : `showOpsLink` (#400, le lien + * « Ops », pour tout administrateur actif : OperationsAccess) et `showRestoreLink` (le lien « Restauration » de l'exploitation, pour le + * seul propriétaire : MetricsAccess, car elle remplace toute la base). Le compte n'est interrogé * que pour un gabarit qui porte le rôle administrateur ; ce n'est qu'un confort d'affichage, la page reste protégée par son contrôleur. */ final class OpsLinkRenderer implements TemplateRendererInterface @@ -19,14 +21,17 @@ final class OpsLinkRenderer implements TemplateRendererInterface public function __construct( private readonly TemplateRendererInterface $inner, private readonly AuthGuard $authGuard, - private readonly MetricsAccess $access, + private readonly OperationsAccess $operations, + private readonly MetricsAccess $owner, ) { } public function render(string $template, array $data = []): string { if (($data['currentUserRole'] ?? null) === UserRole::Admin) { - $data['showOpsLink'] = $this->access->allows($this->authGuard->currentUserOrNull()); + $user = $this->authGuard->currentUserOrNull(); + $data['showOpsLink'] = $this->operations->allows($user); + $data['showRestoreLink'] = $this->owner->allows($user); } return $this->inner->render($template, $data); diff --git a/templates/admin/ops/_header.php b/templates/admin/ops/_header.php index 73524b92..5c0faa85 100644 --- a/templates/admin/ops/_header.php +++ b/templates/admin/ops/_header.php @@ -3,6 +3,7 @@ * En-tête de l'exploitation (#375) : titre, onglets et liens vers les pages voisines (mesures, restauration). * * @var string $section 'backups' ou 'production' + * @var bool|null $showRestoreLink onglet « Restauration » : propriétaire seulement (OpsLinkRenderer) */ $sections = ['backups' => ['/admin/ops', 'Sauvegardes'], 'production' => ['/admin/ops/production', 'Production']]; ?> @@ -15,5 +16,5 @@ > Mesures - Restauration + Restauration diff --git a/templates/partials/admin-tabs.php b/templates/partials/admin-tabs.php index f04e89ff..1f964ecd 100644 --- a/templates/partials/admin-tabs.php +++ b/templates/partials/admin-tabs.php @@ -4,7 +4,7 @@ * (navigation serveur, simples liens), l'onglet courant est marqué aria-current="page". * * @var string $adminTab 'bookings', 'slots', 'groups', 'users', 'assemblies' ou 'accounting' - * @var bool|null $showOpsLink onglet « Ops » : propriétaire seulement (#400, OpsLinkRenderer) + * @var bool|null $showOpsLink onglet « Ops » : tout administrateur actif (#400, OpsLinkRenderer) */ ?>