- 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
366 lines
14 KiB
Python
366 lines
14 KiB
Python
#!/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()
|