ADH6 est le système de gestion d'adhérents de l'association MiNET. Il s'agit d'une application web complète avec une architecture moderne séparant le frontend du backend.
L'application permet de :
- Gérer les adhésions et les cotisations
- Gérer les appareils des membres (attribution d'adresses IP)
- Gérer les chambres et leurs attributions
- Gérer le réseau (switches, ports, VLANs)
- Assurer le suivi de la trésorerie
Le projet est structuré selon une architecture client-serveur moderne :
- Backend : API REST développée en Python avec Flask/Connexion, suivant les principes de la Clean Architecture
- Frontend : Application Angular (version 15)
- API : Spécification OpenAPI (anciennement Swagger) pour la communication entre frontend et backend
- Base de données : MySQL/MariaDB
- Infrastructure : Conteneurs Docker pour le développement et le déploiement
Les fichiers principaux à la racine sont :
docker-compose.yml: Configuration des services Docker pour le développement localdocker-compose-deploy.yml: Configuration des services Docker pour le déploiementopenapi/spec.yaml: Spécification de l'APIMakefile: Commandes pour faciliter le développementREADME.md: Documentation principale du projetrequirements.txt: Dépendances Python globales
- Backend : Python, Flask, Connexion, SQLAlchemy
- Frontend : Angular, TypeScript, Bulma CSS
- API : OpenAPI/Swagger
- Base de données : MySQL/MariaDB
- Authentification : OAuth2, CAS
- Déploiement : Docker, Docker Compose
Le projet est organisé en plusieurs dossiers principaux :
/api_server/: Code source du backend Python/frontend_angular/: Code source du frontend Angular/openapi/: Spécification de l'API/reverse_proxy/: Configuration du reverse proxy (NGINX)/doc/: Documentation supplémentaire
Chacun de ces dossiers est détaillé dans les sections suivantes.
Le backend suit une architecture "Clean Architecture" (ou "Onion Architecture") telle que définie par Robert C. Martin (Uncle Bob). Cette approche permet de séparer les préoccupations et de rendre le code plus maintenable.
/api_server/adh6/: Code source principal du backend/api_server/migrations/: Scripts de migration de la base de données/api_server/test/: Tests unitaires et d'intégration/api_server/openapi/: Fichiers de configuration OpenAPI
Le backend est organisé en modules fonctionnels, chacun gérant une partie distincte de l'application. Chaque module suit la même structure en couches :
-
Entity Layer (Layer 1) - Entités
- Structures de données qui représentent les concepts métier
- Se trouvent dans le dossier global du backend plutôt que dans les modules
-
Use Case Layer (Layer 2) - Cas d'utilisation
- Contient la logique métier (business logic)
- Implémentée dans les "managers" à la racine de chaque module (ex:
member_manager.py) - Indépendante des technologies et frameworks spécifiques
-
Interface Handler Layer (Layer 3) - Gestionnaires d'interface
- Adapte les entrées/sorties pour le Use Case Layer
- Comprend les handlers HTTP et les repositories de stockage
- Se trouve dans les sous-dossiers
httpetstoragede chaque module
-
Framework & Drivers Layer (Layer 4) - Frameworks et drivers
- Bibliothèques et frameworks externes (Flask, SQLAlchemy, etc.)
- Définis dans les requirements.txt
- authentication : Gestion de la sécurité des endpoints et des informations d'authentification (clés API, rôles utilisateur)
- member : Gestion des utilisateurs et de leurs cotisations
- device : Gestion des appareils des membres et attribution des adresses IP
- room : Gestion des chambres
- network : Gestion des appareils réseau
- subnet : Gestion des sous-réseaux à attribuer
- treasury : Gestion de la trésorerie
- metrics : Gestion des métriques de la plateforme
Prenons l'exemple du module device :
/api_server/adh6/device/
__init__.py
device_manager.py # Layer 2: Use Case (logique métier)
/http
device.py # Layer 3: Interface pour l'API HTTP
/interfaces
device_repository.py # Layer 3: Interface abstraite pour le stockage
/storage
device_repository.py # Layer 3: Implémentation de l'interface (SQL)
- Une requête HTTP arrive et est interceptée par Connexion/Flask
- Elle est dirigée vers le handler HTTP approprié (Layer 3)
- Le handler appelle le manager correspondant (Layer 2)
- Le manager implémente la logique métier et utilise les repositories (Layer 3) pour accéder aux données
- Les repositories interagissent avec la base de données via SQLAlchemy (Layer 4)
- La réponse remonte cette chaîne et est renvoyée au client
Les migrations de la base de données sont gérées par Alembic (via Flask-Migrate) et sont stockées dans le dossier /api_server/migrations/.
ADH6 utilise une architecture de base de données robuste basée sur SQLAlchemy, un ORM (Object-Relational Mapping) Python qui facilite l'interaction avec la base de données MySQL/MariaDB.
La configuration de la base de données est centralisée dans /api_server/adh6/config/configuration.py et varie selon l'environnement :
-
Développement local :
- En développement, une base de données MySQL est lancée dans un conteneur Docker
- Les paramètres sont configurés dans le fichier
.envà la racine du projet
-
Production :
- En production, l'application se connecte à une base de données externe
- Les paramètres de connexion sont injectés via des variables d'environnement :
SQLALCHEMY_DATABASE_URI = "mysql+mysqldb://{}:{}@{}/{}".format( os.environ.get("DATABASE_USERNAME"), os.environ.get("DATABASE_PASSWORD"), os.environ.get("DATABASE_HOST"), os.environ.get("DATABASE_DB_NAME") )
-
Tests :
- Pour les tests, une base de données SQLite en mémoire est utilisée
- Cela permet d'exécuter les tests rapidement sans dépendance externe
L'accès aux données suit le principe de la Clean Architecture :
-
Modèles SQLAlchemy :
- Définis dans les dossiers
storage/models.pyde chaque module - Exemple :
/api_server/adh6/network/storage/models.pypour les modèles réseau
- Définis dans les dossiers
-
Repositories :
- Implémentent les interfaces abstraites de chaque module
- Traduisent les opérations CRUD en requêtes SQLAlchemy
- Exemple :
DeviceSQLRepositorydans/api_server/adh6/device/storage/device_repository.py
-
Session SQLAlchemy :
- Gérée par Flask-SQLAlchemy
- Instance globale disponible via
adh6.storage.db.session
Les migrations sont gérées par Alembic (via Flask-Migrate) :
- Les scripts de migration sont stockés dans
/api_server/migrations/versions/ - Ils permettent de faire évoluer le schéma de la base de données tout en préservant les données
- Les migrations sont exécutées automatiquement lors du déploiement
Exemple de migration :
def upgrade():
op.alter_column('ports', 'numero',
existing_type=mysql.VARCHAR(length=255),
nullable=False)-
Tracking des modifications :
- Utilisation de classes comme
RubyHashTrackablepour suivre les modifications - Les changements sont enregistrés dans la table
modifications
- Utilisation de classes comme
-
Connection pooling :
- Configuration du pool de connexions via
SQLALCHEMY_ENGINE_OPTIONS - Détection des connexions inactives avec
pool_pre_ping=True
- Configuration du pool de connexions via
-
Isolation des transactions :
- Niveau d'isolation configurable via
isolation_level - Par défaut réglé sur
SERIALIZABLEpour garantir la cohérence
- Niveau d'isolation configurable via
La sécurité de la base de données est assurée par plusieurs mécanismes :
-
Credentials sécurisés :
- Les identifiants ne sont jamais en dur dans le code
- En production, ils sont injectés via des variables d'environnement
- En développement, ils sont définis dans le fichier
.env(non versionné)
-
ORM :
- L'utilisation de SQLAlchemy limite les risques d'injection SQL
- Les paramètres sont liés de manière sécurisée
-
Isolation :
- En production, la base de données est accessible uniquement depuis l'application
- Des règles de pare-feu limitent les accès externes
En production, l'application peut être configurée pour envoyer les logs et métriques à un système ELK (Elasticsearch, Logstash, Kibana) :
if os.environ.get("ELK_ENABLED", False):
# Configuration des hôtes Elasticsearch
ELK_HOSTS = [
{
'scheme': s.group('scheme'),
'host': s.group('host'),
'port': int(s.group('port'))
}
# ...
]Cela permet de surveiller les performances de la base de données et de détecter les problèmes potentiels.
ADH6 dispose d'un système de logs robuste qui permet le suivi des activités des utilisateurs et le debugging des problèmes de connexion. Ce système est particulièrement utile pour les administrateurs qui aident les membres à résoudre leurs problèmes de réseau.
Le système de logs suit également l'architecture en couches de la Clean Architecture :
-
Interface abstraite :
- Définie dans
/api_server/adh6/device/interfaces/logs_repository.py - La classe
LogsRepositorydéfinit l'interface abstraite avec la méthodeget
- Définie dans
-
Implémentation concrète :
- Implémentée dans
/api_server/adh6/device/storage/logs_repository.py - La classe
ElasticsearchLogsRepositoryfournit l'accès aux logs stockés dans Elasticsearch
- Implémentée dans
-
Manager intermédiaire :
- Défini dans
/api_server/adh6/device/device_logs_manager.py - La classe
DeviceLogsManagercoordonne la récupération des appareils d'un membre et leurs logs associés
- Défini dans
-
Accès depuis la couche métier :
- Implémenté dans
/api_server/adh6/member/member_manager.py - La méthode
get_logsduMemberManagerpermet de récupérer les logs formatés pour un membre spécifique
- Implémenté dans
Les logs sont principalement stockés dans Elasticsearch, ce qui permet :
- Recherche rapide : Elasticsearch est optimisé pour les requêtes textuelles complexes
- Stockage à grande échelle : Capable de gérer des volumes importants de logs
- Agrégation et visualisation : Compatible avec Kibana pour créer des tableaux de bord
ADH6 gère deux types principaux de logs :
-
Logs d'authentification (RADIUS) :
- Traces des tentatives de connexion au réseau
- Informations sur le statut de l'authentification (succès/échec)
- Association entre adresses MAC et utilisateurs
-
Logs DHCP (optionnels) :
- Traces des allocations d'adresses IP
- Informations sur les baux DHCP
- Ces logs peuvent être activés à la demande via le paramètre
dhcp=True
Les requêtes Elasticsearch sont construites dynamiquement en fonction des besoins :
- Filtrage par utilisateur : Récupération des logs mentionnant un nom d'utilisateur spécifique
- Filtrage par appareil : Recherche des logs concernant des adresses MAC particulières
- Variations d'adresses MAC : Prise en compte de différents formats d'adresses MAC pour améliorer les résultats
Exemple de construction de requête :
query = {
"sort": {
'@timestamp': 'desc', # Tri chronologique inverse
},
"query": {
"bool": {
"filter": {
"match": {"program": "radiusd"} # Filtre les logs RADIUS
},
"should": [
# Recherche par nom d'utilisateur
{"match": {"radius_user": member.username}},
# Les adresses MAC sont ajoutées dynamiquement
],
"minimum_should_match": 1,
},
},
"_source": ["@timestamp", "message", "program", "src_mac"], # Champs à récupérer
"size": limit, # Limitation du nombre de résultats
}Le frontend permet aux administrateurs et aux utilisateurs de visualiser les logs pertinents :
- Pour les administrateurs : Vue complète des logs d'un membre via la page de détails du membre
- Pour les utilisateurs : Accès limité à leurs propres logs via leur tableau de bord
L'interface utilisateur propose des options pour :
- Filtrer les types de logs (avec/sans DHCP)
- Actualiser les logs à la demande
- Afficher les logs dans un format lisible avec mise en forme
Le système est conçu pour fonctionner même si Elasticsearch n'est pas disponible :
try:
logs = self.device_logs_manager.get(member=member, dhcp=dhcp)
# Traitement des logs
except LogFetchError:
logging.warning("log_fetch_failed")
return [] # Mode dégradé : retourne une liste videLa configuration du système ELK (Elasticsearch, Logstash, Kibana) est définie dans /api_server/adh6/config/configuration.py :
if os.environ.get("ELK_ENABLED", False):
# Configuration des hôtes Elasticsearch
ELK_HOSTS = [
{
'scheme': s.group('scheme'),
'host': s.group('host'),
'port': int(s.group('port'))
}
# ...
]En environnement de développement, un système de logs simplifié est utilisé pour faciliter les tests.
Les tests se trouvent dans le dossier /api_server/test/ et sont séparés en tests unitaires (/unit/) et tests d'intégration (/integration/).
Le frontend est une application Angular moderne qui communique avec le backend via l'API REST.
/frontend_angular/src/app/: Modules, composants et services Angular/frontend_angular/src/assets/: Ressources statiques (images, icônes, fichiers de traduction)/frontend_angular/src/environments/: Configurations pour les différents environnements/frontend_angular/nginx/: Configuration NGINX pour le déploiement
Le frontend est structuré par modules fonctionnels, chacun représentant une partie de l'application :
- Module principal (
app.module.ts) : Module racine de l'application - Module de routage (
app-routing.module.ts) : Définit les routes de l'application
Chaque fonctionnalité est implémentée à travers un ensemble de composants Angular spécifiques :
-
Composants de membre (
/member/) : Gestion des profils d'adhérents- Liste des membres (
list.component.tsdans/member/list/) - Vue détaillée d'un membre (
view.component.ts) - Édition d'un membre (
create-or-edit.component.ts)
- Liste des membres (
-
Composants d'appareil (
/device/,/member-device/) : Gestion des appareils- Liste des appareils (
list/list.component.ts) - Détails d'un appareil (
list/element/element.component.ts)
- Liste des appareils (
-
Composants de chambre (
/room/) : Gestion des chambres- Liste des chambres (
room-list.component.ts) - Détails d'une chambre (
room-details.component.ts)
- Liste des chambres (
-
Composants réseau (
/switch/,/port/) : Gestion des équipements réseau- Liste des switches (
switch-list.component.ts) - Détails d'un switch (
switch-details.component.ts) - Liste des ports (
port-list.component.ts) - Détails d'un port (
port-details.component.ts)
- Liste des switches (
-
Composants de trésorerie (
/treasury/,/transaction/,/account/) : Gestion financière- Vue trésorerie (
treasury.component.ts) - Liste des transactions (
transaction-list.component.ts) - Liste des comptes (
account-list.component.ts)
- Vue trésorerie (
-
Composants d'authentification (
/auth-management/) : Gestion des accès- Gestion des clés API (
api-key.component.ts)
- Gestion des clés API (
-
Interface utilisateur : Composants génériques pour l'UI
- Barre de navigation (
navbar.component.ts) - Barre de navigation verticale (
vertical-navbar.component.ts) - Pied de page (
footer.component.ts) - Pagination (
pagination.component.ts)
- Barre de navigation (
Les services Angular sont générés automatiquement à partir de la spécification OpenAPI :
/frontend_angular/src/app/api/: Services pour communiquer avec l'API/frontend_angular/src/app/api/model/: Modèles de données générés
L'interface utilisateur est construite avec le framework CSS Bulma, qui fournit des composants responsives et modernes. Les icônes sont gérées avec Font Awesome.
Le frontend supporte l'internationalisation avec des fichiers de traduction pour différentes langues :
/frontend_angular/src/local/: Fichiers de traduction
Le système de routage d'Angular est utilisé pour naviguer entre les différentes vues :
- La page d'accueil (
/portail) pour les utilisateurs non authentifiés - Le tableau de bord (
/dashboard) pour les utilisateurs connectés - Les différentes sections pour la gestion des membres, appareils, etc.
L'authentification utilise le protocole OAuth2 via le service CAS (Central Authentication Service) de MiNET. L'application utilise la bibliothèque angular-auth-oidc-client pour gérer l'authentification côté client.
L'API est au cœur de la communication entre le frontend et le backend. Elle est définie selon la spécification OpenAPI (anciennement Swagger).
/openapi/spec.yaml: Définit toutes les routes, modèles et opérations disponibles dans l'API
Cette spécification est utilisée pour :
- Générer le code du côté serveur (Python/Flask) via la bibliothèque Connexion
- Générer le code client (TypeScript) pour le frontend Angular
- Générer la documentation interactive de l'API
La génération du code est automatisée via des commandes dans le Makefile :
- Pour le backend : génère les interfaces Python avec OpenAPI Generator
- Pour le frontend : génère les services et modèles TypeScript
Les principales routes de l'API sont organisées par ressource :
/health: Vérification de l'état de l'API/profile: Informations sur l'utilisateur connecté/member/: Gestion des membres/device/: Gestion des appareils/room/: Gestion des chambres/switch/: Gestion des switches réseau/port/: Gestion des ports des switches/vlan/: Gestion des VLANs/account/: Gestion des comptes pour la trésorerie/transaction/: Gestion des transactions financières
L'API utilise deux méthodes d'authentification :
- OAuth2 pour les utilisateurs via l'interface web
- Clés API pour les accès programmatiques et les services automatisés (accessible une fois qu'on a la prod)
ADH6 communique directement avec les équipements réseau (switches) à l'aide du protocole SNMP (Simple Network Management Protocol). Cette fonctionnalité est implémentée dans le module network/snmp.
La communication avec les switches suit le même principe d'architecture en couches que le reste de l'application :
- Interface abstraite : Définie dans
/api_server/adh6/network/interfaces/switch_network_manager.py - Implémentation SNMP : Implémentée dans
/api_server/adh6/network/snmp/switch_network_manager.py
La classe principale SwitchSNMPNetworkManager implémente l'interface SwitchNetworkManager et fournit les méthodes pour interagir avec les switches via SNMP.
- Gestion des ports : Activation/désactivation des ports (
update_port_status) - Gestion des VLANs : Lecture et attribution de VLANs aux ports (
get_port_vlan,update_port_vlan) - Gestion de l'authentification : Configuration de l'authentification par port (
get_port_auth,update_port_auth) - MAC Authentication Bypass (MAB) : Activation/désactivation du MAB sur les ports (
get_port_mab,update_port_mab) - Surveillance des ports : Récupération de l'état, de la vitesse et de l'alias d'un port (
get_port_status,get_port_speed,get_port_alias)
La communication SNMP est réalisée à l'aide de la bibliothèque Python pysnmp. Deux fonctions principales sont utilisées :
get_SNMP_value: Récupère des valeurs depuis le switchset_SNMP_value: Configure des paramètres sur le switch
Ces fonctions sont définies dans /api_server/adh6/network/snmp/util/snmp_helper.py.
L'accès aux commandes SNMP est sécurisé via :
- Gestion des communautés SNMP stockées dans la base de données
- Vérification des rôles utilisateur avant d'autoriser les modifications sensibles
- Validation des paramètres pour prévenir les erreurs de configuration
Par exemple, les modifications de VLANs nécessitent des permissions spécifiques selon le VLAN cible :
if (
Roles.NETWORK_WRITE.value not in roles and
(
((vlan == 3 or vlan == 103) and Roles.NETWORK_DEV.value not in roles)
or ((vlan == 2 or vlan == 102) and Roles.NETWORK_PROD.value not in roles)
or ((vlan == 104) and Roles.NETWORK_HOSTING.value not in roles)
)
):
raise UnauthorizedError()L'application utilise plusieurs MIBs (Management Information Base) standards pour interagir avec les switches :
CISCO-VLAN-MEMBERSHIP-MIB: Gestion des VLANsIF-MIB: Gestion des interfacesCISCO-MAC-AUTH-BYPASS-MIB: Configuration du MAC Authentication BypassIEEE8021-PAE-MIB: Gestion de l'authentification 802.1X
Le projet utilise Docker et Docker Compose pour faciliter le développement et le déploiement :
/docker-compose.yml: Configuration pour le développement local/docker-compose-deploy.yml: Configuration pour le déploiement en production/api_server/Dockerfile: Image Docker pour le backend/frontend_angular/Dockerfile: Image Docker pour le frontend/reverse_proxy/Dockerfile: Image Docker pour le reverse proxy
Le reverse proxy (NGINX) est configuré pour :
- Servir l'application frontend
- Rediriger les requêtes API vers le backend
- Gérer les certificats SSL
- Configurer des en-têtes de sécurité appropriés
La configuration se trouve dans /reverse_proxy/default.conf.template.
Le projet est configuré pour fonctionner dans plusieurs environnements :
- Développement : Configuration locale pour les développeurs
- Testing : Environnement de test automatisé
- Production : Environnement de production
Les configurations spécifiques à chaque environnement sont définies dans :
/api_server/adh6/config/configuration.pypour le backend/frontend_angular/src/environments/pour le frontend
Les cas d'utilisation sont classés par priorité selon leur importance pour l'application.
Ces fonctionnalités sont critiques et doivent toujours fonctionner :
-
Gestion des adhésions
- Remarque: La méthode
new_membershipdont il est question dans [README.md] n'existe pas dans l'implémentation actuelle - Ajouter un membre (
MemberManager.update_or_create) - Lire le profil d'un membre (
MemberManager.get_by_login)
- Remarque: La méthode
-
Gestion des appareils
- Ajouter un appareil (
DeviceManager.update_or_create) - Chercher des appareils (
DeviceManager.search)
- Ajouter un appareil (
Ces fonctionnalités sont importantes mais moins critiques :
-
Gestion des profils
- Lire les logs d'un membre (
MemberManager.get_logs) - Changer le mot de passe d'un membre (
MemberManager.change_password) - Mettre à jour le profil d'un membre (
MemberManager.update_partially) - Chercher les membres (
MemberManager.search)
- Lire les logs d'un membre (
-
Gestion avancée des appareils
- Voir les infos d'un appareil (Remarque: cette fonctionnalité est implémentée via
device_repository.get_by_mac()) - Supprimer un appareil (
DeviceManager.delete)
- Voir les infos d'un appareil (Remarque: cette fonctionnalité est implémentée via
Ces fonctionnalités sont utiles mais non critiques pour l'opération quotidienne :
- Gestion administrative
- Supprimer un membre (
MemberManager.delete)
- Supprimer un membre (
Pour ajouter une nouvelle fonctionnalité à ADH6, suivez ces étapes :
- Modifier l'API : Mettez à jour
/openapi/spec.yamlpour ajouter les nouveaux endpoints ou modèles - Générer le code : Utilisez
make generatepour régénérer le code client et serveur - Backend :
- Créez/modifiez un manager dans le module approprié (Layer 2)
- Implémentez la logique métier
- Créez/modifiez les interfaces de repository si nécessaire
- Implémentez le repository pour la persistance des données
- Écrivez des tests
- Frontend :
- Créez/modifiez les composants Angular nécessaires
- Utilisez les services générés pour communiquer avec l'API
- Schéma :
/api_server/adh6/entity/ - Migrations :
/api_server/migrations/ - Requêtes : Modules spécifiques dans
/api_server/adh6/*/storage/
- Managers correspondants dans
/api_server/adh6/*/
- Composants Angular dans
/frontend_angular/src/app/ - Styles globaux dans
/frontend_angular/src/styles.scss
- Backend :
/api_server/adh6/config/ - Frontend :
/frontend_angular/src/environments/ - Docker : Fichiers Docker-compose à la racine
- Tests backend : Exécutez
pytestdans le dossier/api_server/ - Tests frontend : Exécutez
ng testdans le dossier/frontend_angular/ - Logs : Les logs sont générés dans la console Docker et dans les fichiers de log configurés
ADH6 est une application complexe mais bien structurée suivant les principes de la Clean Architecture côté backend et une architecture modulaire côté frontend. Cette séparation des préoccupations facilite la maintenance et l'évolution de l'application.