Digamos que está codificando una función que toma datos de una API externa MyAPI
.
Esa API externa MyAPI
tiene un contrato que establece que devolverá a string
o a number
.
¿Se recomienda para protegerse contra cosas como null
, undefined
, boolean
, etc, incluso aunque no es parte de la API de MyAPI
? En particular, dado que no tiene control sobre esa API, no puede hacer la garantía mediante algo como el análisis de tipo estático, por lo que es mejor prevenir que curar.
Estoy pensando en relación con el principio de robustez .
design
api
api-design
web-services
functions
Adam Thompson
fuente
fuente
<!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>
Respuestas:
Nunca debe confiar en las entradas de su software, independientemente de la fuente. No solo es importante validar los tipos, sino también los rangos de entrada y la lógica empresarial. Según un comentario, esto está bien descrito por OWASP
Si no lo hace, en el mejor de los casos le dejará con datos basura que luego tendrá que limpiar, pero en el peor de los casos, dejará una oportunidad para explotaciones maliciosas si ese servicio ascendente se ve comprometido de alguna manera (por ejemplo, el pirateo de Target). El rango de problemas intermedios incluye poner su aplicación en un estado irrecuperable.
De los comentarios puedo ver que quizás mi respuesta podría necesitar un poco de expansión.
Por "nunca confíe en las entradas", me refiero simplemente a que no puede asumir que siempre recibirá información válida y confiable de los sistemas ascendentes o descendentes, y por lo tanto, siempre debe desinfectar esa entrada lo mejor que pueda o rechazarla eso.
Un argumento surgió en los comentarios que abordaré a modo de ejemplo. Si bien sí, tiene que confiar en su sistema operativo hasta cierto punto, no es irrazonable, por ejemplo, rechazar los resultados de un generador de números aleatorios si le pide un número entre 1 y 10 y responde con "bob".
Del mismo modo, en el caso del OP, definitivamente debe asegurarse de que su aplicación solo acepte entradas válidas del servicio ascendente. Lo que haga cuando no esté bien depende de usted y depende en gran medida de la función comercial real que esté tratando de lograr, pero mínimamente lo registraría para una depuración posterior y de lo contrario se aseguraría de que su aplicación no se ejecute en un estado irrecuperable o inseguro.
Si bien nunca puede saber cada entrada posible que alguien / algo pueda darle, ciertamente puede limitar lo que está permitido en función de los requisitos comerciales y hacer alguna forma de entrada en la lista blanca de entrada en función de eso.
fuente
Si por supuesto. Pero, ¿qué te hace pensar que la respuesta podría ser diferente?
Seguramente no querrás dejar que tu programa se comporte de manera impredecible en caso de que la API no devuelva lo que dice el contrato, ¿no? Entonces al menos tienes que lidiar con ese comportamiento de alguna manera . Una forma mínima de manejo de errores siempre vale el esfuerzo (¡muy mínimo!), Y no hay absolutamente ninguna excusa para no implementar algo como esto.
Sin embargo, la cantidad de esfuerzo que debe invertir para tratar un caso de este tipo depende en gran medida del caso y solo puede responderse en el contexto de su sistema. A menudo, una breve entrada de registro y dejar que la aplicación finalice correctamente puede ser suficiente. A veces, será mejor implementar un manejo detallado de excepciones, lidiar con diferentes formas de valores de retorno "incorrectos", y tal vez tenga que implementar alguna estrategia alternativa.
Pero hace una gran diferencia si está escribiendo solo una aplicación de formato de hoja de cálculo interna, para ser utilizada por menos de 10 personas y donde el impacto financiero de un bloqueo de la aplicación es bastante bajo, o si está creando una nueva conducción autónoma de automóviles sistema, donde un bloqueo de la aplicación puede costar vidas.
Por lo tanto, no hay atajos para reflexionar sobre lo que está haciendo , usar su sentido común siempre es obligatorio.
fuente
El Principio de Robustez - específicamente, la mitad de "sé liberal en lo que aceptas" - es una muy mala idea en el software. Originalmente se desarrolló en el contexto del hardware, donde las restricciones físicas hacen que las tolerancias de ingeniería sean muy importantes, pero en el software, cuando alguien le envía una entrada con formato incorrecto o incorrecto, tiene dos opciones. Puede rechazarlo (preferiblemente con una explicación de lo que salió mal) o puede intentar averiguar qué se supone que significa.
Nunca, nunca, nunca elija esa segunda opción a menos que tenga recursos equivalentes al equipo de búsqueda de Google para lanzar su proyecto, porque eso es lo que se necesita para crear un programa informático que haga algo cercano a un trabajo decente en ese dominio problemático en particular. (E incluso entonces, las sugerencias de Google se sienten como si salieran directamente del campo izquierdo aproximadamente la mitad del tiempo). Si intentas hacerlo, terminarás con un gran dolor de cabeza donde tu programa tratará de interpretarlo con frecuencia. mala entrada como X, cuando el remitente realmente quiso decir Y.
Esto es malo por dos razones. La obvia es porque entonces tienes datos incorrectos en tu sistema. La menos obvia es que, en muchos casos, ni usted ni el remitente se darán cuenta de que algo salió mal hasta mucho más tarde en el camino cuando algo explota en su cara, y de repente tiene un desastre grande y costoso que arreglar y no tiene idea lo que salió mal porque el efecto notable está muy lejos de la causa raíz.
Es por eso que existe el principio Fail Fast; ahorre a todos los involucrados el dolor de cabeza al aplicarlo a sus API.
fuente
En general, el código debe construirse para mantener al menos las siguientes restricciones siempre que sea práctico:
Cuando se le da la entrada correcta, produzca la salida correcta.
Cuando se le da una entrada válida (que puede o no ser correcta), produce una salida válida (del mismo modo).
Cuando reciba una entrada no válida, procese sin efectos secundarios más allá de los causados por la entrada normal o los que se definen como señalización de un error.
En muchas situaciones, los programas esencialmente pasarán por varios fragmentos de datos sin preocuparse especialmente de si son válidos. Si tales fragmentos contienen datos no válidos, la salida del programa probablemente contendrá datos no válidos como consecuencia. A menos que un programa esté específicamente diseñado para validar todos los datos y garantizar que no producirá resultados no válidos, incluso cuando se les da una entrada no válida , los programas que procesan su salida deben permitir la posibilidad de datos no válidos dentro de él.
Si bien a menudo es deseable validar los datos desde el principio, no siempre es particularmente práctico. Entre otras cosas, si la validez de un fragmento de datos depende del contenido de otros fragmentos, y si la mayoría de los datos introducidos en alguna secuencia de pasos se filtrará en el camino, lo que limitará la validación a los datos que lo atraviesan Todas las etapas pueden producir un rendimiento mucho mejor que tratar de validar todo.
Además, incluso si solo se espera que un programa reciba datos prevalidados, a menudo es bueno que cumpla con las restricciones anteriores de todos modos siempre que sea práctico. Repetir la validación completa en cada paso del procesamiento a menudo sería una pérdida de rendimiento importante, pero la cantidad limitada de validación necesaria para mantener las restricciones anteriores puede ser mucho más barata.
fuente
Comparemos los dos escenarios e intentemos llegar a una conclusión.
Escenario 1 Nuestra aplicación supone que la API externa se comportará según el acuerdo.
Escenario 2 Nuestra aplicación supone que la API externa puede comportarse mal, por lo tanto, agregue precauciones.
En general, existe la posibilidad de que cualquier API o software viole los acuerdos; puede deberse a un error o condiciones inesperadas. Incluso una API puede tener problemas en los sistemas internos que resultan en resultados inesperados.
Si nuestro programa está escrito asumiendo que la API externa se adherirá a los acuerdos y evitará agregar precauciones; ¿Quién será la parte que enfrenta los problemas? Seremos nosotros, los que tengamos el código de integración escrito.
Por ejemplo, los valores nulos que ha elegido. Digamos, según el acuerdo de API, la respuesta debe tener valores no nulos; pero si se viola repentinamente, nuestro programa generará NPE.
Por lo tanto, creo que será mejor asegurarse de que su aplicación tenga algún código adicional para abordar escenarios inesperados.
fuente
Siempre debe validar los datos entrantes, ingresados por el usuario o de otra manera, por lo que debe tener un proceso para manejar cuando los datos recuperados de esta API externa no sean válidos.
En términos generales, cualquier costura donde se unen sistemas extraorganizacionales debe requerir autenticación, autorización (si no se define simplemente por autenticación) y validación.
fuente
En general, sí, siempre debe protegerse contra entradas defectuosas, pero dependiendo del tipo de API, "proteger" significa cosas diferentes.
Para una API externa a un servidor, no desea crear accidentalmente un comando que bloquea o compromete el estado del servidor, por lo que debe protegerse contra eso.
Para una API como, por ejemplo, una clase de contenedor (lista, vector, etc.), arrojar excepciones es un resultado perfectamente bueno, comprometer el estado de la instancia de clase puede ser aceptable en cierta medida (por ejemplo, un contenedor ordenado provisto de un operador de comparación defectuoso no lo será). ser ordenado), incluso el bloqueo de la aplicación puede ser aceptable, pero comprometer el estado de la aplicación, por ejemplo, escribir en ubicaciones de memoria aleatorias no relacionadas con la instancia de clase, probablemente no lo sea.
fuente
Para dar una opinión ligeramente diferente: creo que puede ser aceptable simplemente trabajar con los datos que se le proporcionan, incluso si viola su contrato. Esto depende del uso: es algo que DEBE ser una cadena para usted, o es algo que simplemente está mostrando / no usa, etc. En este último caso, simplemente acéptelo. Tengo una API que solo necesita el 1% de los datos entregados por otra API. No podría importarme menos qué tipo de datos hay en el 99%, por lo que nunca lo comprobaré.
Tiene que haber un equilibrio entre "tener errores porque no verifico mis entradas lo suficiente" y "Rechazo datos válidos porque soy demasiado estricto".
fuente
Mi opinión sobre esto es siempre, siempre verificar cada entrada a mi sistema. Eso significa que todos los parámetros devueltos por una API deben verificarse, incluso si mi programa no lo usa. También tiendo a verificar la corrección de cada parámetro que envío a una API. Solo hay dos excepciones a esta regla, ver más abajo.
La razón de la prueba es que si por alguna razón la API / entrada es incorrecta, mi programa no puede confiar en nada. ¿Quizás mi programa estaba vinculado a una versión anterior de la API que hace algo diferente de lo que creo? Tal vez mi programa tropezó con un error en el programa externo que nunca antes había sucedido. O peor aún, sucede todo el tiempo, ¡pero a nadie le importa! ¿Quizás el programa externo está siendo engañado por un hacker para devolver cosas que pueden dañar mi programa o el sistema?
Las dos excepciones para probar todo en mi mundo son:
Rendimiento después de una cuidadosa medición del rendimiento:
Cuando no tienes idea de qué hacer con un error
Exactamente qué tan cuidadosamente verificar las entradas / valores de retorno es una pregunta importante. Como ejemplo, si se dice que la API devuelve una cadena, comprobaría que:
el tipo de datos actully es una cadena
y esa longitud está entre los valores mínimo y máximo. Siempre verifique las cadenas para el tamaño máximo que mi programa puede esperar manejar (devolver cadenas demasiado grandes es un problema de seguridad clásico en los sistemas en red).
Algunas cadenas deben ser verificadas por caracteres o contenido "ilegales" cuando sea relevante. Si su programa puede enviar la cadena para decir una base de datos más tarde, es una buena idea verificar los ataques a la base de datos (buscar inyección SQL). Estas pruebas se realizan mejor en los límites de mi sistema, donde puedo determinar de dónde vino el ataque y puedo fallar temprano. Hacer una prueba de inyección SQL completa puede ser difícil cuando las cadenas se combinan más tarde, por lo que esa prueba debe hacerse antes de llamar a la base de datos, pero si puede encontrar algunos problemas temprano, puede ser útil.
La razón para probar los parámetros que envío a la API es para asegurarme de que obtengo un resultado correcto. Nuevamente, hacer estas pruebas antes de llamar a una API puede parecer innecesario, pero requiere muy poco rendimiento y puede detectar errores en mi programa. Por lo tanto, las pruebas son más valiosas cuando se desarrolla un sistema (pero hoy en día todos los sistemas parecen estar en continuo desarrollo). Dependiendo de los parámetros, las pruebas pueden ser más o menos exhaustivas, pero tiendo a encontrar que a menudo puede establecer valores mínimos y máximos permitidos en la mayoría de los parámetros que mi programa podría crear. ¿Quizás una cadena siempre debe tener al menos 2 caracteres y tener un máximo de 2000 caracteres? El mínimo y el máximo deben estar dentro de lo que permite la API, ya que sé que mi programa nunca usará el rango completo de algunos parámetros.
fuente