From 04c42d9380b7d3bd84c27786ed6c88a72ccfa511 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sun, 18 Oct 2026 18:47:12 +0100 Subject: [PATCH] feat(admin): port site administration to API and PostIt Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 21 +- Directory.Packages.props | 1 + doc/architecture/postit.md | 74 +++- .../PostIt.Tests/AdministrationAccessTests.cs | 35 +- .../PostIt.Tests/AdministrationToolsTests.cs | 227 ++++++++++++ .../Helpers/ServiceCollectionHelpers.cs | 2 + .../AdministrationFieldViewModel.cs | 69 ++++ .../AdministrationPageViewModel.Tools.cs | 334 +++++++++++++++++ .../ViewModels/AdministrationPageViewModel.cs | 57 ++- .../ViewModels/Layout/HomePageViewModel.cs | 8 +- .../Layout/SessionStatusViewModel.cs | 8 +- .../Views/Layout/AdministrationPage.axaml | 163 ++++++++- .../Views/Layout/AdministrationPage.axaml.cs | 78 ++++ src/PostIt/PostIt/Views/Layout/HomePage.axaml | 2 +- .../Administration/AdministrationDtos.cs | 192 ++++++++++ .../AdministrationApiClient.cs | 62 ++++ src/Yavsc.Api/Administration.http | 169 +++++++++ .../ResourceUsageApiController.cs | 2 +- .../SiteAdministrationApiController.cs | 215 +++++++++++ .../ApplicationUserApiController.cs | 6 +- src/Yavsc.Api/Program.cs | 5 + .../AdministrationService.Transfers.cs | 196 ++++++++++ .../Services/AdministrationService.cs | 336 ++++++++++++++++++ ...ministrationServiceCollectionExtensions.cs | 16 + .../Services/IAdministrationService.cs | 34 ++ src/Yavsc.Api/Yavsc.Api.csproj | 1 + .../Fixtures/ApiWebServerFixture.cs | 7 + .../ResourceUsageApiControllerTests.cs | 2 +- .../SiteAdministrationApiTests.cs | 311 ++++++++++++++++ 29 files changed, 2580 insertions(+), 53 deletions(-) create mode 100644 src/PostIt/PostIt.Tests/AdministrationToolsTests.cs create mode 100644 src/PostIt/PostIt/ViewModels/AdministrationFieldViewModel.cs create mode 100644 src/PostIt/PostIt/ViewModels/AdministrationPageViewModel.Tools.cs create mode 100644 src/Yavsc.Abstract/Administration/AdministrationDtos.cs create mode 100644 src/Yavsc.Api.Client/AdministrationApiClient.cs create mode 100644 src/Yavsc.Api/Administration.http create mode 100644 src/Yavsc.Api/Controllers/Administration/SiteAdministrationApiController.cs create mode 100644 src/Yavsc.Api/Services/AdministrationService.Transfers.cs create mode 100644 src/Yavsc.Api/Services/AdministrationService.cs create mode 100644 src/Yavsc.Api/Services/AdministrationServiceCollectionExtensions.cs create mode 100644 src/Yavsc.Api/Services/IAdministrationService.cs create mode 100644 src/test/Yavsc.Api.Tests/SiteAdministrationApiTests.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 81eba2037..86afc7514 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ ### Added +* [PostIt, Yavsc.Api] Portage des outils des contrôleurs web + d’administration : tableau de bord, rôles et affectations, clients OIDC + (paramètres, collections et secrets), scopes API et modèles de courriels. + Le dernier administrateur ne peut pas être exclu ; les suppressions et + rotations de secrets demandent une confirmation dans PostIt. +* [Yavsc.Api] Routes `api/v1/administration`, DTO dédiés, client + `AdministrationApiClient` et documentation OpenAPI protégée en développement. + Les secrets OIDC sont hachés en SHA-256 et seule leur création restitue + la valeur en clair, sans cache. +* [PostIt, Yavsc.Api] Export et import JSON transactionnel des données + métier des comptes, billets et activités. Les comptes sont transférés + sans mots de passe, jetons, confirmations ou rôles ; les contrôles + d’accès des billets sont conservés et leurs références sont validées. + * [PostIt] Service `CameraStreamer` (préliminaire) : détection des périphériques de capture vidéo (DirectShow sous Windows via le paquet `DirectShowLib`, `/dev/video*` sous Linux) et création d'un média @@ -13,12 +27,11 @@ * [PostIt] Une seule page Administration regroupe les utilisateurs et leurs usages, avec le détail d’usage de l’utilisateur sélectionné. - L’accès depuis l’accueil et sa commande sont réservés au rôle `Moderator`, + L’accès depuis l’accueil et sa commande sont désormais réservés au rôle `Administrator`, vérifié via le profil du backend. « Mon profil » conserve la gestion du compte et accueille l’usage personnel, sans consulter les usages globaux. -* [Yavsc.Api] Le rôle `Moderator` peut lire les listes d’utilisateurs et - d’usages ; les administrateurs conservent leurs droits existants et - les autres opérations sur les utilisateurs restent administrateur uniquement. +* [Yavsc.Api] Les listes d’utilisateurs et d’usages globaux sont désormais + réservées au rôle `Administrator`, comme les autres outils d’administration. * [PostIt] La page des blogs passe en deux colonnes lorsque la largeur de la fenêtre atteint sa hauteur : billet à gauche, recherche, commandes et diff --git a/Directory.Packages.props b/Directory.Packages.props index b0b055b98..1e3577c72 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -33,6 +33,7 @@ + diff --git a/doc/architecture/postit.md b/doc/architecture/postit.md index faa67ad38..2ba6cb812 100644 --- a/doc/architecture/postit.md +++ b/doc/architecture/postit.md @@ -56,17 +56,19 @@ pas la destruction du processus par Android. ## Administration et profil personnel L'accueil présente un seul accès **Administration**, visible et exécutable -uniquement pour le rôle exact `Moderator`. `SessionStatusViewModel.RefreshAsync` +uniquement pour le rôle exact `Administrator`. `SessionStatusViewModel.RefreshAsync` lit les rôles dans `GET account/me` après le rafraîchissement de session ; une erreur de lecture masque l'accès et affiche une erreur dans la bannière. La déconnexion invalide aussi les vérifications de droits encore en vol. -`AdministrationPage` regroupe deux onglets : utilisateurs et usages globaux. +`AdministrationPage` regroupe sept onglets : utilisateurs, usages globaux, +rôles, clients OIDC, scopes API, courriels et données. Les en-têtes se +répartissent sur plusieurs lignes sur les écrans étroits. Le détail d'un utilisateur associe son usage par `UserId`, sans dépendre du nom affiché. Les erreurs ne déclenchent pas de repli sur l'usage personnel. -Le backend autorise la lecture des listes aux rôles `Moderator` et -`Administrator`, mais garde les autres opérations utilisateurs sous la -politique `AdministratorOnly`. L'autorisation serveur reste indispensable, +Le backend réserve aussi les listes aux administrateurs, sous la +politique `AdministratorOnly`. Le rôle `Moderator` seul n'ouvre plus +l'administration de PostIt. L'autorisation serveur reste indispensable, indépendamment de la visibilité du bouton dans PostIt. **Mon profil** réutilise la page de gestion du compte existante et les @@ -74,6 +76,68 @@ endpoints `account/me` pour lire et enregistrer les informations personnelles. Elle affiche aussi `resource-usage/me`, sans appeler la liste des usages globaux. Une indisponibilité de l'usage n'empêche pas la gestion du profil. +### Outils d'administration du site + +Les outils portés correspondent aux contrôleurs de +`Yavsc.Org/Controllers/Administration` ; le site web existant est conservé. +Les nouvelles routes vivent dans Yavsc.Api sous `api/v1/administration`. +Les données passent par des records dédiés de Yavsc.Abstract et par +`AdministrationApiClient`, jamais par les entités EF. + +| Routes | Fonction | +| --- | --- | +| `summary`, `roles`, `roles/ensure` | Statistiques, informations du runtime, rôles standards | +| `roles/{id}/members[/{userId}]` | Membres, inscription et exclusion | +| `clients[/{id}]` | Paramètres scalaires des clients OAuth/OIDC | +| `clients/{id}/collections/{collection}[/{rowId}]` | Redirect URIs, post-logout URIs, scopes, grant types, CORS, restrictions IdP, claims et propriétés | +| `clients/{id}/secrets[/regenerate]`, `clients/{id}/secrets/{secretId}` | Ajout, rotation et suppression des secrets | +| `scopes[/{id}]` | Création, consultation, modification et suppression des scopes API | +| `mailing-criteria`, `mailing-templates[/{id}]` | Critères de diffusion et modèles de courriels | +| `transfer` | Export par GET, import transactionnel par POST | + +Les formulaires de clients, scopes et modèles sont construits à partir +des propriétés de leurs DTO typés, afin de conserver tous les champs de +l'interface web. Les booléens et énumérations disposent de contrôles dédiés ; +les valeurs numériques et annotations de validation sont vérifiées avant +l'envoi puis à nouveau côté serveur. + +Un nouveau client démarre avec `authorization_code`, PKCE et les scopes +`openid` et `profile`. Les autres types d'autorisation restent configurables +dans ses collections. Une rotation remplace tous les secrets précédents et +expire par défaut après 90 jours. Le serveur conserve uniquement le SHA-256 +du secret partagé ; les lectures ne rendent que les métadonnées. +PostIt efface la valeur nouvellement créée au changement de client, à la +prochaine opération et au détachement de la page. Une réponse de création +de secret reçue après déconnexion est ignorée ; un rafraîchissement sans +droits efface les données d'administration précédemment chargées. Supprimer un objet, +exclure un membre, remplacer les secrets ou importer demande une confirmation +explicite. Le serveur refuse l'exclusion du dernier administrateur. + +Le transfert JSON version 1 comprend les informations métier des comptes +(identifiant, nom, e-mail, nom complet, avatar, calendrier et limites de +stockage), les billets (contenu, auteur, publication, cercles et étiquettes) +et les paramètres des activités. Ce n'est pas une sauvegarde complète de +la base : fichiers, sessions, mots de passe, jetons, rôles, moyens de paiement, +adresses et historique d'audit ne sont pas transférés. L'e-mail d'un compte +existant, clé alternative du modèle actuel, ne peut pas être changé par import. +Les nouveaux comptes n'ont ni mot de passe ni e-mail confirmé. + +Les identifiants des billets sont préservés et la séquence PostgreSQL est +réajustée après l'import. Tous les auteurs et cercles doivent exister et +les parents d'activités doivent former un graphe sans cycle. Une erreur +annule l'ensemble de l'import ; aucun repli n'élargit les permissions d'un +billet. Les fichiers importés et requêtes sont limités à 16 Mio ; les gros +exports doivent être répartis en plusieurs transferts de même schéma. +Si plusieurs étiquettes portent déjà le même nom, l'import réutilise celle +ayant le plus petit identifiant sans supprimer les autres. + +L'initialisation du premier administrateur reste le mécanisme web existant, +et n'est pas proposée aux utilisateurs non administrateurs par PostIt. +En développement, `/openapi/administration.json` décrit uniquement ces outils, +avec accès `AdministratorOnly`. Les requêtes de référence sont dans +[Administration.http](../../src/Yavsc.Api/Administration.http) ; l'adresse +locale d'exemple est celle du serveur de tests, à adapter au déploiement. + ## Lecture des billets et fichiers utilisateur La page des blogs affiche par défaut une vue de lecture : titre, login diff --git a/src/PostIt/PostIt.Tests/AdministrationAccessTests.cs b/src/PostIt/PostIt.Tests/AdministrationAccessTests.cs index ef1466e5f..50e64f713 100644 --- a/src/PostIt/PostIt.Tests/AdministrationAccessTests.cs +++ b/src/PostIt/PostIt.Tests/AdministrationAccessTests.cs @@ -16,12 +16,13 @@ namespace PostIt.Tests; public class AdministrationAccessTests { [AvaloniaTheory] - [InlineData("Moderator", true)] - [InlineData("Administrator", false)] + [InlineData("Moderator", false)] + [InlineData("Administrator", true)] + [InlineData("administrator", false)] [InlineData("Performer", false)] [InlineData("moderator", false)] [InlineData("", false)] - public async Task Home_administration_visibility_and_command_follow_the_moderator_role(string role, bool allowed) + public async Task Home_administration_visibility_and_command_follow_the_administrator_role(string role, bool allowed) { await using var api = new ProfileTransport(role); var session = new SessionStatusViewModel { Api = api }; @@ -35,7 +36,7 @@ public class AdministrationAccessTests button => Equals(button.Content, "Administration")); Assert.False(button.IsVisible); await session.RefreshAsync(); - Assert.Equal(allowed, session.IsModerator); + Assert.Equal(allowed, session.IsAdministrator); Assert.Equal(allowed, button.IsVisible); Assert.Equal(allowed, vm.OpenAdministration.CanExecute(null)); Assert.DoesNotContain(page.GetVisualDescendants().OfType