Files
crm_ecosplay/src/Service/KeycloakAdminService.php

324 lines
10 KiB
PHP
Raw Normal View History

2026-04-01 15:42:52 +02:00
<?php
namespace App\Service;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class KeycloakAdminService
{
private const PATH_USERS = '/users';
private const PATH_GROUPS = '/groups';
private const AUTH_BEARER = 'Bearer ';
feat: comptabilite + prestataires + rapport financier + stats dynamiques Comptabilite (Super Admin) : - ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE (journal ventes, grand livre, FEC, balance agee, reglements, commissions Stripe 1.5%+0.25E, couts services) - Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli, tableau pagine, champ signature DocuSeal - Signature electronique DocuSeal + callback + envoi email signe avec template dedie (compta_export_signed.html.twig) - Rapport financier public (RapportFinancierPdf) : recettes par service, depenses (Stripe, infra, prestataires), bilan excedent/deficit - Codes comptables clients EC-XXXX (plus de 411xxx) Prestataires (Super Admin) : - Entite Prestataire (raisonSociale, siret, email, phone, adresse) - Entite FacturePrestataire (numFacture, montantHt, montantTtc, year, month, isPaid, PDF via Vich) - CRUD complet avec recherche SIRET via proxy API data.gouv.fr - Commande cron app:reminder:factures-prestataire (5 du mois) - Factures prestataires integrees dans export couts services - Sidebar Super Admin : entree Prestataires + Comptabilite Stats (/admin/stats) : - Cout prestataire dynamique depuis FacturePrestataire - Fusion Infra + Prestataire en "Cout de fonctionnement" - Commission Stripe corrigee (1.5% + 0.25E par transaction) Divers : - DocuSealService::sendComptaForSignature() + getApi() - Customer::generateCodeComptable() format EC-XXXX-XXXXX - Protection double prefixe EC- a la creation client - Bouton regenerer PDF cache quand advert state=accepted - Modals sans script inline (data-modal-open/close dans app.js) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
/** Groupes requis pour le CRM E-Cosplay */
feat: complete glassmorphism redesign across all templates + Keycloak groups auto-provisioning Templates updated to glassmorphism (40+ files): - templates/admin/clients/ (create, index): glass cards, input-glass inputs, btn-gold buttons, glass table headers, semi-transparent badges - templates/admin/dashboard.html.twig: glass KPI cards - templates/admin/profil/index.html.twig: glass form panels - templates/admin/revendeurs/ (create, edit, index): glass cards and tables - templates/admin/services/index.html.twig: glass service cards - templates/admin/status/ (index, manage): glass panels - templates/admin/sync/index.html.twig: glass panels - templates/admin/facturation/index.html.twig: glass tables - templates/admin/membres.html.twig: glass form, checkboxes with esy-* group values (esy-web, esy-mail, esy-mailer, esy-analytics, esy-monitor, esy-defender, esy-translate, esy-signature, esy-creator, esy-aide, esy-meet, esy-tchat, esy-ndd), Keycloak groups column in table, available groups section - templates/admin/stats/index.html.twig: glass KPI cards, glass-gold CA TTC, factures emises/payees/impayees cards, services renamed to Esy-*, rounded progress bars, bg-gray-200 track backgrounds - templates/security/ (2fa_email, 2fa_google, forgot_password, set_password, set_password_expired): glass headers, glass-heavy cards, input-glass - templates/legal/ (cgu, cgv, cookie, conformite, hebergement, mention_legal, rgpd, tarif): removed thick borders, font-black to font-bold, text-3xl to text-2xl headings - templates/attestation/ (verify, not_found): glass panels - templates/espace_client/index.html.twig: glass panels - templates/espace_prestataire/index.html.twig: glass panels - templates/external_redirect.html.twig: glass card - templates/status/index.html.twig: glass panels - templates/email/base.html.twig: gradient gold header, rounded-16px container, semi-transparent bg, soft shadow, footer address - templates/emails/*.html.twig (9 files): removed 4px borders, font-weight 900 to 700 - templates/pdf/*.html.twig (4 files): rounded borders, gradient header, lighter borders Keycloak auto-provisioning: - src/Service/KeycloakAdminService.php: added REQUIRED_GROUPS constant (15 groups: siteconseil_admin, siteconseil_member, esy-web, esy-mail, esy-mailer, esy-analytics, esy-monitor, esy-defender, esy-translate, esy-signature, esy-creator, esy-aide, esy-meet, esy-tchat, esy-ndd), ensureRequiredGroups() method that checks existing groups and creates missing ones, createGroup() method, getRequiredGroups() static accessor - src/Controller/Admin/MembresController.php: calls ensureRequiredGroups() on page load, shows flash for each auto-created group, fetches user groups per member, passes availableGroups to template Stats controller updated: - src/Controller/Admin/StatsController.php: services renamed to Esy-* (13 services), added factures_emises/payees/impayees KPI data OAuth fix: - src/Security/KeycloakAuthenticator.php: removed dd() debug calls, restored flash message on auth failure with error detail Config: - .env: KEYCLOAK_ADMIN_CLIENT_ID=crm_siteconseil_admin, secret updated - .env.local: same updates - ansible/env.local.j2: KEYCLOAK_ADMIN_CLIENT_ID=crm_siteconseil_admin Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:34:35 +02:00
private const REQUIRED_GROUPS = [
feat: comptabilite + prestataires + rapport financier + stats dynamiques Comptabilite (Super Admin) : - ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE (journal ventes, grand livre, FEC, balance agee, reglements, commissions Stripe 1.5%+0.25E, couts services) - Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli, tableau pagine, champ signature DocuSeal - Signature electronique DocuSeal + callback + envoi email signe avec template dedie (compta_export_signed.html.twig) - Rapport financier public (RapportFinancierPdf) : recettes par service, depenses (Stripe, infra, prestataires), bilan excedent/deficit - Codes comptables clients EC-XXXX (plus de 411xxx) Prestataires (Super Admin) : - Entite Prestataire (raisonSociale, siret, email, phone, adresse) - Entite FacturePrestataire (numFacture, montantHt, montantTtc, year, month, isPaid, PDF via Vich) - CRUD complet avec recherche SIRET via proxy API data.gouv.fr - Commande cron app:reminder:factures-prestataire (5 du mois) - Factures prestataires integrees dans export couts services - Sidebar Super Admin : entree Prestataires + Comptabilite Stats (/admin/stats) : - Cout prestataire dynamique depuis FacturePrestataire - Fusion Infra + Prestataire en "Cout de fonctionnement" - Commission Stripe corrigee (1.5% + 0.25E par transaction) Divers : - DocuSealService::sendComptaForSignature() + getApi() - Customer::generateCodeComptable() format EC-XXXX-XXXXX - Protection double prefixe EC- a la creation client - Bouton regenerer PDF cache quand advert state=accepted - Modals sans script inline (data-modal-open/close dans app.js) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
'superadmin',
'super_admin_asso',
'gp_asso',
'gp_contest',
'gp_mail',
'gp_mailling',
'gp_member',
'gp_ndd',
'gp_sign',
'gp_ticket',
feat: complete glassmorphism redesign across all templates + Keycloak groups auto-provisioning Templates updated to glassmorphism (40+ files): - templates/admin/clients/ (create, index): glass cards, input-glass inputs, btn-gold buttons, glass table headers, semi-transparent badges - templates/admin/dashboard.html.twig: glass KPI cards - templates/admin/profil/index.html.twig: glass form panels - templates/admin/revendeurs/ (create, edit, index): glass cards and tables - templates/admin/services/index.html.twig: glass service cards - templates/admin/status/ (index, manage): glass panels - templates/admin/sync/index.html.twig: glass panels - templates/admin/facturation/index.html.twig: glass tables - templates/admin/membres.html.twig: glass form, checkboxes with esy-* group values (esy-web, esy-mail, esy-mailer, esy-analytics, esy-monitor, esy-defender, esy-translate, esy-signature, esy-creator, esy-aide, esy-meet, esy-tchat, esy-ndd), Keycloak groups column in table, available groups section - templates/admin/stats/index.html.twig: glass KPI cards, glass-gold CA TTC, factures emises/payees/impayees cards, services renamed to Esy-*, rounded progress bars, bg-gray-200 track backgrounds - templates/security/ (2fa_email, 2fa_google, forgot_password, set_password, set_password_expired): glass headers, glass-heavy cards, input-glass - templates/legal/ (cgu, cgv, cookie, conformite, hebergement, mention_legal, rgpd, tarif): removed thick borders, font-black to font-bold, text-3xl to text-2xl headings - templates/attestation/ (verify, not_found): glass panels - templates/espace_client/index.html.twig: glass panels - templates/espace_prestataire/index.html.twig: glass panels - templates/external_redirect.html.twig: glass card - templates/status/index.html.twig: glass panels - templates/email/base.html.twig: gradient gold header, rounded-16px container, semi-transparent bg, soft shadow, footer address - templates/emails/*.html.twig (9 files): removed 4px borders, font-weight 900 to 700 - templates/pdf/*.html.twig (4 files): rounded borders, gradient header, lighter borders Keycloak auto-provisioning: - src/Service/KeycloakAdminService.php: added REQUIRED_GROUPS constant (15 groups: siteconseil_admin, siteconseil_member, esy-web, esy-mail, esy-mailer, esy-analytics, esy-monitor, esy-defender, esy-translate, esy-signature, esy-creator, esy-aide, esy-meet, esy-tchat, esy-ndd), ensureRequiredGroups() method that checks existing groups and creates missing ones, createGroup() method, getRequiredGroups() static accessor - src/Controller/Admin/MembresController.php: calls ensureRequiredGroups() on page load, shows flash for each auto-created group, fetches user groups per member, passes availableGroups to template Stats controller updated: - src/Controller/Admin/StatsController.php: services renamed to Esy-* (13 services), added factures_emises/payees/impayees KPI data OAuth fix: - src/Security/KeycloakAuthenticator.php: removed dd() debug calls, restored flash message on auth failure with error detail Config: - .env: KEYCLOAK_ADMIN_CLIENT_ID=crm_siteconseil_admin, secret updated - .env.local: same updates - ansible/env.local.j2: KEYCLOAK_ADMIN_CLIENT_ID=crm_siteconseil_admin Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:34:35 +02:00
];
2026-04-01 15:42:52 +02:00
private ?string $accessToken = null;
private ?int $tokenExpiresAt = null;
public function __construct(
private HttpClientInterface $httpClient,
#[Autowire(env: 'OAUTH_KEYCLOAK_URL')] private string $keycloakUrl,
#[Autowire(env: 'OAUTH_KEYCLOAK_REALM')] private string $realm,
#[Autowire(env: 'KEYCLOAK_ADMIN_CLIENT_ID')] private string $clientId,
#[Autowire(env: 'KEYCLOAK_ADMIN_CLIENT_SECRET')] private string $clientSecret,
) {
}
feat: complete glassmorphism redesign across all templates + Keycloak groups auto-provisioning Templates updated to glassmorphism (40+ files): - templates/admin/clients/ (create, index): glass cards, input-glass inputs, btn-gold buttons, glass table headers, semi-transparent badges - templates/admin/dashboard.html.twig: glass KPI cards - templates/admin/profil/index.html.twig: glass form panels - templates/admin/revendeurs/ (create, edit, index): glass cards and tables - templates/admin/services/index.html.twig: glass service cards - templates/admin/status/ (index, manage): glass panels - templates/admin/sync/index.html.twig: glass panels - templates/admin/facturation/index.html.twig: glass tables - templates/admin/membres.html.twig: glass form, checkboxes with esy-* group values (esy-web, esy-mail, esy-mailer, esy-analytics, esy-monitor, esy-defender, esy-translate, esy-signature, esy-creator, esy-aide, esy-meet, esy-tchat, esy-ndd), Keycloak groups column in table, available groups section - templates/admin/stats/index.html.twig: glass KPI cards, glass-gold CA TTC, factures emises/payees/impayees cards, services renamed to Esy-*, rounded progress bars, bg-gray-200 track backgrounds - templates/security/ (2fa_email, 2fa_google, forgot_password, set_password, set_password_expired): glass headers, glass-heavy cards, input-glass - templates/legal/ (cgu, cgv, cookie, conformite, hebergement, mention_legal, rgpd, tarif): removed thick borders, font-black to font-bold, text-3xl to text-2xl headings - templates/attestation/ (verify, not_found): glass panels - templates/espace_client/index.html.twig: glass panels - templates/espace_prestataire/index.html.twig: glass panels - templates/external_redirect.html.twig: glass card - templates/status/index.html.twig: glass panels - templates/email/base.html.twig: gradient gold header, rounded-16px container, semi-transparent bg, soft shadow, footer address - templates/emails/*.html.twig (9 files): removed 4px borders, font-weight 900 to 700 - templates/pdf/*.html.twig (4 files): rounded borders, gradient header, lighter borders Keycloak auto-provisioning: - src/Service/KeycloakAdminService.php: added REQUIRED_GROUPS constant (15 groups: siteconseil_admin, siteconseil_member, esy-web, esy-mail, esy-mailer, esy-analytics, esy-monitor, esy-defender, esy-translate, esy-signature, esy-creator, esy-aide, esy-meet, esy-tchat, esy-ndd), ensureRequiredGroups() method that checks existing groups and creates missing ones, createGroup() method, getRequiredGroups() static accessor - src/Controller/Admin/MembresController.php: calls ensureRequiredGroups() on page load, shows flash for each auto-created group, fetches user groups per member, passes availableGroups to template Stats controller updated: - src/Controller/Admin/StatsController.php: services renamed to Esy-* (13 services), added factures_emises/payees/impayees KPI data OAuth fix: - src/Security/KeycloakAuthenticator.php: removed dd() debug calls, restored flash message on auth failure with error detail Config: - .env: KEYCLOAK_ADMIN_CLIENT_ID=crm_siteconseil_admin, secret updated - .env.local: same updates - ansible/env.local.j2: KEYCLOAK_ADMIN_CLIENT_ID=crm_siteconseil_admin Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:34:35 +02:00
/**
* Verifie que tous les groupes requis existent dans Keycloak, cree ceux qui manquent.
*
* @return list<string> liste des groupes crees
*/
public function ensureRequiredGroups(): array
{
$existingGroups = $this->listGroups();
$existingNames = array_map(fn (array $g) => $g['name'], $existingGroups);
$created = [];
foreach (self::REQUIRED_GROUPS as $groupName) {
if (!\in_array($groupName, $existingNames, true) && $this->createGroup($groupName)) {
$created[] = $groupName;
feat: complete glassmorphism redesign across all templates + Keycloak groups auto-provisioning Templates updated to glassmorphism (40+ files): - templates/admin/clients/ (create, index): glass cards, input-glass inputs, btn-gold buttons, glass table headers, semi-transparent badges - templates/admin/dashboard.html.twig: glass KPI cards - templates/admin/profil/index.html.twig: glass form panels - templates/admin/revendeurs/ (create, edit, index): glass cards and tables - templates/admin/services/index.html.twig: glass service cards - templates/admin/status/ (index, manage): glass panels - templates/admin/sync/index.html.twig: glass panels - templates/admin/facturation/index.html.twig: glass tables - templates/admin/membres.html.twig: glass form, checkboxes with esy-* group values (esy-web, esy-mail, esy-mailer, esy-analytics, esy-monitor, esy-defender, esy-translate, esy-signature, esy-creator, esy-aide, esy-meet, esy-tchat, esy-ndd), Keycloak groups column in table, available groups section - templates/admin/stats/index.html.twig: glass KPI cards, glass-gold CA TTC, factures emises/payees/impayees cards, services renamed to Esy-*, rounded progress bars, bg-gray-200 track backgrounds - templates/security/ (2fa_email, 2fa_google, forgot_password, set_password, set_password_expired): glass headers, glass-heavy cards, input-glass - templates/legal/ (cgu, cgv, cookie, conformite, hebergement, mention_legal, rgpd, tarif): removed thick borders, font-black to font-bold, text-3xl to text-2xl headings - templates/attestation/ (verify, not_found): glass panels - templates/espace_client/index.html.twig: glass panels - templates/espace_prestataire/index.html.twig: glass panels - templates/external_redirect.html.twig: glass card - templates/status/index.html.twig: glass panels - templates/email/base.html.twig: gradient gold header, rounded-16px container, semi-transparent bg, soft shadow, footer address - templates/emails/*.html.twig (9 files): removed 4px borders, font-weight 900 to 700 - templates/pdf/*.html.twig (4 files): rounded borders, gradient header, lighter borders Keycloak auto-provisioning: - src/Service/KeycloakAdminService.php: added REQUIRED_GROUPS constant (15 groups: siteconseil_admin, siteconseil_member, esy-web, esy-mail, esy-mailer, esy-analytics, esy-monitor, esy-defender, esy-translate, esy-signature, esy-creator, esy-aide, esy-meet, esy-tchat, esy-ndd), ensureRequiredGroups() method that checks existing groups and creates missing ones, createGroup() method, getRequiredGroups() static accessor - src/Controller/Admin/MembresController.php: calls ensureRequiredGroups() on page load, shows flash for each auto-created group, fetches user groups per member, passes availableGroups to template Stats controller updated: - src/Controller/Admin/StatsController.php: services renamed to Esy-* (13 services), added factures_emises/payees/impayees KPI data OAuth fix: - src/Security/KeycloakAuthenticator.php: removed dd() debug calls, restored flash message on auth failure with error detail Config: - .env: KEYCLOAK_ADMIN_CLIENT_ID=crm_siteconseil_admin, secret updated - .env.local: same updates - ansible/env.local.j2: KEYCLOAK_ADMIN_CLIENT_ID=crm_siteconseil_admin Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:34:35 +02:00
}
}
return $created;
}
/**
* Creer un groupe dans Keycloak.
*/
public function createGroup(string $groupName): bool
{
$token = $this->getToken();
$response = $this->httpClient->request('POST', $this->getAdminUrl(self::PATH_GROUPS), [
feat: complete glassmorphism redesign across all templates + Keycloak groups auto-provisioning Templates updated to glassmorphism (40+ files): - templates/admin/clients/ (create, index): glass cards, input-glass inputs, btn-gold buttons, glass table headers, semi-transparent badges - templates/admin/dashboard.html.twig: glass KPI cards - templates/admin/profil/index.html.twig: glass form panels - templates/admin/revendeurs/ (create, edit, index): glass cards and tables - templates/admin/services/index.html.twig: glass service cards - templates/admin/status/ (index, manage): glass panels - templates/admin/sync/index.html.twig: glass panels - templates/admin/facturation/index.html.twig: glass tables - templates/admin/membres.html.twig: glass form, checkboxes with esy-* group values (esy-web, esy-mail, esy-mailer, esy-analytics, esy-monitor, esy-defender, esy-translate, esy-signature, esy-creator, esy-aide, esy-meet, esy-tchat, esy-ndd), Keycloak groups column in table, available groups section - templates/admin/stats/index.html.twig: glass KPI cards, glass-gold CA TTC, factures emises/payees/impayees cards, services renamed to Esy-*, rounded progress bars, bg-gray-200 track backgrounds - templates/security/ (2fa_email, 2fa_google, forgot_password, set_password, set_password_expired): glass headers, glass-heavy cards, input-glass - templates/legal/ (cgu, cgv, cookie, conformite, hebergement, mention_legal, rgpd, tarif): removed thick borders, font-black to font-bold, text-3xl to text-2xl headings - templates/attestation/ (verify, not_found): glass panels - templates/espace_client/index.html.twig: glass panels - templates/espace_prestataire/index.html.twig: glass panels - templates/external_redirect.html.twig: glass card - templates/status/index.html.twig: glass panels - templates/email/base.html.twig: gradient gold header, rounded-16px container, semi-transparent bg, soft shadow, footer address - templates/emails/*.html.twig (9 files): removed 4px borders, font-weight 900 to 700 - templates/pdf/*.html.twig (4 files): rounded borders, gradient header, lighter borders Keycloak auto-provisioning: - src/Service/KeycloakAdminService.php: added REQUIRED_GROUPS constant (15 groups: siteconseil_admin, siteconseil_member, esy-web, esy-mail, esy-mailer, esy-analytics, esy-monitor, esy-defender, esy-translate, esy-signature, esy-creator, esy-aide, esy-meet, esy-tchat, esy-ndd), ensureRequiredGroups() method that checks existing groups and creates missing ones, createGroup() method, getRequiredGroups() static accessor - src/Controller/Admin/MembresController.php: calls ensureRequiredGroups() on page load, shows flash for each auto-created group, fetches user groups per member, passes availableGroups to template Stats controller updated: - src/Controller/Admin/StatsController.php: services renamed to Esy-* (13 services), added factures_emises/payees/impayees KPI data OAuth fix: - src/Security/KeycloakAuthenticator.php: removed dd() debug calls, restored flash message on auth failure with error detail Config: - .env: KEYCLOAK_ADMIN_CLIENT_ID=crm_siteconseil_admin, secret updated - .env.local: same updates - ansible/env.local.j2: KEYCLOAK_ADMIN_CLIENT_ID=crm_siteconseil_admin Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:34:35 +02:00
'headers' => ['Authorization' => self::AUTH_BEARER.$token],
'json' => ['name' => $groupName],
]);
return 201 === $response->getStatusCode();
}
2026-04-01 15:42:52 +02:00
/**
* Creer un utilisateur dans Keycloak.
*
* @return array{created: bool, keycloakId: string|null, tempPassword: string|null}
*/
public function createUser(string $email, string $firstName, string $lastName): array
{
$token = $this->getToken();
$tempPassword = bin2hex(random_bytes(8));
// Creer l'utilisateur
$response = $this->httpClient->request('POST', $this->getAdminUrl(self::PATH_USERS), [
'headers' => ['Authorization' => self::AUTH_BEARER.$token],
2026-04-01 15:42:52 +02:00
'json' => [
'username' => $email,
'email' => $email,
'firstName' => $firstName,
'lastName' => $lastName,
'enabled' => true,
'emailVerified' => true,
'requiredActions' => ['UPDATE_PASSWORD', 'CONFIGURE_TOTP'],
'credentials' => [
[
'type' => 'password',
'value' => $tempPassword,
'temporary' => true,
],
],
],
]);
if (201 !== $response->getStatusCode()) {
return ['created' => false, 'keycloakId' => null, 'tempPassword' => null];
}
// Recuperer l'ID du user cree
$locationHeader = $response->getHeaders(false)['location'][0] ?? '';
$keycloakId = basename($locationHeader);
if ('' === $keycloakId) {
$keycloakId = $this->getUserIdByEmail($email);
}
return [
'created' => true,
'keycloakId' => $keycloakId,
'tempPassword' => $tempPassword,
];
}
/**
* Ajouter un utilisateur a un groupe Keycloak.
*/
public function addUserToGroup(string $keycloakId, string $groupName): bool
{
$groupId = $this->getGroupIdByName($groupName);
if (null === $groupId) {
return false;
}
$token = $this->getToken();
$response = $this->httpClient->request('PUT', $this->getAdminUrl(self::PATH_USERS.'/'.$keycloakId.'/groups/'.$groupId), [
'headers' => ['Authorization' => self::AUTH_BEARER.$token],
2026-04-01 15:42:52 +02:00
]);
return 204 === $response->getStatusCode();
}
/**
* Supprimer un utilisateur de Keycloak.
*/
public function deleteUser(string $keycloakId): bool
{
$token = $this->getToken();
$response = $this->httpClient->request('DELETE', $this->getAdminUrl(self::PATH_USERS.'/'.$keycloakId), [
'headers' => ['Authorization' => self::AUTH_BEARER.$token],
2026-04-01 15:42:52 +02:00
]);
return 204 === $response->getStatusCode();
}
/**
* Mettre a jour les infos d'un utilisateur dans Keycloak.
*/
public function updateUser(string $keycloakId, string $firstName, string $lastName, string $email): bool
{
$token = $this->getToken();
$response = $this->httpClient->request('PUT', $this->getAdminUrl(self::PATH_USERS.'/'.$keycloakId), [
'headers' => ['Authorization' => self::AUTH_BEARER.$token],
2026-04-01 15:42:52 +02:00
'json' => [
'firstName' => $firstName,
'lastName' => $lastName,
'email' => $email,
'username' => $email,
],
]);
return 204 === $response->getStatusCode();
}
/**
* Reset le mot de passe d'un utilisateur dans Keycloak.
*/
public function resetPassword(string $keycloakId, string $newPassword): bool
{
$token = $this->getToken();
$response = $this->httpClient->request('PUT', $this->getAdminUrl(self::PATH_USERS.'/'.$keycloakId.'/reset-password'), [
'headers' => ['Authorization' => self::AUTH_BEARER.$token],
2026-04-01 15:42:52 +02:00
'json' => [
'type' => 'password',
'value' => $newPassword,
'temporary' => false,
],
]);
return 204 === $response->getStatusCode();
}
/**
* Envoyer un email de reset password a l'utilisateur.
*/
public function sendResetPasswordEmail(string $keycloakId): bool
{
$token = $this->getToken();
$response = $this->httpClient->request('PUT', $this->getAdminUrl(self::PATH_USERS.'/'.$keycloakId.'/execute-actions-email'), [
'headers' => ['Authorization' => self::AUTH_BEARER.$token],
2026-04-01 15:42:52 +02:00
'json' => ['UPDATE_PASSWORD'],
]);
return 200 === $response->getStatusCode() || 204 === $response->getStatusCode();
}
/**
* Recuperer les groupes d'un utilisateur.
*
* @return list<string>
*/
public function getUserGroups(string $keycloakId): array
{
$token = $this->getToken();
$response = $this->httpClient->request('GET', $this->getAdminUrl(self::PATH_USERS.'/'.$keycloakId.'/groups'), [
'headers' => ['Authorization' => self::AUTH_BEARER.$token],
2026-04-01 15:42:52 +02:00
]);
$groups = $response->toArray(false);
return array_map(fn (array $g) => $g['name'], $groups);
}
/**
* Lister tous les utilisateurs du realm.
*
* @return list<array<string, mixed>>
*/
public function listUsers(int $max = 100): array
{
$token = $this->getToken();
$response = $this->httpClient->request('GET', $this->getAdminUrl(self::PATH_USERS), [
'headers' => ['Authorization' => self::AUTH_BEARER.$token],
2026-04-01 15:42:52 +02:00
'query' => ['max' => $max],
]);
return $response->toArray(false);
}
/**
* Recuperer l'ID d'un utilisateur par email.
*/
public function getUserIdByEmail(string $email): ?string
{
$token = $this->getToken();
$response = $this->httpClient->request('GET', $this->getAdminUrl(self::PATH_USERS), [
'headers' => ['Authorization' => self::AUTH_BEARER.$token],
2026-04-01 15:42:52 +02:00
'query' => ['email' => $email, 'exact' => 'true'],
]);
$users = $response->toArray(false);
return $users[0]['id'] ?? null;
}
feat: complete glassmorphism redesign across all templates + Keycloak groups auto-provisioning Templates updated to glassmorphism (40+ files): - templates/admin/clients/ (create, index): glass cards, input-glass inputs, btn-gold buttons, glass table headers, semi-transparent badges - templates/admin/dashboard.html.twig: glass KPI cards - templates/admin/profil/index.html.twig: glass form panels - templates/admin/revendeurs/ (create, edit, index): glass cards and tables - templates/admin/services/index.html.twig: glass service cards - templates/admin/status/ (index, manage): glass panels - templates/admin/sync/index.html.twig: glass panels - templates/admin/facturation/index.html.twig: glass tables - templates/admin/membres.html.twig: glass form, checkboxes with esy-* group values (esy-web, esy-mail, esy-mailer, esy-analytics, esy-monitor, esy-defender, esy-translate, esy-signature, esy-creator, esy-aide, esy-meet, esy-tchat, esy-ndd), Keycloak groups column in table, available groups section - templates/admin/stats/index.html.twig: glass KPI cards, glass-gold CA TTC, factures emises/payees/impayees cards, services renamed to Esy-*, rounded progress bars, bg-gray-200 track backgrounds - templates/security/ (2fa_email, 2fa_google, forgot_password, set_password, set_password_expired): glass headers, glass-heavy cards, input-glass - templates/legal/ (cgu, cgv, cookie, conformite, hebergement, mention_legal, rgpd, tarif): removed thick borders, font-black to font-bold, text-3xl to text-2xl headings - templates/attestation/ (verify, not_found): glass panels - templates/espace_client/index.html.twig: glass panels - templates/espace_prestataire/index.html.twig: glass panels - templates/external_redirect.html.twig: glass card - templates/status/index.html.twig: glass panels - templates/email/base.html.twig: gradient gold header, rounded-16px container, semi-transparent bg, soft shadow, footer address - templates/emails/*.html.twig (9 files): removed 4px borders, font-weight 900 to 700 - templates/pdf/*.html.twig (4 files): rounded borders, gradient header, lighter borders Keycloak auto-provisioning: - src/Service/KeycloakAdminService.php: added REQUIRED_GROUPS constant (15 groups: siteconseil_admin, siteconseil_member, esy-web, esy-mail, esy-mailer, esy-analytics, esy-monitor, esy-defender, esy-translate, esy-signature, esy-creator, esy-aide, esy-meet, esy-tchat, esy-ndd), ensureRequiredGroups() method that checks existing groups and creates missing ones, createGroup() method, getRequiredGroups() static accessor - src/Controller/Admin/MembresController.php: calls ensureRequiredGroups() on page load, shows flash for each auto-created group, fetches user groups per member, passes availableGroups to template Stats controller updated: - src/Controller/Admin/StatsController.php: services renamed to Esy-* (13 services), added factures_emises/payees/impayees KPI data OAuth fix: - src/Security/KeycloakAuthenticator.php: removed dd() debug calls, restored flash message on auth failure with error detail Config: - .env: KEYCLOAK_ADMIN_CLIENT_ID=crm_siteconseil_admin, secret updated - .env.local: same updates - ansible/env.local.j2: KEYCLOAK_ADMIN_CLIENT_ID=crm_siteconseil_admin Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:34:35 +02:00
/**
* Lister tous les groupes du realm.
*
* @return list<array{id: string, name: string}>
*/
public function listGroups(): array
{
$token = $this->getToken();
$response = $this->httpClient->request('GET', $this->getAdminUrl(self::PATH_GROUPS), [
feat: complete glassmorphism redesign across all templates + Keycloak groups auto-provisioning Templates updated to glassmorphism (40+ files): - templates/admin/clients/ (create, index): glass cards, input-glass inputs, btn-gold buttons, glass table headers, semi-transparent badges - templates/admin/dashboard.html.twig: glass KPI cards - templates/admin/profil/index.html.twig: glass form panels - templates/admin/revendeurs/ (create, edit, index): glass cards and tables - templates/admin/services/index.html.twig: glass service cards - templates/admin/status/ (index, manage): glass panels - templates/admin/sync/index.html.twig: glass panels - templates/admin/facturation/index.html.twig: glass tables - templates/admin/membres.html.twig: glass form, checkboxes with esy-* group values (esy-web, esy-mail, esy-mailer, esy-analytics, esy-monitor, esy-defender, esy-translate, esy-signature, esy-creator, esy-aide, esy-meet, esy-tchat, esy-ndd), Keycloak groups column in table, available groups section - templates/admin/stats/index.html.twig: glass KPI cards, glass-gold CA TTC, factures emises/payees/impayees cards, services renamed to Esy-*, rounded progress bars, bg-gray-200 track backgrounds - templates/security/ (2fa_email, 2fa_google, forgot_password, set_password, set_password_expired): glass headers, glass-heavy cards, input-glass - templates/legal/ (cgu, cgv, cookie, conformite, hebergement, mention_legal, rgpd, tarif): removed thick borders, font-black to font-bold, text-3xl to text-2xl headings - templates/attestation/ (verify, not_found): glass panels - templates/espace_client/index.html.twig: glass panels - templates/espace_prestataire/index.html.twig: glass panels - templates/external_redirect.html.twig: glass card - templates/status/index.html.twig: glass panels - templates/email/base.html.twig: gradient gold header, rounded-16px container, semi-transparent bg, soft shadow, footer address - templates/emails/*.html.twig (9 files): removed 4px borders, font-weight 900 to 700 - templates/pdf/*.html.twig (4 files): rounded borders, gradient header, lighter borders Keycloak auto-provisioning: - src/Service/KeycloakAdminService.php: added REQUIRED_GROUPS constant (15 groups: siteconseil_admin, siteconseil_member, esy-web, esy-mail, esy-mailer, esy-analytics, esy-monitor, esy-defender, esy-translate, esy-signature, esy-creator, esy-aide, esy-meet, esy-tchat, esy-ndd), ensureRequiredGroups() method that checks existing groups and creates missing ones, createGroup() method, getRequiredGroups() static accessor - src/Controller/Admin/MembresController.php: calls ensureRequiredGroups() on page load, shows flash for each auto-created group, fetches user groups per member, passes availableGroups to template Stats controller updated: - src/Controller/Admin/StatsController.php: services renamed to Esy-* (13 services), added factures_emises/payees/impayees KPI data OAuth fix: - src/Security/KeycloakAuthenticator.php: removed dd() debug calls, restored flash message on auth failure with error detail Config: - .env: KEYCLOAK_ADMIN_CLIENT_ID=crm_siteconseil_admin, secret updated - .env.local: same updates - ansible/env.local.j2: KEYCLOAK_ADMIN_CLIENT_ID=crm_siteconseil_admin Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:34:35 +02:00
'headers' => ['Authorization' => self::AUTH_BEARER.$token],
]);
return $response->toArray(false);
}
/**
* @return list<string>
*/
public static function getRequiredGroups(): array
{
return self::REQUIRED_GROUPS;
}
2026-04-01 15:42:52 +02:00
private function getGroupIdByName(string $groupName): ?string
{
$token = $this->getToken();
$response = $this->httpClient->request('GET', $this->getAdminUrl(self::PATH_GROUPS), [
'headers' => ['Authorization' => self::AUTH_BEARER.$token],
2026-04-01 15:42:52 +02:00
'query' => ['search' => $groupName, 'exact' => 'true'],
]);
$groups = $response->toArray(false);
foreach ($groups as $group) {
if ($group['name'] === $groupName) {
return $group['id'];
}
}
return null;
}
private function getToken(): string
{
if (null !== $this->accessToken && null !== $this->tokenExpiresAt && time() < $this->tokenExpiresAt) {
return $this->accessToken;
}
$response = $this->httpClient->request('POST', $this->keycloakUrl.'/realms/'.$this->realm.'/protocol/openid-connect/token', [
'body' => [
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'grant_type' => 'client_credentials',
],
]);
$data = $response->toArray();
$this->accessToken = $data['access_token'];
$this->tokenExpiresAt = time() + ($data['expires_in'] ?? 300) - 30;
return $this->accessToken;
}
private function getAdminUrl(string $path): string
{
return $this->keycloakUrl.'/admin/realms/'.$this->realm.$path;
}
}