From 4921a0691cfbf301594052a917a1d9444a4a81be Mon Sep 17 00:00:00 2001 From: Alpinux Date: Mon, 4 May 2026 00:59:25 +0200 Subject: [PATCH] Admin : gestion groupes, services par groupes, badge admin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - admin/groups.php : liste/création/suppression des groupes Keycloak avec comptage des membres et services associés par groupe - admin/services.php : remplace requires_adherent par sélection multi-groupes - inc/services.php : modèle groups[], migration auto depuis requires_adherent, helper service_accessible() pour l'accès contextuel - inc/keycloak.php : kc_list_groups, kc_create_group, kc_delete_group, kc_group_members - profile.php : badge Admin visible dans la tuile Mon compte - index.php : utilise service_accessible() avec les groupes de l'utilisateur Co-Authored-By: Claude Sonnet 4.6 --- web/admin/groups.php | 127 +++++++++++++++++++++++++++++++++++++++++ web/admin/members.php | 1 + web/admin/services.php | 67 ++++++++++++++-------- web/assets/style.css | 2 + web/inc/keycloak.php | 23 ++++++++ web/inc/services.php | 29 +++++++--- web/index.php | 9 ++- web/profile.php | 7 ++- 8 files changed, 228 insertions(+), 37 deletions(-) create mode 100644 web/admin/groups.php diff --git a/web/admin/groups.php b/web/admin/groups.php new file mode 100644 index 0000000..0a64b27 --- /dev/null +++ b/web/admin/groups.php @@ -0,0 +1,127 @@ +getMessage(); } + } + } + + if ($action === 'delete') { + $group_id = $_POST['group_id'] ?? ''; + if ($group_id) { + try { + kc_delete_group($group_id); + set_flash('success', 'Groupe supprimé.'); + header('Location: /admin/groups.php'); exit; + } catch (Exception $e) { $errors[] = $e->getMessage(); } + } + } +} + +$groups = kc_list_groups(); +$services = services_list(); + +// Indexe les services par groupe requis +$services_by_group = []; +foreach ($services as $s) { + foreach ($s['groups'] ?? [] as $g) { + $services_by_group[$g][] = $s['name']; + } +} + +$title = 'Gestion des groupes'; +require __DIR__ . '/../views/layout.php'; +?> + +
+ + + +
+ + + +
+

Groupes Keycloak ()

+ +

Aucun groupe trouvé.

+ + + + + + + + + + + + + + + +
NomMembresServices associésActions
+ + + + + + + + +
+ + + +
+
+ +
+ + +
+

Créer un groupe

+
+ +
+ + +
+ +
+
+
+ + diff --git a/web/admin/members.php b/web/admin/members.php index b47bad3..5bfc1e2 100644 --- a/web/admin/members.php +++ b/web/admin/members.php @@ -28,6 +28,7 @@ require __DIR__ . '/../views/layout.php';

Gestion des membres

diff --git a/web/admin/services.php b/web/admin/services.php index e7828f9..7c5ca84 100644 --- a/web/admin/services.php +++ b/web/admin/services.php @@ -2,36 +2,39 @@ require_once __DIR__ . '/../inc/config.php'; require_once __DIR__ . '/../inc/auth.php'; require_once __DIR__ . '/../inc/services.php'; +require_once __DIR__ . '/../inc/keycloak.php'; session_start_safe(); require_admin(); +$kc_groups = kc_list_groups(); +$group_names = array_column($kc_groups, 'name'); + if ($_SERVER['REQUEST_METHOD'] === 'POST') { $updated = []; foreach ($_POST as $key => $val) { if (!str_starts_with($key, 'name_')) continue; $idx = substr($key, 5); $updated[] = [ - 'name' => trim($val), - 'url' => trim($_POST["url_$idx"] ?? ''), - 'description' => trim($_POST["description_$idx"] ?? ''), - 'requires_adherent' => isset($_POST["requires_$idx"]), - 'visible' => isset($_POST["visible_$idx"]), + 'name' => trim($val), + 'url' => trim($_POST["url_$idx"] ?? ''), + 'description' => trim($_POST["description_$idx"] ?? ''), + 'groups' => (array)($_POST["groups_$idx"] ?? []), + 'visible' => isset($_POST["visible_$idx"]), ]; } - // Ajout d'un nouveau service si rempli $new_name = trim($_POST['new_name'] ?? ''); if ($new_name) { $updated[] = [ - 'name' => $new_name, - 'url' => trim($_POST['new_url'] ?? ''), - 'description' => trim($_POST['new_description'] ?? ''), - 'requires_adherent' => isset($_POST['new_requires']), - 'visible' => isset($_POST['new_visible']), + 'name' => $new_name, + 'url' => trim($_POST['new_url'] ?? ''), + 'description' => trim($_POST['new_description'] ?? ''), + 'groups' => (array)($_POST['new_groups'] ?? []), + 'visible' => isset($_POST['new_visible']), ]; } services_save($updated); - set_flash('success', 'Configuration des services sauvegardée.'); + set_flash('success', 'Services sauvegardés.'); header('Location: /admin/services.php'); exit; } @@ -46,16 +49,12 @@ require __DIR__ . '/../views/layout.php';

Paramétrage des services

-

- Définissez quels services sont accessibles aux simples inscrits - et lesquels nécessitent une adhésion validée. -

-
@@ -63,7 +62,7 @@ require __DIR__ . '/../views/layout.php'; - + @@ -73,26 +72,46 @@ require __DIR__ . '/../views/layout.php'; - - - +
Nom URL DescriptionAdhérent requisGroupes requis Visible
- > + +
+ + + + + Aucun groupe + +
>
+
+ + + +
+
- - +
+ +
diff --git a/web/assets/style.css b/web/assets/style.css index ca565d7..3f7ec2c 100644 --- a/web/assets/style.css +++ b/web/assets/style.css @@ -256,6 +256,8 @@ td.center { text-align: center; } padding: .3rem .5rem; font-size: .85rem; } .new-row td { background: #eef1ff; } +.group-checks { display: flex; flex-direction: column; gap: .2rem; } +.group-check-label { display: flex; align-items: center; gap: .4rem; font-size: .8rem; cursor: pointer; white-space: nowrap; } /* ── Helpers ──────────────────────────────────────────────────────── */ .text-success { color: var(--success); } diff --git a/web/inc/keycloak.php b/web/inc/keycloak.php index ab229d0..bc958c2 100644 --- a/web/inc/keycloak.php +++ b/web/inc/keycloak.php @@ -174,6 +174,29 @@ function kc_list_users(int $max = 200): array { return $users; } +function kc_list_groups(): array { + $resp = _kc_request('GET', '/groups?max=200&briefRepresentation=false'); + if ($resp['status'] !== 200) return []; + return json_decode($resp['body'], true) ?? []; +} + +function kc_create_group(string $name): void { + $resp = _kc_request('POST', '/groups', ['name' => $name]); + if ($resp['status'] === 409) throw new RuntimeException("Le groupe « $name » existe déjà."); + if ($resp['status'] !== 201) throw new RuntimeException("Erreur création groupe ({$resp['status']})"); +} + +function kc_delete_group(string $group_id): void { + $resp = _kc_request('DELETE', "/groups/$group_id"); + if ($resp['status'] >= 300) throw new RuntimeException("Erreur suppression groupe ({$resp['status']})"); +} + +function kc_group_members(string $group_id, int $max = 200): array { + $resp = _kc_request('GET', "/groups/$group_id/members?max=$max"); + if ($resp['status'] !== 200) return []; + return json_decode($resp['body'], true) ?? []; +} + function _kc_group_id(string $group_name): string { $resp = _kc_request('GET', '/groups?search=' . urlencode($group_name)); $groups = json_decode($resp['body'], true) ?? []; diff --git a/web/inc/services.php b/web/inc/services.php index 8604554..67710d3 100644 --- a/web/inc/services.php +++ b/web/inc/services.php @@ -2,23 +2,38 @@ require_once __DIR__ . '/config.php'; const DEFAULT_SERVICES = [ - ['name' => 'Wiki', 'url' => 'https://wiki.alpinux.org', 'description' => 'Documentation et guides', 'requires_adherent' => false, 'visible' => true], - ['name' => 'Gitea', 'url' => 'https://gitea.alpinux.org', 'description' => 'Forge de code', 'requires_adherent' => false, 'visible' => true], - ['name' => 'Quiz interactifs','url' => 'https://dynamic.alpinux.org', 'description' => 'Jeux et quiz', 'requires_adherent' => false, 'visible' => true], - ['name' => 'Install Party', 'url' => 'https://installparty.alpinux.org', 'description' => 'Événements et ateliers', 'requires_adherent' => false, 'visible' => true], - ['name' => 'Nextcloud', 'url' => 'https://cloud.alpinux.org', 'description' => 'Stockage et collaboration', 'requires_adherent' => true, 'visible' => true], - ['name' => 'Dolibarr', 'url' => 'https://dolibarr.alpinux.org', 'description' => 'Gestion de l\'association', 'requires_adherent' => true, 'visible' => true], + ['name' => 'Wiki', 'url' => 'https://wiki.alpinux.org', 'description' => 'Documentation et guides', 'groups' => [], 'visible' => true], + ['name' => 'Gitea', 'url' => 'https://gitea.alpinux.org', 'description' => 'Forge de code', 'groups' => [], 'visible' => true], + ['name' => 'Quiz interactifs','url' => 'https://dynamic.alpinux.org', 'description' => 'Jeux et quiz', 'groups' => [], 'visible' => true], + ['name' => 'Install Party', 'url' => 'https://installparty.alpinux.org', 'description' => 'Événements et ateliers', 'groups' => [], 'visible' => true], + ['name' => 'Nextcloud', 'url' => 'https://cloud.alpinux.org', 'description' => 'Stockage et collaboration', 'groups' => ['adherents'], 'visible' => true], + ['name' => 'Dolibarr', 'url' => 'https://dolibarr.alpinux.org', 'description' => 'Gestion de l\'association', 'groups' => ['adherents'], 'visible' => true], ]; function services_list(): array { $file = SERVICES_FILE; if (is_file($file)) { $data = json_decode(file_get_contents($file), true); - if (is_array($data)) return $data; + if (is_array($data)) return array_map('_service_normalize', $data); } return DEFAULT_SERVICES; } +function _service_normalize(array $s): array { + // Migre l'ancien champ requires_adherent vers groups + if (!array_key_exists('groups', $s)) { + $s['groups'] = ($s['requires_adherent'] ?? false) ? [ADHERENT_GROUP] : []; + } + unset($s['requires_adherent']); + return $s; +} + +function service_accessible(array $service, array $user_groups, bool $is_admin): bool { + if ($is_admin) return true; + $required = $service['groups'] ?? []; + return empty($required) || (bool) array_intersect($required, $user_groups); +} + function services_save(array $services): void { $dir = dirname(SERVICES_FILE); if (!is_dir($dir)) mkdir($dir, 0755, true); diff --git a/web/index.php b/web/index.php index a1b5cf6..e063a59 100644 --- a/web/index.php +++ b/web/index.php @@ -9,10 +9,9 @@ $user = current_user(); $title = 'Portail Alpinux'; $services = services_list(); -$is_adherent = $user && ( - $user['is_adherent'] || - ($user['is_admin'] ?? false) -); +$is_admin = $user && ($user['is_admin'] ?? false); +$is_adherent = $user && ($user['is_adherent'] || $is_admin); +$user_groups = $user['groups'] ?? []; require __DIR__ . '/views/layout.php'; ?> @@ -69,7 +68,7 @@ require __DIR__ . '/views/layout.php';

Nos services

+ $can_access = service_accessible($s, $user_groups, $is_admin); ?>
diff --git a/web/profile.php b/web/profile.php index 774c260..543a13b 100644 --- a/web/profile.php +++ b/web/profile.php @@ -186,7 +186,12 @@ require __DIR__ . '/views/layout.php';
Mon compte
-
+
+ + + Admin + +
Modifier