feat(mobile): Implement Keycloak WebView authentication with HTTP callback

- Replace flutter_appauth with custom WebView implementation to resolve deep link issues
- Add KeycloakWebViewAuthService with integrated WebView for seamless authentication
- Configure Android manifest for HTTP cleartext traffic support
- Add network security config for development environment (192.168.1.11)
- Update Keycloak client to use HTTP callback endpoint (http://192.168.1.11:8080/auth/callback)
- Remove obsolete keycloak_auth_service.dart and temporary scripts
- Clean up dependencies and regenerate injection configuration
- Tested successfully on multiple Android devices (Xiaomi 2201116TG, SM A725F)

BREAKING CHANGE: Authentication flow now uses WebView instead of external browser
- Users will see Keycloak login page within the app instead of browser redirect
- Resolves ERR_CLEARTEXT_NOT_PERMITTED and deep link state management issues
- Maintains full OIDC compliance with PKCE flow and secure token storage

Technical improvements:
- WebView with custom navigation delegate for callback handling
- Automatic token extraction and user info parsing from JWT
- Proper error handling and user feedback
- Consistent authentication state management across app lifecycle
This commit is contained in:
DahoudG
2025-09-15 01:44:16 +00:00
parent 73459b3092
commit f89f6167cc
290 changed files with 34563 additions and 3528 deletions

View File

@@ -0,0 +1,240 @@
#!/bin/bash
# Test d'intégration Keycloak avec UnionFlow
# Auteur: UnionFlow Team
echo "🧪 Test d'intégration Keycloak avec UnionFlow"
echo "============================================="
# Variables de configuration
KEYCLOAK_URL="http://localhost:8180"
UNIONFLOW_URL="http://localhost:8080"
REALM_NAME="unionflow"
CLIENT_ID="unionflow-server"
CLIENT_SECRET="unionflow-secret-2025"
# Couleurs pour l'affichage
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Test 1: Vérifier que Keycloak est accessible
test_keycloak_accessible() {
echo -e "${YELLOW}🔍 Test 1: Vérification de l'accessibilité de Keycloak...${NC}"
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$KEYCLOAK_URL/realms/$REALM_NAME/.well-known/openid-configuration")
if [ "$RESPONSE" = "200" ]; then
echo -e "${GREEN}✅ Keycloak est accessible et le realm '$REALM_NAME' existe${NC}"
return 0
else
echo -e "${RED}❌ Keycloak n'est pas accessible (Code: $RESPONSE)${NC}"
return 1
fi
}
# Test 2: Obtenir un token d'accès avec les identifiants utilisateur
test_user_authentication() {
echo -e "${YELLOW}🔍 Test 2: Authentification utilisateur avec Keycloak...${NC}"
# Test avec l'utilisateur admin
TOKEN_RESPONSE=$(curl -s -X POST "$KEYCLOAK_URL/realms/$REALM_NAME/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin&password=admin123&grant_type=password&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET")
ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | grep -o '"access_token":"[^"]*' | cut -d'"' -f4)
if [ -n "$ACCESS_TOKEN" ]; then
echo -e "${GREEN}✅ Authentification réussie pour l'utilisateur 'admin'${NC}"
echo -e "${CYAN}🔑 Token obtenu (tronqué): ${ACCESS_TOKEN:0:50}...${NC}"
return 0
else
echo -e "${RED}❌ Échec de l'authentification${NC}"
echo -e "${RED}Réponse: $TOKEN_RESPONSE${NC}"
return 1
fi
}
# Test 3: Vérifier que UnionFlow est accessible
test_unionflow_accessible() {
echo -e "${YELLOW}🔍 Test 3: Vérification de l'accessibilité d'UnionFlow...${NC}"
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$UNIONFLOW_URL/health")
if [ "$RESPONSE" = "200" ]; then
echo -e "${GREEN}✅ UnionFlow est accessible${NC}"
return 0
else
echo -e "${RED}❌ UnionFlow n'est pas accessible (Code: $RESPONSE)${NC}"
return 1
fi
}
# Test 4: Tester l'accès à un endpoint protégé sans token
test_protected_endpoint_without_token() {
echo -e "${YELLOW}🔍 Test 4: Accès à un endpoint protégé sans token...${NC}"
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$UNIONFLOW_URL/api/organisations")
if [ "$RESPONSE" = "401" ] || [ "$RESPONSE" = "403" ]; then
echo -e "${GREEN}✅ Endpoint protégé correctement (Code: $RESPONSE)${NC}"
return 0
else
echo -e "${RED}❌ Endpoint non protégé ou erreur inattendue (Code: $RESPONSE)${NC}"
return 1
fi
}
# Test 5: Tester l'accès à un endpoint protégé avec token
test_protected_endpoint_with_token() {
echo -e "${YELLOW}🔍 Test 5: Accès à un endpoint protégé avec token...${NC}"
# Obtenir un token d'accès
TOKEN_RESPONSE=$(curl -s -X POST "$KEYCLOAK_URL/realms/$REALM_NAME/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin&password=admin123&grant_type=password&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET")
ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | grep -o '"access_token":"[^"]*' | cut -d'"' -f4)
if [ -n "$ACCESS_TOKEN" ]; then
# Tester l'accès avec le token
RESPONSE=$(curl -s -w "%{http_code}" -H "Authorization: Bearer $ACCESS_TOKEN" "$UNIONFLOW_URL/api/organisations")
HTTP_CODE=$(echo "$RESPONSE" | tail -c 4)
BODY=$(echo "$RESPONSE" | head -c -4)
if [ "$HTTP_CODE" = "200" ]; then
echo -e "${GREEN}✅ Accès autorisé avec token valide${NC}"
echo -e "${CYAN}📋 Réponse: ${BODY:0:100}...${NC}"
return 0
else
echo -e "${RED}❌ Accès refusé malgré le token valide (Code: $HTTP_CODE)${NC}"
echo -e "${RED}Réponse: $BODY${NC}"
return 1
fi
else
echo -e "${RED}❌ Impossible d'obtenir le token d'accès${NC}"
return 1
fi
}
# Test 6: Tester les différents utilisateurs et leurs rôles
test_user_roles() {
echo -e "${YELLOW}🔍 Test 6: Vérification des rôles utilisateurs...${NC}"
USERS=("admin:admin123:ADMIN" "president:president123:PRESIDENT" "secretaire:secretaire123:SECRETAIRE" "membre1:membre123:MEMBRE")
for user_info in "${USERS[@]}"; do
IFS=':' read -r username password expected_role <<< "$user_info"
echo -e "${CYAN} 🔍 Test utilisateur: $username${NC}"
TOKEN_RESPONSE=$(curl -s -X POST "$KEYCLOAK_URL/realms/$REALM_NAME/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=$username&password=$password&grant_type=password&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET")
ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | grep -o '"access_token":"[^"]*' | cut -d'"' -f4)
if [ -n "$ACCESS_TOKEN" ]; then
echo -e "${GREEN} ✅ Authentification réussie pour '$username'${NC}"
# Décoder le token JWT pour vérifier les rôles (base64 decode de la partie payload)
PAYLOAD=$(echo $ACCESS_TOKEN | cut -d'.' -f2)
# Ajouter du padding si nécessaire
case $((${#PAYLOAD} % 4)) in
2) PAYLOAD="${PAYLOAD}==" ;;
3) PAYLOAD="${PAYLOAD}=" ;;
esac
DECODED=$(echo $PAYLOAD | base64 -d 2>/dev/null)
if [[ "$DECODED" == *"$expected_role"* ]]; then
echo -e "${GREEN} ✅ Rôle '$expected_role' trouvé dans le token${NC}"
else
echo -e "${YELLOW} ⚠️ Rôle '$expected_role' non trouvé dans le token${NC}"
fi
else
echo -e "${RED} ❌ Échec de l'authentification pour '$username'${NC}"
fi
done
}
# Test 7: Vérifier la configuration OpenAPI/Swagger
test_openapi_integration() {
echo -e "${YELLOW}🔍 Test 7: Vérification de l'intégration OpenAPI...${NC}"
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$UNIONFLOW_URL/q/swagger-ui")
if [ "$RESPONSE" = "200" ]; then
echo -e "${GREEN}✅ Interface Swagger UI accessible${NC}"
# Vérifier que la spécification OpenAPI contient les informations de sécurité
OPENAPI_SPEC=$(curl -s "$UNIONFLOW_URL/q/openapi")
if [[ "$OPENAPI_SPEC" == *"securitySchemes"* ]]; then
echo -e "${GREEN}✅ Configuration de sécurité présente dans OpenAPI${NC}"
else
echo -e "${YELLOW}⚠️ Configuration de sécurité manquante dans OpenAPI${NC}"
fi
return 0
else
echo -e "${RED}❌ Interface Swagger UI non accessible (Code: $RESPONSE)${NC}"
return 1
fi
}
# Fonction principale pour exécuter tous les tests
run_all_tests() {
echo -e "${CYAN}🚀 Démarrage des tests d'intégration...${NC}"
echo ""
TOTAL_TESTS=7
PASSED_TESTS=0
# Exécuter tous les tests
test_keycloak_accessible && ((PASSED_TESTS++))
echo ""
test_user_authentication && ((PASSED_TESTS++))
echo ""
test_unionflow_accessible && ((PASSED_TESTS++))
echo ""
test_protected_endpoint_without_token && ((PASSED_TESTS++))
echo ""
test_protected_endpoint_with_token && ((PASSED_TESTS++))
echo ""
test_user_roles && ((PASSED_TESTS++))
echo ""
test_openapi_integration && ((PASSED_TESTS++))
echo ""
# Résumé des résultats
echo -e "${CYAN}📊 RÉSUMÉ DES TESTS${NC}"
echo -e "${CYAN}==================${NC}"
echo -e "Tests réussis: ${GREEN}$PASSED_TESTS${NC}/$TOTAL_TESTS"
if [ $PASSED_TESTS -eq $TOTAL_TESTS ]; then
echo -e "${GREEN}🎉 TOUS LES TESTS SONT PASSÉS ! L'intégration Keycloak fonctionne parfaitement.${NC}"
echo ""
echo -e "${CYAN}✨ Configuration finale:${NC}"
echo -e " • Keycloak: $KEYCLOAK_URL/realms/$REALM_NAME"
echo -e " • UnionFlow: $UNIONFLOW_URL"
echo -e " • Client ID: $CLIENT_ID"
echo -e " • Authentification: ✅ Fonctionnelle"
echo -e " • Autorisation: ✅ Fonctionnelle"
echo -e " • API Protection: ✅ Fonctionnelle"
echo ""
echo -e "${GREEN}🚀 L'application UnionFlow est prête pour la production !${NC}"
return 0
else
echo -e "${RED}❌ Certains tests ont échoué. Vérifiez la configuration.${NC}"
return 1
fi
}
# Exécuter tous les tests
run_all_tests