feat: WebSocket temps réel + Finance Workflow + corrections
- Task #6: WebSocket /ws/dashboard + Kafka events (5 topics) * Backend: KafkaEventProducer, KafkaEventConsumer * Mobile: WebSocketService (reconnection, heartbeat, typed events) * DashboardBloc: Auto-refresh depuis WebSocket events - Finance Workflow: approbations + budgets (backend + mobile) * Backend: entities, services, resources, migrations Flyway V6 * Mobile: features finance_workflow complète avec BLoC - Corrections DI: interfaces IRepository partout * IProfileRepository, IOrganizationRepository, IMembreRepository * GetIt configuré avec @injectable - Spec-Kit: constitution + templates mis à jour * .specify/memory/constitution.md enrichie * Templates agent, plan, spec, tasks, checklist - Nettoyage: fichiers temporaires supprimés Signed-off-by: lions dev Team
This commit is contained in:
205
unionflow/scripts/apply-page-security.ps1
Normal file
205
unionflow/scripts/apply-page-security.ps1
Normal file
@@ -0,0 +1,205 @@
|
||||
# Script PowerShell pour appliquer la sécurisation automatique aux pages XHTML
|
||||
# Usage: .\apply-page-security.ps1
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Mapping des chemins de pages vers leurs rôles autorisés
|
||||
$pageSecurityMap = @{
|
||||
# Super Admin
|
||||
"super-admin/dashboard.xhtml" = "SUPER_ADMIN"
|
||||
"super-admin/dashboard-enhanced.xhtml" = "SUPER_ADMIN"
|
||||
"super-admin/entites/gestion-enhanced.xhtml" = "SUPER_ADMIN"
|
||||
"super-admin/roles/gestion.xhtml" = "SUPER_ADMIN"
|
||||
"super-admin/types/organisations.xhtml" = "SUPER_ADMIN"
|
||||
|
||||
# Admin Organisation
|
||||
"admin/audit.xhtml" = "ADMIN,SUPER_ADMIN"
|
||||
"admin/backup.xhtml" = "ADMIN,SUPER_ADMIN"
|
||||
"admin/settings.xhtml" = "ADMIN,SUPER_ADMIN"
|
||||
"admin/users.xhtml" = "ADMIN,SUPER_ADMIN"
|
||||
"secure/admin/utilisateurs.xhtml" = "ADMIN,SUPER_ADMIN"
|
||||
"secure/admin/audit.xhtml" = "ADMIN,SUPER_ADMIN"
|
||||
"secure/admin/parametres.xhtml" = "ADMIN,SUPER_ADMIN"
|
||||
"secure/admin/roles.xhtml" = "ADMIN,SUPER_ADMIN"
|
||||
"admin/audit/journal.xhtml" = "ADMIN,SUPER_ADMIN"
|
||||
|
||||
# Gestion des membres
|
||||
"secure/membre/inscription.xhtml" = "SECRETAIRE,ADMIN"
|
||||
"secure/membre/recherche.xhtml" = "SECRETAIRE,TRESORIER,RESPONSABLE_SOCIAL,RESPONSABLE_EVENEMENTS,ADMIN"
|
||||
"secure/membre/profil.xhtml" = "SECRETAIRE,ADMIN"
|
||||
"secure/membre/import.xhtml" = "SECRETAIRE,ADMIN"
|
||||
"secure/membre/export.xhtml" = "SECRETAIRE,TRESORIER,ADMIN"
|
||||
"secure/membre/cotisations.xhtml" = "TRESORIER,SECRETAIRE,ADMIN"
|
||||
|
||||
# Gestion financière
|
||||
"admin/finance/caisse.xhtml" = "TRESORIER,ADMIN"
|
||||
"secure/finance/tresorerie.xhtml" = "TRESORIER,ADMIN"
|
||||
"secure/finance/budgets.xhtml" = "TRESORIER,ADMIN"
|
||||
"secure/finance/bilans.xhtml" = "TRESORIER,ADMIN"
|
||||
"secure/comptabilite/gestion.xhtml" = "TRESORIER,ADMIN"
|
||||
"admin/cotisations/gestion.xhtml" = "TRESORIER,SECRETAIRE,ADMIN"
|
||||
"secure/cotisation/collect.xhtml" = "TRESORIER,ADMIN"
|
||||
"secure/cotisation/paiement.xhtml" = "TRESORIER,SECRETAIRE,ADMIN"
|
||||
"secure/cotisation/reminders.xhtml" = "TRESORIER,SECRETAIRE,ADMIN"
|
||||
"secure/cotisation/report.xhtml" = "TRESORIER,ADMIN"
|
||||
"secure/cotisation/rapports.xhtml" = "TRESORIER,ADMIN"
|
||||
|
||||
# Gestion des événements
|
||||
"admin/evenements/liste.xhtml" = "RESPONSABLE_EVENEMENTS,SECRETAIRE,ADMIN"
|
||||
"admin/evenements/creation.xhtml" = "RESPONSABLE_EVENEMENTS,SECRETAIRE,ADMIN"
|
||||
"admin/evenements/gestion.xhtml" = "RESPONSABLE_EVENEMENTS,SECRETAIRE,ADMIN"
|
||||
"admin/evenements/participants.xhtml" = "RESPONSABLE_EVENEMENTS,SECRETAIRE,ADMIN"
|
||||
"secure/evenement/creation.xhtml" = "RESPONSABLE_EVENEMENTS,SECRETAIRE,ADMIN"
|
||||
"secure/evenement/gestion.xhtml" = "RESPONSABLE_EVENEMENTS,SECRETAIRE,ADMIN"
|
||||
"secure/evenement/create.xhtml" = "RESPONSABLE_EVENEMENTS,SECRETAIRE,ADMIN"
|
||||
"secure/evenement/planification.xhtml" = "RESPONSABLE_EVENEMENTS,ADMIN"
|
||||
"secure/evenement/logistique.xhtml" = "RESPONSABLE_EVENEMENTS,ADMIN"
|
||||
"secure/evenement/bilan.xhtml" = "RESPONSABLE_EVENEMENTS,SECRETAIRE,ADMIN"
|
||||
"secure/evenement/reservations.xhtml" = "RESPONSABLE_EVENEMENTS,ADMIN"
|
||||
"secure/evenement/participants.xhtml" = "RESPONSABLE_EVENEMENTS,SECRETAIRE,ADMIN"
|
||||
"secure/evenement/calendar.xhtml" = "ALL"
|
||||
"secure/evenement/calendrier.xhtml" = "ALL"
|
||||
"secure/evenement/participation.xhtml" = "ALL"
|
||||
|
||||
# Gestion des aides sociales
|
||||
"admin/aides/gestion.xhtml" = "RESPONSABLE_SOCIAL,ADMIN"
|
||||
"admin/demandes/gestion.xhtml" = "RESPONSABLE_SOCIAL,ADMIN"
|
||||
"admin/demandes/aide-sociale.xhtml" = "RESPONSABLE_SOCIAL,ADMIN"
|
||||
"secure/aide/demande.xhtml" = "ALL"
|
||||
"secure/aide/statistiques.xhtml" = "RESPONSABLE_SOCIAL,ADMIN"
|
||||
"secure/aide/historique.xhtml" = "ALL"
|
||||
|
||||
# Adhésions
|
||||
"secure/adhesion/liste.xhtml" = "SECRETAIRE,ADMIN"
|
||||
"secure/adhesion/demande.xhtml" = "ALL"
|
||||
"secure/adhesion/new.xhtml" = "SECRETAIRE,ADMIN"
|
||||
"secure/adhesion/renouvellement.xhtml" = "ALL"
|
||||
"secure/adhesion/validation.xhtml" = "SECRETAIRE,ADMIN"
|
||||
"secure/adhesion/history.xhtml" = "SECRETAIRE,ADMIN"
|
||||
"secure/adhesion/historique.xhtml" = "ALL"
|
||||
"secure/adhesion/pending.xhtml" = "SECRETAIRE,ADMIN"
|
||||
"secure/adhesion/cartes-membres.xhtml" = "SECRETAIRE,ADMIN"
|
||||
|
||||
# Rapports
|
||||
"secure/reports.xhtml" = "TRESORIER,SECRETAIRE,ADMIN"
|
||||
"secure/rapport/details.xhtml" = "TRESORIER,SECRETAIRE,ADMIN"
|
||||
"secure/rapport/export.xhtml" = "TRESORIER,SECRETAIRE,ADMIN"
|
||||
"secure/rapport/activites.xhtml" = "SECRETAIRE,ADMIN"
|
||||
"secure/rapport/finances.xhtml" = "TRESORIER,ADMIN"
|
||||
"secure/rapport/membres.xhtml" = "SECRETAIRE,ADMIN"
|
||||
"secure/rapport/tableaux-bord.xhtml" = "TRESORIER,SECRETAIRE,ADMIN"
|
||||
"admin/rapports/finances.xhtml" = "TRESORIER,ADMIN"
|
||||
"admin/rapports/statistiques.xhtml" = "ADMIN"
|
||||
|
||||
# Pages personnelles
|
||||
"secure/profile.xhtml" = "ALL"
|
||||
"secure/personnel/profil.xhtml" = "ALL"
|
||||
"secure/personnel/activites.xhtml" = "ALL"
|
||||
"secure/personnel/agenda.xhtml" = "ALL"
|
||||
"secure/personnel/documents.xhtml" = "ALL"
|
||||
"secure/personnel/notifications.xhtml" = "ALL"
|
||||
"secure/personnel/preferences.xhtml" = "ALL"
|
||||
"secure/personnel/favoris.xhtml" = "ALL"
|
||||
"secure/personnel/parametres.xhtml" = "ALL"
|
||||
"membre/cotisations.xhtml" = "ALL"
|
||||
"membre/dashboard.xhtml" = "MEMBRE_ACTIF"
|
||||
|
||||
# Aide et support
|
||||
"secure/aide/faq.xhtml" = "ALL"
|
||||
"secure/aide/guide.xhtml" = "ALL"
|
||||
"secure/aide/support.xhtml" = "ALL"
|
||||
"secure/aide/tutoriels.xhtml" = "ALL"
|
||||
"secure/aide/nouveautes.xhtml" = "ALL"
|
||||
"secure/aide/apropos.xhtml" = "ALL"
|
||||
"secure/aide/documentation.xhtml" = "ALL"
|
||||
"secure/aide/suggestions.xhtml" = "ALL"
|
||||
"secure/aide/tickets.xhtml" = "ALL"
|
||||
|
||||
# Communication
|
||||
"secure/communication/notifications.xhtml" = "SECRETAIRE,ADMIN"
|
||||
|
||||
# Documents
|
||||
"admin/documents/gestion.xhtml" = "SECRETAIRE,ADMIN"
|
||||
"secure/documents/mes-documents.xhtml" = "ALL"
|
||||
|
||||
# Utilitaires
|
||||
"secure/outils/exports-masse.xhtml" = "TRESORIER,SECRETAIRE,ADMIN"
|
||||
"secure/stats.xhtml" = "ADMIN"
|
||||
"secure/souscription/dashboard.xhtml" = "ADMIN"
|
||||
}
|
||||
|
||||
$basePath = "C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-client-quarkus-primefaces-freya\src\main\resources\META-INF\resources\pages"
|
||||
|
||||
$securityComponent = @"
|
||||
<!-- Sécurisation de la page basée sur les rôles -->
|
||||
<ui:include src="/templates/components/security/page-access-control.xhtml">
|
||||
<ui:param name="allowedRoles" value="__ROLES__" />
|
||||
</ui:include>
|
||||
|
||||
"@
|
||||
|
||||
$processedCount = 0
|
||||
$skippedCount = 0
|
||||
$errorCount = 0
|
||||
|
||||
Write-Host "============================================" -ForegroundColor Cyan
|
||||
Write-Host " Application de la Sécurisation des Pages" -ForegroundColor Cyan
|
||||
Write-Host "============================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
foreach ($page in $pageSecurityMap.Keys) {
|
||||
$filePath = Join-Path $basePath $page
|
||||
$roles = $pageSecurityMap[$page]
|
||||
|
||||
if (-not (Test-Path $filePath)) {
|
||||
Write-Host "[SKIP] $page (fichier introuvable)" -ForegroundColor Yellow
|
||||
$skippedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
$content = Get-Content $filePath -Raw -Encoding UTF8
|
||||
|
||||
# Vérifier si la sécurité est déjà appliquée
|
||||
if ($content -match "page-access-control\.xhtml") {
|
||||
Write-Host "[SKIP] $page (déjà sécurisée)" -ForegroundColor Gray
|
||||
$skippedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
# Insérer le composant de sécurité après <ui:composition> ou <ui:define name="content">
|
||||
$securityBlock = $securityComponent -replace "__ROLES__", $roles
|
||||
|
||||
if ($content -match '<ui:composition[^>]*>') {
|
||||
$content = $content -replace '(<ui:composition[^>]*>)', "`$1`n$securityBlock"
|
||||
}
|
||||
elseif ($content -match '<ui:define name="content">') {
|
||||
$content = $content -replace '(<ui:define name="content">)', "`$1`n$securityBlock"
|
||||
}
|
||||
else {
|
||||
Write-Host "[ERROR] $page (impossible de trouver le point d'insertion)" -ForegroundColor Red
|
||||
$errorCount++
|
||||
continue
|
||||
}
|
||||
|
||||
# Sauvegarder le fichier
|
||||
Set-Content -Path $filePath -Value $content -Encoding UTF8 -NoNewline
|
||||
|
||||
Write-Host "[OK] $page → Rôles: $roles" -ForegroundColor Green
|
||||
$processedCount++
|
||||
}
|
||||
catch {
|
||||
Write-Host "[ERROR] $page : $_" -ForegroundColor Red
|
||||
$errorCount++
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "============================================" -ForegroundColor Cyan
|
||||
Write-Host " Résumé" -ForegroundColor Cyan
|
||||
Write-Host "============================================" -ForegroundColor Cyan
|
||||
Write-Host "Pages sécurisées : $processedCount" -ForegroundColor Green
|
||||
Write-Host "Pages ignorées : $skippedCount" -ForegroundColor Yellow
|
||||
Write-Host "Erreurs : $errorCount" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "✓ Script terminé avec succès!" -ForegroundColor Green
|
||||
44
unionflow/scripts/create-missing-users.sh
Normal file
44
unionflow/scripts/create-missing-users.sh
Normal file
@@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
KEYCLOAK_URL="http://localhost:8180"
|
||||
REALM_NAME="unionflow"
|
||||
TEST_PASSWORD="Test@123"
|
||||
|
||||
# Obtenir le token
|
||||
TOKEN=$(curl -s -X POST "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" \
|
||||
-d "client_id=admin-cli" -d "username=admin" -d "password=admin" -d "grant_type=password" | \
|
||||
grep -o '"access_token":"[^"]*' | cut -d'"' -f4)
|
||||
|
||||
echo "Token obtenu, création des utilisateurs manquants..."
|
||||
|
||||
# Fonction de création simplifiée
|
||||
create() {
|
||||
USER=$1
|
||||
EMAIL=$2
|
||||
FIRST=$3
|
||||
LAST=$4
|
||||
ROLE=$5
|
||||
|
||||
curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"'$USER'","email":"'$EMAIL'","firstName":"'$FIRST'","lastName":"'$LAST'","enabled":true,"emailVerified":true,"credentials":[{"type":"password","value":"'$TEST_PASSWORD'","temporary":false}]}'
|
||||
|
||||
sleep 1
|
||||
UID=$(curl -s "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users?username=$USER" \
|
||||
-H "Authorization: Bearer $TOKEN" | grep -o '"id":"[^"]*' | cut -d'"' -f4 | head -1)
|
||||
|
||||
ROLEDATA=$(curl -s "$KEYCLOAK_URL/admin/realms/$REALM_NAME/roles/$ROLE" -H "Authorization: Bearer $TOKEN")
|
||||
|
||||
curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users/$UID/role-mappings/realm" \
|
||||
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "[$ROLEDATA]"
|
||||
|
||||
echo " ✅ $USER créé"
|
||||
}
|
||||
|
||||
create "tresorier.mukefi" "tresorier.mukefi@unionflow.test" "Tresorier" "MUKEFI" "TRESORIER"
|
||||
create "secretaire.mukefi" "secretaire.mukefi@unionflow.test" "Secretaire" "MUKEFI" "SECRETAIRE"
|
||||
create "credit.mukefi" "credit.mukefi@unionflow.test" "Credit" "MUKEFI" "RESPONSABLE_CREDIT"
|
||||
create "secretaire.meska" "secretaire.meska@unionflow.test" "Secretaire" "MESKA" "SECRETAIRE"
|
||||
create "evenements.meska" "evenements.meska@unionflow.test" "Evenements" "MESKA" "RESPONSABLE_EVENEMENTS"
|
||||
|
||||
echo "✅ Utilisateurs manquants créés"
|
||||
133
unionflow/scripts/create-organisations-api.sh
Normal file
133
unionflow/scripts/create-organisations-api.sh
Normal file
@@ -0,0 +1,133 @@
|
||||
#!/bin/bash
|
||||
# Création des organisations MUKEFI et MESKA via l'API REST
|
||||
|
||||
BACKEND_URL="http://localhost:8085"
|
||||
KEYCLOAK_URL="http://localhost:8180"
|
||||
REALM="unionflow"
|
||||
CLIENT_ID="unionflow-server"
|
||||
|
||||
echo "========================================================================"
|
||||
echo "Création des organisations MUKEFI et MESKA via l'API"
|
||||
echo "========================================================================"
|
||||
|
||||
# 1. Obtenir un token JWT avec le compte superadmin
|
||||
echo ""
|
||||
echo "📡 Obtention du token JWT..."
|
||||
TOKEN_RESPONSE=$(curl -s -X POST "$KEYCLOAK_URL/realms/$REALM/protocol/openid-connect/token" \
|
||||
-d "client_id=$CLIENT_ID" \
|
||||
-d "username=superadmin" \
|
||||
-d "password=Test@123" \
|
||||
-d "grant_type=password")
|
||||
|
||||
ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | grep -o '"access_token":"[^"]*' | cut -d'"' -f4)
|
||||
|
||||
if [ -z "$ACCESS_TOKEN" ]; then
|
||||
echo "❌ Erreur: Impossible d'obtenir le token JWT"
|
||||
echo " Vérifiez que le backend est démarré sur $BACKEND_URL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Token JWT obtenu"
|
||||
|
||||
# 2. Créer MUKEFI (Mutuelle)
|
||||
echo ""
|
||||
echo "🏦 Création de MUKEFI (Mutuelle)..."
|
||||
|
||||
MUKEFI_JSON='{
|
||||
"nom": "Mutuelle d'\''Épargne et de Crédit des Fonctionnaires et Indépendants",
|
||||
"nomCourt": "MUKEFI",
|
||||
"description": "Mutuelle d'\''épargne et de crédit dédiée aux fonctionnaires et travailleurs indépendants de Côte d'\''Ivoire",
|
||||
"email": "contact@mukefi.org",
|
||||
"telephone": "+225 07 00 00 00 01",
|
||||
"siteWeb": "https://mukefi.org",
|
||||
"typeOrganisation": "MUTUELLE_EPARGNE_CREDIT",
|
||||
"statut": "ACTIVE",
|
||||
"dateFondation": "2020-01-15",
|
||||
"numeroEnregistrement": "MUT-CI-2020-001",
|
||||
"devise": "XOF",
|
||||
"budgetAnnuel": 500000000,
|
||||
"cotisationObligatoire": true,
|
||||
"montantCotisationAnnuelle": 50000,
|
||||
"objectifs": "Favoriser l'\''épargne et l'\''accès au crédit pour les membres",
|
||||
"activitesPrincipales": "Épargne, crédit, micro-crédit, formation financière",
|
||||
"partenaires": "Banque Centrale des États de l'\''Afrique de l'\''Ouest (BCEAO)",
|
||||
"latitude": 5.3364,
|
||||
"longitude": -4.0267
|
||||
}'
|
||||
|
||||
MUKEFI_RESPONSE=$(curl -s -X POST "$BACKEND_URL/api/organisations" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$MUKEFI_JSON")
|
||||
|
||||
if echo "$MUKEFI_RESPONSE" | grep -q '"id"'; then
|
||||
echo "✅ MUKEFI créée avec succès"
|
||||
MUKEFI_ID=$(echo $MUKEFI_RESPONSE | grep -o '"id":"[^"]*' | cut -d'"' -f4)
|
||||
echo " ID: $MUKEFI_ID"
|
||||
else
|
||||
echo "⚠️ MUKEFI: $(echo $MUKEFI_RESPONSE | head -c 200)"
|
||||
fi
|
||||
|
||||
# 3. Créer MESKA (Association)
|
||||
echo ""
|
||||
echo "🤝 Création de MESKA (Association)..."
|
||||
|
||||
MESKA_JSON='{
|
||||
"nom": "Mouvement d'\''Entraide et de Solidarité de Koumassi et Adjamé",
|
||||
"nomCourt": "MESKA",
|
||||
"description": "Association communautaire d'\''entraide et de solidarité basée à Abidjan",
|
||||
"email": "contact@meska.org",
|
||||
"telephone": "+225 07 00 00 00 02",
|
||||
"siteWeb": "https://meska.org",
|
||||
"typeOrganisation": "ASSOCIATION",
|
||||
"statut": "ACTIVE",
|
||||
"dateFondation": "2018-06-20",
|
||||
"numeroEnregistrement": "ASSO-CI-2018-045",
|
||||
"devise": "XOF",
|
||||
"budgetAnnuel": 25000000,
|
||||
"cotisationObligatoire": true,
|
||||
"montantCotisationAnnuelle": 25000,
|
||||
"objectifs": "Promouvoir la solidarité et l'\''entraide entre les membres des communes de Koumassi et Adjamé",
|
||||
"activitesPrincipales": "Aide sociale, événements communautaires, formations, projets collectifs",
|
||||
"partenaires": "Mairie de Koumassi, Mairie d'\''Adjamé",
|
||||
"latitude": 5.2931,
|
||||
"longitude": -3.9468
|
||||
}'
|
||||
|
||||
MESKA_RESPONSE=$(curl -s -X POST "$BACKEND_URL/api/organisations" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$MESKA_JSON")
|
||||
|
||||
if echo "$MESKA_RESPONSE" | grep -q '"id"'; then
|
||||
echo "✅ MESKA créée avec succès"
|
||||
MESKA_ID=$(echo $MESKA_RESPONSE | grep -o '"id":"[^"]*' | cut -d'"' -f4)
|
||||
echo " ID: $MESKA_ID"
|
||||
else
|
||||
echo "⚠️ MESKA: $(echo $MESKA_RESPONSE | head -c 200)"
|
||||
fi
|
||||
|
||||
# 4. Vérifier les organisations créées
|
||||
echo ""
|
||||
echo "📋 Vérification des organisations créées..."
|
||||
|
||||
ORGS_LIST=$(curl -s -X GET "$BACKEND_URL/api/organisations" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN")
|
||||
|
||||
echo "$ORGS_LIST" | grep -o '"nom":"[^"]*' | cut -d'"' -f4
|
||||
|
||||
echo ""
|
||||
echo "========================================================================"
|
||||
echo "✅ Création des organisations terminée"
|
||||
echo "========================================================================"
|
||||
echo ""
|
||||
echo "🏦 MUKEFI:"
|
||||
echo " → Type: Mutuelle d'épargne et de crédit"
|
||||
echo " → Email: contact@mukefi.org"
|
||||
echo " → Modules: Cotisations, Épargne/Crédit, Comptabilité, Documents"
|
||||
echo ""
|
||||
echo "🤝 MESKA:"
|
||||
echo " → Type: Association"
|
||||
echo " → Email: contact@meska.org"
|
||||
echo " → Modules: Cotisations, Événements, Solidarité, Documents"
|
||||
echo ""
|
||||
138
unionflow/scripts/create-organisations.sql
Normal file
138
unionflow/scripts/create-organisations.sql
Normal file
@@ -0,0 +1,138 @@
|
||||
-- Script de création des organisations de test MUKEFI et MESKA
|
||||
-- UnionFlow - Configuration initiale
|
||||
|
||||
-- ============================================================================
|
||||
-- 1. CRÉATION DE L'ORGANISATION MUKEFI (Mutuelle d'épargne et de crédit)
|
||||
-- ============================================================================
|
||||
|
||||
-- Supprimer l'organisation si elle existe déjà
|
||||
DELETE FROM organisation WHERE code = 'MUKEFI';
|
||||
|
||||
-- Créer MUKEFI
|
||||
INSERT INTO organisation (
|
||||
id,
|
||||
code,
|
||||
nom,
|
||||
sigle,
|
||||
type_organisation,
|
||||
email,
|
||||
telephone,
|
||||
adresse,
|
||||
ville,
|
||||
pays,
|
||||
date_creation,
|
||||
date_modification,
|
||||
cree_par,
|
||||
modifie_par,
|
||||
version,
|
||||
actif
|
||||
) VALUES (
|
||||
gen_random_uuid(),
|
||||
'MUKEFI',
|
||||
'Mutuelle d''Épargne et de Crédit des Fonctionnaires et Indépendants',
|
||||
'MUKEFI',
|
||||
'MUTUELLE_EPARGNE_CREDIT',
|
||||
'contact@mukefi.org',
|
||||
'+225 07 00 00 00 01',
|
||||
'01 BP 1234 Abidjan 01',
|
||||
'Abidjan',
|
||||
'Côte d''Ivoire',
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP,
|
||||
'superadmin@unionflow.test',
|
||||
'superadmin@unionflow.test',
|
||||
0,
|
||||
true
|
||||
);
|
||||
|
||||
-- Activer les modules pour MUKEFI
|
||||
-- Note: Vous devrez adapter cette partie selon votre schéma de table module_organisation
|
||||
|
||||
COMMENT ON TABLE organisation IS 'MUKEFI - Mutuelle créée pour tests avec modules: Cotisations, Épargne/Crédit, Comptabilité, Documents, Notifications';
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- 2. CRÉATION DE L'ORGANISATION MESKA (Association)
|
||||
-- ============================================================================
|
||||
|
||||
-- Supprimer l'organisation si elle existe déjà
|
||||
DELETE FROM organisation WHERE code = 'MESKA';
|
||||
|
||||
-- Créer MESKA
|
||||
INSERT INTO organisation (
|
||||
id,
|
||||
code,
|
||||
nom,
|
||||
sigle,
|
||||
type_organisation,
|
||||
email,
|
||||
telephone,
|
||||
adresse,
|
||||
ville,
|
||||
pays,
|
||||
date_creation,
|
||||
date_modification,
|
||||
cree_par,
|
||||
modifie_par,
|
||||
version,
|
||||
actif
|
||||
) VALUES (
|
||||
gen_random_uuid(),
|
||||
'MESKA',
|
||||
'Mouvement d''Entraide et de Solidarité de Koumassi et Adjamé',
|
||||
'MESKA',
|
||||
'ASSOCIATION',
|
||||
'contact@meska.org',
|
||||
'+225 07 00 00 00 02',
|
||||
'Commune de Koumassi, Abidjan',
|
||||
'Abidjan',
|
||||
'Côte d''Ivoire',
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP,
|
||||
'superadmin@unionflow.test',
|
||||
'superadmin@unionflow.test',
|
||||
0,
|
||||
true
|
||||
);
|
||||
|
||||
-- Activer les modules pour MESKA
|
||||
COMMENT ON TABLE organisation IS 'MESKA - Association créée pour tests avec modules: Cotisations, Événements, Solidarité, Documents, Notifications';
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- 3. VÉRIFICATION
|
||||
-- ============================================================================
|
||||
|
||||
-- Lister les organisations créées
|
||||
SELECT
|
||||
code,
|
||||
nom,
|
||||
sigle,
|
||||
type_organisation,
|
||||
email,
|
||||
ville,
|
||||
actif
|
||||
FROM organisation
|
||||
WHERE code IN ('MUKEFI', 'MESKA')
|
||||
ORDER BY code;
|
||||
|
||||
-- Afficher les IDs pour référence
|
||||
\echo '==================================================================='
|
||||
\echo 'Organisations créées:'
|
||||
\echo '==================================================================='
|
||||
SELECT
|
||||
'MUKEFI' as organisation,
|
||||
id,
|
||||
nom,
|
||||
email
|
||||
FROM organisation
|
||||
WHERE code = 'MUKEFI'
|
||||
UNION ALL
|
||||
SELECT
|
||||
'MESKA' as organisation,
|
||||
id,
|
||||
nom,
|
||||
email
|
||||
FROM organisation
|
||||
WHERE code = 'MESKA';
|
||||
\echo '==================================================================='
|
||||
33
unionflow/scripts/fix-users.sh
Normal file
33
unionflow/scripts/fix-users.sh
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
KEYCLOAK_URL="http://localhost:8180"
|
||||
REALM_NAME="unionflow"
|
||||
TEST_PASSWORD="Test@123"
|
||||
|
||||
TOKEN=$(curl -s -X POST "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" \
|
||||
-d "client_id=admin-cli" -d "username=admin" -d "password=admin" -d "grant_type=password" | \
|
||||
grep -o '"access_token":"[^"]*' | cut -d'"' -f4)
|
||||
|
||||
create() {
|
||||
USER=$1; EMAIL=$2; FIRST=$3; LAST=$4; ROLE=$5
|
||||
|
||||
curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users" \
|
||||
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
||||
-d '{"username":"'$USER'","email":"'$EMAIL'","firstName":"'$FIRST'","lastName":"'$LAST'","enabled":true,"emailVerified":true,"credentials":[{"type":"password","value":"'$TEST_PASSWORD'","temporary":false}]}'
|
||||
|
||||
sleep 1
|
||||
USERID=$(curl -s "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users?username=$USER" \
|
||||
-H "Authorization: Bearer $TOKEN" | grep -o '"id":"[^"]*' | cut -d'"' -f4 | head -1)
|
||||
|
||||
ROLEDATA=$(curl -s "$KEYCLOAK_URL/admin/realms/$REALM_NAME/roles/$ROLE" -H "Authorization: Bearer $TOKEN")
|
||||
curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users/$USERID/role-mappings/realm" \
|
||||
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "[$ROLEDATA]" > /dev/null
|
||||
echo " ✅ $USER"
|
||||
}
|
||||
|
||||
echo "Création des utilisateurs manquants..."
|
||||
create "tresorier.mukefi" "tresorier.mukefi@unionflow.test" "Tresorier" "MUKEFI" "TRESORIER"
|
||||
create "secretaire.mukefi" "secretaire.mukefi@unionflow.test" "Secretaire" "MUKEFI" "SECRETAIRE"
|
||||
create "credit.mukefi" "credit.mukefi@unionflow.test" "Credit" "MUKEFI" "RESPONSABLE_CREDIT"
|
||||
create "secretaire.meska" "secretaire.meska@unionflow.test" "Secretaire" "MESKA" "SECRETAIRE"
|
||||
create "evenements.meska" "evenements.meska@unionflow.test" "Evenements" "MESKA" "RESPONSABLE_EVENEMENTS"
|
||||
echo "✅ Terminé"
|
||||
87
unionflow/scripts/flyway-repair-dev.sql
Normal file
87
unionflow/scripts/flyway-repair-dev.sql
Normal file
@@ -0,0 +1,87 @@
|
||||
-- =============================================================================
|
||||
-- Script de réparation Flyway — UnionFlow (mode dev)
|
||||
-- =============================================================================
|
||||
-- Exécuter UNE SEULE FOIS sur la base PostgreSQL "unionflow" AVANT de
|
||||
-- relancer le serveur.
|
||||
--
|
||||
-- Contexte :
|
||||
-- Hibernate (mode "update") avait créé certaines tables avant que Flyway
|
||||
-- ne prenne le relais. Résultat : la table types_reference existe mais
|
||||
-- manque la colonne "valeur_systeme" qu'attend V3.0.
|
||||
-- flyway_schema_history s'arrête à la version 2.10.
|
||||
--
|
||||
-- Solution :
|
||||
-- 1. Ajouter les colonnes manquantes (ajoutées par V3.x mais absentes
|
||||
-- car Hibernate avait créé les tables avant).
|
||||
-- 2. Marquer V3.0 → V3.6 comme "déjà appliquées" (success=true) dans
|
||||
-- flyway_schema_history, afin que Flyway ne les reexécute pas.
|
||||
-- 3. Flyway n'exécutera alors que V3.7 (seed des membres de test).
|
||||
-- =============================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- 1. Ajouter les colonnes manquantes dues au mode Hibernate "update"
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
-- V3.0 a besoin de cette colonne dans types_reference
|
||||
ALTER TABLE types_reference
|
||||
ADD COLUMN IF NOT EXISTS valeur_systeme BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
-- V3.5 peut avoir ajouté des colonnes d'adresse dans organisations
|
||||
-- (ajout sécurisé — sans erreur si déjà présentes)
|
||||
ALTER TABLE organisations ADD COLUMN IF NOT EXISTS adresse VARCHAR(255);
|
||||
ALTER TABLE organisations ADD COLUMN IF NOT EXISTS ville VARCHAR(100);
|
||||
ALTER TABLE organisations ADD COLUMN IF NOT EXISTS code_postal VARCHAR(20);
|
||||
ALTER TABLE organisations ADD COLUMN IF NOT EXISTS pays VARCHAR(100);
|
||||
ALTER TABLE organisations ADD COLUMN IF NOT EXISTS region VARCHAR(100);
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- 2. Marquer V3.0 à V3.6 comme appliquées dans flyway_schema_history
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
max_rank INT;
|
||||
BEGIN
|
||||
SELECT COALESCE(MAX(installed_rank), 0) INTO max_rank FROM flyway_schema_history;
|
||||
|
||||
INSERT INTO flyway_schema_history
|
||||
(installed_rank, version, description, type, script, checksum,
|
||||
installed_by, installed_on, execution_time, success)
|
||||
VALUES
|
||||
(max_rank + 1, '3.0', 'Optimisation Structure Donnees', 'SQL',
|
||||
'V3.0__Optimisation_Structure_Donnees.sql', 0, current_user, NOW(), 0, true),
|
||||
|
||||
(max_rank + 2, '3.1', 'Add Module Disponible FK', 'SQL',
|
||||
'V3.1__Add_Module_Disponible_FK.sql', 0, current_user, NOW(), 0, true),
|
||||
|
||||
(max_rank + 3, '3.2', 'Seed Types Reference', 'SQL',
|
||||
'V3.2__Seed_Types_Reference.sql', 0, current_user, NOW(), 0, true),
|
||||
|
||||
(max_rank + 4, '3.3', 'Optimisation Index Performance', 'SQL',
|
||||
'V3.3__Optimisation_Index_Performance.sql', 0, current_user, NOW(), 0, true),
|
||||
|
||||
(max_rank + 5, '3.4', 'LCB FT Anti Blanchiment', 'SQL',
|
||||
'V3.4__LCB_FT_Anti_Blanchiment.sql', 0, current_user, NOW(), 0, true),
|
||||
|
||||
(max_rank + 6, '3.5', 'Add Organisation Address Fields', 'SQL',
|
||||
'V3.5__Add_Organisation_Address_Fields.sql', 0, current_user, NOW(), 0, true),
|
||||
|
||||
(max_rank + 7, '3.6', 'Create Test Organisations', 'SQL',
|
||||
'V3.6__Create_Test_Organisations.sql', 0, current_user, NOW(), 0, true)
|
||||
|
||||
ON CONFLICT (version) DO NOTHING;
|
||||
END $$;
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- 3. Vérification finale
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
SELECT version, description, success, installed_on
|
||||
FROM flyway_schema_history
|
||||
ORDER BY installed_rank;
|
||||
-- Vous devriez voir : ..., 2.10, 3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6
|
||||
-- V3.7 sera appliquée par Flyway au prochain démarrage du serveur.
|
||||
215
unionflow/scripts/kafka/GUIDE_TESTS.md
Normal file
215
unionflow/scripts/kafka/GUIDE_TESTS.md
Normal file
@@ -0,0 +1,215 @@
|
||||
# 🧪 Guide de Tests - Kafka + WebSocket UnionFlow
|
||||
|
||||
## ✅ Statut Actuel
|
||||
|
||||
Backend démarré avec succès :
|
||||
- ✅ 5 Producers Kafka connectés
|
||||
- ✅ 5 Consumers Kafka connectés (group: `unionflow-websocket-server`)
|
||||
- ✅ WebSocket endpoint actif sur `/ws/dashboard`
|
||||
- ✅ Aucune erreur `UNKNOWN_TOPIC_OR_PARTITION`
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Test 1 : Swagger UI
|
||||
|
||||
### Action
|
||||
Ouvre ton navigateur : **http://localhost:8085/q/swagger-ui**
|
||||
|
||||
### Résultat attendu
|
||||
- Page Swagger UI affichée
|
||||
- Liste de tous les endpoints REST
|
||||
- Section "Schemas" avec les DTOs
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Test 2 : WebSocket - Test HTML
|
||||
|
||||
### Action
|
||||
|
||||
1. Ouvre le fichier HTML dans ton navigateur :
|
||||
```
|
||||
C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\scripts\kafka\test-websocket.html
|
||||
```
|
||||
|
||||
2. Clique sur le bouton **"🔗 Connecter"**
|
||||
|
||||
### Résultat attendu
|
||||
|
||||
✅ **Status change vers "✅ Connecté"**
|
||||
✅ **Message dans la console** : "Connexion WebSocket établie avec succès !"
|
||||
✅ **Dans les logs backend** : Tu devrais voir :
|
||||
```
|
||||
INFO WebSocket connection opened from ...
|
||||
```
|
||||
|
||||
### Test Ping/Pong
|
||||
|
||||
1. Clique sur **"📤 Envoyer Ping"**
|
||||
2. Tu devrais recevoir un **pong** du serveur
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Test 3 : Publier un Event Kafka → Vérifier Réception WebSocket
|
||||
|
||||
### Étape 1 : Ouvrir 3 fenêtres
|
||||
|
||||
1. **Fenêtre 1** : Logs backend Quarkus (déjà ouverte)
|
||||
2. **Fenêtre 2** : Navigateur avec `test-websocket.html` (WebSocket connecté)
|
||||
3. **Fenêtre 3** : PowerShell pour publier l'event
|
||||
|
||||
### Étape 2 : Publier un Event de Test
|
||||
|
||||
Dans PowerShell (Fenêtre 3) :
|
||||
|
||||
```powershell
|
||||
cd C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\scripts\kafka
|
||||
.\test-event.bat
|
||||
```
|
||||
|
||||
**Ou manuellement** :
|
||||
|
||||
```cmd
|
||||
echo {"eventType":"APPROVAL_APPROVED","timestamp":"2026-03-14T19:30:00Z","organizationId":"test-org-123","data":{"id":"test-1","amount":50000}} | docker exec -i kafka /opt/kafka/bin/kafka-console-producer.sh --topic unionflow.finance.approvals --bootstrap-server localhost:9092
|
||||
```
|
||||
|
||||
### Résultat attendu
|
||||
|
||||
#### Dans les logs backend (Fenêtre 1) :
|
||||
```
|
||||
INFO [dev.lions.unionflow.server.messaging.KafkaEventConsumer] (smallrye-kafka-consumer-thread-1)
|
||||
Received finance approval event: key=..., value={"eventType":"APPROVAL_APPROVED",...}
|
||||
```
|
||||
|
||||
#### Dans le WebSocket HTML (Fenêtre 2) :
|
||||
```
|
||||
[19:30:15] RECEIVED: APPROVAL_APPROVED
|
||||
{
|
||||
"eventType": "APPROVAL_APPROVED",
|
||||
"timestamp": "2026-03-14T19:30:00Z",
|
||||
"organizationId": "test-org-123",
|
||||
"data": { "id": "test-1", "amount": 50000 }
|
||||
}
|
||||
```
|
||||
|
||||
✅ **Si tu vois l'event dans le WebSocket HTML → SUCCÈS COMPLET !**
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Test 4 : Vérifier les Topics Kafka
|
||||
|
||||
### Lister tous les messages d'un topic
|
||||
|
||||
```cmd
|
||||
docker exec kafka /opt/kafka/bin/kafka-console-consumer.sh --topic unionflow.finance.approvals --bootstrap-server localhost:9092 --from-beginning
|
||||
```
|
||||
|
||||
Appuie sur `Ctrl+C` pour arrêter.
|
||||
|
||||
### Compter les messages
|
||||
|
||||
```cmd
|
||||
docker exec kafka /opt/kafka/bin/kafka-run-class.sh kafka.tools.GetOffsetShell --broker-list localhost:9092 --topic unionflow.finance.approvals
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Test 5 : Application Mobile Flutter
|
||||
|
||||
### Lancer l'app mobile
|
||||
|
||||
```bash
|
||||
cd C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\unionflow-mobile-apps
|
||||
flutter run --dart-define=ENV=dev
|
||||
```
|
||||
|
||||
### Résultat attendu dans la console mobile
|
||||
|
||||
```
|
||||
I/flutter (12345): ✅ WebSocket connecté
|
||||
I/flutter (12345): DashboardBloc: WebSocket initialisé
|
||||
I/flutter (12345): DashboardBloc: WebSocket connecté - Temps réel actif
|
||||
```
|
||||
|
||||
### Test complet Mobile + Backend
|
||||
|
||||
1. **Mobile** : Ouvre le Dashboard
|
||||
2. **Backend** : Publie un event via Swagger ou Kafka console
|
||||
3. **Mobile** : Le dashboard devrait se rafraîchir automatiquement !
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Test 6 : End-to-End avec Swagger UI
|
||||
|
||||
### Scénario : Approbation Finance
|
||||
|
||||
1. **Ouvre Swagger UI** : http://localhost:8085/q/swagger-ui
|
||||
2. **Trouve l'endpoint** : `POST /api/v1/finance/approvals/{id}/approve`
|
||||
3. **Exécute la requête** avec un ID de test
|
||||
4. **Vérifie les logs backend** :
|
||||
```
|
||||
INFO KafkaEventProducer: Publishing approval event...
|
||||
INFO KafkaEventConsumer: Received finance approval event...
|
||||
INFO WebSocketBroadcastService: Broadcasting to X clients...
|
||||
```
|
||||
5. **Vérifie le WebSocket HTML** : L'event devrait apparaître !
|
||||
|
||||
---
|
||||
|
||||
## 📊 Résumé des Tests
|
||||
|
||||
| Test | Objectif | Status |
|
||||
|------|----------|--------|
|
||||
| 1. Swagger UI | Accès API REST | ⏳ À tester |
|
||||
| 2. WebSocket HTML | Connexion WebSocket | ⏳ À tester |
|
||||
| 3. Kafka → WebSocket | Flux complet | ⏳ À tester |
|
||||
| 4. Kafka Topics | Vérification messages | ⏳ À tester |
|
||||
| 5. Mobile Flutter | WebSocket mobile | ⏳ À tester |
|
||||
| 6. End-to-End Swagger | Workflow complet | ⏳ À tester |
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### WebSocket ne se connecte pas
|
||||
|
||||
**Vérifier** :
|
||||
```cmd
|
||||
netstat -an | findstr 8085
|
||||
```
|
||||
|
||||
Doit afficher : `0.0.0.0:8085 ... LISTENING`
|
||||
|
||||
### Events Kafka non reçus
|
||||
|
||||
**Vérifier les consumers** :
|
||||
```cmd
|
||||
docker exec kafka /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group unionflow-websocket-server
|
||||
```
|
||||
|
||||
Doit afficher :
|
||||
- LAG = 0 (tous les messages consommés)
|
||||
- 5 topics assignés au group
|
||||
|
||||
### Backend logs
|
||||
|
||||
**Augmenter le niveau de log** dans `application.properties` :
|
||||
```properties
|
||||
quarkus.log.category."dev.lions.unionflow.server.messaging".level=DEBUG
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Succès Final
|
||||
|
||||
Quand tous ces tests passent :
|
||||
- ✅ Backend publie events dans Kafka
|
||||
- ✅ Consumers consomment events
|
||||
- ✅ WebSocket broadcast aux clients
|
||||
- ✅ Mobile reçoit events en temps réel
|
||||
- ✅ Dashboard auto-refresh
|
||||
|
||||
**→ Architecture Event-Driven 100% FONCTIONNELLE ! 🎉**
|
||||
|
||||
---
|
||||
|
||||
**Commence par le Test 2 (WebSocket HTML)** - c'est le plus visuel et le plus rapide pour vérifier que tout fonctionne !
|
||||
155
unionflow/scripts/kafka/README.md
Normal file
155
unionflow/scripts/kafka/README.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# Scripts Kafka pour UnionFlow
|
||||
|
||||
## Problème résolu
|
||||
|
||||
Les erreurs `UNKNOWN_TOPIC_OR_PARTITION` apparaissent car **les topics Kafka n'existent pas encore**.
|
||||
|
||||
Ton Kafka actuel (conteneur `kafka` sur port 9092) fonctionne parfaitement. Il faut juste créer les 5 topics.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Solution : Créer les Topics
|
||||
|
||||
### Option 1 : Exécuter le script (Recommandé)
|
||||
|
||||
#### Windows (PowerShell ou CMD)
|
||||
|
||||
```cmd
|
||||
cd C:\Users\dadyo\PersonalProjects\lions-workspace\unionflow\scripts\kafka
|
||||
create-topics.bat
|
||||
```
|
||||
|
||||
#### Linux/Mac
|
||||
|
||||
```bash
|
||||
cd /path/to/unionflow/scripts/kafka
|
||||
chmod +x create-topics.sh
|
||||
./create-topics.sh
|
||||
```
|
||||
|
||||
### Option 2 : Commandes manuelles (si le script échoue)
|
||||
|
||||
Copie-colle ces commandes une par une dans PowerShell/CMD :
|
||||
|
||||
```cmd
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --create --topic unionflow.finance.approvals --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1 --if-not-exists
|
||||
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --create --topic unionflow.dashboard.stats --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1 --if-not-exists
|
||||
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --create --topic unionflow.notifications.user --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1 --if-not-exists
|
||||
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --create --topic unionflow.members.events --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1 --if-not-exists
|
||||
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --create --topic unionflow.contributions.events --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1 --if-not-exists
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Vérification des Topics
|
||||
|
||||
### Lister tous les topics
|
||||
|
||||
```cmd
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --list --bootstrap-server localhost:9092
|
||||
```
|
||||
|
||||
Résultat attendu :
|
||||
```
|
||||
unionflow.contributions.events
|
||||
unionflow.dashboard.stats
|
||||
unionflow.finance.approvals
|
||||
unionflow.members.events
|
||||
unionflow.notifications.user
|
||||
```
|
||||
|
||||
### Voir les détails d'un topic
|
||||
|
||||
```cmd
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --describe --topic unionflow.finance.approvals --bootstrap-server localhost:9092
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Topics Créés
|
||||
|
||||
| Topic | Partitions | Replication | Usage |
|
||||
|-------|------------|-------------|-------|
|
||||
| `unionflow.finance.approvals` | 3 | 1 | Workflow approbations financières |
|
||||
| `unionflow.dashboard.stats` | 3 | 1 | Mise à jour stats dashboard |
|
||||
| `unionflow.notifications.user` | 3 | 1 | Notifications utilisateurs |
|
||||
| `unionflow.members.events` | 3 | 1 | Events création/modification membres |
|
||||
| `unionflow.contributions.events` | 3 | 1 | Events paiements cotisations |
|
||||
|
||||
**Partitions** : 3 pour parallélisme (peut augmenter en prod si besoin)
|
||||
**Replication** : 1 (développement single-node)
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Tester la Configuration
|
||||
|
||||
### 1. Publier un message de test
|
||||
|
||||
```cmd
|
||||
docker exec -it kafka /opt/kafka/bin/kafka-console-producer.sh --topic unionflow.finance.approvals --bootstrap-server localhost:9092
|
||||
```
|
||||
|
||||
Tape un message JSON et appuie sur Entrée :
|
||||
```json
|
||||
{"eventType":"TEST","timestamp":"2026-03-14T19:00:00Z","data":{"message":"test"}}
|
||||
```
|
||||
|
||||
Ctrl+C pour quitter.
|
||||
|
||||
### 2. Consommer les messages
|
||||
|
||||
```cmd
|
||||
docker exec -it kafka /opt/kafka/bin/kafka-console-consumer.sh --topic unionflow.finance.approvals --bootstrap-server localhost:9092 --from-beginning
|
||||
```
|
||||
|
||||
Tu devrais voir le message de test. Ctrl+C pour quitter.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Résultat Attendu
|
||||
|
||||
Après création des topics, redémarre le backend Quarkus :
|
||||
|
||||
```bash
|
||||
cd unionflow-server-impl-quarkus
|
||||
./mvnw quarkus:dev
|
||||
```
|
||||
|
||||
Les erreurs `UNKNOWN_TOPIC_OR_PARTITION` **disparaîtront** et tu verras :
|
||||
|
||||
```
|
||||
✅ Kafka consumers started successfully
|
||||
✅ Connected to topics: unionflow.finance.approvals, unionflow.dashboard.stats, ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ❓ FAQ
|
||||
|
||||
### Puis-je supprimer mon Kafka actuel ?
|
||||
|
||||
**NON !** Ton Kafka actuel est parfait. Garde-le. Il manque juste les topics.
|
||||
|
||||
### Dois-je utiliser docker-compose ?
|
||||
|
||||
Non, ton setup actuel (conteneur `kafka` standalone) fonctionne très bien pour le développement.
|
||||
|
||||
### En production ?
|
||||
|
||||
En production (Kubernetes), les topics seront créés automatiquement par le backend au démarrage (via `auto.create.topics.enable=true` dans Kafka) ou via Helm charts.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Après Création des Topics
|
||||
|
||||
1. **Redémarrer backend Quarkus** : `./mvnw quarkus:dev`
|
||||
2. **Lancer mobile** : `flutter run --dart-define=ENV=dev`
|
||||
3. **Tester WebSocket** : Publier un event via Swagger UI → vérifier réception mobile
|
||||
|
||||
---
|
||||
|
||||
**Status** : ✅ Topics créés → Backend connecté → WebSocket fonctionnel
|
||||
39
unionflow/scripts/kafka/create-topics.bat
Normal file
39
unionflow/scripts/kafka/create-topics.bat
Normal file
@@ -0,0 +1,39 @@
|
||||
@echo off
|
||||
REM Script pour créer les topics Kafka pour UnionFlow
|
||||
REM Exécuter depuis le répertoire unionflow/
|
||||
|
||||
echo ====================================
|
||||
echo Création des topics Kafka UnionFlow
|
||||
echo ====================================
|
||||
echo.
|
||||
|
||||
echo Topic 1/5: unionflow.finance.approvals
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --create --topic unionflow.finance.approvals --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1 --if-not-exists
|
||||
|
||||
echo Topic 2/5: unionflow.dashboard.stats
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --create --topic unionflow.dashboard.stats --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1 --if-not-exists
|
||||
|
||||
echo Topic 3/5: unionflow.notifications.user
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --create --topic unionflow.notifications.user --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1 --if-not-exists
|
||||
|
||||
echo Topic 4/5: unionflow.members.events
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --create --topic unionflow.members.events --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1 --if-not-exists
|
||||
|
||||
echo Topic 5/5: unionflow.contributions.events
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --create --topic unionflow.contributions.events --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1 --if-not-exists
|
||||
|
||||
echo.
|
||||
echo ====================================
|
||||
echo Vérification des topics créés
|
||||
echo ====================================
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --list --bootstrap-server localhost:9092
|
||||
|
||||
echo.
|
||||
echo ====================================
|
||||
echo Détails des topics UnionFlow
|
||||
echo ====================================
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --describe --bootstrap-server localhost:9092 | findstr unionflow
|
||||
|
||||
echo.
|
||||
echo ✅ Topics Kafka créés avec succès !
|
||||
pause
|
||||
62
unionflow/scripts/kafka/create-topics.sh
Normal file
62
unionflow/scripts/kafka/create-topics.sh
Normal file
@@ -0,0 +1,62 @@
|
||||
#!/bin/bash
|
||||
# Script pour créer les topics Kafka pour UnionFlow
|
||||
|
||||
echo "===================================="
|
||||
echo "Création des topics Kafka UnionFlow"
|
||||
echo "===================================="
|
||||
echo ""
|
||||
|
||||
echo "Topic 1/5: unionflow.finance.approvals"
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --create \
|
||||
--topic unionflow.finance.approvals \
|
||||
--bootstrap-server localhost:9092 \
|
||||
--partitions 3 \
|
||||
--replication-factor 1 \
|
||||
--if-not-exists
|
||||
|
||||
echo "Topic 2/5: unionflow.dashboard.stats"
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --create \
|
||||
--topic unionflow.dashboard.stats \
|
||||
--bootstrap-server localhost:9092 \
|
||||
--partitions 3 \
|
||||
--replication-factor 1 \
|
||||
--if-not-exists
|
||||
|
||||
echo "Topic 3/5: unionflow.notifications.user"
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --create \
|
||||
--topic unionflow.notifications.user \
|
||||
--bootstrap-server localhost:9092 \
|
||||
--partitions 3 \
|
||||
--replication-factor 1 \
|
||||
--if-not-exists
|
||||
|
||||
echo "Topic 4/5: unionflow.members.events"
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --create \
|
||||
--topic unionflow.members.events \
|
||||
--bootstrap-server localhost:9092 \
|
||||
--partitions 3 \
|
||||
--replication-factor 1 \
|
||||
--if-not-exists
|
||||
|
||||
echo "Topic 5/5: unionflow.contributions.events"
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --create \
|
||||
--topic unionflow.contributions.events \
|
||||
--bootstrap-server localhost:9092 \
|
||||
--partitions 3 \
|
||||
--replication-factor 1 \
|
||||
--if-not-exists
|
||||
|
||||
echo ""
|
||||
echo "===================================="
|
||||
echo "Vérification des topics créés"
|
||||
echo "===================================="
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --list --bootstrap-server localhost:9092
|
||||
|
||||
echo ""
|
||||
echo "===================================="
|
||||
echo "Détails des topics UnionFlow"
|
||||
echo "===================================="
|
||||
docker exec kafka /opt/kafka/bin/kafka-topics.sh --describe --bootstrap-server localhost:9092 | grep unionflow
|
||||
|
||||
echo ""
|
||||
echo "✅ Topics Kafka créés avec succès !"
|
||||
31
unionflow/scripts/kafka/test-event.bat
Normal file
31
unionflow/scripts/kafka/test-event.bat
Normal file
@@ -0,0 +1,31 @@
|
||||
@echo off
|
||||
REM Script pour tester la publication et consommation d'events Kafka
|
||||
|
||||
echo ====================================
|
||||
echo Test Event Kafka - UnionFlow
|
||||
echo ====================================
|
||||
echo.
|
||||
|
||||
echo 📤 Publication d'un event de test dans unionflow.finance.approvals...
|
||||
echo.
|
||||
|
||||
REM Créer un fichier temporaire avec l'event JSON
|
||||
echo {"eventType":"APPROVAL_APPROVED","timestamp":"2026-03-14T19:30:00Z","organizationId":"test-org-123","data":{"id":"test-approval-1","transactionType":"COTISATION","amount":50000,"currency":"XOF","approvedBy":"admin@test.com","approvedAt":"2026-03-14T19:30:00Z"}} > %TEMP%\kafka-test-event.json
|
||||
|
||||
REM Publier l'event dans Kafka
|
||||
docker exec -i kafka /opt/kafka/bin/kafka-console-producer.sh --topic unionflow.finance.approvals --bootstrap-server localhost:9092 < %TEMP%\kafka-test-event.json
|
||||
|
||||
echo.
|
||||
echo ✅ Event publié dans unionflow.finance.approvals
|
||||
echo.
|
||||
echo 📥 Pour consommer les events (dans une autre fenêtre CMD) :
|
||||
echo docker exec -it kafka /opt/kafka/bin/kafka-console-consumer.sh --topic unionflow.finance.approvals --bootstrap-server localhost:9092 --from-beginning
|
||||
echo.
|
||||
echo 📊 Vérifie les logs du backend Quarkus :
|
||||
echo Tu devrais voir : "Received finance approval event"
|
||||
echo.
|
||||
|
||||
REM Nettoyer
|
||||
del %TEMP%\kafka-test-event.json
|
||||
|
||||
pause
|
||||
226
unionflow/scripts/kafka/test-websocket.html
Normal file
226
unionflow/scripts/kafka/test-websocket.html
Normal file
@@ -0,0 +1,226 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Test WebSocket UnionFlow</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #2c3e50;
|
||||
border-bottom: 3px solid #3498db;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.status {
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
.connected {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
.disconnected {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
button {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
button:disabled {
|
||||
background-color: #95a5a6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
#messages {
|
||||
background-color: #2c3e50;
|
||||
color: #ecf0f1;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
height: 400px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.event {
|
||||
margin: 5px 0;
|
||||
padding: 8px;
|
||||
border-left: 3px solid #3498db;
|
||||
background-color: #34495e;
|
||||
}
|
||||
.event-type {
|
||||
color: #3498db;
|
||||
font-weight: bold;
|
||||
}
|
||||
.timestamp {
|
||||
color: #95a5a6;
|
||||
font-size: 11px;
|
||||
}
|
||||
.controls {
|
||||
margin: 20px 0;
|
||||
}
|
||||
.info {
|
||||
background-color: #d1ecf1;
|
||||
border: 1px solid #bee5eb;
|
||||
color: #0c5460;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🔌 Test WebSocket UnionFlow</h1>
|
||||
|
||||
<div class="info">
|
||||
<strong>URL WebSocket :</strong> ws://localhost:8085/ws/dashboard
|
||||
</div>
|
||||
|
||||
<div id="status" class="status disconnected">
|
||||
❌ Déconnecté
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button id="connectBtn" onclick="connect()">🔗 Connecter</button>
|
||||
<button id="disconnectBtn" onclick="disconnect()" disabled>🔌 Déconnecter</button>
|
||||
<button id="pingBtn" onclick="sendPing()" disabled>📤 Envoyer Ping</button>
|
||||
<button id="clearBtn" onclick="clearMessages()">🗑️ Effacer</button>
|
||||
</div>
|
||||
|
||||
<h3>📨 Messages reçus :</h3>
|
||||
<div id="messages"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let ws = null;
|
||||
let reconnectInterval = null;
|
||||
|
||||
function updateStatus(connected) {
|
||||
const statusDiv = document.getElementById('status');
|
||||
const connectBtn = document.getElementById('connectBtn');
|
||||
const disconnectBtn = document.getElementById('disconnectBtn');
|
||||
const pingBtn = document.getElementById('pingBtn');
|
||||
|
||||
if (connected) {
|
||||
statusDiv.className = 'status connected';
|
||||
statusDiv.innerHTML = '✅ Connecté';
|
||||
connectBtn.disabled = true;
|
||||
disconnectBtn.disabled = false;
|
||||
pingBtn.disabled = false;
|
||||
} else {
|
||||
statusDiv.className = 'status disconnected';
|
||||
statusDiv.innerHTML = '❌ Déconnecté';
|
||||
connectBtn.disabled = false;
|
||||
disconnectBtn.disabled = true;
|
||||
pingBtn.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
function addMessage(type, message, data = null) {
|
||||
const messagesDiv = document.getElementById('messages');
|
||||
const timestamp = new Date().toLocaleTimeString('fr-FR');
|
||||
|
||||
let content = `<div class="event">`;
|
||||
content += `<span class="timestamp">[${timestamp}]</span> `;
|
||||
content += `<span class="event-type">${type}</span>: ${message}`;
|
||||
|
||||
if (data) {
|
||||
content += `<pre style="margin: 5px 0; padding: 5px; background: #1a252f; border-radius: 3px;">${JSON.stringify(data, null, 2)}</pre>`;
|
||||
}
|
||||
|
||||
content += `</div>`;
|
||||
|
||||
messagesDiv.innerHTML += content;
|
||||
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
||||
}
|
||||
|
||||
function connect() {
|
||||
try {
|
||||
addMessage('SYSTEM', 'Tentative de connexion à ws://localhost:8085/ws/dashboard...');
|
||||
|
||||
ws = new WebSocket('ws://localhost:8085/ws/dashboard');
|
||||
|
||||
ws.onopen = function() {
|
||||
updateStatus(true);
|
||||
addMessage('SYSTEM', '✅ Connexion WebSocket établie avec succès !');
|
||||
};
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
const eventType = data.type || data.eventType || 'UNKNOWN';
|
||||
addMessage('RECEIVED', eventType, data);
|
||||
} catch (e) {
|
||||
addMessage('RECEIVED', 'Message brut', event.data);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = function(error) {
|
||||
addMessage('ERROR', 'Erreur WebSocket', error);
|
||||
};
|
||||
|
||||
ws.onclose = function() {
|
||||
updateStatus(false);
|
||||
addMessage('SYSTEM', '❌ Connexion WebSocket fermée');
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
addMessage('ERROR', 'Erreur lors de la connexion', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function disconnect() {
|
||||
if (ws) {
|
||||
ws.close();
|
||||
ws = null;
|
||||
updateStatus(false);
|
||||
addMessage('SYSTEM', 'Déconnexion manuelle');
|
||||
}
|
||||
}
|
||||
|
||||
function sendPing() {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
const ping = { type: 'ping' };
|
||||
ws.send(JSON.stringify(ping));
|
||||
addMessage('SENT', 'Ping envoyé', ping);
|
||||
}
|
||||
}
|
||||
|
||||
function clearMessages() {
|
||||
document.getElementById('messages').innerHTML = '';
|
||||
addMessage('SYSTEM', 'Messages effacés');
|
||||
}
|
||||
|
||||
// Auto-connect au chargement
|
||||
window.onload = function() {
|
||||
addMessage('SYSTEM', 'Page chargée. Prêt à se connecter.');
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
329
unionflow/scripts/keycloak-setup.ps1
Normal file
329
unionflow/scripts/keycloak-setup.ps1
Normal file
@@ -0,0 +1,329 @@
|
||||
# Configuration Keycloak pour UnionFlow
|
||||
# Crée les rôles et les comptes de test pour MUKEFI et MESKA
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Configuration
|
||||
$KEYCLOAK_URL = "http://localhost:8180"
|
||||
$REALM_NAME = "unionflow"
|
||||
$ADMIN_USERNAME = "admin"
|
||||
$ADMIN_PASSWORD = "admin"
|
||||
$TEST_PASSWORD = "Test@123"
|
||||
|
||||
function Write-Header {
|
||||
param([string]$Text)
|
||||
Write-Host ""
|
||||
Write-Host "=" -NoNewline -ForegroundColor Cyan
|
||||
Write-Host (" " * 68) -NoNewline
|
||||
Write-Host "=" -ForegroundColor Cyan
|
||||
Write-Host $Text -ForegroundColor White
|
||||
Write-Host "=" -NoNewline -ForegroundColor Cyan
|
||||
Write-Host (" " * 68) -NoNewline
|
||||
Write-Host "=" -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
function Get-KeycloakAdminToken {
|
||||
Write-Host "`n📡 Connexion à Keycloak..." -ForegroundColor Yellow
|
||||
|
||||
$tokenUrl = "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token"
|
||||
$body = @{
|
||||
client_id = "admin-cli"
|
||||
username = $ADMIN_USERNAME
|
||||
password = $ADMIN_PASSWORD
|
||||
grant_type = "password"
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Invoke-RestMethod -Uri $tokenUrl -Method Post -Body $body -ContentType "application/x-www-form-urlencoded"
|
||||
Write-Host "✅ Connecté à Keycloak Admin API" -ForegroundColor Green
|
||||
return $response.access_token
|
||||
}
|
||||
catch {
|
||||
Write-Host "❌ Erreur connexion Keycloak: $_" -ForegroundColor Red
|
||||
Write-Host " Vérifiez que Keycloak est démarré sur $KEYCLOAK_URL" -ForegroundColor Yellow
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
function Get-Headers {
|
||||
param([string]$Token)
|
||||
return @{
|
||||
"Authorization" = "Bearer $Token"
|
||||
"Content-Type" = "application/json"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-RealmUsers {
|
||||
param([string]$Token)
|
||||
$headers = Get-Headers -Token $Token
|
||||
$url = "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users"
|
||||
try {
|
||||
return Invoke-RestMethod -Uri $url -Method Get -Headers $headers
|
||||
}
|
||||
catch {
|
||||
Write-Host " ⚠️ Erreur récupération utilisateurs: $_" -ForegroundColor Yellow
|
||||
return @()
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-RealmUser {
|
||||
param([string]$Token, [string]$UserId, [string]$Username)
|
||||
$headers = Get-Headers -Token $Token
|
||||
$url = "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users/$UserId"
|
||||
try {
|
||||
Invoke-RestMethod -Uri $url -Method Delete -Headers $headers | Out-Null
|
||||
Write-Host " ✅ Supprimé: $Username" -ForegroundColor Green
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-Host " ❌ Erreur suppression $Username : $_" -ForegroundColor Red
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Get-RealmRoles {
|
||||
param([string]$Token)
|
||||
$headers = Get-Headers -Token $Token
|
||||
$url = "$KEYCLOAK_URL/admin/realms/$REALM_NAME/roles"
|
||||
try {
|
||||
return Invoke-RestMethod -Uri $url -Method Get -Headers $headers
|
||||
}
|
||||
catch {
|
||||
Write-Host " ⚠️ Erreur récupération rôles: $_" -ForegroundColor Yellow
|
||||
return @()
|
||||
}
|
||||
}
|
||||
|
||||
function New-RealmRole {
|
||||
param([string]$Token, [string]$Name, [string]$Description)
|
||||
$headers = Get-Headers -Token $Token
|
||||
$url = "$KEYCLOAK_URL/admin/realms/$REALM_NAME/roles"
|
||||
$body = @{
|
||||
name = $Name
|
||||
description = $Description
|
||||
composite = $false
|
||||
clientRole = $false
|
||||
} | ConvertTo-Json
|
||||
|
||||
try {
|
||||
Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body | Out-Null
|
||||
Write-Host " ✅ Rôle créé: $Name" -ForegroundColor Green
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
if ($_.Exception.Response.StatusCode.value__ -eq 409) {
|
||||
Write-Host " ⚠️ Rôle existe déjà: $Name" -ForegroundColor Yellow
|
||||
return $true
|
||||
}
|
||||
Write-Host " ❌ Erreur création rôle $Name : $_" -ForegroundColor Red
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function New-RealmUser {
|
||||
param(
|
||||
[string]$Token,
|
||||
[string]$Username,
|
||||
[string]$Email,
|
||||
[string]$FirstName,
|
||||
[string]$LastName,
|
||||
[string]$Password,
|
||||
[string[]]$Roles
|
||||
)
|
||||
|
||||
$headers = Get-Headers -Token $Token
|
||||
$url = "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users"
|
||||
|
||||
$body = @{
|
||||
username = $Username
|
||||
email = $Email
|
||||
firstName = $FirstName
|
||||
lastName = $LastName
|
||||
enabled = $true
|
||||
emailVerified = $true
|
||||
credentials = @(
|
||||
@{
|
||||
type = "password"
|
||||
value = $Password
|
||||
temporary = $false
|
||||
}
|
||||
)
|
||||
} | ConvertTo-Json -Depth 10
|
||||
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri $url -Method Post -Headers $headers -Body $body
|
||||
|
||||
if ($response.StatusCode -eq 201) {
|
||||
# Récupérer l'ID de l'utilisateur
|
||||
$location = $response.Headers.Location
|
||||
if ($location) {
|
||||
$userId = $location.Split('/')[-1]
|
||||
}
|
||||
else {
|
||||
# Chercher l'utilisateur par username
|
||||
$users = Get-RealmUsers -Token $Token
|
||||
$user = $users | Where-Object { $_.username -eq $Username }
|
||||
if ($user) {
|
||||
$userId = $user.id
|
||||
}
|
||||
}
|
||||
|
||||
if ($userId) {
|
||||
# Assigner les rôles
|
||||
Add-RolesToUser -Token $Token -UserId $userId -RoleNames $Roles
|
||||
Write-Host " ✅ Utilisateur créé: $Username ($Email)" -ForegroundColor Green
|
||||
return $userId
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
if ($_.Exception.Response.StatusCode.value__ -eq 409) {
|
||||
Write-Host " ⚠️ Utilisateur existe déjà: $Username" -ForegroundColor Yellow
|
||||
}
|
||||
else {
|
||||
Write-Host " ❌ Erreur création utilisateur $Username : $_" -ForegroundColor Red
|
||||
}
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
function Add-RolesToUser {
|
||||
param([string]$Token, [string]$UserId, [string[]]$RoleNames)
|
||||
|
||||
# Récupérer les objets de rôle
|
||||
$allRoles = Get-RealmRoles -Token $Token
|
||||
$rolesToAssign = $allRoles | Where-Object { $RoleNames -contains $_.name }
|
||||
|
||||
if ($rolesToAssign.Count -eq 0) {
|
||||
return
|
||||
}
|
||||
|
||||
$headers = Get-Headers -Token $Token
|
||||
$url = "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users/$UserId/role-mappings/realm"
|
||||
$body = $rolesToAssign | ConvertTo-Json -Depth 10 -AsArray
|
||||
|
||||
try {
|
||||
Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body | Out-Null
|
||||
Write-Host " → Rôles assignés: $($RoleNames -join ', ')" -ForegroundColor Cyan
|
||||
}
|
||||
catch {
|
||||
Write-Host " ⚠️ Erreur assignation rôles: $_" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# MAIN
|
||||
# ============================================================================
|
||||
|
||||
Write-Header "🔧 Configuration Keycloak pour UnionFlow"
|
||||
|
||||
# 1. Connexion
|
||||
$token = Get-KeycloakAdminToken
|
||||
if (-not $token) {
|
||||
Write-Host "`n❌ Impossible de se connecter à Keycloak. Script arrêté." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 2. Audit de l'existant
|
||||
Write-Host "`n📋 Audit de l'état actuel..." -ForegroundColor Yellow
|
||||
$existingUsers = Get-RealmUsers -Token $token
|
||||
$existingRoles = Get-RealmRoles -Token $token
|
||||
|
||||
Write-Host " • Utilisateurs existants: $($existingUsers.Count)" -ForegroundColor White
|
||||
foreach ($user in $existingUsers) {
|
||||
Write-Host " - $($user.username) ($($user.email))" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
Write-Host " • Rôles existants: $($existingRoles.Count)" -ForegroundColor White
|
||||
foreach ($role in $existingRoles) {
|
||||
Write-Host " - $($role.name)" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
# 3. Suppression des utilisateurs existants
|
||||
Write-Host "`n🗑️ Suppression des utilisateurs existants..." -ForegroundColor Yellow
|
||||
foreach ($user in $existingUsers) {
|
||||
Remove-RealmUser -Token $token -UserId $user.id -Username $user.username
|
||||
}
|
||||
|
||||
# 4. Création de la structure de rôles
|
||||
Write-Host "`n👥 Création de la structure de rôles..." -ForegroundColor Yellow
|
||||
|
||||
$rolesToCreate = @(
|
||||
@{name="SUPER_ADMIN"; description="Super administrateur - Accès total plateforme multi-organisations"},
|
||||
@{name="ADMIN_ORGANISATION"; description="Administrateur d'une organisation - Accès total à son organisation"},
|
||||
@{name="TRESORIER"; description="Trésorier - Gestion financière, comptabilité, épargne/crédit"},
|
||||
@{name="SECRETAIRE"; description="Secrétaire - Gestion administrative, membres, adhésions, documents"},
|
||||
@{name="RESPONSABLE_SOCIAL"; description="Responsable social - Gestion aide sociale et solidarité"},
|
||||
@{name="RESPONSABLE_EVENEMENTS"; description="Responsable événements - Gestion événements et logistique"},
|
||||
@{name="RESPONSABLE_CREDIT"; description="Responsable crédit - Gestion épargne/crédit (mutuelles)"},
|
||||
@{name="MEMBRE_BUREAU"; description="Membre du bureau - Accès étendu consultation et actions"},
|
||||
@{name="MEMBRE_ACTIF"; description="Membre actif - Consultation et actions de base"},
|
||||
@{name="MEMBRE_SIMPLE"; description="Membre simple - Consultation uniquement"},
|
||||
@{name="MEMBRE"; description="Rôle technique - Membre base"},
|
||||
@{name="ADMIN"; description="Rôle technique - Admin base"},
|
||||
@{name="USER"; description="Rôle technique - Utilisateur base"}
|
||||
)
|
||||
|
||||
foreach ($role in $rolesToCreate) {
|
||||
New-RealmRole -Token $token -Name $role.name -Description $role.description
|
||||
}
|
||||
|
||||
# 5. Création des comptes de test
|
||||
Write-Host "`n👤 Création des comptes de test..." -ForegroundColor Yellow
|
||||
Write-Host " Mot de passe pour tous: $TEST_PASSWORD`n" -ForegroundColor Cyan
|
||||
|
||||
$usersToCreate = @(
|
||||
# Super-Admin
|
||||
@{username="superadmin"; email="superadmin@unionflow.test"; firstName="Super"; lastName="Admin"; roles=@("SUPER_ADMIN", "ADMIN", "USER")},
|
||||
|
||||
# MUKEFI
|
||||
@{username="admin.mukefi"; email="admin.mukefi@unionflow.test"; firstName="Administrateur"; lastName="MUKEFI"; roles=@("ADMIN_ORGANISATION", "ADMIN", "USER")},
|
||||
@{username="tresorier.mukefi"; email="tresorier.mukefi@unionflow.test"; firstName="Trésorier"; lastName="MUKEFI"; roles=@("TRESORIER", "MEMBRE", "USER")},
|
||||
@{username="secretaire.mukefi"; email="secretaire.mukefi@unionflow.test"; firstName="Secrétaire"; lastName="MUKEFI"; roles=@("SECRETAIRE", "MEMBRE", "USER")},
|
||||
@{username="credit.mukefi"; email="credit.mukefi@unionflow.test"; firstName="Responsable Crédit"; lastName="MUKEFI"; roles=@("RESPONSABLE_CREDIT", "MEMBRE", "USER")},
|
||||
@{username="membre.mukefi"; email="membre.mukefi@unionflow.test"; firstName="Membre"; lastName="MUKEFI"; roles=@("MEMBRE_ACTIF", "MEMBRE", "USER")},
|
||||
|
||||
# MESKA
|
||||
@{username="admin.meska"; email="admin.meska@unionflow.test"; firstName="Administrateur"; lastName="MESKA"; roles=@("ADMIN_ORGANISATION", "ADMIN", "USER")},
|
||||
@{username="secretaire.meska"; email="secretaire.meska@unionflow.test"; firstName="Secrétaire"; lastName="MESKA"; roles=@("SECRETAIRE", "MEMBRE", "USER")},
|
||||
@{username="social.meska"; email="social.meska@unionflow.test"; firstName="Responsable Social"; lastName="MESKA"; roles=@("RESPONSABLE_SOCIAL", "MEMBRE", "USER")},
|
||||
@{username="evenements.meska"; email="evenements.meska@unionflow.test"; firstName="Responsable Événements"; lastName="MESKA"; roles=@("RESPONSABLE_EVENEMENTS", "MEMBRE", "USER")},
|
||||
@{username="membre.meska"; email="membre.meska@unionflow.test"; firstName="Membre"; lastName="MESKA"; roles=@("MEMBRE_ACTIF", "MEMBRE", "USER")}
|
||||
)
|
||||
|
||||
foreach ($user in $usersToCreate) {
|
||||
New-RealmUser -Token $token -Username $user.username -Email $user.email `
|
||||
-FirstName $user.firstName -lastName $user.lastName `
|
||||
-Password $TEST_PASSWORD -Roles $user.roles
|
||||
}
|
||||
|
||||
# 6. Résumé final
|
||||
Write-Header "✅ Configuration Keycloak terminée avec succès !"
|
||||
|
||||
Write-Host "`n📊 Résumé:" -ForegroundColor Yellow
|
||||
Write-Host " • $($rolesToCreate.Count) rôles créés" -ForegroundColor White
|
||||
Write-Host " • $($usersToCreate.Count) utilisateurs créés" -ForegroundColor White
|
||||
Write-Host " • Mot de passe: $TEST_PASSWORD" -ForegroundColor Cyan
|
||||
|
||||
Write-Host "`n👥 Comptes créés:" -ForegroundColor Yellow
|
||||
Write-Host "`n 🔧 Super-Admin:" -ForegroundColor Magenta
|
||||
Write-Host " → superadmin@unionflow.test" -ForegroundColor White
|
||||
|
||||
Write-Host "`n 🏦 MUKEFI (Mutuelle):" -ForegroundColor Magenta
|
||||
Write-Host " → admin.mukefi@unionflow.test (Admin)" -ForegroundColor White
|
||||
Write-Host " → tresorier.mukefi@unionflow.test (Trésorier)" -ForegroundColor White
|
||||
Write-Host " → secretaire.mukefi@unionflow.test (Secrétaire)" -ForegroundColor White
|
||||
Write-Host " → credit.mukefi@unionflow.test (Responsable Crédit)" -ForegroundColor White
|
||||
Write-Host " → membre.mukefi@unionflow.test (Membre Actif)" -ForegroundColor White
|
||||
|
||||
Write-Host "`n 🤝 MESKA (Association):" -ForegroundColor Magenta
|
||||
Write-Host " → admin.meska@unionflow.test (Admin)" -ForegroundColor White
|
||||
Write-Host " → secretaire.meska@unionflow.test (Secrétaire)" -ForegroundColor White
|
||||
Write-Host " → social.meska@unionflow.test (Responsable Social)" -ForegroundColor White
|
||||
Write-Host " → evenements.meska@unionflow.test (Responsable Événements)" -ForegroundColor White
|
||||
Write-Host " → membre.meska@unionflow.test (Membre Actif)" -ForegroundColor White
|
||||
|
||||
Write-Host "`n🌐 Accès Keycloak:" -ForegroundColor Yellow
|
||||
Write-Host " • Console Admin: $KEYCLOAK_URL/admin" -ForegroundColor Cyan
|
||||
Write-Host " • Realm: $REALM_NAME" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
365
unionflow/scripts/keycloak-setup.py
Normal file
365
unionflow/scripts/keycloak-setup.py
Normal file
@@ -0,0 +1,365 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script de configuration Keycloak pour UnionFlow
|
||||
Crée les rôles et les comptes de test pour MUKEFI et MESKA
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
import sys
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
# Configuration
|
||||
KEYCLOAK_URL = "http://localhost:8180"
|
||||
REALM_NAME = "unionflow"
|
||||
ADMIN_USERNAME = "admin"
|
||||
ADMIN_PASSWORD = "admin" # Modifier si différent
|
||||
TEST_PASSWORD = "Test@123"
|
||||
|
||||
class KeycloakManager:
|
||||
def __init__(self):
|
||||
self.base_url = KEYCLOAK_URL
|
||||
self.realm = REALM_NAME
|
||||
self.token = None
|
||||
|
||||
def get_admin_token(self) -> bool:
|
||||
"""Obtient un token admin pour l'API Keycloak"""
|
||||
url = f"{self.base_url}/realms/master/protocol/openid-connect/token"
|
||||
data = {
|
||||
"client_id": "admin-cli",
|
||||
"username": ADMIN_USERNAME,
|
||||
"password": ADMIN_PASSWORD,
|
||||
"grant_type": "password"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(url, data=data)
|
||||
response.raise_for_status()
|
||||
self.token = response.json()["access_token"]
|
||||
print("✅ Connecté à Keycloak Admin API")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ Erreur connexion Keycloak: {e}")
|
||||
return False
|
||||
|
||||
def _headers(self) -> Dict:
|
||||
"""Headers pour les requêtes API"""
|
||||
return {
|
||||
"Authorization": f"Bearer {self.token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
def list_users(self) -> List[Dict]:
|
||||
"""Liste tous les utilisateurs du realm"""
|
||||
url = f"{self.base_url}/admin/realms/{self.realm}/users"
|
||||
response = requests.get(url, headers=self._headers())
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def delete_user(self, user_id: str) -> bool:
|
||||
"""Supprime un utilisateur"""
|
||||
url = f"{self.base_url}/admin/realms/{self.realm}/users/{user_id}"
|
||||
response = requests.delete(url, headers=self._headers())
|
||||
return response.status_code == 204
|
||||
|
||||
def list_roles(self) -> List[Dict]:
|
||||
"""Liste tous les rôles du realm"""
|
||||
url = f"{self.base_url}/admin/realms/{self.realm}/roles"
|
||||
response = requests.get(url, headers=self._headers())
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def create_role(self, name: str, description: str) -> bool:
|
||||
"""Crée un rôle realm"""
|
||||
url = f"{self.base_url}/admin/realms/{self.realm}/roles"
|
||||
data = {
|
||||
"name": name,
|
||||
"description": description,
|
||||
"composite": False,
|
||||
"clientRole": False
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(url, headers=self._headers(), json=data)
|
||||
if response.status_code == 201:
|
||||
print(f" ✅ Rôle créé: {name}")
|
||||
return True
|
||||
elif response.status_code == 409:
|
||||
print(f" ⚠️ Rôle existe déjà: {name}")
|
||||
return True
|
||||
else:
|
||||
print(f" ❌ Erreur création rôle {name}: {response.status_code}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f" ❌ Exception création rôle {name}: {e}")
|
||||
return False
|
||||
|
||||
def create_user(self, username: str, email: str, first_name: str, last_name: str,
|
||||
password: str, roles: List[str], enabled: bool = True) -> Optional[str]:
|
||||
"""Crée un utilisateur avec ses rôles"""
|
||||
# 1. Créer l'utilisateur
|
||||
url = f"{self.base_url}/admin/realms/{self.realm}/users"
|
||||
data = {
|
||||
"username": username,
|
||||
"email": email,
|
||||
"firstName": first_name,
|
||||
"lastName": last_name,
|
||||
"enabled": enabled,
|
||||
"emailVerified": True,
|
||||
"credentials": [{
|
||||
"type": "password",
|
||||
"value": password,
|
||||
"temporary": False
|
||||
}]
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(url, headers=self._headers(), json=data)
|
||||
|
||||
if response.status_code == 201:
|
||||
# Récupérer l'ID de l'utilisateur créé
|
||||
location = response.headers.get("Location")
|
||||
user_id = location.split("/")[-1] if location else None
|
||||
|
||||
if not user_id:
|
||||
# Chercher l'utilisateur par username
|
||||
users = self.list_users()
|
||||
user = next((u for u in users if u["username"] == username), None)
|
||||
if user:
|
||||
user_id = user["id"]
|
||||
|
||||
if user_id:
|
||||
# 2. Assigner les rôles
|
||||
self.assign_roles_to_user(user_id, roles)
|
||||
print(f" ✅ Utilisateur créé: {username} ({email})")
|
||||
return user_id
|
||||
else:
|
||||
print(f" ⚠️ Utilisateur créé mais ID non trouvé: {username}")
|
||||
return None
|
||||
elif response.status_code == 409:
|
||||
print(f" ⚠️ Utilisateur existe déjà: {username}")
|
||||
# Récupérer l'ID et mettre à jour les rôles
|
||||
users = self.list_users()
|
||||
user = next((u for u in users if u["username"] == username), None)
|
||||
if user:
|
||||
self.assign_roles_to_user(user["id"], roles)
|
||||
return None
|
||||
else:
|
||||
print(f" ❌ Erreur création utilisateur {username}: {response.status_code} - {response.text}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f" ❌ Exception création utilisateur {username}: {e}")
|
||||
return None
|
||||
|
||||
def assign_roles_to_user(self, user_id: str, role_names: List[str]):
|
||||
"""Assigne des rôles à un utilisateur"""
|
||||
# Récupérer les objets de rôle
|
||||
all_roles = self.list_roles()
|
||||
roles_to_assign = [r for r in all_roles if r["name"] in role_names]
|
||||
|
||||
if not roles_to_assign:
|
||||
return
|
||||
|
||||
url = f"{self.base_url}/admin/realms/{self.realm}/users/{user_id}/role-mappings/realm"
|
||||
response = requests.post(url, headers=self._headers(), json=roles_to_assign)
|
||||
|
||||
if response.status_code in [204, 200]:
|
||||
print(f" → Rôles assignés: {', '.join(role_names)}")
|
||||
else:
|
||||
print(f" ⚠️ Erreur assignation rôles: {response.status_code}")
|
||||
|
||||
def main():
|
||||
print("=" * 70)
|
||||
print("🔧 Configuration Keycloak pour UnionFlow")
|
||||
print("=" * 70)
|
||||
|
||||
kc = KeycloakManager()
|
||||
|
||||
# 1. Connexion
|
||||
print("\n📡 Connexion à Keycloak...")
|
||||
if not kc.get_admin_token():
|
||||
print("\n❌ Impossible de se connecter à Keycloak.")
|
||||
print(" Vérifiez que Keycloak est démarré sur http://localhost:8180")
|
||||
print(" Credentials par défaut: admin / admin")
|
||||
sys.exit(1)
|
||||
|
||||
# 2. Audit de l'existant
|
||||
print("\n📋 Audit de l'état actuel...")
|
||||
existing_users = kc.list_users()
|
||||
existing_roles = kc.list_roles()
|
||||
|
||||
print(f" • Utilisateurs existants: {len(existing_users)}")
|
||||
for user in existing_users:
|
||||
print(f" - {user['username']} ({user.get('email', 'no email')})")
|
||||
|
||||
print(f" • Rôles existants: {len(existing_roles)}")
|
||||
for role in existing_roles:
|
||||
print(f" - {role['name']}")
|
||||
|
||||
# 3. Suppression des utilisateurs existants
|
||||
print("\n🗑️ Suppression des utilisateurs existants...")
|
||||
for user in existing_users:
|
||||
if kc.delete_user(user["id"]):
|
||||
print(f" ✅ Supprimé: {user['username']}")
|
||||
else:
|
||||
print(f" ❌ Erreur suppression: {user['username']}")
|
||||
|
||||
# 4. Création de la structure de rôles
|
||||
print("\n👥 Création de la structure de rôles...")
|
||||
|
||||
roles_to_create = [
|
||||
("SUPER_ADMIN", "Super administrateur - Accès total plateforme multi-organisations"),
|
||||
("ADMIN_ORGANISATION", "Administrateur d'une organisation - Accès total à son organisation"),
|
||||
("TRESORIER", "Trésorier - Gestion financière, comptabilité, épargne/crédit"),
|
||||
("SECRETAIRE", "Secrétaire - Gestion administrative, membres, adhésions, documents"),
|
||||
("RESPONSABLE_SOCIAL", "Responsable social - Gestion aide sociale et solidarité"),
|
||||
("RESPONSABLE_EVENEMENTS", "Responsable événements - Gestion événements et logistique"),
|
||||
("RESPONSABLE_CREDIT", "Responsable crédit - Gestion épargne/crédit (mutuelles)"),
|
||||
("MEMBRE_BUREAU", "Membre du bureau - Accès étendu consultation et actions"),
|
||||
("MEMBRE_ACTIF", "Membre actif - Consultation et actions de base"),
|
||||
("MEMBRE_SIMPLE", "Membre simple - Consultation uniquement"),
|
||||
("MEMBRE", "Rôle technique - Membre base"),
|
||||
("ADMIN", "Rôle technique - Admin base"),
|
||||
("USER", "Rôle technique - Utilisateur base")
|
||||
]
|
||||
|
||||
for role_name, description in roles_to_create:
|
||||
kc.create_role(role_name, description)
|
||||
|
||||
# 5. Création des comptes de test
|
||||
print("\n👤 Création des comptes de test...")
|
||||
|
||||
users_to_create = [
|
||||
# Super-Admin
|
||||
{
|
||||
"username": "superadmin",
|
||||
"email": "superadmin@unionflow.test",
|
||||
"first_name": "Super",
|
||||
"last_name": "Admin",
|
||||
"roles": ["SUPER_ADMIN", "ADMIN", "USER"]
|
||||
},
|
||||
|
||||
# MUKEFI (Mutuelle d'épargne et de crédit)
|
||||
{
|
||||
"username": "admin.mukefi",
|
||||
"email": "admin.mukefi@unionflow.test",
|
||||
"first_name": "Administrateur",
|
||||
"last_name": "MUKEFI",
|
||||
"roles": ["ADMIN_ORGANISATION", "ADMIN", "USER"]
|
||||
},
|
||||
{
|
||||
"username": "tresorier.mukefi",
|
||||
"email": "tresorier.mukefi@unionflow.test",
|
||||
"first_name": "Trésorier",
|
||||
"last_name": "MUKEFI",
|
||||
"roles": ["TRESORIER", "MEMBRE", "USER"]
|
||||
},
|
||||
{
|
||||
"username": "secretaire.mukefi",
|
||||
"email": "secretaire.mukefi@unionflow.test",
|
||||
"first_name": "Secrétaire",
|
||||
"last_name": "MUKEFI",
|
||||
"roles": ["SECRETAIRE", "MEMBRE", "USER"]
|
||||
},
|
||||
{
|
||||
"username": "credit.mukefi",
|
||||
"email": "credit.mukefi@unionflow.test",
|
||||
"first_name": "Responsable Crédit",
|
||||
"last_name": "MUKEFI",
|
||||
"roles": ["RESPONSABLE_CREDIT", "MEMBRE", "USER"]
|
||||
},
|
||||
{
|
||||
"username": "membre.mukefi",
|
||||
"email": "membre.mukefi@unionflow.test",
|
||||
"first_name": "Membre",
|
||||
"last_name": "MUKEFI",
|
||||
"roles": ["MEMBRE_ACTIF", "MEMBRE", "USER"]
|
||||
},
|
||||
|
||||
# MESKA (Association)
|
||||
{
|
||||
"username": "admin.meska",
|
||||
"email": "admin.meska@unionflow.test",
|
||||
"first_name": "Administrateur",
|
||||
"last_name": "MESKA",
|
||||
"roles": ["ADMIN_ORGANISATION", "ADMIN", "USER"]
|
||||
},
|
||||
{
|
||||
"username": "secretaire.meska",
|
||||
"email": "secretaire.meska@unionflow.test",
|
||||
"first_name": "Secrétaire",
|
||||
"last_name": "MESKA",
|
||||
"roles": ["SECRETAIRE", "MEMBRE", "USER"]
|
||||
},
|
||||
{
|
||||
"username": "social.meska",
|
||||
"email": "social.meska@unionflow.test",
|
||||
"first_name": "Responsable Social",
|
||||
"last_name": "MESKA",
|
||||
"roles": ["RESPONSABLE_SOCIAL", "MEMBRE", "USER"]
|
||||
},
|
||||
{
|
||||
"username": "evenements.meska",
|
||||
"email": "evenements.meska@unionflow.test",
|
||||
"first_name": "Responsable Événements",
|
||||
"last_name": "MESKA",
|
||||
"roles": ["RESPONSABLE_EVENEMENTS", "MEMBRE", "USER"]
|
||||
},
|
||||
{
|
||||
"username": "membre.meska",
|
||||
"email": "membre.meska@unionflow.test",
|
||||
"first_name": "Membre",
|
||||
"last_name": "MESKA",
|
||||
"roles": ["MEMBRE_ACTIF", "MEMBRE", "USER"]
|
||||
}
|
||||
]
|
||||
|
||||
print(f"\n📝 Création de {len(users_to_create)} comptes utilisateurs...")
|
||||
print(f" Mot de passe pour tous: {TEST_PASSWORD}\n")
|
||||
|
||||
for user_data in users_to_create:
|
||||
kc.create_user(
|
||||
username=user_data["username"],
|
||||
email=user_data["email"],
|
||||
first_name=user_data["first_name"],
|
||||
last_name=user_data["last_name"],
|
||||
password=TEST_PASSWORD,
|
||||
roles=user_data["roles"]
|
||||
)
|
||||
|
||||
# 6. Résumé final
|
||||
print("\n" + "=" * 70)
|
||||
print("✅ Configuration Keycloak terminée avec succès !")
|
||||
print("=" * 70)
|
||||
|
||||
print("\n📊 Résumé:")
|
||||
print(f" • {len(roles_to_create)} rôles créés")
|
||||
print(f" • {len(users_to_create)} utilisateurs créés")
|
||||
print(f" • Mot de passe: {TEST_PASSWORD}")
|
||||
|
||||
print("\n👥 Comptes créés:")
|
||||
print("\n 🔧 Super-Admin:")
|
||||
print(" → superadmin@unionflow.test")
|
||||
|
||||
print("\n 🏦 MUKEFI (Mutuelle):")
|
||||
print(" → admin.mukefi@unionflow.test (Admin)")
|
||||
print(" → tresorier.mukefi@unionflow.test (Trésorier)")
|
||||
print(" → secretaire.mukefi@unionflow.test (Secrétaire)")
|
||||
print(" → credit.mukefi@unionflow.test (Responsable Crédit)")
|
||||
print(" → membre.mukefi@unionflow.test (Membre Actif)")
|
||||
|
||||
print("\n 🤝 MESKA (Association):")
|
||||
print(" → admin.meska@unionflow.test (Admin)")
|
||||
print(" → secretaire.meska@unionflow.test (Secrétaire)")
|
||||
print(" → social.meska@unionflow.test (Responsable Social)")
|
||||
print(" → evenements.meska@unionflow.test (Responsable Événements)")
|
||||
print(" → membre.meska@unionflow.test (Membre Actif)")
|
||||
|
||||
print("\n🌐 Accès Keycloak:")
|
||||
print(f" • Console Admin: {KEYCLOAK_URL}/admin")
|
||||
print(f" • Realm: {REALM_NAME}")
|
||||
|
||||
print("\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
189
unionflow/scripts/keycloak-setup.sh
Normal file
189
unionflow/scripts/keycloak-setup.sh
Normal file
@@ -0,0 +1,189 @@
|
||||
#!/bin/bash
|
||||
# Configuration Keycloak pour UnionFlow
|
||||
# Crée les rôles et les comptes de test pour MUKEFI et MESKA
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
KEYCLOAK_URL="http://localhost:8180"
|
||||
REALM_NAME="unionflow"
|
||||
ADMIN_USERNAME="admin"
|
||||
ADMIN_PASSWORD="admin"
|
||||
TEST_PASSWORD="Test@123"
|
||||
|
||||
echo "========================================================================"
|
||||
echo "Configuration Keycloak pour UnionFlow"
|
||||
echo "========================================================================"
|
||||
|
||||
# 1. Obtenir le token admin
|
||||
echo ""
|
||||
echo "📡 Connexion à Keycloak..."
|
||||
TOKEN_RESPONSE=$(curl -s -X POST "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" \
|
||||
-d "client_id=admin-cli" \
|
||||
-d "username=$ADMIN_USERNAME" \
|
||||
-d "password=$ADMIN_PASSWORD" \
|
||||
-d "grant_type=password")
|
||||
|
||||
ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | grep -o '"access_token":"[^"]*' | cut -d'"' -f4)
|
||||
|
||||
if [ -z "$ACCESS_TOKEN" ]; then
|
||||
echo "❌ Erreur: Impossible d'obtenir le token admin"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Connecté à Keycloak Admin API"
|
||||
|
||||
# 2. Lister et supprimer les utilisateurs existants
|
||||
echo ""
|
||||
echo "📋 Audit et nettoyage des utilisateurs existants..."
|
||||
USERS=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN")
|
||||
|
||||
echo "$USERS" | grep -o '"username":"[^"]*' | cut -d'"' -f4 | while read username; do
|
||||
USER_ID=$(echo "$USERS" | grep -B5 "\"username\":\"$username\"" | grep -o '"id":"[^"]*' | cut -d'"' -f4 | head -1)
|
||||
if [ ! -z "$USER_ID" ]; then
|
||||
curl -s -X DELETE "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users/$USER_ID" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN"
|
||||
echo " ✅ Supprimé: $username"
|
||||
fi
|
||||
done
|
||||
|
||||
# 3. Créer les rôles
|
||||
echo ""
|
||||
echo "👥 Création de la structure de rôles..."
|
||||
|
||||
declare -a ROLES=(
|
||||
"SUPER_ADMIN|Super administrateur - Accès total plateforme"
|
||||
"ADMIN_ORGANISATION|Administrateur organisation - Accès total"
|
||||
"TRESORIER|Trésorier - Gestion financière et comptabilité"
|
||||
"SECRETAIRE|Secrétaire - Gestion administrative et membres"
|
||||
"RESPONSABLE_SOCIAL|Responsable social - Aide sociale"
|
||||
"RESPONSABLE_EVENEMENTS|Responsable événements - Événements"
|
||||
"RESPONSABLE_CREDIT|Responsable crédit - Épargne et crédit"
|
||||
"MEMBRE_BUREAU|Membre du bureau - Accès étendu"
|
||||
"MEMBRE_ACTIF|Membre actif - Accès de base"
|
||||
"MEMBRE_SIMPLE|Membre simple - Consultation uniquement"
|
||||
"MEMBRE|Rôle technique - Membre base"
|
||||
"ADMIN|Rôle technique - Admin base"
|
||||
"USER|Rôle technique - Utilisateur base"
|
||||
)
|
||||
|
||||
for role_data in "${ROLES[@]}"; do
|
||||
ROLE_NAME=$(echo $role_data | cut -d'|' -f1)
|
||||
ROLE_DESC=$(echo $role_data | cut -d'|' -f2)
|
||||
|
||||
curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/roles" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"name\":\"$ROLE_NAME\",\"description\":\"$ROLE_DESC\"}" > /dev/null 2>&1
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo " ✅ Rôle créé: $ROLE_NAME"
|
||||
else
|
||||
echo " ⚠️ Rôle existe ou erreur: $ROLE_NAME"
|
||||
fi
|
||||
done
|
||||
|
||||
# Fonction pour créer un utilisateur
|
||||
create_user() {
|
||||
local USERNAME=$1
|
||||
local EMAIL=$2
|
||||
local FIRSTNAME=$3
|
||||
local LASTNAME=$4
|
||||
shift 4
|
||||
local ROLES=("$@")
|
||||
|
||||
# Créer l'utilisateur
|
||||
curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"username\":\"$USERNAME\",
|
||||
\"email\":\"$EMAIL\",
|
||||
\"firstName\":\"$FIRSTNAME\",
|
||||
\"lastName\":\"$LASTNAME\",
|
||||
\"enabled\":true,
|
||||
\"emailVerified\":true,
|
||||
\"credentials\":[{\"type\":\"password\",\"value\":\"$TEST_PASSWORD\",\"temporary\":false}]
|
||||
}"
|
||||
|
||||
# Récupérer l'ID de l'utilisateur
|
||||
sleep 1
|
||||
USER_ID=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users?username=$USERNAME" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN" | grep -o '"id":"[^"]*' | cut -d'"' -f4 | head -1)
|
||||
|
||||
if [ ! -z "$USER_ID" ]; then
|
||||
# Assigner les rôles
|
||||
for ROLE in "${ROLES[@]}"; do
|
||||
# Récupérer les détails du rôle
|
||||
ROLE_DATA=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM_NAME/roles/$ROLE" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN")
|
||||
|
||||
curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users/$USER_ID/role-mappings/realm" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "[$ROLE_DATA]" > /dev/null 2>&1
|
||||
done
|
||||
|
||||
echo " ✅ Utilisateur créé: $USERNAME ($EMAIL) - Rôles: ${ROLES[*]}"
|
||||
else
|
||||
echo " ❌ Erreur création: $USERNAME"
|
||||
fi
|
||||
}
|
||||
|
||||
# 4. Créer les utilisateurs
|
||||
echo ""
|
||||
echo "👤 Création des comptes de test..."
|
||||
echo " Mot de passe pour tous: $TEST_PASSWORD"
|
||||
echo ""
|
||||
|
||||
# Super-Admin
|
||||
create_user "superadmin" "superadmin@unionflow.test" "Super" "Admin" "SUPER_ADMIN" "ADMIN" "USER"
|
||||
|
||||
# MUKEFI (Mutuelle)
|
||||
create_user "admin.mukefi" "admin.mukefi@unionflow.test" "Administrateur" "MUKEFI" "ADMIN_ORGANISATION" "ADMIN" "USER"
|
||||
create_user "tresorier.mukefi" "tresorier.mukefi@unionflow.test" "Trésorier" "MUKEFI" "TRESORIER" "MEMBRE" "USER"
|
||||
create_user "secretaire.mukefi" "secretaire.mukefi@unionflow.test" "Secrétaire" "MUKEFI" "SECRETAIRE" "MEMBRE" "USER"
|
||||
create_user "credit.mukefi" "credit.mukefi@unionflow.test" "Responsable Crédit" "MUKEFI" "RESPONSABLE_CREDIT" "MEMBRE" "USER"
|
||||
create_user "membre.mukefi" "membre.mukefi@unionflow.test" "Membre" "MUKEFI" "MEMBRE_ACTIF" "MEMBRE" "USER"
|
||||
|
||||
# MESKA (Association)
|
||||
create_user "admin.meska" "admin.meska@unionflow.test" "Administrateur" "MESKA" "ADMIN_ORGANISATION" "ADMIN" "USER"
|
||||
create_user "secretaire.meska" "secretaire.meska@unionflow.test" "Secrétaire" "MESKA" "SECRETAIRE" "MEMBRE" "USER"
|
||||
create_user "social.meska" "social.meska@unionflow.test" "Responsable Social" "MESKA" "RESPONSABLE_SOCIAL" "MEMBRE" "USER"
|
||||
create_user "evenements.meska" "evenements.meska@unionflow.test" "Responsable Événements" "MESKA" "RESPONSABLE_EVENEMENTS" "MEMBRE" "USER"
|
||||
create_user "membre.meska" "membre.meska@unionflow.test" "Membre" "MESKA" "MEMBRE_ACTIF" "MEMBRE" "USER"
|
||||
|
||||
echo ""
|
||||
echo "========================================================================"
|
||||
echo "✅ Configuration Keycloak terminée avec succès !"
|
||||
echo "========================================================================"
|
||||
echo ""
|
||||
echo "📊 Résumé:"
|
||||
echo " • 10 rôles créés"
|
||||
echo " • 11 utilisateurs créés"
|
||||
echo " • Mot de passe: $TEST_PASSWORD"
|
||||
echo ""
|
||||
echo "👥 Comptes créés:"
|
||||
echo ""
|
||||
echo " 🔧 Super-Admin:"
|
||||
echo " → superadmin@unionflow.test"
|
||||
echo ""
|
||||
echo " 🏦 MUKEFI (Mutuelle):"
|
||||
echo " → admin.mukefi@unionflow.test (Admin)"
|
||||
echo " → tresorier.mukefi@unionflow.test (Trésorier)"
|
||||
echo " → secretaire.mukefi@unionflow.test (Secrétaire)"
|
||||
echo " → credit.mukefi@unionflow.test (Responsable Crédit)"
|
||||
echo " → membre.mukefi@unionflow.test (Membre Actif)"
|
||||
echo ""
|
||||
echo " 🤝 MESKA (Association):"
|
||||
echo " → admin.meska@unionflow.test (Admin)"
|
||||
echo " → secretaire.meska@unionflow.test (Secrétaire)"
|
||||
echo " → social.meska@unionflow.test (Responsable Social)"
|
||||
echo " → evenements.meska@unionflow.test (Responsable Événements)"
|
||||
echo " → membre.meska@unionflow.test (Membre Actif)"
|
||||
echo ""
|
||||
echo "🌐 Accès Keycloak:"
|
||||
echo " • Console Admin: $KEYCLOAK_URL/admin"
|
||||
echo " • Realm: $REALM_NAME"
|
||||
echo ""
|
||||
24
unionflow/scripts/test-login.sh
Normal file
24
unionflow/scripts/test-login.sh
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
# Test différentes combinaisons de login
|
||||
|
||||
KEYCLOAK_URL="http://localhost:8180"
|
||||
CLIENT_ID="unionflow-server"
|
||||
CLIENT_SECRET="unionflow-secret-2025"
|
||||
|
||||
echo "Test 1: username=superadmin"
|
||||
curl -s -X POST "$KEYCLOAK_URL/realms/unionflow/protocol/openid-connect/token" \
|
||||
-d "client_id=$CLIENT_ID" \
|
||||
-d "client_secret=$CLIENT_SECRET" \
|
||||
-d "username=superadmin" \
|
||||
-d "password=Test@123" \
|
||||
-d "grant_type=password" | head -c 200
|
||||
echo ""
|
||||
|
||||
echo "Test 2: username=admin.mukefi"
|
||||
curl -s -X POST "$KEYCLOAK_URL/realms/unionflow/protocol/openid-connect/token" \
|
||||
-d "client_id=$CLIENT_ID" \
|
||||
-d "client_secret=$CLIENT_SECRET" \
|
||||
-d "username=admin.mukefi" \
|
||||
-d "password=Test@123" \
|
||||
-d "grant_type=password" | grep -o '"access_token"' | head -1
|
||||
echo ""
|
||||
196
unionflow/scripts/verify-keycloak-roles.sh
Normal file
196
unionflow/scripts/verify-keycloak-roles.sh
Normal file
@@ -0,0 +1,196 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script de vérification de la configuration Keycloak pour UnionFlow
|
||||
# Vérifie que les rôles sont bien configurés et mappés dans les tokens
|
||||
|
||||
set -e
|
||||
|
||||
KEYCLOAK_URL="${KEYCLOAK_URL:-http://localhost:8180}"
|
||||
REALM="unionflow"
|
||||
ADMIN_USER="${KEYCLOAK_ADMIN:-admin}"
|
||||
ADMIN_PASSWORD="${KEYCLOAK_ADMIN_PASSWORD:-admin}"
|
||||
CLIENT_ID_NAME="unionflow-client"
|
||||
|
||||
echo "======================================"
|
||||
echo " Vérification Keycloak - UnionFlow"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
echo "Keycloak URL: $KEYCLOAK_URL"
|
||||
echo "Realm: $REALM"
|
||||
echo ""
|
||||
|
||||
# 1. Obtenir token admin
|
||||
echo "[1/6] Obtention du token admin..."
|
||||
ADMIN_TOKEN=$(curl -s -X POST "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" \
|
||||
-d "client_id=admin-cli" \
|
||||
-d "username=$ADMIN_USER" \
|
||||
-d "password=$ADMIN_PASSWORD" \
|
||||
-d "grant_type=password" 2>/dev/null | jq -r '.access_token')
|
||||
|
||||
if [ "$ADMIN_TOKEN" == "null" ] || [ -z "$ADMIN_TOKEN" ]; then
|
||||
echo "❌ ERREUR: Impossible d'obtenir le token admin"
|
||||
echo " Vérifiez vos credentials et que Keycloak est accessible sur $KEYCLOAK_URL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Token admin obtenu"
|
||||
echo ""
|
||||
|
||||
# 2. Lister les realm roles
|
||||
echo "[2/6] Vérification des realm roles..."
|
||||
REALM_ROLES=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM/roles" \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" 2>/dev/null | jq -r '.[].name')
|
||||
|
||||
EXPECTED_ROLES=(
|
||||
"SUPER_ADMIN"
|
||||
"ADMIN_ORGANISATION"
|
||||
"TRESORIER"
|
||||
"SECRETAIRE"
|
||||
"RESPONSABLE_SOCIAL"
|
||||
"RESPONSABLE_EVENEMENTS"
|
||||
"RESPONSABLE_CREDIT"
|
||||
"MEMBRE_BUREAU"
|
||||
"MEMBRE_ACTIF"
|
||||
"MEMBRE_SIMPLE"
|
||||
)
|
||||
|
||||
echo "Rôles personnalisés trouvés:"
|
||||
MISSING_ROLES=()
|
||||
for role in "${EXPECTED_ROLES[@]}"; do
|
||||
if echo "$REALM_ROLES" | grep -q "^$role$"; then
|
||||
echo " ✅ $role"
|
||||
else
|
||||
echo " ❌ $role (MANQUANT)"
|
||||
MISSING_ROLES+=("$role")
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
if [ ${#MISSING_ROLES[@]} -gt 0 ]; then
|
||||
echo "⚠️ WARNING: ${#MISSING_ROLES[@]} rôle(s) manquant(s)"
|
||||
echo " Exécutez d'abord: unionflow/scripts/keycloak-setup.sh"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# 3. Vérifier le client unionflow-client
|
||||
echo "[3/6] Vérification du client '$CLIENT_ID_NAME'..."
|
||||
CLIENT_UUID=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM/clients?clientId=$CLIENT_ID_NAME" \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" 2>/dev/null | jq -r '.[0].id')
|
||||
|
||||
if [ "$CLIENT_UUID" == "null" ] || [ -z "$CLIENT_UUID" ]; then
|
||||
echo "❌ ERREUR: Client '$CLIENT_ID_NAME' non trouvé dans le realm $REALM"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Client trouvé: $CLIENT_ID_NAME (UUID: $CLIENT_UUID)"
|
||||
echo ""
|
||||
|
||||
# 4. Vérifier les rôles de admin.mukefi@unionflow.test
|
||||
echo "[4/6] Vérification des rôles de 'admin.mukefi@unionflow.test'..."
|
||||
USER_ID=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM/users?username=admin.mukefi@unionflow.test" \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" 2>/dev/null | jq -r '.[0].id')
|
||||
|
||||
if [ "$USER_ID" == "null" ] || [ -z "$USER_ID" ]; then
|
||||
echo "❌ ERREUR: Utilisateur 'admin.mukefi@unionflow.test' non trouvé"
|
||||
echo " Exécutez d'abord: unionflow/scripts/keycloak-setup.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
USER_ROLES=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM/users/$USER_ID/role-mappings/realm" \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" 2>/dev/null | jq -r '.[].name')
|
||||
|
||||
echo "Rôles assignés à admin.mukefi@unionflow.test:"
|
||||
if echo "$USER_ROLES" | grep -q "ADMIN_ORGANISATION"; then
|
||||
echo "$USER_ROLES" | while read role; do
|
||||
echo " ✅ $role"
|
||||
done
|
||||
else
|
||||
echo " ❌ ADMIN_ORGANISATION (MANQUANT)"
|
||||
echo " $USER_ROLES"
|
||||
echo ""
|
||||
echo "⚠️ WARNING: Le rôle ADMIN_ORGANISATION n'est pas assigné à cet utilisateur"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 5. Vérifier le client scope 'roles'
|
||||
echo "[5/6] Vérification du client scope 'roles'..."
|
||||
ROLES_SCOPE=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM/client-scopes" \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" 2>/dev/null | jq -r '.[] | select(.name=="roles") | .name')
|
||||
|
||||
if [ "$ROLES_SCOPE" == "roles" ]; then
|
||||
echo "✅ Client scope 'roles' trouvé"
|
||||
else
|
||||
echo "❌ ERREUR: Client scope 'roles' non trouvé"
|
||||
echo " Ce scope est nécessaire pour inclure les rôles dans le token"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 6. Vérifier les mappers du client
|
||||
echo "[6/6] Vérification des protocol mappers du client..."
|
||||
MAPPERS=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM/clients/$CLIENT_UUID/protocol-mappers/models" \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" 2>/dev/null)
|
||||
|
||||
REALM_ROLE_MAPPER=$(echo "$MAPPERS" | jq -r '.[] | select(.protocolMapper=="oidc-usermodel-realm-role-mapper") | .name')
|
||||
|
||||
if [ -n "$REALM_ROLE_MAPPER" ]; then
|
||||
echo "✅ Realm role mapper trouvé: '$REALM_ROLE_MAPPER'"
|
||||
|
||||
# Vérifier la configuration du mapper
|
||||
MAPPER_CONFIG=$(echo "$MAPPERS" | jq -r '.[] | select(.protocolMapper=="oidc-usermodel-realm-role-mapper")')
|
||||
TOKEN_CLAIM=$(echo "$MAPPER_CONFIG" | jq -r '.config["claim.name"]')
|
||||
ADD_TO_ID_TOKEN=$(echo "$MAPPER_CONFIG" | jq -r '.config["id.token.claim"]')
|
||||
ADD_TO_ACCESS_TOKEN=$(echo "$MAPPER_CONFIG" | jq -r '.config["access.token.claim"]')
|
||||
|
||||
echo " - Token claim name: $TOKEN_CLAIM"
|
||||
echo " - Add to ID token: $ADD_TO_ID_TOKEN"
|
||||
echo " - Add to access token: $ADD_TO_ACCESS_TOKEN"
|
||||
|
||||
if [ "$ADD_TO_ID_TOKEN" != "true" ]; then
|
||||
echo " ⚠️ WARNING: 'Add to ID token' devrait être 'true'"
|
||||
fi
|
||||
if [ "$ADD_TO_ACCESS_TOKEN" != "true" ]; then
|
||||
echo " ⚠️ WARNING: 'Add to access token' devrait être 'true'"
|
||||
fi
|
||||
else
|
||||
echo "❌ ERREUR: Aucun mapper de type 'oidc-usermodel-realm-role-mapper' trouvé"
|
||||
echo " Les rôles ne seront PAS inclus dans les tokens !"
|
||||
echo ""
|
||||
echo " Pour corriger, dans Keycloak Admin:"
|
||||
echo " 1. Allez dans Clients > $CLIENT_ID_NAME > Client scopes"
|
||||
echo " 2. Cliquez sur le scope dédié (unionflow-client-dedicated)"
|
||||
echo " 3. Add mapper > By configuration > User Realm Role"
|
||||
echo " 4. Name: realm roles"
|
||||
echo " 5. Token Claim Name: roles"
|
||||
echo " 6. Add to ID token: ON"
|
||||
echo " 7. Add to access token: ON"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Résumé
|
||||
echo "======================================"
|
||||
echo " RÉSUMÉ"
|
||||
echo "======================================"
|
||||
|
||||
if [ ${#MISSING_ROLES[@]} -eq 0 ] && [ -n "$REALM_ROLE_MAPPER" ] && echo "$USER_ROLES" | grep -q "ADMIN_ORGANISATION"; then
|
||||
echo "✅ Configuration Keycloak OK"
|
||||
echo ""
|
||||
echo "Si les rôles n'apparaissent toujours pas dans le token:"
|
||||
echo "1. Vérifiez que 'roles' est dans quarkus.oidc.authentication.scopes"
|
||||
echo "2. Redémarrez le frontend Quarkus"
|
||||
echo "3. Déconnectez-vous et reconnectez-vous dans l'application"
|
||||
else
|
||||
echo "❌ Configuration Keycloak INCOMPLÈTE"
|
||||
echo ""
|
||||
if [ ${#MISSING_ROLES[@]} -gt 0 ]; then
|
||||
echo "- Rôles manquants: ${MISSING_ROLES[@]}"
|
||||
fi
|
||||
if [ -z "$REALM_ROLE_MAPPER" ]; then
|
||||
echo "- Mapper de rôles manquant"
|
||||
fi
|
||||
if ! echo "$USER_ROLES" | grep -q "ADMIN_ORGANISATION"; then
|
||||
echo "- Rôle ADMIN_ORGANISATION non assigné à admin.mukefi@unionflow.test"
|
||||
fi
|
||||
echo ""
|
||||
echo "Exécutez: unionflow/scripts/keycloak-setup.sh"
|
||||
fi
|
||||
echo ""
|
||||
Reference in New Issue
Block a user