Pour plus d'informations, voir la formation ADH6 sur le Nextcloud.
Lien ver le répo : https://gitlab.minet.net/adh6/core
ADH6 est la 6ème version de l'interface de gestion des adhérents à MiNET.
C'est un outil qui permet de superviser l'ensemble du processus d'inscription d'un adhérent à MiNET :
Le projet ADH6 a débuté en l'an 2012 (av JC), rien n'a été fait cette année là mais l'idée avait été lancée. Cette idée a traversé l'histoire des bureaux successifs revenant chaque année tel un running gag.
C'est sous le bureau 2016-2017 que le projet a réellement essayé d'émerger. thunder s'est grandement investi et a rassemblé des gens pour réaliser ce projet. Cela s'est concrétisé par un stage MiNET. Un premier prototype est arrivé et a été rapidement abandonné. Le projet a été repris dans une deuxième version plus aboutie (en 2016) et abandonné en raison des technologies utilisées.
En 2017, une nouvelle vision émerge de Thomas pour la conception d'ADH6 en servant des expériences passées. insolentbacon décide alors de s'investir avec occuria en profitant d'une validation projet JS. Et c'est comme ça que le projet commence à réellement voir le jour.
Vers fin 2020, une version fonctionnelle d'ADH6 est déployée par frazew, elle contient quelques fonctions essentielles (monitoring d'adhérents, check des logs, gestion de la tréso etc...)
Été 2021, lionofinterest implémente la gestion du switch local, et vaktas reprend à bras ouverts la gestion du projet, et complète la version déployée un an avant en implémentant toutes les fonctionnalités, notamment la gestion des inscriptions, les recotisations & attribution des subnets, pour une version déployée en prod fin janvier 2022.
Le gros désavantage d'ADH5 est qu'il est difficilement maintenable. Pourquoi ?
En résumé, ADH5 est un super outil mais trop difficile à maintenir, donc plus à jour.
Lors de la première version sous le règne de thunder, le Python s'est imposé (comme ADH4). Pourquoi ?
Après avoir choisi le python, pour ne pas reproduire les erreurs d'ADH4, l'utilisation d'un framework est devenue obligatoire. Le choix s'est porté sur Django. On peut tout faire avec c'est pratique. Même si la syntaxe est proche du python, le Django c'est particulier, il faut se faire aux fonctions et aux méthodes d'implémentation. Cela demande beaucoup d'apprentissage et cela n'avait pas été anticipé, donc même problème que pour ADH5, d'où l'abandon de la techno.
Une nouvelle façon de concevoir l'application a été pensée lors de la troisième itération, nous détaillons plus précisemment cette idée dans la prochaine partie.
Maintenant, on utilise Flask, qui à l'inverse de Django se contente de gérer son rôle de serveur Web. Pour faciliter la création d'une API on utilise le plugin connexion [pour l'anectdote, c'est une lib développée par Zalando], et pour communiquer avec notre base de donnée, on utilise SQLAlchemy. SQLAlchemy et Flask sont des libs qui sont très connues et réputées en Python.
En plus de ça, on utilise une application web développée en Javascript avec le framework Angular. C'est un framework qui est à la mode à l'heure où j'écris ces lignes.
A partir de la troisième itération, le changement a été radical dans la façon dont a été pensée l'application. A la manière de la philosophie d'Unix on s'est dit que nous allions faire plusieurs applications qui font une seule chose et qui la font bien. Adieu le “gros bloc monolithique” nous allons découper nos fonctions en plusieurs app, et donc utiliser plusieurs technos (et langage).
Il a donc fallut réfléchir au rôle concret d'ADH… C'est un “outil de gestion d'adhérents”, ça gère donc des adhérents et leurs appareils. La gestion du livre journal comptable, la gestion de l'authentification et la gestion des droits des admins, devaient être déplacées dans une autre service. Comme fonctions principales on a dégagé (en découpant finement, mais pas trop):
Ce qui est cool avec cette infra, c'est que chacun des composants sont remplaçable/substituable, par exemple, si un jour quelqu'un décide de faire une autre interface web (parce que Angular est devenu déprécié ou autre) il peut remplacer facil ement. De plus on peut avoir deux interfaces webs connectées à la même API. Ainsi, si on veut faire évoluer une partie du tout, nous n'avons pas à tout réécrire (comme pour ADH5→ADH6)
Par ailleurs, chacun des éléments vise à être le plus simple possible. Si quelqu'un veut modifier l'authentification, par exemple, il sait où aller modifier et si il a à toucher un code simple. ( Keep it simple, stupid!
Pour faire communiquer ces différents composants unitaires, nous avons fait le choix de faire des APIs (voir schéma explicatif ci-dessous). Encore une fois, c'est un concept à la mode et qui semble pérenne. Plus précisemment, nous avons fait des API REST là où nous le pouvions.
En clair, quand vous vous connectez, vous téléchargez une application (une page HTML/CSS/Javascript). Vous exécutez le code qui va faire des requêtes HTTP vers les différentes API pour interagir avec (s'authent, avoir la liste des membres, ajouter un membre etc.)
C'est un serveur web qui renvoie des données (sous forme JSON selon les pages qu'on demande. Il permet aussi de modifier des données selon la méthode HTTP utilisée (GET, POST, PUT, DELETE, etc). En clair c'est l'interface entre la base de données et le monde.
fig. Shéma global du fonctionnement d'une API
Pour définir la spécification de cette API, nous avons utilisé la dernière norme à ce jour (3.0.2) de la spécification OpenAPI
Cette spécification est écrite dans un fichier (sous le format YAML), et définit toutes les adresses de l'API (appelées endpoints dans le jargon), les variables qui peuvent être passées en paramètre et les valeurs de retour. C'est une spécification, elle définit les entrées et sorties, elle ne définit en aucun cas le comportement de l'API (genre la connexion au SQL), ça, c'est le rôle de l'implémentation. Ces valeurs de paramètres ou de valeurs de retour peuvent être extrêmement précise nottament en utilisant la notion de pattern d'OpenAPI. C'est très pratique, notamment quand on a une librairie qui agit comme porte d'entrée en se basant sur cette spécification (cf. 2 paragraphes plus tard)
Franchement, manipuler du YAML c'est dégueulasse. Si vous voulez une interface cool pour bosser avec, vous pouvez aller sur SwaggerHub où vous aurez une jolie interface pour visualiser l'API après avoir collé le YAML.
Pour implémenter facilement la spécification avec Flask, nous utilisons un plugin appelé connexion qui permet de prendre un fichier OpenAPI 2 (sous forme YAML) et de nous faciliter la tâche. Nous avons juste plus qu'à implémenter les méthodes sous forme d'un code simpliste du genre:
def monEndpoint(monParam1, monParam2):
return Contenu, codeD'Erreur
Bref, ça nous fait un code court, simple à comprendre et donc ça fait moins de bugs.
Et surtout, connexion fait tout un tas de checks pour nous avant. Genre typiquement il vérifie que les arguments required sont bien inclus dans la requête, et si c'est pas le cas il répond directement au client avec le bon code d'erreur. De même il vérifie le type des variables.
Maintenant que nous avons évoqué comment l'API communique avec le monde, il faut que nous comprenions qu'est ce qu'elle fait.
Presque tous les endpoints de l'API se contente de faire des requêtes sur la base de donnée SQL. Pour cela nous utilisons une bibliothèque appelée SQLALchemy, un truc béton pour gérer les base de données (le terme pour se la péter c'est ORM. Ça nous permet d'éviter de faire des requêtes SQL (donc d'introduire des failles SQL [Même si c'est pas pour autant qu'on y est immunisé!]).
Je ne vais pas détailler comment SQLAlchemy fonctionne, la syntaxe et tout. C'est pas le lieu pour, si vous voulez en savoir plus allez sur le site d'SQLAlchemy dans la partie tutoriel de l'ORM, ici à l'heure où j'écris ces lignes.
On fait des requêtes SNMP vers les switchs pour interagir avec les ports (désactiver l'authent', activer désactiver les ports, check le status d'un port [si il est branché ou non]). Je vais pas vous faire un cours sur SNMP, je vous invite à vous documenter vous même (mais seulement si vous avez un truc à réparer parce que c'est pas franchement passionnant ). C'est un truc qui a été repris d'ADH5.
Les switchs modifiables sont ceux dans les bâtiments sauf ceux pour le wifi (cas à part pour le U7 car il n'y a pas de switch wifi...) ainsi que le switch du local.
Cette fonctionnalité n'est pas encore implémentée
Via notamment SMTP il sera possible de notifier les adhérents lorsque ces derniers font une cotisation (leur rappelant par la même occasion leur pseudo) ou qu'il achêtent un produit comme un câble.
Pour le configuration du SMTP c'est assez simple puisqu'il suffit d'utiliser les information suivantes:
On utilise à MiNET elasticsearch pour pouvoir stocker les logs comme ceux des appareils des adhérents. C'est toujours bien pratique d'avoir les les erreur quand un adhérent ce connecte mais que ça ne marche pas. On utilise actuellement l'authentification sur le cluster Elasticsearch. Il faut donc utiliser les bon crédentials. Ces derniers peuvent se trouver dans les variables d'environnement des pipelines sur le répo gitlab d'adh6
Pour le frontend on utilise Angular 13 (version à l'heure à laquelle j'écris cette page) qui est un framework typescript. Il y a un gestionnaire de paquet appelé npm yarn.
Avant de pouvoir lacer le serveur de test pour le frontend (sans le backend s'il ont veut) il faut d'abord générer le code typescript à partir de la spécification soit en utilisant le .jar du générateur dans le projet soit avec le Makefile qui est là pour avoir accès à quelques commandes de base pour le projet.
Pour s'authentifier, nous utilisons le protocole OAuth 2. C'est encore une fois un truc à la mode, qui semble durer (c'est ce qui est utilisé quand vous cliquez sur les boutons “Sign in with Facebook/Google/Gitlab”). C'est particulièrement adapté pour les API et c'est 100% compatible avec OpenAPI2. De plus connexion gère nativement ce protocole.
Pour implémenter le serveur d'OAuth, (parce que oui on va pas mettre de bouton Sign in with Google/Facebook/BigBrother), nous avons choisi de ne pas le réimplémenter nous même mais plutôt d'utiliser un outil libre et sécurisé: CAS. CAS est utilisé par l'école pour authentifier les élèves à E-Campus, c'est stable et on peut l'intégrer à notre LDAP.
Si ça vous intéresse de savoir comment ça se passe au niveau du navigateur pour s'authent', nous utilisons l'Implicit Grant. Allez voir la RFC si ça vous intéresse, sinon il y a plein de jolis schémas si vous allez chercher sur votre moteur de recherche préféré (comme duckduckgo).
Voici les commandes lancées lors de la génération du code à partir de la spécification
openjdk-11 es nécessaire pour pouvoir générer le code à partir de la spécification
# Pour le frontend
java -jar openapi/swagger-codegen-cli.jar generate -i openapi/spec.yaml -l typescript-angular -o frontend_angular/src/app/api -t openapi/swagger-codegen-generators/src/main/resources/handlebars/typescript-angular/
sed -i 's/: ModuleWithProviders/: ModuleWithProviders<ApiModule>/g' "$(FRONTEND_PATH)/src/app/api/api.module.ts"
# Pour le backend
java -jar openapi/swagger-codegen-cli.jar generate -i openapi/spec.yaml -l python -o tmpsrc -t openapi/swagger-codegen-generators/src/main/resources/handlebars/python/ --additional-properties packageName=src --model-package entity
cp -r tmpsrc/src/entity/* $(BACKEND_PATH)/src/entity/
rm -rf tmpsrc
Pour simplifier le déploiement en local d'ADH6, un Makefile a été fait afin d'assurer certaines commandes essentielles.
La pluparts des commandes utilisent docker pour fonctionner
Lancer le project en local en générant tout ce qui est nécessaire
make # Déploie le projet en local
make generate-database-fixtures LOGIN=<login LDAP> # Génère les fixtures nécessaires au bon fonctionnement de l'application