Doutor na nuvem: como criamos um serviço de telemedicina para combater o coronavírus em Luxemburgo

Começamos a desenvolver a plataforma ... por acidente. Tudo começou com uma pandemia, devido à qual o governo luxemburguês ordenou a criação de um serviço de consulta online com médicos. Para tirar o máximo proveito da plataforma, eles decidiram adicionar tudo o que é necessário para o trabalho completo dos médicos com os pacientes. A troca de todo o tipo de documentos médicos e até a emissão de receitas de medicamentos não foram excepção - o antigo vendedor colocou tudo imediatamente ao serviço. E então, para que todos os benefícios estivessem seguros, eles nos procuraram com um produto pronto - tudo o que precisávamos era implantar a plataforma em um ponto de presença seguro da nuvem.



Um mês e meio depois, o serviço funcionou com sucesso no data center Luxembourg EDH Tier IV, mas mesmo na primeira reunião, reconhecemos que era um elo fraco em todo o projeto: ao contrário do ponto de presença, a plataforma não podia se orgulhar de segurança e tinha uma dúzia de outras deficiências. A decisão era óbvia - o serviço tinha que ser feito do zero. Restou para convencer o governo de Luxemburgo.









Esse médico está quebrado, traga um novo: por que decidimos fazer uma nova plataforma



Para sugerir colocar uma bala na plataforma antiga, apenas tivemos que dar uma olhada. Em vez de corrigir todos os problemas de segurança possíveis e atualizar o design, o serviço foi mais fácil de construir do zero e tínhamos três bons motivos para isso.



1. O sistema foi desenvolvido sem uma estrutura



Por causa disso, havia um número impensável de problemas na plataforma. Se algum framework popular fosse usado para criar um serviço - Symfony, Laravel, Yii ou qualquer outro - então mesmo desenvolvedores medíocres teriam evitado a maioria dos problemas de segurança, porque ORMs podem preparar consultas para o banco de dados, motores de templates podem codificar o conteúdo recebido de o usuário e os formulários são protegidos por padrão com tokens CSRF, e a autorização e autenticação geralmente estão disponíveis quase que pronto para uso. No mesmo caso, a plataforma deixou nosso desenvolvedor nostálgico pelos dias de estudante - o código parecia quase o mesmo de seu primeiro trabalho de laboratório na universidade.



Por exemplo, veja como a conexão com o banco de dados foi implementada. As credenciais de conexão foram codificadas acima no mesmo arquivo.



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. Havia muitos problemas de segurança na plataforma



Após a auditoria, percebemos que com tais falhas é impossível entrar em produção mesmo com um simples blog, muito menos com uma plataforma com dados confidenciais. Aqui estão alguns deles.



  • Injeção SQL. 90% das solicitações incluíram dados inseridos por usuários sem preparação preliminar.



    $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. O código personalizado não foi filtrado de forma alguma antes da saída:



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





    Além disso, as informações que entraram no banco de dados, como o motivo da consulta ao médico, não foram filtradas antes de serem gravadas no banco de dados ou antes de serem processadas na página.
  • . ID , . . , ID .
  • . , -. , qury-string .



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



  • Bibliotecas de terceiros desatualizadas. No antigo fornecedor, ninguém seguia as versões das bibliotecas de terceiros, que, aliás, em vez de usar o mesmo Composer, eram simplesmente copiadas para o projeto. Além disso, algumas dessas dependências de terceiros foram personalizadas.
  • Armazenamento inseguro de senhas de usuários. Funções criptográficas não confiáveis ​​foram usadas para armazenar senhas.



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



  • Vulnerabilidade CSRF. Nenhum formulário foi protegido com um token CSRF.
  • Falta de proteção contra ataques de força bruta. Simplesmente não estava lá. Não.


Aqui poderíamos continuar indefinidamente, mas esses problemas são suficientes para entender: ou o sistema tinha problemas sérios ou ele próprio era um problema sério.



3. O código era difícil de manter e estender



As questões de segurança não se limitavam a tudo. Para nossa surpresa, o projeto carecia de um sistema de controle de versão. O código estava completamente desestruturado. O diretório raiz do servidor web continha arquivos como ajax-new.php, ajax2.php, e todos eles foram usados ​​no código. Também não houve um delineamento claro em camadas (apresentação, aplicação, dados). Na grande maioria dos casos, o arquivo de código era uma mistura de PHP, HTML e JavaScript.



Tudo isso levou ao fato de que, quando fomos solicitados a fazer um backoffice primitivo para este sistema, a melhor solução foi implantar o Symfony 4 lado a lado em conjunto com o Sonata Admin e não mexer no código existente. É claro que, se nos pedissem para adicionar novas oportunidades para médicos ou pacientes, levaríamos muito tempo e energia. E como não se falava em testes automáticos, a probabilidade de quebrar algo seria extremamente alta.



Tudo isso foi suficiente para o governo de Luxemburgo - recebemos luz verde para desenvolver uma nova plataforma.



The Doctor Rides-Rides: como desenvolvemos uma nova plataforma



Começamos a nos preparar para o desenvolvimento de uma nova plataforma desde o início - mesmo quando vimos a ideia de um antigo fornecedor. Portanto, quando recebemos autorização para desenvolver uma nova plataforma, imediatamente começamos a criar sua versão MVP. Uma equipe de quatro desenvolvedores de PHP e três desenvolvedores front-end lidou com essa tarefa em cerca de três semanas e meia. Todo o trabalho foi realizado no Symfony 5, e apenas videochamadas e chats foram delegados - eles foram implementados usando nosso serviço G-Core Meet. O backoffice para o sistema antigo também foi útil: conseguimos adaptá-lo ao MVP em apenas alguns dias. Como resultado, a versão MVP do sistema cobria 80% da funcionalidade da plataforma antiga. Agora, a propósito, ele também é usado para mais uma tarefa - em um ponto nós clonamos o MVP para o helpdesk da agência de e-health de Luxemburgo,para que os administradores possam ligar para os usuários.



Quando o MVP estava pronto, começamos a desenvolver uma nova plataforma completa. API Platform e ReactJS em conjunto com Next.js para o lado do cliente foram usados ​​como base para a API. Não sem tarefas interessantes.



1. Implementação de notificações



Uma das dificuldades surgiu com as notificações. Como os clientes API podem ser aplicativos móveis e nosso SPA, uma solução combinada é necessária.

Primeiramente, escolhemos o Mercure Hub, com o qual os clientes interagem por meio de SSE (Server Sent Event). Mas não importa como os próprios criadores da API Platform promovessem essa solução, nossa equipe móvel a rejeitava, pois o aplicativo poderia receber notificações com ela apenas em um estado ativo.



Foi assim que chegamos ao Firebase, com o qual conseguimos obter suporte para notificações push nativas em dispositivos móveis, e deixamos o Mercure Hub para aplicativos de navegador. Agora, quando ocorre um evento no sistema, notificamos o usuário por meio do canal privado de que precisamos no Mercure Hub e, além disso, enviamos um push ao Firebase para um dispositivo móvel.



Por que não implementamos tudo imediatamente no Firebase? Tudo é simples aqui: apesar do suporte de clientes da web, navegadores sem a API Push - o mesmo Safari e a maioria dos navegadores móveis - não funcionam com ela. No entanto, ainda estamos planejando implementar notificações push do Firebase para os usuários que usam navegadores compatíveis.



2. Testes funcionais



Outra situação interessante surgiu quando estávamos fazendo testes funcionais para a API. Como você sabe, cada um deles deve trabalhar em um ambiente limpo. Mas a cada vez ficava caro em termos de desempenho elevar a base e preencher os acessórios básicos + acessórios necessários para o teste. Para evitar isso, decidimos no estágio inicial aumentar o banco de dados com base no mapeamento de entidades ( bin/console doctrine:schema:create



) e só então adicionar acessórios básicos ( bin/console doctrine:fixtures:load



).



Em seguida, usando a extensão dama / doctrine-test-bundle, garantimos que a execução de cada teste é envolvida em uma transação e no final do caso de teste a reverte sem commit. Devido a isso, mesmo que alterações sejam feitas no banco de dados durante o teste, elas não são confirmadas e o banco de dados após a execução permanece no mesmo estado de antes do PHPUnit ser iniciado.

Para que pelo menos um teste seja escrito para cada endpoint, fizemos um teste de revisão automática. Ele detecta todas as rotas registradas e verifica se há testes para elas. Portanto, por exemplo, para a rota app_appointment_create, ele verifica se a pasta contém tests/Functional/App/Appointment CreateTest.php



.



Além disso, a qualidade do código é monitorada por PHP-CS-Fixer, php-cpd e PHPStan com extensões como phpstan-strict-rules.



3. Lado do cliente



Para médicos e pacientes, criamos dois aplicativos cliente independentes com a mesma IU e recursos semelhantes. Para estabelecer a reutilização de funcionalidade e UI neles, decidimos usar um monorepositório, que inclui bibliotecas e aplicativos. Ao mesmo tempo, os próprios aplicativos são construídos e implantados independentemente uns dos outros, mas podem depender das mesmas bibliotecas.



Essa abordagem permite criar um recurso (biblioteca) em uma solicitação pull e integrá-lo a todos os aplicativos de que você precisa. Essa vantagem levou ao uso de um monorepositório em vez de implementar recursos em bibliotecas npm separadas e projetos em repositórios diferentes.

Para configurar o monorepositório, a biblioteca ns.js é usada, que desde a caixa permite construir bibliotecas e aplicativos para React e Next.js, e é esta pilha que é usada no projeto. Usamos ESLint e Prettier para controlar a qualidade do código, Jest para escrever testes de unidade e React Testing Library para testar componentes React.



O médico chegou: o que aconteceu no final



Em apenas cinco meses, todos os problemas foram resolvidos e a nova plataforma ficou à disposição dos usuários de qualquer dispositivo: preparamos uma versão web do serviço, além de aplicativos mobile para iOS e Android.



Há mais de 4 meses, o serviço permite que os pacientes recebam consultas online de médicos e dentistas. Eles podem ocorrer em formatos de áudio e vídeo. Como resultado, os médicos escrevem prescrições e compartilham com segurança registros médicos e resultados de exames com os pacientes.



A plataforma está disponível para todos os profissionais de saúde, residentes e trabalhadores em Luxemburgo. Agora ela trabalha em 2 das maiores instituições de saúde do país - no Hospital. Robert Schuman (Hôpitaux Robert Schuman) e o Centro Hospitalar. Emile Mayrisch (Centre Hospitalier Emile Mayrisch). O serviço é implantado em um ponto de presença seguro da nuvem G-Core Labs no data center Luxembourg EDH Tier IV, onde um ambiente virtual é configurado para ele de acordo com as especificações exigidas.



All Articles