Doctor en la nube: como creamos un servicio de telemedicina para combatir el coronavirus en Luxemburgo

Empezamos a desarrollar la plataforma ... por accidente. Todo comenzó con una pandemia, por lo que el gobierno de Luxemburgo ordenó la creación de un servicio de consulta en línea con médicos. Para obtener el máximo beneficio de la plataforma, decidieron agregar todo lo que se necesita para el trabajo completo de los médicos con los pacientes. El intercambio de todo tipo de documentos médicos e incluso la emisión de recetas de medicamentos no fueron una excepción: el antiguo proveedor inmediatamente puso todo en servicio. Y luego, para que todas las bondades estuvieran seguras, recurrieron a nosotros con un producto listo para usar; todo lo que necesitábamos era implementar la plataforma en un punto de presencia seguro de la nube.



Un mes y medio después, el servicio funcionó con éxito en el centro de datos EDH Tier IV de Luxemburgo, pero incluso en la primera reunión, lo reconocimos como un eslabón débil en todo el proyecto: a diferencia del punto de presencia, la plataforma no podía presumir de seguridad y tenía una docena de otras deficiencias. La decisión fue obvia: el servicio tenía que hacerse desde cero. Quedó por convencer al gobierno de Luxemburgo.









Este doctor está roto, trae uno nuevo: por qué decidimos hacer una nueva plataforma



Para sugerir poner una bala en la vieja plataforma, solo tuvimos que echarle un vistazo. En lugar de solucionar todos los posibles problemas de seguridad y actualizar el diseño, el servicio fue más fácil de construir desde cero, y teníamos tres buenas razones para ello.



1. El sistema se desarrolló sin un marco



Debido a esto, hubo una cantidad impensable de problemas en la plataforma. Si se usara algún marco popular para crear un servicio (Symfony, Laravel, Yii o algo más), incluso los desarrolladores mediocres habrían evitado la mayoría de los problemas de seguridad, porque los ORM pueden preparar consultas a la base de datos, los motores de plantillas pueden codificar el contenido recibido de el usuario, y los formularios están protegidos de forma predeterminada con tokens CSRF, y la autorización y la autenticación suelen estar disponibles casi listas para usar. En el mismo caso, la plataforma hizo que nuestro desarrollador sintiera nostalgia por los días de estudiante: el código se veía casi igual que su primer trabajo de laboratorio en la universidad.



Por ejemplo, así es como se implementó la conexión a la base de datos. Las credenciales de conexión se codificaron anteriormente en el mismo archivo.



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. Hubo muchos problemas de seguridad en la plataforma.



Después de la auditoría, nos dimos cuenta de que con tales fallas es imposible entrar en producción incluso con un simple blog, y mucho menos con una plataforma con datos confidenciales. Éstos son algunos de ellos.



  • Inyección SQL. El 90% de las solicitudes incluían datos ingresados ​​por usuarios sin preparación previa.



    $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']
    ));
          
          



  • Vulnerabilidades XSS. El código personalizado no se filtró de ninguna manera antes de la salida:



    <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>
          
          





    Además, la información que entró en la base de datos, como el motivo de la consulta con un médico, no se filtró ni antes de escribir en la base de datos ni antes de procesarla en la página.
  • . ID , . . , ID .
  • . , -. , qury-string .



    $file_dir = $settings['documents']['dir'] . $_SESSION['client']['id'] . DIRECTORY_SEPARATOR . $_GET['id_user'];
          
          



  • Bibliotecas de terceros obsoletas. En el proveedor anterior, nadie siguió las versiones de las bibliotecas de terceros, que, por cierto, en lugar de usar el mismo Composer, simplemente se copiaron en el proyecto. Además, algunas de estas dependencias de terceros se han personalizado.
  • Almacenamiento inseguro de contraseñas de usuarios. Se utilizaron funciones criptográficas poco fiables para almacenar contraseñas.



    $sql = "
    	SELECT id, firstname, lastname
    	FROM user
    	WHERE id=%d AND password=PASSWORD('%s')
    ;";
    $result = $db->query(sprintf($sql, $_SESSION['user']['id'], $_POST['pass']));
          
          



  • Vulnerabilidad CSRF. No se ha protegido ningún formulario con un token CSRF.
  • Falta de protección contra ataques de fuerza bruta. Simplemente no estaba allí. No.


Aquí podríamos seguir y seguir, pero estos problemas son suficientes para entender: o el sistema tenía problemas serios, o él mismo era un problema serio.



3. El código era difícil de mantener y ampliar.



Los problemas de seguridad no se limitaron a todo. Para nuestra sorpresa, el proyecto carecía de un sistema de control de versiones. El código estaba completamente desestructurado. El directorio raíz del servidor web contenía archivos como ajax-new.php, ajax2.php, y todos se usaron en el código. Tampoco hubo una delimitación clara en capas (presentación, aplicación, datos). En la gran mayoría de los casos, el archivo de código era una mezcla de PHP, HTML y JavaScript.



Todo esto llevó al hecho de que cuando nos pidieron que hiciéramos un backoffice primitivo para este sistema, la mejor solución fue implementar Symfony 4 en conjunto con Sonata Admin y no tocar el código existente en absoluto. Está claro que si nos pidieran que agreguemos nuevas oportunidades para médicos o pacientes, nos llevaría mucho tiempo y energía. Y como no se hablaba de pruebas automáticas, la probabilidad de romper algo sería extremadamente alta.



Todo lo anterior fue suficiente para el gobierno de Luxemburgo: se nos dio luz verde para desarrollar una nueva plataforma.



The Doctor Rides-Rides: cómo desarrollamos una nueva plataforma



Comenzamos a prepararnos para el desarrollo de una nueva plataforma desde el principio, incluso cuando vimos la creación de un antiguo proveedor. Por lo tanto, cuando nos dieron el visto bueno para desarrollar una nueva plataforma, inmediatamente comenzamos a crear su versión MVP. Un equipo de cuatro desarrolladores de PHP y tres de front-end hizo frente a esta tarea en aproximadamente tres semanas y media. Todo el trabajo se llevó a cabo en Symfony 5 y solo se delegaron videollamadas y chats, que se implementaron utilizando nuestro servicio G-Core Meet. El backoffice del antiguo sistema también fue útil: logramos adaptarlo a MVP en solo un par de días. Como resultado, la versión MVP del sistema cubría el 80% de la funcionalidad de la plataforma anterior. Ahora, por cierto, también se usa para una tarea más: en un momento clonamos el MVP para el servicio de asistencia de la agencia de salud electrónica de Luxemburgo,para que los administradores puedan llamar a los usuarios.



Cuando el MVP estuvo listo, comenzamos a desarrollar una nueva plataforma completa. La plataforma API y ReactJS junto con Next.js para el lado del cliente se utilizaron como base para la API. No sin tareas interesantes.



1. Implementación de notificaciones



Una de las dificultades surgió con las notificaciones. Dado que los clientes de API podían ser tanto aplicaciones móviles como nuestro SPA, se requería una solución combinada.

Primero, elegimos Mercure Hub, con el que los clientes interactúan a través de SSE (Server Sent Event). Pero por mucho que los propios creadores de la Plataforma API promovieran esta solución, nuestro equipo móvil la rechazó, ya que la aplicación podía recibir notificaciones con ella solo en estado activo.



Así llegamos a Firebase, con el que logramos lograr soporte para notificaciones push nativas en dispositivos móviles, y dejamos Mercure Hub para aplicaciones de navegador. Ahora, cuando ocurrió un evento en el sistema, lo notificamos al usuario a través del canal privado que necesitamos en el Mercure Hub y, además, enviamos un push a Firebase para un dispositivo móvil.



¿Por qué no implementamos todo de inmediato en Firebase? Aquí todo es simple: a pesar del soporte de los clientes web, los navegadores sin la API Push, el mismo Safari y la mayoría de los navegadores móviles, no funcionan con él. Sin embargo, todavía estamos planeando implementar notificaciones push de Firebase para aquellos usuarios que usan navegadores compatibles.



2. Pruebas funcionales



Otra situación interesante surgió cuando estábamos haciendo pruebas funcionales para la API. Como sabes, cada uno de ellos debería funcionar en un entorno limpio. Pero cada vez resultó caro en términos de rendimiento elevar la base y completar los accesorios básicos + accesorios necesarios para las pruebas. Para evitar esto, decidimos en la etapa inicial aumentar la base de datos en función del mapeo de entidades ( bin/console doctrine:schema:create



) y solo luego agregar accesorios básicos ( bin/console doctrine:fixtures:load



).



Luego, usando la extensión dama / doctrine-test-bundle, nos aseguramos de que la ejecución de cada prueba esté envuelta en una transacción y al final del caso de prueba la retrotraiga sin comprometerse. Debido a esto, incluso si se realizan cambios en la base de datos durante la prueba, no se confirman y la base de datos después de la ejecución permanece en el mismo estado que antes de que se lanzara PHPUnit.

Para que se escriba al menos una prueba para cada punto final, hicimos una prueba de revisión automática. Detecta todas las rutas registradas y comprueba si hay pruebas para ellas. Entonces, por ejemplo, para la ruta app_appointment_create, verifica si la carpeta contiene tests/Functional/App/Appointment CreateTest.php



.



Además, la calidad del código es monitoreada por PHP-CS-Fixer, php-cpd y PHPStan con extensiones como phpstan-strict-rules.



3. Lado del cliente



Para médicos y pacientes, hemos creado dos aplicaciones de cliente independientes con la misma interfaz de usuario y capacidades similares. Para establecer la reutilización de la funcionalidad y la UI en ellos, decidimos utilizar un monorrepositorio, que incluye bibliotecas y aplicaciones. Al mismo tiempo, las propias aplicaciones se crean y se implementan de forma independiente entre sí, pero pueden depender de las mismas bibliotecas.



Este enfoque le permite crear una función (biblioteca) en una solicitud de extracción e integrarla en todas las aplicaciones que necesita. Esta ventaja llevó al uso de un monorepository en lugar de implementar características en bibliotecas npm separadas y proyectos en diferentes repositorios.

Para configurar el monorepositorio, se usa la biblioteca ns.js, que desde el cuadro le permite construir bibliotecas y aplicaciones para React y Next.js, y es esta pila la que se usa en el proyecto. Usamos ESLint y Prettier para realizar un seguimiento de la calidad del código, Jest para escribir pruebas unitarias y React Testing Library para probar los componentes de React.



Llegó el médico: lo que pasó al final



En tan solo cinco meses se resolvieron todos los problemas y la nueva plataforma quedó disponible para los usuarios de cualquier dispositivo: preparamos una versión web del servicio, así como aplicaciones móviles para iOS y Android.



Durante más de 4 meses, el servicio ha permitido que los pacientes reciban consultas en línea de médicos y dentistas. Pueden tener lugar en formatos de audio y video. Como resultado, los médicos escriben recetas y comparten de manera segura los registros médicos y los resultados de las pruebas con los pacientes.



La plataforma está disponible para todos los profesionales sanitarios, residentes y trabajadores de Luxemburgo. Ahora trabaja en 2 de las instituciones de salud más grandes del país: en el Hospital. Robert Schuman (Hôpitaux Robert Schuman) y el Centro Hospitalario. Emile Mayrisch (Centro hospitalario Emile Mayrisch). El servicio se implementa en un punto de presencia seguro de la nube de G-Core Labs en el centro de datos EDH Tier IV de Luxemburgo, donde se configura un entorno virtual para él de acuerdo con las especificaciones requeridas.



All Articles