Un mois et demi plus tard, le service a fonctionné avec succès dans le data center luxembourgeois EDH Tier IV, mais même lors de la première réunion, nous l'avons reconnu comme un maillon faible dans l'ensemble du projet: contrairement au point de présence, la plateforme ne pouvait pas se vanter de sécurité et avait une douzaine d’autres lacunes. La décision était évidente - le service devait être fait à partir de zéro. Restait à convaincre le gouvernement luxembourgeois.
Ce médecin est cassé, apportez-en un nouveau: pourquoi nous avons décidé de créer une nouvelle plateforme
Pour suggérer de mettre une balle sur l'ancienne plate-forme, il fallait juste y jeter un coup d'œil. Au lieu de résoudre tous les problèmes de sécurité possibles et de mettre à jour la conception, le service était plus facile à créer à partir de zéro, et nous avions trois bonnes raisons à cela.
1. Le système a été développé sans cadre
Pour cette raison, il y avait un nombre impensable de problèmes dans la plate-forme. Si un cadre populaire avait été utilisé pour créer un service - Symfony, Laravel, Yii ou autre chose - alors même les développeurs médiocres auraient évité la plupart des problèmes de sécurité, car les ORM peuvent préparer des requêtes dans la base de données, les moteurs de création de modèles peuvent endcode le contenu reçu de l'utilisateur, et les formulaires sont protégés par défaut avec des jetons CSRF, et l'autorisation et l'authentification sont généralement disponibles presque immédiatement. Dans le même cas, la plate-forme a rendu notre développeur nostalgique des jours d'étudiant - le code ressemblait presque à son premier travail en laboratoire à l'université.
Par exemple, voici comment la connexion à la base de données a été implémentée. Les informations d'identification de connexion ont été codées en dur ci-dessus dans le même fichier.
if (!isset($db)) { $db = new mysqli($db_info['host'], $db_info['user'], $db_info['pass'], $db_info['db']); if ($db->connect_errno) { die("Failed to connect to MySQL: " . $db->connect_errno); } if (!$db->set_charset("utf8")) { die("Error loading character set utf8 for MySQL: " . $db->connect_errno); } $db->autocommit(false); }
2. Il y avait de nombreux problèmes de sécurité dans la plate-forme
Après l'audit, nous nous sommes rendu compte qu'avec de telles failles, il est impossible de passer en production même avec un simple blog, et encore moins avec une plateforme avec des données confidentielles. En voici quelques uns.
- Injection SQL. 90% des demandes comprenaient des données saisies par les utilisateurs sans préparation préalable.
$sql = " UPDATE user SET firstname='%s', lastname='%s', born='%s', prefix='%s', phone='%s', country_res='%s', extra=%s WHERE id=%d ;"; $result = $db->query(sprintf($sql, $_POST['firstname'], $_POST['lastname'], $_POST['born'], $_POST['prefix'], $_POST['phone'], $_POST['country'], isset($_POST['extra']) ? "'".$_POST['extra']."'" : "NULL", $_SESSION['user']['id'] ));
- Vulnérabilités XSS. Le code personnalisé n'a été filtré d'aucune façon avant la sortie:
<button id="btn-doc-password" class="btn btn-primary btn-large pull-right" data-action="<?= $_GET['action'] ?>"><i class="fas fa-check"></i> <?= _e("Valider") ?></button>
De plus, les informations qui sont entrées dans la base de données, telles que la raison de la consultation d'un médecin, n'étaient pas filtrées avant l'écriture dans la base de données ou avant le rendu sur la page. - . ID , . . , ID .
- . , -. , qury-string .
$file_dir = $settings['documents']['dir'] . $_SESSION['client']['id'] . DIRECTORY_SEPARATOR . $_GET['id_user'];
- Bibliothèques tierces obsolètes. Chez l'ancien fournisseur, personne ne suivait les versions de bibliothèques tierces qui, d'ailleurs, au lieu d'utiliser le même Composer, étaient simplement copiées dans le projet. De plus, certaines de ces dépendances tierces ont été personnalisées.
- Stockage non sécurisé des mots de passe utilisateur. Des fonctions cryptographiques peu fiables ont été utilisées pour stocker les mots de passe.
$sql = " SELECT id, firstname, lastname FROM user WHERE id=%d AND password=PASSWORD('%s') ;"; $result = $db->query(sprintf($sql, $_SESSION['user']['id'], $_POST['pass']));
- Vulnérabilité CSRF. Aucun formulaire n'a été sécurisé avec un jeton CSRF.
- Manque de protection contre les attaques par force brute. Ce n'était tout simplement pas là. Non.
Ici, nous pourrions continuer encore et encore, mais ces problèmes suffisent à comprendre: soit le système avait de graves problèmes, soit il était lui-même un problème sérieux.
3. Le code était difficile à maintenir et à étendre
Les problèmes de sécurité ne se limitaient pas à tout. À notre grande surprise, le projet ne disposait pas d'un système de contrôle de version. Le code était complètement déstructuré. Le répertoire racine du serveur Web contenait des fichiers tels que ajax-new.php, ajax2.php, et ils étaient tous utilisés dans le code. Il n'y avait pas non plus de délimitation claire en couches (présentation, application, données). Dans la grande majorité des cas, le fichier de code était un mélange de PHP, HTML et JavaScript.
Tout cela a conduit au fait que lorsqu'on nous a demandé de créer un backoffice primitif pour ce système, la meilleure solution était de déployer Symfony 4 côte à côte en conjonction avec Sonata Admin et de ne pas toucher du tout au code existant. Il est clair que si on nous demandait d'ajouter de nouvelles opportunités pour les médecins ou les patients, cela nous prendrait beaucoup de temps et d'énergie. Et comme il n'était pas question de tests automatiques, la probabilité de casser quelque chose serait extrêmement élevée.
Tout ce qui précède était suffisant pour le gouvernement luxembourgeois - nous avons reçu le feu vert pour développer une nouvelle plateforme.
The Doctor Rides-Rides: Comment nous avons développé une nouvelle plateforme
Nous avons commencé à préparer le développement d'une nouvelle plate-forme dès le début - même lorsque nous avons vu l'idée d'un ancien fournisseur. Par conséquent, lorsque nous avons eu le feu vert pour développer une nouvelle plate-forme, nous avons immédiatement commencé à créer sa version MVP. Une équipe de quatre développeurs PHP et de trois développeurs frontaux s'est acquittée de cette tâche en environ trois semaines et demie. Tous les travaux ont été effectués sur Symfony 5, et seuls les appels vidéo et les chats ont été délégués - ils ont été mis en œuvre à l'aide de notre service G-Core Meet. Le backoffice de l'ancien système s'est également avéré utile: nous avons réussi à l'adapter au MVP en quelques jours seulement. En conséquence, la version MVP du système couvrait 80% des fonctionnalités de l'ancienne plateforme. Maintenant, en passant, il est également utilisé pour une autre tâche - à un moment donné, nous avons cloné le MVP pour le helpdesk de l'agence luxembourgeoise de santé en ligne,afin que les administrateurs puissent appeler les utilisateurs.
Lorsque le MVP était prêt, nous avons commencé à développer une nouvelle plate-forme à part entière. API Platform et ReactJS en conjonction avec Next.js pour le côté client ont été utilisés comme base pour l'API. Non sans tâches intéressantes.
1. Mise en œuvre des notifications
L'une des difficultés a surgi avec les notifications. Étant donné que les clients API pouvaient être à la fois des applications mobiles et notre SPA, une solution combinée était nécessaire.
Tout d'abord, nous avons choisi le Mercure Hub, avec lequel les clients interagissent via SSE (Server Sent Event). Mais quelle que soit la manière dont les créateurs de la plate-forme API ont eux-mêmes promu cette solution, notre équipe mobile l'a rejetée, car l'application ne pouvait recevoir des notifications avec elle qu'à l'état actif.
C'est ainsi que nous sommes arrivés à Firebase, avec lequel nous avons réussi à obtenir la prise en charge des notifications push natives sur les appareils mobiles, et avons quitté le Mercure Hub pour les applications de navigateur. Désormais, lorsqu'un événement s'est produit dans le système, nous en informons l'utilisateur via le canal privé dont nous avons besoin dans Mercure Hub et, en plus, envoyons un push à Firebase pour un appareil mobile.
Pourquoi n'avons-nous pas tout mis en œuvre immédiatement sur Firebase? Tout est simple ici: malgré le support des clients Web, les navigateurs sans l'API Push - le même Safari et la plupart des navigateurs mobiles - ne fonctionnent pas avec. Cependant, nous prévoyons toujours de mettre en œuvre des notifications push de Firebase pour les utilisateurs utilisant des navigateurs pris en charge.
2. Tests fonctionnels
Une autre situation intéressante est survenue lorsque nous faisions des tests fonctionnels pour l'API. Comme vous le savez, chacun d'eux doit travailler dans un environnement propre. Mais à chaque fois, il s'est avéré coûteux en termes de performances de surélever la base et de remplir les montages de base + les montages nécessaires aux tests. Pour éviter cela, nous avons décidé au stade initial de monter la base de données en fonction du mappage d'entités (
bin/console doctrine:schema:create
) et ensuite seulement d'ajouter des fixtures de base (
bin/console doctrine:fixtures:load
).
Ensuite, en utilisant l'extension dama / doctrine-test-bundle, nous nous sommes assurés que l'exécution de chaque test est enveloppée dans une transaction et à la fin du cas de test, elle la recule sans validation. Pour cette raison, même si des modifications sont apportées à la base de données pendant le test, elles ne sont pas validées et la base de données après l'exécution reste dans le même état qu'avant le lancement de PHPUnit.
Afin qu'au moins un test soit écrit pour chaque point de terminaison, nous avons effectué un test de révision automatique. Il détecte tous les itinéraires enregistrés et vérifie les tests pour eux. Ainsi, par exemple, pour la route app_appointment_create, il vérifie si le dossier contient
tests/Functional/App/Appointment CreateTest.php
.
De plus, la qualité du code est surveillée par PHP-CS-Fixer, php-cpd et PHPStan avec des extensions comme phpstan-strict-rules.
3. Côté client
Pour les médecins et les patients, nous avons créé deux applications client indépendantes avec la même interface utilisateur et des capacités similaires. Pour établir la réutilisation des fonctionnalités et de l'interface utilisateur en eux, nous avons décidé d'utiliser un monorépertoire, qui comprend des bibliothèques et des applications. Dans le même temps, les applications elles-mêmes sont construites et déployées indépendamment les unes des autres, mais peuvent dépendre des mêmes bibliothèques.
Cette approche vous permet de créer une fonctionnalité (bibliothèque) en une seule pull request et de l'intégrer dans toutes les applications dont vous avez besoin. Cet avantage a conduit à l'utilisation d'un monorépertoire au lieu d'implémenter des fonctionnalités dans des bibliothèques npm séparées et des projets dans différents référentiels.
Pour configurer le monorepository, la bibliothèque ns.js est utilisée, qui à partir de la boîte vous permet de créer des bibliothèques et des applications pour React et Next.js, et c'est cette pile qui est utilisée dans le projet. Nous utilisons ESLint et Prettier pour suivre la qualité du code, Jest pour écrire des tests unitaires et React Testing Library pour tester les composants React.
Le médecin est arrivé: ce qui s'est passé à la fin
En seulement cinq mois, tous les problèmes ont été résolus et la nouvelle plate-forme est devenue disponible pour les utilisateurs de n'importe quel appareil: nous avons préparé une version Web du service, ainsi que des applications mobiles pour iOS et Android.
Depuis plus de 4 mois, le service permet aux patients de recevoir des consultations en ligne de médecins et de dentistes. Ils peuvent avoir lieu dans les deux formats audio et vidéo. En conséquence, les médecins rédigent des ordonnances et partagent en toute sécurité les dossiers médicaux et les résultats des tests avec les patients.
La plateforme est accessible à tous les professionnels de santé, résidents et travailleurs au Luxembourg. Maintenant, elle travaille dans 2 des plus grands établissements de santé du pays - à l'hôpital. Robert Schuman (Hôpitaux Robert Schuman) et le centre hospitalier. Emile Mayrisch (Centre Hospitalier Emile Mayrisch). Le service est déployé dans un point de présence sécurisé du cloud G-Core Labs dans le data center Luxembourg EDH Tier IV, où un environnement virtuel est configuré pour celui-ci conformément aux spécifications requises.