Eineinhalb Monate später arbeitete der Dienst erfolgreich im EDH Tier IV-Rechenzentrum in Luxemburg, aber bereits beim ersten Treffen erkannten wir ihn als schwaches Glied im gesamten Projekt: Im Gegensatz zur Präsenz konnte sich die Plattform nicht rühmen der Sicherheit und hatte ein Dutzend andere Mängel. Die Entscheidung lag auf der Hand - der Service musste von Grund auf neu durchgeführt werden. Blieb, um die Regierung von Luxemburg zu überzeugen.
Dieser Arzt ist kaputt, bringen Sie einen neuen mit: Warum wir uns entschieden haben, eine neue Plattform zu schaffen
Um vorzuschlagen, eine Kugel auf die alte Plattform zu setzen, mussten wir nur einen Blick darauf werfen. Anstatt alle möglichen Sicherheitsprobleme zu beheben und das Design zu aktualisieren, war es einfacher, den Service von Grund auf neu zu erstellen, und wir hatten drei gute Gründe dafür.
1. Das System wurde ohne Framework entwickelt
Aus diesem Grund gab es eine undenkbare Anzahl von Problemen auf der Plattform. Wenn ein beliebtes Framework zum Erstellen eines Dienstes verwendet worden wäre - Symfony, Laravel, Yii oder etwas anderes -, hätten selbst mittelmäßige Entwickler die meisten Sicherheitsprobleme vermieden, da ORMs Abfragen an die Datenbank vorbereiten können und Template-Engines den empfangenen Inhalt endcodieren können Der Benutzer und Formulare sind standardmäßig mit CSRF-Token geschützt, und Autorisierung und Authentifizierung sind normalerweise fast sofort verfügbar. Im gleichen Fall machte die Plattform unseren Entwickler für Studententage nostalgisch - der Code sah fast genauso aus wie seine erste Laborarbeit an der Universität.
Hier ist beispielsweise, wie die Datenbankverbindung implementiert wurde. Die Verbindungsanmeldeinformationen wurden oben in derselben Datei fest codiert.
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. Es gab viele Sicherheitsprobleme auf der Plattform
Nach dem Audit haben wir festgestellt, dass es mit solchen Fehlern unmöglich ist, selbst mit einem einfachen Blog in die Produktion zu gehen, geschweige denn mit einer Plattform mit vertraulichen Daten. Hier sind einige davon.
- SQL-Injektion. 90% der Anfragen enthielten Daten, die von Benutzern ohne vorherige Vorbereitung eingegeben wurden.
$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'] ));
- XSS-Schwachstellen. Der benutzerdefinierte Code wurde vor der Ausgabe in keiner Weise gefiltert:
<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>
Darüber hinaus wurden die in die Datenbank aufgenommenen Informationen, z. B. der Grund für die Konsultation eines Arztes, weder vor dem Schreiben in die Datenbank noch vor dem Rendern auf der Seite gefiltert. - . ID , . . , ID .
- . , -. , qury-string .
$file_dir = $settings['documents']['dir'] . $_SESSION['client']['id'] . DIRECTORY_SEPARATOR . $_GET['id_user'];
- Veraltete Bibliotheken von Drittanbietern. Beim alten Anbieter folgte niemand den Versionen von Bibliotheken von Drittanbietern, die übrigens, anstatt denselben Composer zu verwenden, einfach in das Projekt kopiert wurden. Darüber hinaus wurden einige dieser Abhängigkeiten von Drittanbietern angepasst.
- Unsichere Speicherung von Benutzerkennwörtern. Zum Speichern von Passwörtern wurden unzuverlässige kryptografische Funktionen verwendet.
$sql = " SELECT id, firstname, lastname FROM user WHERE id=%d AND password=PASSWORD('%s') ;"; $result = $db->query(sprintf($sql, $_SESSION['user']['id'], $_POST['pass']));
- CSRF-Sicherheitslücke. Es wurde kein Formular mit einem CSRF-Token gesichert.
- Mangel an Schutz vor Brute-Force-Angriffen. Es war einfach nicht da. Nein.
Hier könnten wir weiter und weiter machen, aber diese Probleme reichen aus, um zu verstehen: Entweder hatte das System ernsthafte Probleme, oder es war selbst ein ernstes Problem.
3. Der Code war schwer zu pflegen und zu erweitern
Sicherheitsprobleme waren nicht auf alles beschränkt. Zu unserer Überraschung fehlte dem Projekt ein Versionskontrollsystem. Der Code war völlig unstrukturiert. Das Stammverzeichnis des Webservers enthielt Dateien wie ajax-new.php, ajax2.php und alle wurden im Code verwendet. Es gab auch keine klare Abgrenzung in Schichten (Präsentation, Anwendung, Daten). In den allermeisten Fällen war die Codedatei eine Mischung aus PHP, HTML und JavaScript.
All dies führte dazu, dass die beste Lösung darin bestand, Symfony 4 nebeneinander in Verbindung mit Sonata Admin bereitzustellen und den vorhandenen Code überhaupt nicht zu berühren, als wir gebeten wurden, ein primitives Backoffice für dieses System zu erstellen. Es ist klar, dass es uns viel Zeit und Energie kosten würde, wenn wir gebeten würden, neue Möglichkeiten für Ärzte oder Patienten hinzuzufügen. Und da nicht von automatischen Tests die Rede war, wäre die Wahrscheinlichkeit, etwas zu beschädigen, extrem hoch.
All dies genügte der luxemburgischen Regierung - wir erhielten grünes Licht für die Entwicklung einer neuen Plattform.
The Doctor Rides-Rides: Wie wir eine neue Plattform entwickelt haben
Wir haben von Anfang an begonnen, uns auf die Entwicklung einer neuen Plattform vorzubereiten - selbst als wir die Idee eines alten Anbieters sahen. Als wir die Erlaubnis erhielten, eine neue Plattform zu entwickeln, begannen wir sofort mit der Erstellung der MVP-Version. Ein Team von vier PHP- und drei Front-End-Entwicklern hat diese Aufgabe in etwa dreieinhalb Wochen bewältigt. Alle Arbeiten wurden an Symfony 5 durchgeführt und nur Videoanrufe und Chats wurden delegiert - sie wurden mithilfe unseres G-Core Meet-Service implementiert. Das Backoffice für das alte System hat sich ebenfalls als nützlich erwiesen: Wir haben es in nur wenigen Tagen geschafft, es an MVP anzupassen. Infolgedessen deckte die MVP-Version des Systems 80% der Funktionalität der alten Plattform ab. Jetzt wird es übrigens auch für eine weitere Aufgabe verwendet - irgendwann haben wir den MVP für den Helpdesk der luxemburgischen E-Health-Agentur geklont.damit Administratoren dort Benutzer anrufen können.
Als das MVP fertig war, begannen wir mit der Entwicklung einer vollwertigen neuen Plattform. API-Plattform und ReactJS in Verbindung mit Next.js für die Clientseite wurden als Basis für die API verwendet. Nicht ohne interessante Aufgaben.
1. Benachrichtigungen implementieren
Eine der Schwierigkeiten trat bei Benachrichtigungen auf. Da API-Clients sowohl mobile Anwendungen als auch unser SPA sein können, war eine kombinierte Lösung erforderlich.
Zunächst haben wir uns für den Mercure Hub entschieden, mit dem Kunden über SSE (Server Sent Event) interagieren. Unabhängig davon, wie die Entwickler der API-Plattform diese Lösung selbst beworben haben, lehnte unser mobiles Team sie ab, da die Anwendung nur in einem aktiven Zustand Benachrichtigungen erhalten konnte.
Auf diese Weise kamen wir zu Firebase, mit dem wir die Unterstützung für native Push-Benachrichtigungen auf Mobilgeräten erreichen konnten, und verließen den Mercure Hub für Browseranwendungen. Wenn nun ein Ereignis im System aufgetreten ist, benachrichtigen wir den Benutzer über den privaten Kanal, den wir im Mercure Hub benötigen, und senden zusätzlich einen Push für ein mobiles Gerät an Firebase.
Warum haben wir nicht sofort alles auf Firebase implementiert? Hier ist alles einfach: Trotz der Unterstützung von Web-Clients funktionieren Browser ohne Push-API - dieselbe Safari und die meisten mobilen Browser - nicht damit. Wir planen jedoch weiterhin, Push-Benachrichtigungen von Firebase für Benutzer zu implementieren, die unterstützte Browser verwenden.
2. Funktionsprüfungen
Eine weitere interessante Situation ergab sich, als wir Funktionstests für die API durchführten. Wie Sie wissen, sollte jeder von ihnen in einer sauberen Umgebung arbeiten. Aber jedes Mal stellte sich heraus, dass es in Bezug auf die Leistung teuer war, die Basis anzuheben und die für das Testen erforderlichen Grundvorrichtungen + Vorrichtungen auszufüllen. Um dies zu vermeiden, haben wir uns in der Anfangsphase entschieden, die Datenbank basierend auf Entity Mapping (
bin/console doctrine:schema:create
) zu erhöhen und erst dann Basic Fixtures (
bin/console doctrine:fixtures:load
) hinzuzufügen .
Anschließend haben wir mithilfe der Erweiterung dama / doctrine-test-bundle sichergestellt, dass die Ausführung jedes Tests in eine Transaktion eingeschlossen ist und am Ende des Testfalls ohne Verpflichtung zurückgesetzt wird. Selbst wenn während des Tests Änderungen an der Datenbank vorgenommen werden, werden diese nicht festgeschrieben, und die Datenbank bleibt nach dem Ausführen im selben Zustand wie vor dem Start von PHPUnit.
Damit für jeden Endpunkt mindestens ein Test geschrieben wird, haben wir einen automatischen Überprüfungstest durchgeführt. Es erkennt alle registrierten Routen und sucht nach Tests für sie. So wird beispielsweise für die Route app_appointment_create geprüft, ob der Ordner enthält
tests/Functional/App/Appointment CreateTest.php
.
Zusätzlich wird die Qualität des Codes von PHP-CS-Fixer, php-cpd und PHPStan mit Erweiterungen wie phpstan-strict-rules überwacht.
3. Client-Seite
Für Ärzte und Patienten haben wir zwei unabhängige Clientanwendungen mit derselben Benutzeroberfläche und ähnlichen Funktionen erstellt. Um die Wiederverwendung von Funktionalität und Benutzeroberfläche zu etablieren, haben wir uns für ein Monorepository entschieden, das Bibliotheken und Anwendungen enthält. Gleichzeitig werden die Anwendungen selbst unabhängig voneinander erstellt und bereitgestellt, können jedoch von denselben Bibliotheken abhängen.
Mit diesem Ansatz können Sie eine Funktion (Bibliothek) in einer Pull-Anforderung erstellen und in alle von Ihnen benötigten Anwendungen integrieren. Dieser Vorteil führte zur Verwendung eines Monorepositorys, anstatt Funktionen in separaten npm-Bibliotheken und -Projekten in verschiedenen Repositorys zu implementieren.
Zum Konfigurieren des Monorepositorys wird die Bibliothek ns.js verwendet, mit der Sie aus der Box Bibliotheken und Anwendungen für React und Next.js erstellen können. Dieser Stapel wird im Projekt verwendet. Wir verwenden ESLint und Prettier, um die Codequalität zu verfolgen, Jest, um Komponententests zu schreiben, und React Testing Library, um React-Komponenten zu testen.
Der Arzt kam: Was am Ende passiert ist
In nur fünf Monaten waren alle Probleme behoben und die neue Plattform wurde für Benutzer aller Geräte verfügbar: Wir haben eine Webversion des Dienstes sowie mobile Anwendungen für iOS und Android vorbereitet.
Seit mehr als 4 Monaten ermöglicht der Service Patienten, Online-Konsultationen von Ärzten und Zahnärzten zu erhalten. Sie können sowohl im Audio- als auch im Videoformat stattfinden. Infolgedessen schreiben Ärzte Rezepte und teilen medizinische Aufzeichnungen und Testergebnisse sicher mit Patienten.
Die Plattform steht allen Angehörigen der Gesundheitsberufe, Anwohnern und Arbeitnehmern in Luxemburg zur Verfügung. Jetzt arbeitet sie in zwei der größten Gesundheitseinrichtungen des Landes - im Krankenhaus. Robert Schuman (Hôpitaux Robert Schuman) und das Krankenhauszentrum. Emile Mayrisch (Center Hospitalier Emile Mayrisch). Der Service wird an einem sicheren Ort der G-Core Labs-Cloud im Luxemburger EDH Tier IV-Rechenzentrum bereitgestellt, wo eine virtuelle Umgebung gemäß den erforderlichen Spezifikationen dafür konfiguriert wird.