Testa le regole di sicurezza di Cloud Firestore

Durante la creazione dell'app, potresti voler limitare l'accesso al tuo database Cloud Firestore. Tuttavia, prima del lancio, avrai bisogno di una strategia più Cloud Firestore Security Rules. Con l'emulatore Cloud Firestore, oltre a prototipare e testare le funzionalità e il comportamento generali della tua app, puoi scrivere unit test che controllano il comportamento del tuo Cloud Firestore Security Rules.

Guida rapida

Per alcuni casi di test di base con regole semplici, prova l'esempio di avvio rapido.

Comprendere Cloud Firestore Security Rules

Implementa Firebase Authentication e Cloud Firestore Security Rules per l'autenticazione, l'autorizzazione e la convalida dei dati serverless quando utilizzi le librerie client web e mobile.

Cloud Firestore Security Rules include due parti:

  1. Un'istruzione match che identifica i documenti nel tuo database.
  2. Un'espressione allow che controlla l'accesso a questi documenti.

Firebase Authentication verifica le credenziali degli utenti e fornisce le basi per i sistemi di accesso basati su utenti e ruoli.

Ogni richiesta di database da una libreria client mobile/web Cloud Firestore viene valutata in base alle regole di sicurezza prima di leggere o scrivere dati. Se le regole negano l'accesso a uno dei percorsi dei documenti specificati, l'intera richiesta non va a buon fine.

Scopri di più su Cloud Firestore Security Rules in Inizia a utilizzare Cloud Firestore Security Rules.

Installare l'emulatore

Per installare l'emulatore Cloud Firestore, utilizza l'interfaccia a riga di comando Firebase ed esegui il comando riportato di seguito:

firebase setup:emulators:firestore

Esegui l'emulatore

Inizia inizializzando un progetto Firebase nella directory di lavoro. Questo è un primo passaggio comune quando si utilizza l'interfaccia a riga di comando di Firebase.

firebase init

Avvia l'emulatore utilizzando il seguente comando. L'emulatore verrà eseguito finché non interrompi il processo:

firebase emulators:start --only firestore

In molti casi, devi avviare l'emulatore, eseguire una suite di test e poi spegnerlo dopo l'esecuzione dei test. Puoi farlo facilmente utilizzando il comando emulators:exec:

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

Una volta avviato, l'emulatore tenterà di essere eseguito su una porta predefinita (8080). Puoi modificare la porta dell'emulatore modificando la sezione "emulators" del file firebase.json:

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

Prima di eseguire l'emulatore

Prima di iniziare a utilizzare l'emulatore, tieni presente quanto segue:

  • L'emulatore caricherà inizialmente le regole specificate nel campo firestore.rules del file firebase.json. Si prevede il nome di un file locale contenente il tuo Cloud Firestore Security Rules e applica queste regole a tutti i progetti. Se non fornisci il percorso del file locale o non utilizzi il metodo loadFirestoreRules come descritto di seguito, l'emulatore considera tutti i progetti come se avessero regole aperte.
  • Sebbene la maggior parte degli SDK Firebase funzioni direttamente con gli emulatori, solo la libreria @firebase/rules-unit-testing supporta la simulazione auth nelle regole di sicurezza, semplificando notevolmente i test unitari. Inoltre, la libreria supporta alcune funzionalità specifiche dell'emulatore, come la cancellazione di tutti i dati, come elencato di seguito.
  • Gli emulatori accettano anche i token di Firebase Auth di produzione forniti tramite gli SDK client e valutano le regole di conseguenza, il che consente di connettere l'applicazione direttamente agli emulatori nei test di integrazione e manuali.

Esegui test delle unità locali

Esegui test delle unità locali con l'SDK JavaScript v9

Firebase distribuisce una libreria di unit test delle regole di sicurezza sia con l'SDK JavaScript versione 9 sia con l'SDK versione 8. Le API della libreria sono molto diverse. Ti consigliamo la libreria di test v9, che è più snella e richiede meno configurazione per connettersi agli emulatori ed evitare così in modo sicuro l'uso accidentale di risorse di produzione. Per la compatibilità con le versioni precedenti, continuiamo a rendere disponibile la libreria di test v8.

Utilizza il modulo @firebase/rules-unit-testing per interagire con l'emulatore che viene eseguito localmente. Se si verificano timeout o errori ECONNREFUSED, verifica che l'emulatore sia effettivamente in esecuzione.

Ti consigliamo vivamente di utilizzare una versione recente di Node.js per poter utilizzare la notazione async/await. Quasi tutto il comportamento che potresti voler testare riguarda le funzioni asincrone e il modulo di test è progettato per funzionare con il codice basato su promesse.

La libreria di test delle unità delle regole v9 è sempre a conoscenza degli emulatori e non tocca mai le risorse di produzione.

Importa la libreria utilizzando le istruzioni di importazione modulare v9. Ad esempio:

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 volta importati, l'implementazione dei test delle unità prevede:

  • Creazione e configurazione di un RulesTestEnvironment con una chiamata a initializeTestEnvironment.
  • Configurazione dei dati di test senza attivare le regole, utilizzando un metodo pratico che consente di aggirarle temporaneamente, RulesTestEnvironment.withSecurityRulesDisabled.
  • Configurazione della suite di test e degli hook pre/post test con chiamate per pulire i dati e l'ambiente di test, ad esempio RulesTestEnvironment.cleanup() o RulesTestEnvironment.clearFirestore().
  • Implementazione di scenari di test che simulano gli stati di autenticazione utilizzando RulesTestEnvironment.authenticatedContext e RulesTestEnvironment.unauthenticatedContext.

Metodi comuni e funzioni di utilità

Consulta anche Metodi di test specifici dell'emulatore nell'SDK v9.

initializeTestEnvironment() => RulesTestEnvironment

Questa funzione inizializza un ambiente di test per il test delle unità delle regole. Chiama questa funzione per prima per la configurazione del test. L'esecuzione corretta richiede l'esecuzione degli emulatori.

La funzione accetta un oggetto facoltativo che definisce un TestEnvironmentConfig, che può essere costituito da un ID progetto e dalle impostazioni di configurazione dell'emulatore.

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

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

Questo metodo crea un RulesTestContext, che si comporta come un utente di autenticazione autenticato. Le richieste create tramite il contesto restituito avranno un token di autenticazione simulato allegato. (Facoltativo) Passa un oggetto che definisce rivendicazioni personalizzate o override per i payload dei token di autenticazione.

Utilizza l'oggetto contesto di test restituito nei test per accedere a qualsiasi istanza dell'emulatore configurata, incluse quelle configurate 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(), '/users/alice'), { ... });

RulesTestEnvironment.unauthenticatedContext() => RulesTestContext

Questo metodo crea un RulesTestContext, che si comporta come un client che non ha eseguito l'accesso tramite autenticazione. Le richieste create tramite il contesto restituito non avranno token Firebase Auth allegati.

Utilizza l'oggetto contesto di test restituito nei test per accedere a qualsiasi istanza dell'emulatore configurata, incluse quelle configurate 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()

Esegui una funzione di configurazione del test con un contesto che si comporta come se le regole di sicurezza fossero disattivate.

Questo metodo accetta una funzione di callback, che accetta il contesto di bypass delle regole di sicurezza e restituisce una promessa. Il contesto verrà eliminato una volta che la promessa viene risolta / rifiutata.

RulesTestEnvironment.cleanup()

Questo metodo elimina tutti i RulesTestContexts creati nell'ambiente di test e pulisce le risorse sottostanti, consentendo un'uscita pulita.

Questo metodo non modifica in alcun modo lo stato degli emulatori. Per reimpostare i dati tra i test, utilizza il metodo di cancellazione dei dati specifico dell'emulatore dell'applicazione.

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

Questa è una funzione di utilità per i casi di test.

La funzione asserisce che la promessa fornita che racchiude un'operazione dell'emulatore verrà risolta senza violazioni delle regole di sicurezza.

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

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

Questa è una funzione di utilità per i casi di test.

La funzione asserisce che la promessa fornita che racchiude un'operazione dell'emulatore verrà rifiutata con una violazione delle regole di sicurezza.

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

Metodi specifici dell'emulatore

Consulta anche i metodi di test e le funzioni di utilità comuni nell'SDK v9.

RulesTestEnvironment.clearFirestore() => Promise<void>

Questo metodo cancella i dati nel database Firestore appartenenti al projectId configurato per l'emulatore Firestore.

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

Questo metodo recupera un'istanza di Firestore per questo contesto di test. L'istanza dell'SDK client Firebase JS restituita può essere utilizzata con le API dell'SDK client (modulare v9 o compatibilità v9).

Visualizzare le valutazioni delle regole

L'emulatore Cloud Firestore ti consente di visualizzare le richieste dei client nell'interfaccia utente di Emulator Suite, incluso il tracciamento della valutazione per le regole di sicurezza di Firebase.

Apri la scheda Firestore > Richieste per visualizzare la sequenza di valutazione dettagliata per ogni richiesta.

Monitoraggio delle richieste dell&#39;emulatore Firestore che mostra le valutazioni delle regole di sicurezza

Generare report di test

Dopo aver eseguito una serie di test, puoi accedere ai report sulla copertura dei test che mostrano come è stata valutata ciascuna delle tue regole di sicurezza.

Per ottenere i report, esegui una query su un endpoint esposto nell'emulatore durante l'esecuzione. Per una versione compatibile con il browser, utilizza il seguente URL:

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

In questo modo, le regole vengono suddivise in espressioni e sottoespressioni su cui puoi passare il mouse per visualizzare ulteriori informazioni, tra cui il numero di valutazioni e i valori restituiti. Per la versione JSON non elaborata di questi dati, includi il seguente URL nella query:

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

Differenze tra l'emulatore e la produzione

  1. Non è necessario creare esplicitamente un progetto Cloud Firestore. L'emulatore crea automaticamente qualsiasi istanza a cui viene eseguito l'accesso.
  2. L'emulatore Cloud Firestore non funziona con il flusso normale Firebase Authentication. Nell'SDK Firebase Test, invece, abbiamo fornito il metodo initializeTestApp() nella libreria rules-unit-testing, che accetta un campo auth. L'handle Firebase creato utilizzando questo metodo si comporterà come se l'autenticazione fosse andata a buon fine come qualsiasi entità tu fornisca. Se trasmetti null, il comportamento sarà quello di un utente non autenticato (ad esempio, le regole auth != null non andranno a buon fine).

Risolvere i problemi noti

Durante l'utilizzo dell'emulatore Cloud Firestore, potresti riscontrare i seguenti problemi noti. Segui le indicazioni riportate di seguito per risolvere eventuali comportamenti anomali che stai riscontrando. Queste note sono scritte tenendo presente la libreria di unit test delle regole di sicurezza, ma gli approcci generali sono applicabili a qualsiasi SDK Firebase.

Il comportamento del test non è coerente

Se i test vengono superati e non superati occasionalmente, anche senza modifiche ai test stessi, potrebbe essere necessario verificare che siano sequenziati correttamente. La maggior parte delle interazioni con l'emulatore sono asincrone, quindi verifica che tutto il codice asincrono sia sequenziato correttamente. Puoi correggere la sequenza concatenando le promesse o utilizzando liberamente la notazione await.

In particolare, esamina le seguenti operazioni asincrone:

  • Impostazione di regole di sicurezza, ad esempio con initializeTestEnvironment.
  • Lettura e scrittura dei dati, ad esempio con db.collection("users").doc("alice").get().
  • Asserzioni operative, tra cui assertSucceeds e assertFails.

I test vengono superati solo al primo caricamento dell'emulatore

L'emulatore è stateful. Memorizza tutti i dati scritti in memoria, quindi tutti i dati vengono persi ogni volta che l'emulatore si spegne. Se esegui più test sullo stesso ID progetto, ogni test può produrre dati che potrebbero influenzare i test successivi. Per aggirare questo comportamento, puoi utilizzare uno dei seguenti metodi:

  • Utilizza ID progetto univoci per ogni test. Tieni presente che se scegli di farlo, dovrai chiamare initializeTestEnvironment come parte di ogni test; le regole vengono caricate automaticamente solo per l'ID progetto predefinito.
  • Ristruttura i test in modo che non interagiscano con i dati scritti in precedenza (ad esempio, utilizza una raccolta diversa per ogni test).
  • Elimina tutti i dati scritti durante un test.

La configurazione del test è molto complicata

Quando configuri il test, potresti voler modificare i dati in un modo che i tuoi Cloud Firestore Security Rules non consentono. Se le regole rendono complessa la configurazione del test, prova a utilizzare RulesTestEnvironment.withSecurityRulesDisabled nei passaggi di configurazione, in modo che le letture e le scritture non attivino errori PERMISSION_DENIED.

Dopodiché, il test può eseguire operazioni come utente autenticato o non autenticato utilizzando rispettivamente RulesTestEnvironment.authenticatedContext e unauthenticatedContext. In questo modo puoi verificare che il tuo Cloud Firestore Security Rules consenta / neghi correttamente i diversi casi.