Controllare l'accesso a campi specifici

Questa pagina si basa sui concetti di Strutturare le regole di sicurezza e Scrivere le condizioni per le Regole di sicurezza per spiegare come puoi usare le regole di sicurezza di Firestore per creare regole che consentano ai client di eseguire operazioni su alcuni campi e non su altri di un documento.

A volte potresti voler controllare le modifiche a un documento non a livello di documento, ma a livello di campo.

Ad esempio, potresti voler consentire a un cliente di creare o modificare un documento, ma non di modificare determinati campi al suo interno. In alternativa, potresti volere imporre che ogni documento creato da un cliente contenga sempre un determinato insieme di campi. Questa guida spiega come eseguire alcune di queste attività utilizzando le Regole di sicurezza di Firestore.

Consentire l'accesso in lettura solo per campi specifici

Le letture in Firestore vengono eseguite a livello di documento. Puoi recuperare il documento completo o non recuperare nulla. Non c'è modo di recuperare un documento parziale. È impossibile usare sole regole di sicurezza per impedire agli utenti di leggere campi specifici all'interno di un documento.

Se in un documento ci sono determinati campi che vuoi mantenere nascosti per alcuni utenti, il modo migliore è inserirli in un documento separato. Per istanza, ti consigliamo di creare un documento in una sottoraccolta private in questo modo:

/employees/{emp_id}

  name: "Alice Hamilton",
  department: 461,
  start_date: <timestamp>

/employees/{emp_id}/private/finances

    salary: 80000,
    bonus_mult: 1.25,
    perf_review: 4.2

Poi puoi aggiungere regole di sicurezza con diversi livelli di accesso per le due raccolte. In questo esempio vengono utilizzate le rivendicazioni di autenticazione personalizzate dire che solo gli utenti con la richiesta di autorizzazione personalizzata role uguale a Finance possono visualizzare le informazioni finanziarie di un dipendente.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow any logged in user to view the public employee data
    match /employees/{emp_id} {
      allow read: if request.resource.auth != null
      // Allow only users with the custom auth claim of "Finance" to view
      // the employee's financial data
      match /private/finances {
        allow read: if request.resource.auth &&
          request.resource.auth.token.role == 'Finance'
      }
    }
  }
}

Limitare i campi durante la creazione del documento

Firestore è privo di schemi, il che significa che non ci sono limitazioni a livello di database per i campi contenuti in un documento. Sebbene questa flexibilità possa semplificare lo sviluppo, a volte potresti dover assicurarti che i client possano creare solo documenti che contengono campi specifici o che non contengono altri campi.

Puoi creare queste regole esaminando il metodo keys dell'oggetto request.resource.data. Questo è un elenco di tutti i campi che il cliente sta tentando di scrivere in questo nuovo documento. Combinando questo insieme di campi con funzioni come hasOnly() o hasAny(), puoi aggiungere una logica che limiti i tipi di documenti a cui un utente può aggiungere Firestore

Richiedere campi specifici nei nuovi documenti

Supponiamo che tu voglia assicurarti che tutti i documenti creati in un restaurant raccolta conteneva almeno un campo name, location e city. Puoi farlo chiamando hasAll() nell'elenco delle chiavi del nuovo documento.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document contains a name
    // location, and city field
    match /restaurant/{restId} {
      allow create: if request.resource.data.keys().hasAll(['name', 'location', 'city']);
    }
  }
}

In questo modo è possibile creare ristoranti anche con altri campi, ma viene garantito che tutti i documenti creati da un cliente contengano almeno questi tre campi.

Vieta la pubblicazione di offerte per campi specifici nei nuovi documenti

Analogamente, puoi impedire ai client di creare documenti che contengono campi specifici utilizzando hasAny() a fronte di un elenco di campi vietati. Questo metodo restituisce true se una documento contiene uno qualsiasi di questi campi, quindi è consigliabile per vietare determinati campi.

Nell'esempio seguente, i clienti non sono autorizzati a creare un documento contenente un campo average_score o rating_count poiché verranno aggiunti da una chiamata al server in un secondo momento.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document does *not*
    // contain an average_score or rating_count field.
    match /restaurant/{restId} {
      allow create: if (!request.resource.data.keys().hasAny(
        ['average_score', 'rating_count']));
    }
  }
}

Creazione di una lista consentita di campi per i nuovi documenti

Invece di vietare determinati campi nei nuovi documenti, ti consigliamo di creare un elenco solo dei campi consentiti esplicitamente nei nuovi documenti. Poi puoi utilizzare la funzione hasOnly() per assicurarti che i nuovi documenti creati contengano solo questi campi (o un sottoinsieme di questi campi) e nessun altro.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document doesn't contain
    // any fields besides the ones listed below.
    match /restaurant/{restId} {
      allow create: if (request.resource.data.keys().hasOnly(
        ['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

Combinazione di campi obbligatori e facoltativi

Puoi combinare le operazioni hasAll e hasOnly nella tua sicurezza per richiedere alcuni campi e consentirne l'accesso ad altri. Ad esempio, questo esempio richiede che tutti i nuovi documenti contengano i campi name, location e city e, facoltativamente, consente i campi address, hours e cuisine.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document has a name,
    // location, and city field, and optionally address, hours, or cuisine field
    match /restaurant/{restId} {
      allow create: if (request.resource.data.keys().hasAll(['name', 'location', 'city'])) &&
       (request.resource.data.keys().hasOnly(
           ['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

In uno scenario reale, ti consigliamo di spostare questa logica in una funzione di supporto per evitare di duplicare il codice e combinare più facilmente i campi facoltativi e obbligatori in un unico elenco, come segue:

service cloud.firestore {
  match /databases/{database}/documents {
    function verifyFields(required, optional) {
      let allAllowedFields = required.concat(optional);
      return request.resource.data.keys().hasAll(required) &&
        request.resource.data.keys().hasOnly(allAllowedFields);
    }
    match /restaurant/{restId} {
      allow create: if verifyFields(['name', 'location', 'city'],
        ['address', 'hours', 'cuisine']);
    }
  }
}

Limitare i campi in caso di aggiornamento

Una pratica di sicurezza comune è consentire ai client di modificare solo alcuni campi e non altri. Non puoi farlo solo esaminando l'elenco request.resource.data.keys() descritto nella sezione precedente, poiché questo elenco rappresenta il documento completo così come apparirà dopo l'aggiornamento e includerà quindi i campi che il cliente non ha modificato.

Tuttavia, se utilizzassi la funzione diff(), potresti confrontare request.resource.data con l'oggetto resource.data, che rappresenta il documento nel database prima dell'aggiornamento. Viene creato un oggetto mapDiff, che contiene tutte le modifiche tra due mappe diverse.

Chiamando il metodo affectedKeys() su questa mappa di differenze, puoi ottenere un insieme di campi modificati in una modifica. Poi puoi utilizzare funzioni come hasOnly() o hasAny() per assicurarti che questo insieme contenga (o meno) determinati elementi.

Impedire la modifica di alcuni campi

Utilizzando il metodo hasAny() sul set generato da affectedKeys() e poi negando il risultato, puoi rifiutare qualsiasi richiesta del client che tenta di modificare i campi che non vuoi modificare.

Ad esempio, potresti voler consentire ai clienti di aggiornare le informazioni su un Ristorante, ma non modificare il punteggio medio o il numero di recensioni.

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
      // Allow the client to update a document only if that document doesn't
      // change the average_score or rating_count fields
      allow update: if (!request.resource.data.diff(resource.data).affectedKeys()
        .hasAny(['average_score', 'rating_count']));
    }
  }
}

Possibilità di modificare solo alcuni campi

Anziché specificare i campi che non vuoi modificare, puoi anche utilizzare la hasOnly() per specificare un elenco di campi da modificare. In genere si tratta considerata più sicura perché le scritture nei nuovi campi dei documenti vengono non sono consentiti per impostazione predefinita finché non li autorizzi esplicitamente nelle regole di sicurezza.

Ad esempio, anziché non consentire il campo average_score e rating_count, puoi creare regole di sicurezza che consentano ai client di modificare solo i campi name, location, city, address, hours e cuisine.

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
    // Allow a client to update only these 6 fields in a document
      allow update: if (request.resource.data.diff(resource.data).affectedKeys()
        .hasOnly(['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

Ciò significa che se, in un'iterazione futura della tua app, i documenti relativi al ristorante includi un campo telephone, i tentativi di modifica del campo non andranno a buon fine finché non torni indietro e aggiungi quel campo all'elenco hasOnly() del riquadro le regole del caso.

Applicazione dei tipi di campo

Un altro effetto dell'assenza di schema in Firestore è che non viene applicata alcuna verifica a livello di database per i tipi di dati che possono essere archiviati in campi specifici. Si tratta di qualcosa che puoi applicare nelle regole di sicurezza, con l'operatore is.

Ad esempio, la seguente regola di sicurezza impone che l'elemento score di una recensione il campo deve essere un numero intero, i campi headline, content e author_name sono stringhe e review_date è un timestamp.

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
      // Restaurant rules go here...
      match /review/{reviewId} {
        allow create: if (request.resource.data.score is int &&
          request.resource.data.headline is string &&
          request.resource.data.content is string &&
          request.resource.data.author_name is string &&
          request.resource.data.review_date is timestamp
        );
      }
    }
  }
}

I tipi di dati validi per l'operatore is sono bool, bytes, float, int, list, latlng, number, path, map, string e timestamp. is operatore supporta anche i dati constraint, duration, set e map_diff ma poiché questi sono generati dal linguaggio stesso delle regole di sicurezza non generati dai clienti, raramente li usi nella pratica diverse applicazioni.

I tipi di dati list e map non supportano i valori generici o gli argomenti del tipo. In altre parole, puoi utilizzare le regole di sicurezza per imporre che un determinato campo contenga un elenco o una mappa, ma non puoi imporre che un campo contenga un elenco di tutti gli interi o tutte le stringhe.

Analogamente, puoi utilizzare le regole di sicurezza per applicare valori di tipo a voci specifiche in un elenco o in una mappa (utilizzando rispettivamente la notazione delle parentesi graffe o i nomi delle chiavi), ma non esiste una scorciatoia per applicare contemporaneamente i tipi di dati di tutti i membri di una mappa o di un elenco.

Ad esempio, le seguenti regole assicurano che un campo tags in un documento contenga un elenco e che la prima voce sia una stringa. Inoltre, garantisce che il campo product contenga una mappa che a sua volta contiene un nome prodotto che è una stringa e una quantità che è un numero intero.

service cloud.firestore {
  match /databases/{database}/documents {
  match /orders/{orderId} {
    allow create: if request.resource.data.tags is list &&
      request.resource.data.tags[0] is string &&
      request.resource.data.product is map &&
      request.resource.data.product.name is string &&
      request.resource.data.product.quantity is int
      }
    }
  }
}

I tipi di campo devono essere applicati sia durante la creazione che l'aggiornamento di un documento. Pertanto, considera la creazione di una funzione helper che nella sezione per la creazione e l'aggiornamento delle regole di sicurezza.

service cloud.firestore {
  match /databases/{database}/documents {

  function reviewFieldsAreValidTypes(docData) {
     return docData.score is int &&
          docData.headline is string &&
          docData.content is string &&
          docData.author_name is string &&
          docData.review_date is timestamp;
  }

   match /restaurant/{restId} {
      // Restaurant rules go here...
      match /review/{reviewId} {
        allow create: if reviewFieldsAreValidTypes(request.resource.data) &&
          // Other rules may go here
        allow update: if reviewFieldsAreValidTypes(request.resource.data) &&
          // Other rules may go here
      }
    }
  }
}

Tipi di applicazione per i campi facoltativi

È importante ricordare che chiami request.resource.data.foo su una documento in cui foo non esiste genera un errore; pertanto qualsiasi la regola di sicurezza che effettua la chiamata rifiuta la richiesta. Puoi gestire questo situazione utilizzando get su request.resource.data. Il metodo get ti consente di fornire un'entità argomento predefinito per il campo che stai recuperando da una mappa se tale campo non esiste.

Ad esempio, se i documenti delle recensioni contengono anche un campo facoltativo photo_url e un campo tags facoltativo che vuoi verificare sono stringhe e elenchi puoi farlo riscrivendo il reviewFieldsAreValidTypes in un modo simile al seguente:

  function reviewFieldsAreValidTypes(docData) {
     return docData.score is int &&
          docData.headline is string &&
          docData.content is string &&
          docData.author_name is string &&
          docData.review_date is timestamp &&
          docData.get('photo_url', '') is string &&
          docData.get('tags', []) is list;
  }

Vengono rifiutati i documenti in cui esiste tags, ma non è un elenco, o documenti autorizzati che non contengono un campo tags (o photo_url).

Non sono mai consentite scritture parziali

Un'ultima nota sulle Regole di sicurezza di Firestore: consentono al client di apportare una modifica a un documento o rifiutano l'intera modifica. Non puoi creare regole di sicurezza che accettano scritture in alcuni campi della documento rifiutando altre persone nella stessa operazione.