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:
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()
|
||||
Reference in New Issue
Block a user