Probar reglas de seguridad

Mientras desarrollas tu aplicación, puede que quieras restringir el acceso a tu base de datos de Firestore en modo nativo. Sin embargo, antes de lanzar la aplicación, necesitarás reglas de seguridad de Firestore más detalladas. Con el emulador de Firestore en modo nativo, además de crear prototipos y probar las funciones y el comportamiento generales de tu aplicación, puedes escribir pruebas unitarias que comprueben el comportamiento de tus reglas de seguridad de Firestore.

Guía de inicio rápido

Para ver algunos casos de prueba básicos con reglas sencillas, prueba la muestra de inicio rápido.

Información sobre las reglas de seguridad de Firestore

Implementa Firebase Authentication y reglas de seguridad de Firestore para la autenticación, la autorización y la validación de datos sin servidor cuando uses las bibliotecas de cliente web y para móviles.

Las reglas de seguridad de Firestore incluyen dos partes:

  1. Una match instrucción que identifica documentos de tu base de datos.
  2. Una expresión allow que controla el acceso a esos documentos.

Firebase Authentication verifica las credenciales de los usuarios y proporciona la base para los sistemas de acceso basados en usuarios y en roles.

Cada solicitud de base de datos de una biblioteca de cliente web o para dispositivos móviles de Firestore se evalúa en función de tus reglas de seguridad antes de leer o escribir datos. Si las reglas deniegan el acceso a alguna de las rutas de documentos especificadas, se producirá un error en toda la solicitud.

Consulta más información sobre las reglas de seguridad de Firestore en el artículo Empezar a usar las reglas de seguridad de Firestore.

Instalar el emulador

Para instalar el emulador de Firestore en modo nativo, usa la CLI de Firebase y ejecuta el siguiente comando:

firebase setup:emulators:firestore

Ejecutar el emulador

Empieza inicializando un proyecto de Firebase en tu directorio de trabajo. Este es un primer paso habitual cuando se usa la CLI de Firebase.

firebase init

Inicia el emulador con el siguiente comando. El emulador se ejecutará hasta que finalices el proceso:

firebase emulators:start --only firestore

En muchos casos, querrás iniciar el emulador, ejecutar un conjunto de pruebas y, a continuación, apagarlo después de que se hayan ejecutado las pruebas. Puedes hacerlo fácilmente con el comando emulators:exec:

firebase emulators:exec --only firestore "./my-test-script.sh"

Cuando se inicie, el emulador intentará ejecutarse en un puerto predeterminado (8080). Puedes cambiar el puerto del emulador modificando la sección "emulators" de tu archivo firebase.json:

{
  // ...
  "emulators": {
    "firestore": {
      "port": "YOUR_PORT"
    }
  }
}

Antes de ejecutar el emulador

Antes de empezar a usar el emulador, ten en cuenta lo siguiente:

  • El emulador cargará inicialmente las reglas especificadas en el campo firestore.rules de tu archivo firebase.json. Espera el nombre de un archivo local que contenga tus reglas de seguridad de Firestore y aplica esas reglas a todos los proyectos. Si no proporcionas la ruta del archivo local o usas el método loadFirestoreRules como se describe más abajo, el emulador tratará todos los proyectos como si tuvieran reglas abiertas.
  • Aunque la mayoría de los SDKs de Firebase funcionan directamente con los emuladores, solo la biblioteca @firebase/rules-unit-testing admite la simulación auth en las reglas de seguridad, lo que facilita mucho las pruebas unitarias. Además, la biblioteca admite algunas funciones específicas del emulador, como borrar todos los datos, tal como se indica a continuación.
  • Los emuladores también aceptarán tokens de autenticación de Firebase de producción proporcionados a través de SDKs de cliente y evaluarán las reglas en consecuencia, lo que permite conectar tu aplicación directamente a los emuladores en pruebas de integración y manuales.

Ejecutar pruebas unitarias locales

Ejecutar pruebas unitarias locales con el SDK de JavaScript v9

Firebase distribuye una biblioteca de pruebas unitarias de reglas de seguridad con su SDK de JavaScript de la versión 9 y con su SDK de la versión 8. Las APIs de la biblioteca son significativamente diferentes. Te recomendamos la biblioteca de pruebas de la versión 9, que es más sencilla y requiere menos configuración para conectarse a emuladores, por lo que se evita el uso accidental de recursos de producción. Para mantener la retrocompatibilidad, seguimos ofreciendo la biblioteca de pruebas de la versión 8.

Usa el módulo @firebase/rules-unit-testing para interactuar con el emulador que se ejecuta de forma local. Si se agota el tiempo de espera o se producen errores ECONNREFUSED, comprueba que el emulador esté en ejecución.

Te recomendamos que uses una versión reciente de Node.js para poder usar la notación async/await. Casi todo el comportamiento que quieras probar implica funciones asíncronas, y el módulo de pruebas está diseñado para funcionar con código basado en promesas.

La biblioteca de pruebas unitarias de reglas de la versión 9 siempre tiene en cuenta los emuladores y nunca accede a tus recursos de producción.

Importa la biblioteca mediante instrucciones de importación modular de la versión 9. Por ejemplo:

import {
  assertFails,
  assertSucceeds,
  initializeTestEnvironment
} from "@firebase/rules-unit-testing"

// Use `const { … } = require("@firebase/rules-unit-testing")` if imports are not supported
// Or we suggest `const testing = require("@firebase/rules-unit-testing")` if necessary.

Una vez importadas, la implementación de pruebas unitarias implica lo siguiente:

  • Crear y configurar un RulesTestEnvironment con una llamada a initializeTestEnvironment.
  • Configurar datos de prueba sin activar reglas mediante un método práctico que te permita omitirlas temporalmente: RulesTestEnvironment.withSecurityRulesDisabled.
  • Configurar un conjunto de pruebas y ganchos de antes y después de cada prueba con llamadas para limpiar los datos y el entorno de prueba, como RulesTestEnvironment.cleanup() o RulesTestEnvironment.clearFirestore().
  • Implementar casos de prueba que simulen estados de autenticación mediante RulesTestEnvironment.authenticatedContext y RulesTestEnvironment.unauthenticatedContext.

Métodos comunes y funciones de utilidad

Consulta también los métodos de prueba específicos del emulador en el SDK v9.

initializeTestEnvironment() => RulesTestEnvironment

Esta función inicializa un entorno de prueba para las pruebas unitarias de reglas. Llama a esta función primero para configurar la prueba. Para que se ejecute correctamente, los emuladores deben estar en funcionamiento.

La función acepta un objeto opcional que define un TestEnvironmentConfig, que puede constar de un ID de proyecto y ajustes de configuración del emulador.

let testEnv = await initializeTestEnvironment({
  projectId: "demo-project-1234",
  firestore: {
    rules: fs.readFileSync("firestore.rules", "utf8"),
  },
});

RulesTestEnvironment.authenticatedContext({ user_id: string, tokenOptions?: TokenOptions }) => RulesTestContext

Este método crea un RulesTestContext, que se comporta como un usuario de autenticación autenticado. Las solicitudes creadas a través del contexto devuelto tendrán un token de autenticación simulado adjunto. Opcionalmente, puedes enviar un objeto que defina reclamaciones personalizadas o anulaciones de las cargas útiles de los tokens de autenticación.

Usa el objeto de contexto de prueba devuelto en tus pruebas para acceder a cualquier instancia de emulador configurada, incluidas las configuradas con initializeTestEnvironment.

// Assuming a Firestore app and the Firestore emulator for this example
import { setDoc } from "firebase/firestore";

const alice = testEnv.authenticatedContext("alice", {  });
// Use the Firestore instance associated with this context
await assertSucceeds(setDoc(alice.firestore().doc('/users/alice'), { ... });

RulesTestEnvironment.unauthenticatedContext() => RulesTestContext

Este método crea un RulesTestContext, que se comporta como un cliente que no ha iniciado sesión mediante la autenticación. Las solicitudes creadas a través del contexto devuelto no tendrán tokens de autenticación de Firebase adjuntos.

Usa el objeto de contexto de prueba devuelto en tus pruebas para acceder a cualquier instancia de emulador configurada, incluidas las configuradas con initializeTestEnvironment.

// Assuming a Cloud Storage app and the Storage emulator for this example
import { getStorage, ref, deleteObject } from "firebase/storage";

const alice = testEnv.unauthenticatedContext();

// Use the Cloud Storage instance associated with this context
const desertRef = ref(alice.storage(), 'images/desert.jpg');
await assertSucceeds(deleteObject(desertRef));

RulesTestEnvironment.withSecurityRulesDisabled()

Ejecuta una función de configuración de prueba con un contexto que se comporte como si las reglas de seguridad estuvieran inhabilitadas.

Este método toma una función de retrollamada, que toma el contexto de omisión de reglas de seguridad y devuelve una promesa. El contexto se destruirá una vez que se resuelva o rechace la promesa.

RulesTestEnvironment.cleanup()

Este método destruye todos los RulesTestContexts creados en el entorno de prueba y limpia los recursos subyacentes, lo que permite salir de forma limpia.

Este método no cambia el estado de los emuladores de ninguna manera. Para restablecer los datos entre pruebas, usa el método de borrado de datos específico del emulador de aplicaciones.

assertSucceeds(pr: Promise<any>)) => Promise<any>

Esta es una función de utilidad de caso de prueba.

La función afirma que la promesa proporcionada que envuelve una operación del emulador se resolverá sin infracciones de las reglas de seguridad.

await assertSucceeds(setDoc(alice.firestore(), '/users/alice'), { ... });

assertFails(pr: Promise<any>)) => Promise<any>

Esta es una función de utilidad de caso de prueba.

La función afirma que la promesa proporcionada que envuelve una operación del emulador se rechazará con una infracción de las reglas de seguridad.

await assertFails(setDoc(alice.firestore(), '/users/bob'), { ... });

Métodos específicos del emulador

Consulta también los métodos de prueba y las funciones de utilidad comunes del SDK v9.

RulesTestEnvironment.clearFirestore() => Promise<void>

Este método borra los datos de la base de datos de Firestore que pertenecen al projectId configurado para el emulador de Firestore.

RulesTestContext.firestore(settings?: Firestore.FirestoreSettings) => Firestore;

Este método obtiene una instancia de Firestore para este contexto de prueba. La instancia del SDK de cliente de JavaScript de Firebase devuelta se puede usar con las APIs del SDK de cliente (modular de la versión 9 o de compatibilidad de la versión 9).

Visualizar evaluaciones de reglas

El emulador de Firestore en modo nativo te permite visualizar las solicitudes de clientes en la interfaz de usuario de Emulator Suite, incluido el seguimiento de la evaluación de las reglas de seguridad de Firebase.

Abra la pestaña Firestore > Solicitudes para ver la secuencia de evaluación detallada de cada solicitud.

Monitor de solicitudes del emulador de Firestore que muestra las evaluaciones de las reglas de seguridad

Generar informes de prueba

Después de ejecutar un conjunto de pruebas, puedes acceder a informes de cobertura de pruebas que muestran cómo se ha evaluado cada una de tus reglas de seguridad.

Para obtener los informes, consulta un endpoint expuesto en el emulador mientras se está ejecutando. Para obtener una versión compatible con el navegador, usa la siguiente URL:

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage.html

De esta forma, las reglas se dividen en expresiones y subexpresiones sobre las que puedes colocar el cursor para obtener más información, como el número de evaluaciones y los valores devueltos. Para obtener la versión JSON sin formato de estos datos, incluye la siguiente URL en tu consulta:

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage

Diferencias entre el emulador y la producción

  1. No es necesario que crees explícitamente un proyecto de Firestore en modo nativo. El emulador crea automáticamente cualquier instancia a la que se acceda.
  2. El emulador de Firestore en modo nativo no funciona con el flujo normal de Firebase Authentication. En su lugar, en el SDK de prueba de Firebase, hemos proporcionado el método initializeTestApp() en la biblioteca rules-unit-testing, que utiliza un campo auth. El identificador de Firebase creado con este método se comportará como si se hubiera autenticado correctamente como la entidad que proporciones. Si envías null, se comportará como un usuario no autenticado (por ejemplo, las reglas auth != null fallarán).

Solucionar problemas conocidos

Mientras usas el emulador de Firestore en modo nativo, es posible que te encuentres con los siguientes problemas conocidos. Sigue las instrucciones que se indican a continuación para solucionar cualquier comportamiento irregular que observes. Estas notas se han escrito teniendo en cuenta la biblioteca de pruebas unitarias de las reglas de seguridad, pero los enfoques generales se pueden aplicar a cualquier SDK de Firebase.

El comportamiento de la prueba no es coherente

Si tus pruebas se superan o no de forma ocasional, aunque no hayas hecho ningún cambio en ellas, puede que tengas que verificar que estén secuenciadas correctamente. La mayoría de las interacciones con el emulador son asíncronas, así que comprueba que todo el código asíncrono esté secuenciado correctamente. Puedes corregir la secuencia encadenando promesas o usando la notación await con frecuencia.

En concreto, revisa las siguientes operaciones asíncronas:

  • Definir reglas de seguridad, como initializeTestEnvironment.
  • Leer y escribir datos, por ejemplo, con db.collection("users").doc("alice").get().
  • Afirmaciones operativas, como assertSucceeds y assertFails.

Las pruebas solo se superan la primera vez que cargas el emulador

El emulador tiene estado. Almacena en memoria todos los datos que se escriben en él, por lo que se pierden todos los datos cada vez que se cierra el emulador. Si ejecutas varias pruebas en el mismo ID de proyecto, cada prueba puede generar datos que influyan en las pruebas posteriores. Puede usar cualquiera de los siguientes métodos para evitar este comportamiento:

  • Usa IDs de proyecto únicos para cada prueba. Ten en cuenta que, si decides hacerlo, tendrás que llamar a initializeTestEnvironment como parte de cada prueba. Las reglas solo se cargan automáticamente para el ID de proyecto predeterminado.
  • Reestructura las pruebas para que no interactúen con los datos escritos anteriormente (por ejemplo, usa una colección diferente para cada prueba).
  • Elimina todos los datos escritos durante una prueba.

La configuración de las pruebas es muy complicada

Al configurar la prueba, puede que quieras modificar los datos de una forma que las reglas de seguridad de Firestore no permitan. Si tus reglas complican la configuración de las pruebas, prueba a usar RulesTestEnvironment.withSecurityRulesDisabled en los pasos de configuración para que las lecturas y las escrituras no activen errores de PERMISSION_DENIED.

Después, tu prueba puede realizar operaciones como usuario autenticado o no autenticado mediante RulesTestEnvironment.authenticatedContext y unauthenticatedContext, respectivamente. De esta forma, puedes validar que tus reglas de seguridad de Firestore permiten o deniegan diferentes casos correctamente.