Refactoring
This commit is contained in:
515
setup_keycloak.py
Normal file
515
setup_keycloak.py
Normal file
@@ -0,0 +1,515 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script de configuration automatique Keycloak pour UnionFlow
|
||||
Crée le realm, les rôles, le client et tous les utilisateurs nécessaires
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
class KeycloakSetup:
|
||||
def __init__(self, base_url: str = "http://localhost:8180", admin_user: str = "admin", admin_password: str = "admin123"):
|
||||
self.base_url = base_url
|
||||
self.admin_user = admin_user
|
||||
self.admin_password = admin_password
|
||||
self.admin_token = None
|
||||
self.session = requests.Session()
|
||||
|
||||
def print_status(self, message: str, status: str = "INFO"):
|
||||
"""Affiche un message avec un statut coloré"""
|
||||
icons = {"INFO": "🔍", "SUCCESS": "✅", "ERROR": "❌", "WARNING": "⚠️"}
|
||||
print(f"{icons.get(status, '📋')} {message}")
|
||||
|
||||
def wait_for_keycloak(self, max_attempts: int = 30) -> bool:
|
||||
"""Attend que Keycloak soit disponible"""
|
||||
self.print_status("Attente de la disponibilité de Keycloak...")
|
||||
|
||||
for attempt in range(max_attempts):
|
||||
try:
|
||||
response = self.session.get(f"{self.base_url}", timeout=5)
|
||||
if response.status_code == 200:
|
||||
self.print_status("Keycloak est disponible", "SUCCESS")
|
||||
return True
|
||||
except requests.exceptions.RequestException:
|
||||
pass
|
||||
|
||||
if attempt < max_attempts - 1:
|
||||
time.sleep(2)
|
||||
|
||||
self.print_status("Keycloak n'est pas disponible", "ERROR")
|
||||
return False
|
||||
|
||||
def get_admin_token(self) -> bool:
|
||||
"""Obtient le token d'administration"""
|
||||
self.print_status("Obtention du token administrateur...")
|
||||
|
||||
# Essayons d'abord avec les credentials par défaut
|
||||
credentials_to_try = [
|
||||
(self.admin_user, self.admin_password),
|
||||
("admin", "admin"),
|
||||
("admin", "password"),
|
||||
]
|
||||
|
||||
for username, password in credentials_to_try:
|
||||
try:
|
||||
data = {
|
||||
"username": username,
|
||||
"password": password,
|
||||
"grant_type": "password",
|
||||
"client_id": "admin-cli"
|
||||
}
|
||||
|
||||
response = self.session.post(
|
||||
f"{self.base_url}/realms/master/protocol/openid-connect/token",
|
||||
data=data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
token_data = response.json()
|
||||
self.admin_token = token_data.get("access_token")
|
||||
if self.admin_token:
|
||||
self.print_status(f"Token obtenu avec {username}/{password}", "SUCCESS")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
# Si aucun credential ne fonctionne, essayons de créer un admin
|
||||
self.print_status("Tentative de création d'un compte admin...", "WARNING")
|
||||
return self._create_initial_admin()
|
||||
|
||||
def _create_initial_admin(self) -> bool:
|
||||
"""Tente de créer un compte admin initial"""
|
||||
try:
|
||||
# En mode dev, Keycloak peut permettre la création d'admin via l'API
|
||||
admin_data = {
|
||||
"username": self.admin_user,
|
||||
"password": self.admin_password,
|
||||
"enabled": True
|
||||
}
|
||||
|
||||
# Essayons plusieurs endpoints possibles
|
||||
endpoints = [
|
||||
f"{self.base_url}/admin/realms/master/users",
|
||||
f"{self.base_url}/auth/admin/realms/master/users"
|
||||
]
|
||||
|
||||
for endpoint in endpoints:
|
||||
try:
|
||||
response = self.session.post(
|
||||
endpoint,
|
||||
json=admin_data,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
if response.status_code in [201, 409]: # 409 = already exists
|
||||
return self.get_admin_token()
|
||||
except:
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
self.print_status("Impossible d'obtenir un token admin. Configuration manuelle requise.", "ERROR")
|
||||
return False
|
||||
|
||||
def create_realm(self, realm_name: str = "unionflow") -> bool:
|
||||
"""Crée le realm UnionFlow"""
|
||||
self.print_status(f"Création du realm {realm_name}...")
|
||||
|
||||
realm_data = {
|
||||
"realm": realm_name,
|
||||
"enabled": True,
|
||||
"displayName": "UnionFlow",
|
||||
"loginWithEmailAllowed": True,
|
||||
"duplicateEmailsAllowed": False,
|
||||
"resetPasswordAllowed": True,
|
||||
"editUsernameAllowed": False,
|
||||
"bruteForceProtected": False,
|
||||
"registrationAllowed": False,
|
||||
"rememberMe": True,
|
||||
"verifyEmail": False,
|
||||
"loginTheme": "keycloak",
|
||||
"accountTheme": "keycloak",
|
||||
"adminTheme": "keycloak",
|
||||
"emailTheme": "keycloak"
|
||||
}
|
||||
|
||||
try:
|
||||
response = self.session.post(
|
||||
f"{self.base_url}/admin/realms",
|
||||
json=realm_data,
|
||||
headers={
|
||||
"Authorization": f"Bearer {self.admin_token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
self.print_status(f"Realm {realm_name} créé avec succès", "SUCCESS")
|
||||
return True
|
||||
elif response.status_code == 409:
|
||||
self.print_status(f"Realm {realm_name} existe déjà", "WARNING")
|
||||
return True
|
||||
else:
|
||||
self.print_status(f"Erreur lors de la création du realm: {response.status_code}", "ERROR")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.print_status(f"Exception lors de la création du realm: {e}", "ERROR")
|
||||
return False
|
||||
|
||||
def create_client(self, realm_name: str = "unionflow", client_id: str = "unionflow-mobile") -> bool:
|
||||
"""Crée le client pour l'application mobile"""
|
||||
self.print_status(f"Création du client {client_id}...")
|
||||
|
||||
client_data = {
|
||||
"clientId": client_id,
|
||||
"enabled": True,
|
||||
"publicClient": True,
|
||||
"directAccessGrantsEnabled": True,
|
||||
"standardFlowEnabled": True,
|
||||
"implicitFlowEnabled": False,
|
||||
"serviceAccountsEnabled": False,
|
||||
"authorizationServicesEnabled": False,
|
||||
"redirectUris": ["*"],
|
||||
"webOrigins": ["*"],
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"access.token.lifespan": "300",
|
||||
"client.secret.creation.time": "0"
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
response = self.session.post(
|
||||
f"{self.base_url}/admin/realms/{realm_name}/clients",
|
||||
json=client_data,
|
||||
headers={
|
||||
"Authorization": f"Bearer {self.admin_token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
self.print_status(f"Client {client_id} créé avec succès", "SUCCESS")
|
||||
return True
|
||||
elif response.status_code == 409:
|
||||
self.print_status(f"Client {client_id} existe déjà", "WARNING")
|
||||
return True
|
||||
else:
|
||||
self.print_status(f"Erreur lors de la création du client: {response.status_code}", "ERROR")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.print_status(f"Exception lors de la création du client: {e}", "ERROR")
|
||||
return False
|
||||
|
||||
def create_roles(self, realm_name: str = "unionflow") -> bool:
|
||||
"""Crée tous les rôles nécessaires"""
|
||||
roles = [
|
||||
"SUPER_ADMINISTRATEUR",
|
||||
"RESPONSABLE_TECHNIQUE",
|
||||
"RESPONSABLE_MEMBRES",
|
||||
"MEMBRE_ACTIF",
|
||||
"MEMBRE_SIMPLE"
|
||||
]
|
||||
|
||||
self.print_status("Création des rôles...")
|
||||
|
||||
success_count = 0
|
||||
for role in roles:
|
||||
role_data = {
|
||||
"name": role,
|
||||
"description": f"Rôle {role} pour UnionFlow"
|
||||
}
|
||||
|
||||
try:
|
||||
response = self.session.post(
|
||||
f"{self.base_url}/admin/realms/{realm_name}/roles",
|
||||
json=role_data,
|
||||
headers={
|
||||
"Authorization": f"Bearer {self.admin_token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
)
|
||||
|
||||
if response.status_code in [201, 409]: # 201 = created, 409 = already exists
|
||||
self.print_status(f" ✓ Rôle {role} configuré", "SUCCESS")
|
||||
success_count += 1
|
||||
else:
|
||||
self.print_status(f" ✗ Erreur pour le rôle {role}: {response.status_code}", "ERROR")
|
||||
|
||||
except Exception as e:
|
||||
self.print_status(f" ✗ Exception pour le rôle {role}: {e}", "ERROR")
|
||||
|
||||
return success_count == len(roles)
|
||||
|
||||
def create_user(self, realm_name: str, username: str, email: str, first_name: str,
|
||||
last_name: str, password: str, role: str) -> bool:
|
||||
"""Crée un utilisateur avec son mot de passe et son rôle"""
|
||||
self.print_status(f"Création de l'utilisateur {username}...")
|
||||
|
||||
# 1. Créer l'utilisateur
|
||||
user_data = {
|
||||
"username": username,
|
||||
"email": email,
|
||||
"firstName": first_name,
|
||||
"lastName": last_name,
|
||||
"enabled": True,
|
||||
"emailVerified": True,
|
||||
"credentials": [{
|
||||
"type": "password",
|
||||
"value": password,
|
||||
"temporary": False
|
||||
}]
|
||||
}
|
||||
|
||||
try:
|
||||
# Créer l'utilisateur
|
||||
response = self.session.post(
|
||||
f"{self.base_url}/admin/realms/{realm_name}/users",
|
||||
json=user_data,
|
||||
headers={
|
||||
"Authorization": f"Bearer {self.admin_token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
self.print_status(f" ✓ Utilisateur {username} créé", "SUCCESS")
|
||||
elif response.status_code == 409:
|
||||
self.print_status(f" ⚠ Utilisateur {username} existe déjà", "WARNING")
|
||||
else:
|
||||
self.print_status(f" ✗ Erreur création utilisateur {username}: {response.status_code}", "ERROR")
|
||||
return False
|
||||
|
||||
# 2. Obtenir l'ID de l'utilisateur
|
||||
response = self.session.get(
|
||||
f"{self.base_url}/admin/realms/{realm_name}/users?username={username}",
|
||||
headers={"Authorization": f"Bearer {self.admin_token}"}
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
self.print_status(f" ✗ Impossible de récupérer l'ID de {username}", "ERROR")
|
||||
return False
|
||||
|
||||
users = response.json()
|
||||
if not users:
|
||||
self.print_status(f" ✗ Utilisateur {username} non trouvé", "ERROR")
|
||||
return False
|
||||
|
||||
user_id = users[0]["id"]
|
||||
|
||||
# 3. Définir le mot de passe (au cas où)
|
||||
password_data = {
|
||||
"type": "password",
|
||||
"value": password,
|
||||
"temporary": False
|
||||
}
|
||||
|
||||
self.session.put(
|
||||
f"{self.base_url}/admin/realms/{realm_name}/users/{user_id}/reset-password",
|
||||
json=password_data,
|
||||
headers={
|
||||
"Authorization": f"Bearer {self.admin_token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
)
|
||||
|
||||
# 4. Assigner le rôle
|
||||
role_response = self.session.get(
|
||||
f"{self.base_url}/admin/realms/{realm_name}/roles/{role}",
|
||||
headers={"Authorization": f"Bearer {self.admin_token}"}
|
||||
)
|
||||
|
||||
if role_response.status_code == 200:
|
||||
role_data = role_response.json()
|
||||
|
||||
assign_response = self.session.post(
|
||||
f"{self.base_url}/admin/realms/{realm_name}/users/{user_id}/role-mappings/realm",
|
||||
json=[role_data],
|
||||
headers={
|
||||
"Authorization": f"Bearer {self.admin_token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
)
|
||||
|
||||
if assign_response.status_code in [204, 200]:
|
||||
self.print_status(f" ✓ Rôle {role} assigné à {username}", "SUCCESS")
|
||||
else:
|
||||
self.print_status(f" ⚠ Erreur assignation rôle à {username}", "WARNING")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.print_status(f" ✗ Exception pour {username}: {e}", "ERROR")
|
||||
return False
|
||||
|
||||
def create_all_users(self, realm_name: str = "unionflow") -> bool:
|
||||
"""Crée tous les utilisateurs nécessaires"""
|
||||
users = [
|
||||
{
|
||||
"username": "superadmin",
|
||||
"email": "superadmin@unionflow.com",
|
||||
"first_name": "Super",
|
||||
"last_name": "Admin",
|
||||
"password": "SuperAdmin123!",
|
||||
"role": "SUPER_ADMINISTRATEUR"
|
||||
},
|
||||
{
|
||||
"username": "marie.active",
|
||||
"email": "marie.active@unionflow.com",
|
||||
"first_name": "Marie",
|
||||
"last_name": "Active",
|
||||
"password": "Marie123!",
|
||||
"role": "MEMBRE_ACTIF"
|
||||
},
|
||||
{
|
||||
"username": "jean.simple",
|
||||
"email": "jean.simple@unionflow.com",
|
||||
"first_name": "Jean",
|
||||
"last_name": "Simple",
|
||||
"password": "Jean123!",
|
||||
"role": "MEMBRE_SIMPLE"
|
||||
},
|
||||
{
|
||||
"username": "tech.lead",
|
||||
"email": "tech.lead@unionflow.com",
|
||||
"first_name": "Tech",
|
||||
"last_name": "Lead",
|
||||
"password": "TechLead123!",
|
||||
"role": "RESPONSABLE_TECHNIQUE"
|
||||
},
|
||||
{
|
||||
"username": "rh.manager",
|
||||
"email": "rh.manager@unionflow.com",
|
||||
"first_name": "RH",
|
||||
"last_name": "Manager",
|
||||
"password": "RhManager123!",
|
||||
"role": "RESPONSABLE_MEMBRES"
|
||||
}
|
||||
]
|
||||
|
||||
self.print_status("Création de tous les utilisateurs...")
|
||||
success_count = 0
|
||||
|
||||
for user in users:
|
||||
if self.create_user(realm_name, **user):
|
||||
success_count += 1
|
||||
|
||||
return success_count == len(users)
|
||||
|
||||
def test_authentication(self, realm_name: str = "unionflow", client_id: str = "unionflow-mobile") -> bool:
|
||||
"""Teste l'authentification de tous les comptes"""
|
||||
test_accounts = [
|
||||
("marie.active", "Marie123!"),
|
||||
("superadmin", "SuperAdmin123!"),
|
||||
("jean.simple", "Jean123!")
|
||||
]
|
||||
|
||||
self.print_status("Test d'authentification des comptes...")
|
||||
success_count = 0
|
||||
|
||||
for username, password in test_accounts:
|
||||
try:
|
||||
data = {
|
||||
"username": username,
|
||||
"password": password,
|
||||
"grant_type": "password",
|
||||
"client_id": client_id
|
||||
}
|
||||
|
||||
response = self.session.post(
|
||||
f"{self.base_url}/realms/{realm_name}/protocol/openid-connect/token",
|
||||
data=data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"}
|
||||
)
|
||||
|
||||
if response.status_code == 200 and "access_token" in response.json():
|
||||
self.print_status(f" ✓ {username} : AUTHENTIFICATION RÉUSSIE", "SUCCESS")
|
||||
success_count += 1
|
||||
else:
|
||||
self.print_status(f" ✗ {username} : Échec d'authentification", "ERROR")
|
||||
|
||||
except Exception as e:
|
||||
self.print_status(f" ✗ {username} : Exception {e}", "ERROR")
|
||||
|
||||
return success_count == len(test_accounts)
|
||||
|
||||
def setup_complete(self) -> bool:
|
||||
"""Exécute la configuration complète"""
|
||||
self.print_status("=" * 80)
|
||||
self.print_status("🚀 CONFIGURATION AUTOMATIQUE KEYCLOAK UNIONFLOW")
|
||||
self.print_status("=" * 80)
|
||||
|
||||
# 1. Attendre Keycloak
|
||||
if not self.wait_for_keycloak():
|
||||
return False
|
||||
|
||||
# 2. Obtenir le token admin
|
||||
if not self.get_admin_token():
|
||||
self.print_status("Configuration manuelle requise:", "ERROR")
|
||||
self.print_status("1. Ouvrez http://localhost:8180", "INFO")
|
||||
self.print_status("2. Créez un compte admin", "INFO")
|
||||
self.print_status("3. Relancez ce script", "INFO")
|
||||
return False
|
||||
|
||||
# 3. Créer le realm
|
||||
if not self.create_realm():
|
||||
return False
|
||||
|
||||
# 4. Créer le client
|
||||
if not self.create_client():
|
||||
return False
|
||||
|
||||
# 5. Créer les rôles
|
||||
if not self.create_roles():
|
||||
return False
|
||||
|
||||
# 6. Créer les utilisateurs
|
||||
if not self.create_all_users():
|
||||
return False
|
||||
|
||||
# 7. Tester l'authentification
|
||||
time.sleep(2) # Attendre un peu pour que tout soit prêt
|
||||
if not self.test_authentication():
|
||||
self.print_status("Certains comptes ne fonctionnent pas encore", "WARNING")
|
||||
|
||||
self.print_status("=" * 80)
|
||||
self.print_status("✅ CONFIGURATION TERMINÉE AVEC SUCCÈS !")
|
||||
self.print_status("=" * 80)
|
||||
self.print_status("")
|
||||
self.print_status("🎯 COMPTES CRÉÉS :")
|
||||
self.print_status(" • superadmin / SuperAdmin123! (SUPER_ADMINISTRATEUR)")
|
||||
self.print_status(" • marie.active / Marie123! (MEMBRE_ACTIF)")
|
||||
self.print_status(" • jean.simple / Jean123! (MEMBRE_SIMPLE)")
|
||||
self.print_status(" • tech.lead / TechLead123! (RESPONSABLE_TECHNIQUE)")
|
||||
self.print_status(" • rh.manager / RhManager123! (RESPONSABLE_MEMBRES)")
|
||||
self.print_status("")
|
||||
self.print_status("🚀 PRÊT POUR L'APPLICATION MOBILE UNIONFLOW !")
|
||||
self.print_status(" Testez avec: python test_auth.py")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
"""Fonction principale"""
|
||||
setup = KeycloakSetup()
|
||||
|
||||
try:
|
||||
success = setup.setup_complete()
|
||||
sys.exit(0 if success else 1)
|
||||
except KeyboardInterrupt:
|
||||
print("\n❌ Configuration interrompue par l'utilisateur")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"❌ Erreur inattendue: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user