Digamos que você esteja codificando uma função que recebe entrada de uma API externa MyAPI
.
Essa API externa MyAPI
possui um contrato que declara que retornará a string
ou a number
.
É recomendado para proteger contra coisas como null
, undefined
, boolean
, etc., mesmo que isso não faz parte da API de MyAPI
? Em particular, como você não tem controle sobre essa API, não pode fazer a garantia por meio de uma análise de tipo estática, por isso é melhor prevenir do que remediar?
design
api
api-design
web-services
functions
Adam Thompson
fonte
fonte
<!doctype html><html><head><title>504 Gateway Timeout</title></head><body>The server was unable to process your request. Make sure you have typed the address correctly. If the problem persists, please try again later.</body></html>
Respostas:
Você nunca deve confiar nas entradas do seu software, independentemente da fonte. Não apenas a validação dos tipos é importante, mas também as faixas de entrada e a lógica de negócios. Por um comentário, isso é bem descrito pela OWASP
Não fazer isso, na melhor das hipóteses, deixará você com dados de lixo que você precisará limpar posteriormente, mas, na pior das hipóteses, você terá uma oportunidade para explorações mal-intencionadas se esse serviço upstream for comprometido de alguma forma (qv o Target hack). A variedade de problemas entre eles inclui colocar seu aplicativo em um estado irrecuperável.
Pelos comentários, vejo que talvez minha resposta possa usar um pouco de expansão.
Por "nunca confie nas entradas", quero dizer simplesmente que você não pode assumir que sempre receberá informações válidas e confiáveis de sistemas upstream ou downstream e, portanto, deve sempre limpar essas entradas da melhor maneira possível ou rejeitar isto.
Um argumento apareceu nos comentários que abordarei a título de exemplo. Embora sim, você precisa confiar em seu sistema operacional até certo ponto, não é razoável, por exemplo, rejeitar os resultados de um gerador de números aleatórios se você solicitar um número entre 1 e 10 e ele responder com "bob".
Da mesma forma, no caso do OP, você deve garantir definitivamente que seu aplicativo esteja aceitando apenas entradas válidas do serviço upstream. O que você faz quando não está bem depende de você e depende muito da função de negócios real que você está tentando realizar, mas você o registraria no mínimo para depuração posterior e garantirá que seu aplicativo não funcione em um estado irrecuperável ou inseguro.
Embora você nunca possa conhecer todas as entradas possíveis que alguém / alguma coisa possa fornecer, você certamente pode limitar o que é permitido com base nos requisitos de negócios e fazer alguma forma de inclusão na lista de permissões com base nisso.
fonte
Sim claro. Mas o que faz você pensar que a resposta pode ser diferente?
Você certamente não deseja que seu programa se comporte de maneira imprevisível, caso a API não retorne o que o contrato diz, não é? Assim, pelo menos você tem que lidar com o comportamento como um de alguma forma . Uma forma mínima de tratamento de erros sempre vale o esforço (muito mínimo!), E não há desculpa para não implementar algo assim.
No entanto, quanto esforço você deve investir para lidar com esse caso depende muito do caso e só pode ser respondido no contexto do seu sistema. Muitas vezes, basta uma pequena entrada de log e deixar o aplicativo terminar normalmente. Às vezes, será melhor implementar algum tratamento detalhado de exceções, lidar com diferentes formas de valores de retorno "errados" e talvez tenha que implementar alguma estratégia de fallback.
Mas faz muita diferença se você estiver escrevendo apenas algum aplicativo interno de formatação de planilha, para ser usado por menos de 10 pessoas e onde o impacto financeiro de uma falha no aplicativo é bastante baixo, ou se você estiver criando um novo carro autônomo sistema, onde uma falha no aplicativo pode custar vidas.
Portanto, não há atalho para refletir sobre o que você está fazendo , usar o bom senso é sempre obrigatório.
fonte
O princípio da robustez - especificamente, a metade "seja liberal no que você aceita" - é uma péssima idéia em software. Ele foi originalmente desenvolvido no contexto do hardware, onde as restrições físicas tornam as tolerâncias de engenharia muito importantes, mas no software, quando alguém envia uma entrada incorreta ou incorreta, você tem duas opções. Você pode rejeitá-lo (de preferência com uma explicação sobre o que deu errado) ou pode tentar descobrir o que deveria significar.
Nunca, nunca, nunca escolha a segunda opção, a menos que você tenha recursos equivalentes à equipe de Pesquisa do Google para lançar em seu projeto, porque é o necessário para criar um programa de computador que faça algo parecido com um trabalho decente nesse domínio de problema específico. (E mesmo assim, as sugestões do Google parecem estar saindo diretamente do campo esquerdo cerca de metade do tempo.) Se você tentar fazer isso, terá uma enorme dor de cabeça em que seu programa tentará interpretar com frequência entrada ruim como X, quando o que o remetente realmente queria dizer era Y.
Isso é ruim por duas razões. O óbvio é porque você tem dados ruins no seu sistema. O menos óbvio é que, em muitos casos, nem você nem o remetente perceberão que algo deu errado até muito mais tarde, quando algo explode em seu rosto e, de repente, você tem uma grande e cara bagunça para consertar e não faz ideia. o que deu errado porque o efeito perceptível está tão longe removido da causa raiz.
É por isso que o princípio Fail Fast existe; economize a todos os envolvidos a dor de cabeça aplicando-a às suas APIs.
fonte
Em geral, o código deve ser construído para manter, pelo menos, as seguintes restrições sempre que possível:
Quando receber a entrada correta, produza a saída correta.
Quando receber uma entrada válida (que pode ou não estar correta), produza uma saída válida (da mesma forma).
Quando receber uma entrada inválida, processe-a sem efeitos colaterais além daqueles causados pela entrada normal ou definidos como sinalização de erro.
Em muitas situações, os programas passam essencialmente por vários blocos de dados sem se preocupar particularmente se são válidos. Se esses pedaços contiverem dados inválidos, a saída do programa provavelmente conterá dados inválidos como conseqüência. A menos que um programa seja projetado especificamente para validar todos os dados e garantir que ele não produza saída inválida, mesmo quando recebida entrada inválida , os programas que processam sua saída devem permitir a possibilidade de dados inválidos dentro dele.
Embora a validação de dados no início seja frequentemente desejável, nem sempre é particularmente prático. Entre outras coisas, se a validade de um pedaço de dados depende do conteúdo de outros pedaços, e se a maioria dos dados alimentados em alguma sequência de etapas será filtrada ao longo do caminho, limitando a validação aos dados que o fazem passar todos os estágios podem gerar um desempenho muito melhor do que tentar validar tudo.
Além disso, mesmo que apenas se espere que um programa receba dados pré-validados, geralmente é bom que ele mantenha as restrições acima de qualquer maneira, sempre que possível. Repetir a validação completa em todas as etapas do processamento costuma ser uma grande perda de desempenho, mas a quantidade limitada de validação necessária para manter as restrições acima pode ser muito mais barata.
fonte
Vamos comparar os dois cenários e tentar chegar a uma conclusão.
Cenário 1 Nosso aplicativo assume que a API externa se comportará conforme o contrato.
Cenário 2 Nosso aplicativo assume que a API externa pode se comportar mal, portanto, adicione precauções.
Em geral, existe a chance de qualquer API ou software violar os contratos; pode ser devido a um bug ou a condições inesperadas. Mesmo uma API pode estar tendo problemas nos sistemas internos, resultando em resultados inesperados.
Se nosso programa for escrito, assumindo que a API externa cumprirá os contratos e evitará adicionar precauções; quem será a parte que enfrenta os problemas? Seremos nós, quem escreveu o código de integração.
Por exemplo, os valores nulos que você selecionou. Digamos, de acordo com o contrato da API, a resposta deve ter valores não nulos; mas se for subitamente violado, nosso programa resultará em NPEs.
Portanto, acredito que será melhor garantir que seu aplicativo tenha algum código adicional para lidar com cenários inesperados.
fonte
Você sempre deve validar os dados recebidos - inseridos pelo usuário ou não - para ter um processo em andamento para lidar quando os dados recuperados dessa API externa forem inválidos.
De um modo geral, qualquer emenda em que os sistemas extra-organizacionais se encontrem deve exigir autenticação, autorização (se não definida simplesmente pela autenticação) e validação.
fonte
Em geral, sim, você deve sempre se proteger contra entradas defeituosas, mas, dependendo do tipo de API, "guarda" significa coisas diferentes.
Para uma API externa para um servidor, você não deseja criar acidentalmente um comando que trava ou comprometa o estado do servidor; portanto, você deve se proteger.
Para uma API como, por exemplo, uma classe de contêiner (lista, vetor, etc.), lançar exceções é um resultado perfeitamente adequado, comprometendo o estado da instância da classe pode ser aceitável até certo ponto (por exemplo, um contêiner classificado com um operador de comparação com falha não será ser classificada), mesmo travar o aplicativo pode ser aceitável, mas comprometer o estado do aplicativo - por exemplo, gravar em locais de memória aleatórios não relacionados à instância da classe - provavelmente não é.
fonte
Para dar uma opinião um pouco diferente: acho que pode ser aceitável trabalhar apenas com os dados fornecidos, mesmo que isso viole o contrato. Isso depende do uso: é algo que DEVE ser uma string para você, ou é algo que você está apenas exibindo / não usa etc. No último caso, basta aceitá-lo. Eu tenho uma API que só precisa de 1% dos dados entregues por outra API. Eu não poderia me importar menos com o tipo de dados nos 99%, por isso nunca vou verificá-los.
É preciso haver um equilíbrio entre "ter erros porque não verifico minhas entradas o suficiente" e "rejeito dados válidos porque sou muito rigoroso".
fonte
Minha opinião é sempre verificar sempre todas as entradas do meu sistema. Isso significa que todos os parâmetros retornados de uma API devem ser verificados, mesmo que meu programa não o utilize. Costumo verificar também todos os parâmetros enviados a uma API para verificar se estão corretos. Existem apenas duas exceções a esta regra, veja abaixo.
O motivo do teste é que, se por algum motivo a API / entrada estiver incorreta, meu programa não poderá confiar em nada. Talvez meu programa tenha sido vinculado a uma versão antiga da API que faça algo diferente do que eu acredito? Talvez meu programa tenha encontrado um bug no programa externo que nunca havia acontecido antes. Ou pior, acontece o tempo todo, mas ninguém liga! Talvez o programa externo esteja sendo enganado por um hacker para devolver coisas que podem prejudicar meu programa ou o sistema?
As duas exceções para testar tudo no meu mundo são:
Desempenho após uma cuidadosa medição do desempenho:
Quando você não tem idéia do que fazer com um erro
Exatamente com que cuidado verificar as entradas / valores de retorno é uma questão importante. Por exemplo, se a API retornar uma string, eu verificaria se:
o tipo de dados realmente é uma sequência
e esse comprimento está entre os valores mínimo e máximo. Sempre verifique as strings quanto ao tamanho máximo que meu programa pode esperar (retornar strings muito grandes é um problema de segurança clássico em sistemas em rede).
Algumas seqüências de caracteres devem ser verificadas quanto a caracteres ou conteúdo "ilegais" quando isso for relevante. Se o seu programa enviar a string para dizer um banco de dados posteriormente, é uma boa ideia verificar se há ataques no banco de dados (procure por injeção de SQL). É melhor fazer esses testes nas fronteiras do meu sistema, onde posso identificar de onde veio o ataque e falhar cedo. A execução de um teste completo de injeção de SQL pode ser difícil quando as cadeias são combinadas posteriormente, para que o teste seja realizado antes de chamar o banco de dados, mas se você encontrar alguns problemas mais cedo, poderá ser útil.
O motivo para testar os parâmetros que eu envio à API é garantir que eu receba de volta um resultado correto. Novamente, fazer esses testes antes de chamar uma API pode parecer desnecessário, mas requer muito pouco desempenho e pode detectar erros no meu programa. Portanto, os testes são mais valiosos no desenvolvimento de um sistema (mas hoje em dia todo sistema parece estar em desenvolvimento contínuo). Dependendo dos parâmetros, os testes podem ser mais ou menos completos, mas eu costumo achar que você pode definir valores mínimos e máximos permitidos na maioria dos parâmetros que meu programa poderia criar. Talvez uma string sempre deva ter pelo menos 2 caracteres e ter no máximo 2000 caracteres? O mínimo e o máximo devem estar dentro do que a API permite, pois sei que meu programa nunca usará toda a gama de alguns parâmetros.
fonte