Questa pagina si basa sui concetti illustrati in Strutturare le regole di sicurezza e Scrivere condizioni per le regole di sicurezza per spiegare come puoi utilizzare Cloud Firestore Security Rules per creare regole che consentano ai client di eseguire operazioni su alcuni campi di un documento, ma non su altri.
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 del documento. Oppure potresti voler imporre che qualsiasi documento creato da un cliente contenga un determinato insieme di campi. Questa guida spiega come eseguire alcune di queste attività utilizzando Cloud Firestore Security Rules.
Consentire l'accesso in lettura solo per campi specifici
Le letture in Cloud Firestore vengono eseguite a livello di documento. Recuperi l'intero documento o non recuperi nulla. Non è possibile recuperare un documento parziale. È impossibile utilizzare solo le regole di sicurezza per impedire agli utenti di leggere campi specifici all'interno di un documento.
Se ci sono determinati campi all'interno di un documento che vuoi nascondere ad alcuni utenti, il modo migliore è inserirli in un documento separato. Ad esempio, potresti prendere in considerazione la creazione di un documento in una sottoraccolta private
come segue:
/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, utilizziamo rivendicazioni di autenticazione personalizzate
per indicare che solo gli utenti con la rivendicazione di autenticazione 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 di documenti
Cloud Firestore è senza schema, il che significa che non ci sono restrizioni a livello di database per i campi contenuti in un documento. Sebbene questa flessibilità possa semplificare lo sviluppo, a volte vorrai 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 client
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 che un utente può aggiungere a
Cloud Firestore.
Richiedere campi specifici nei nuovi documenti
Supponiamo che tu voglia assicurarti che tutti i documenti creati in una raccolta restaurant
contengano 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']);
}
}
}
Ciò consente di creare ristoranti anche con altri campi, ma garantisce che tutti i documenti creati da un client contengano almeno questi tre campi.
Vietare campi specifici nei nuovi documenti
Allo stesso modo, puoi impedire ai client di creare documenti che contengono
campi specifici utilizzando hasAny()
rispetto a un elenco di campi vietati. Questo metodo restituisce true se un documento contiene uno di questi campi, quindi probabilmente vuoi negare il risultato per vietare determinati campi.
Ad esempio, nel seguente esempio, i client non sono autorizzati a creare un
documento che contenga un campo average_score
o rating_count
, poiché questi
campi 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, potresti voler creare
un elenco dei soli campi esplicitamente consentiti nei nuovi documenti. Poi
puoi utilizzare la funzione hasOnly()
per assicurarti che tutti 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
nelle regole di sicurezza per richiedere alcuni campi e consentirne altri. Ad esempio, questo esempio
richiede che tutti i nuovi documenti contengano i campi name
, location
e city
e consente facoltativamente 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, potresti voler spostare questa logica in una funzione helper 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']);
}
}
}
Limitazione dei campi durante l'aggiornamento
Una pratica di sicurezza comune consiste nel consentire ai client di modificare solo alcuni campi e non
altri. Non puoi farlo semplicemente esaminando l'elenco request.resource.data.keys()
descritto nella sezione precedente, poiché questo elenco rappresenta il documento completo come apparirebbe dopo l'aggiornamento e includerebbe quindi i campi che il cliente non ha modificato.
Tuttavia, se utilizzi la funzione diff()
, puoi confrontare request.resource.data
con l'oggetto resource.data
, che rappresenta il documento nel database prima dell'aggiornamento. Viene creato un oggetto mapDiff
, ovvero un oggetto contenente tutte le modifiche tra due mappe diverse.
Chiamando il metodo affectedKeys()
su questo mapDiff, 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 non contenga) determinati elementi.
Impedire la modifica di alcuni campi
Utilizzando il metodo hasAny()
sul set generato da affectedKeys()
e negando il risultato, puoi rifiutare qualsiasi richiesta del client che tenti di
modificare i campi che non vuoi che vengano modificati.
Ad esempio, potresti voler consentire ai clienti di aggiornare le informazioni su un ristorante, ma non di 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']));
}
}
}
Consentendo la modifica solo di determinati campi
Anziché specificare i campi che non vuoi modificare, puoi anche utilizzare la funzione
hasOnly()
per specificare un elenco di campi che vuoi modificare. In genere, questo approccio è considerato più sicuro perché le scritture nei nuovi campi dei documenti non sono consentite per impostazione predefinita finché non le autorizzi esplicitamente nelle regole di sicurezza.
Ad esempio, anziché non consentire i campi average_score
e rating_count
,
potresti 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 una futura iterazione della tua app, i documenti del ristorante
includono un campo telephone
, i tentativi di modificare questo campo non andranno a buon fine
finché non torni indietro e aggiungi il campo all'elenco hasOnly()
nelle regole di sicurezza.
Applicazione dei tipi di campo
Un altro effetto della mancanza di schema di Cloud Firestore è che non esiste
alcuna applicazione a livello di database per i tipi di dati che possono essere archiviati in
campi specifici. Tuttavia, puoi applicare questa limitazione nelle regole di sicurezza
con l'operatore is
.
Ad esempio, la seguente regola di sicurezza impone che il campo score
di una recensione sia un numero intero, che i campi headline
, content
e author_name
siano stringhe e che review_date
sia 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
. L'operatore is
supporta anche i tipi di dati constraint
, duration
, set
e map_diff
, ma poiché questi vengono generati dal linguaggio delle regole di sicurezza stesso e non dai client, raramente vengono utilizzati nella maggior parte delle applicazioni pratiche.
I tipi di dati list
e map
non supportano i generici o gli argomenti di 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 i numeri interi o tutte le stringhe.
Allo stesso modo, puoi utilizzare le regole di sicurezza per applicare valori di tipo a voci specifiche di un elenco o una mappa (utilizzando rispettivamente la notazione tra parentesi 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 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 campi devono essere applicati sia durante la creazione che l'aggiornamento di un documento. Pertanto, ti consigliamo di creare una funzione helper che puoi chiamare sia nella sezione di creazione che in quella di 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
}
}
}
}
Imposizione dei tipi per i campi facoltativi
È importante ricordare che chiamare request.resource.data.foo
in un documento in cui foo
non esiste genera un errore, pertanto qualsiasi regola di sicurezza che effettua questa chiamata negherà la richiesta. Puoi gestire questa situazione utilizzando il metodo get
su request.resource.data
. Il metodo get
ti consente di fornire un
argomento predefinito per il campo che stai recuperando da una mappa se questo campo
non esiste.
Ad esempio, se i documenti di revisione contengono anche un campo photo_url
facoltativo
e un campo tags
facoltativo che vuoi verificare siano rispettivamente stringhe ed elenchi, puoi farlo riscrivendo la
funzione reviewFieldsAreValidTypes
in 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, mentre
vengono comunque accettati i documenti che non contengono un campo tags
(o photo_url
).
Le scritture parziali non sono mai consentite
Un'ultima nota su Cloud Firestore Security Rules è che consentono al client di apportare una modifica a un documento o rifiutano l'intera modifica. Non puoi creare regole di sicurezza che accettino scritture in alcuni campi del documento rifiutandone altri nella stessa operazione.