Interroger des données de manière sécurisée
Cette page s'appuie sur les concepts décrits dans les sections Structurer les règles de sécurité et Écrire des conditions pour les règles de sécurité pour expliquer comment les règles de sécurité Firestore interagissent avec les requêtes. Elle examine en détail la façon dont les règles de sécurité affectent les requêtes que vous écrivez et explique comment vous assurer que vos requêtes utilisent les mêmes contraintes que vos règles de sécurité. Cette page explique également comment écrire des règles de sécurité autorisant ou refusant des requêtes basées sur des propriétés de requête telles que limit
et orderBy
.
Les règles ne sont pas des filtres
Lorsque vous écrivez des requêtes pour récupérer des documents, gardez à l'esprit que les règles de sécurité ne sont pas des filtres. Les requêtes utilisent une approche de type "tout ou rien". Pour économiser du temps et des ressources, Firestore évalue une requête par rapport à son ensemble de résultats potentiel au lieu des valeurs des champs réelles de tous vos documents. Si une requête peut potentiellement renvoyer des documents que le client n'est pas autorisé à lire, la requête entière échoue.
Requêtes et règles de sécurité
Comme le montrent les exemples ci-dessous, vous devez écrire vos requêtes en fonction des contraintes de vos règles de sécurité.
Sécuriser et interroger des documents en fonction de auth.uid
L'exemple suivant montre comment écrire une requête pour récupérer des documents protégés par une règle de sécurité. Prenons l'exemple d'une base de données qui contient une collection de documents story
:
/stories/{storyid}
{
title: "A Great Story",
content: "Once upon a time...",
author: "some_auth_id",
published: false
}
En plus des champs title
et content
, chaque document stocke les champs author
et published
à utiliser pour le contrôle des accès. Ces exemples supposent que l'application utilise Firebase Authentication pour définir le champ author
sur l'UID de l'utilisateur qui a créé le document. Firebase Authentication insère également la variable request.auth
dans les règles de sécurité.
La règle de sécurité suivante utilise les variables request.auth
et resource.data
pour limiter l'accès en lecture et en écriture pour chaque élément story
à son auteur :
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{storyid} {
// Only the authenticated user who authored the document can read or write
allow read, write: if request.auth != null && request.auth.uid == resource.data.author;
}
}
}
Supposons que votre application contient une page qui présente à l'utilisateur une liste de documents story
dont il est l'auteur. Vous voudrez peut-être utiliser la requête suivante pour renseigner cette page. Toutefois, cette requête échouera, car elle n'inclut pas les mêmes contraintes que vos règles de sécurité :
Non valide : les contraintes de requête ne correspondent pas aux contraintes des règles de sécurité.
// This query will fail
db.collection("stories").get()
La requête échoue même si l'utilisateur actuel est l'auteur de tous les documents story
. En effet, lorsque Firestore applique vos règles de sécurité, la requête est évaluée par rapport à son ensemble de résultats potentiel, et non par rapport aux propriétés de document réelles de votre base de données. Si une requête peut potentiellement inclure des documents qui ne respectent pas vos règles de sécurité, la requête échoue.
En revanche, la requête suivante réussit, car elle inclut la même contrainte sur le champ author
que les règles de sécurité :
Valide : les contraintes de requête correspondent aux contraintes des règles de sécurité.
var user = firebase.auth().currentUser;
db.collection("stories").where("author", "==", user.uid).get()
Sécuriser et interroger des documents en fonction d'un champ
Pour illustrer davantage l'interaction entre les requêtes et les règles, les règles de sécurité suivantes développent l'accès en lecture pour la collection stories
pour permettre à tout utilisateur de lire les documents story
dont le champ published
est défini sur true
.
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{storyid} {
// Anyone can read a published story; only story authors can read unpublished stories
allow read: if resource.data.published == true || (request.auth != null && request.auth.uid == resource.data.author);
// Only story authors can write
allow write: if request.auth != null && request.auth.uid == resource.data.author;
}
}
}
La requête pour les pages publiées doit inclure les mêmes contraintes que les règles de sécurité :
db.collection("stories").where("published", "==", true).get()
La contrainte de requête .where("published", "==", true)
garantit que resource.data.published
est défini sur true
pour n'importe quel résultat. Par conséquent, cette requête répond aux règles de sécurité et est autorisée à lire les données.
OR
requêtes
Lors de l'évaluation d'une requête logique OR
(or
, in
ou array-contains-any
)
par rapport à un ensemble de règles, Firestore évalue chaque valeur de comparaison
séparément. Chaque valeur de comparaison doit respecter les contraintes de la règle de sécurité. Par exemple, pour la règle suivante :
match /mydocuments/{doc} {
allow read: if resource.data.x > 5;
}
Non valide : la requête ne garantit pas x > 5
pour tous les documents potentiels
// These queries will fail
query(db.collection("mydocuments"),
or(where("x", "==", 1),
where("x", "==", 6)
)
)
query(db.collection("mydocuments"),
where("x", "in", [1, 3, 6, 42, 99])
)
Valide : la requête garantit x > 5
pour tous les documents potentiels
query(db.collection("mydocuments"),
or(where("x", "==", 6),
where("x", "==", 42)
)
)
query(db.collection("mydocuments"),
where("x", "in", [6, 42, 99, 105, 200])
)
Évaluer les contraintes sur les requêtes
Vos règles de sécurité peuvent également accepter ou refuser des requêtes en fonction de leurs contraintes.
La variable request.query
contient les propriétés limit
, offset
et orderBy
d'une requête. Par exemple, vos règles de sécurité peuvent refuser toute requête qui ne limite pas le nombre maximal de documents récupérés à une certaine plage :
allow list: if request.query.limit <= 10;
L'ensemble de règles suivant montre comment écrire des règles de sécurité qui évaluent les contraintes appliquées aux requêtes. Cet exemple développe l'ensemble de règles stories
précédent avec les modifications suivantes :
- L'ensemble de règles sépare la règle de lecture en règles pour
get
etlist
. - La règle
get
limite la récupération des documents individuels aux documents publics ou aux documents écrits par l'utilisateur. - La règle
list
applique les mêmes restrictions queget
, sauf pour les requêtes. Elle vérifie également la limite de la requête, puis refuse toute requête sans limite ou avec une limite supérieure à 10. - L'ensemble de règles définit une fonction
authorOrPublished()
pour éviter la duplication de code.
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{storyid} {
// Returns `true` if the requested story is 'published'
// or the user authored the story
function authorOrPublished() {
return resource.data.published == true || request.auth.uid == resource.data.author;
}
// Deny any query not limited to 10 or fewer documents
// Anyone can query published stories
// Authors can query their unpublished stories
allow list: if request.query.limit <= 10 &&
authorOrPublished();
// Anyone can retrieve a published story
// Only a story's author can retrieve an unpublished story
allow get: if authorOrPublished();
// Only a story's author can write to a story
allow write: if request.auth.uid == resource.data.author;
}
}
}
Requêtes de groupe de collections et règles de sécurité
Par défaut, les requêtes sont limitées à une seule collection et elles ne récupèrent que les résultats de cette collection. Les requêtes de groupe de collections permettent d'extraire les résultats d'un groupe de collections constitué de toutes les collections ayant le même ID. Cette section explique comment sécuriser vos requêtes de groupe de collections à l'aide de règles de sécurité.
Sécuriser et interroger des documents en fonction de groupes de collections
Dans vos règles de sécurité, vous devez explicitement autoriser les requêtes de groupe de collections en écrivant une règle pour le groupe de collections :
- Assurez-vous que
rules_version = '2';
est la première ligne de votre ensemble de règles. Les requêtes de groupe de collections exigent le comportement de la nouvelle syntaxe générique récursive{name=**}
des règles de sécurité version 2. - Écrivez une règle pour votre groupe de collections en utilisant
match /{path=**}/[COLLECTION_ID]/{doc}
.
Par exemple, considérons un forum organisé en documents forum
contenant des sous-collections posts
:
/forums/{forumid}/posts/{postid}
{
author: "some_auth_id",
authorname: "some_username",
content: "I just read a great story.",
}
Dans cette application, nous rendons les posts modifiables par leurs propriétaires et lisibles par les utilisateurs authentifiés :
service cloud.firestore {
match /databases/{database}/documents {
match /forums/{forumid}/posts/{post} {
// Only authenticated users can read
allow read: if request.auth != null;
// Only the post author can write
allow write: if request.auth != null && request.auth.uid == resource.data.author;
}
}
}
Tout utilisateur authentifié peut récupérer les posts de n'importe quel forum :
db.collection("forums/technology/posts").get()
Mais que se passe-t-il si vous souhaitez afficher les posts de l'utilisateur actuel sur tous les forums ?
Vous pouvez utiliser une requête de groupe de collections pour récupérer les résultats de toutes les collections posts
:
var user = firebase.auth().currentUser;
db.collectionGroup("posts").where("author", "==", user.uid).get()
Dans vos règles de sécurité, vous devez autoriser cette requête en écrivant une règle de lecture ou de liste pour le groupe de collections posts
:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Authenticated users can query the posts collection group
// Applies to collection queries, collection group queries, and
// single document retrievals
match /{path=**}/posts/{post} {
allow read: if request.auth != null;
}
match /forums/{forumid}/posts/{postid} {
// Only a post's author can write to a post
allow write: if request.auth != null && request.auth.uid == resource.data.author;
}
}
}
Notez toutefois que ces règles s'appliquent à toutes les collections disposant de l'ID posts
, quelle que soit la hiérarchie. Par exemple, ces règles s'appliquent à l'ensemble des collections posts
suivantes :
/posts/{postid}
/forums/{forumid}/posts/{postid}
/forums/{forumid}/subforum/{subforumid}/posts/{postid}
Sécuriser des requêtes de groupe de collections en fonction d'un champ
À l'instar des requêtes appliquées à une seule collection, les requêtes de groupe de collections doivent également respecter les contraintes définies par vos règles de sécurité. Par exemple, nous pouvons ajouter un champ published
à chaque post de forum, comme nous l'avons fait dans l'exemple stories
ci-dessus :
/forums/{forumid}/posts/{postid}
{
author: "some_auth_id",
authorname: "some_username",
content: "I just read a great story.",
published: false
}
Nous pouvons ensuite écrire des règles pour le groupe de collections posts
en fonction de l'état published
et du post author
:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Returns `true` if the requested post is 'published'
// or the user authored the post
function authorOrPublished() {
return resource.data.published == true || request.auth.uid == resource.data.author;
}
match /{path=**}/posts/{post} {
// Anyone can query published posts
// Authors can query their unpublished posts
allow list: if authorOrPublished();
// Anyone can retrieve a published post
// Authors can retrieve an unpublished post
allow get: if authorOrPublished();
}
match /forums/{forumid}/posts/{postid} {
// Only a post's author can write to a post
allow write: if request.auth.uid == resource.data.author;
}
}
}
Ces règles permettent aux clients Web, Apple et Android d'effectuer les requêtes suivantes :
Tout utilisateur peut récupérer les posts publiés sur un forum :
db.collection("forums/technology/posts").where('published', '==', true).get()
Tout utilisateur peut récupérer les posts publiés par un auteur sur tous les forums :
db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
Les auteurs peuvent récupérer tous leurs posts publiés et non publiés sur tous les forums :
var user = firebase.auth().currentUser; db.collectionGroup("posts").where("author", "==", user.uid).get()
Sécuriser et interroger des documents en fonction du groupe de collections et du chemin d'accès au document
Dans certains cas, vous pouvez limiter les requêtes de groupe de collections en fonction du chemin d'accès au document. Pour créer ces restrictions, vous pouvez utiliser les mêmes techniques permettant de sécuriser et d'interroger des documents en fonction d'un champ.
Prenons l'exemple d'une application qui effectue le suivi des transactions de chaque utilisateur entre plusieurs cours boursiers et cryptomonnaies :
/users/{userid}/exchange/{exchangeid}/transactions/{transaction}
{
amount: 100,
exchange: 'some_exchange_name',
timestamp: April 1, 2019 at 12:00:00 PM UTC-7,
user: "some_auth_id",
}
Notez le champ user
. Même si nous savons quel utilisateur possède un document transaction
à partir du chemin d'accès du document, nous dupliquons ces informations dans chaque document transaction
, car cela nous permet d'effectuer deux opérations :
Écrire des requêtes de groupe de collections limitées aux documents qui incluent une valeur
/users/{userid}
spécifique dans leur chemin d'accès au document. Exemple :var user = firebase.auth().currentUser; // Return current user's last five transactions across all exchanges db.collectionGroup("transactions").where("user", "==", user).orderBy('timestamp').limit(5)
Appliquer cette restriction à toutes les requêtes du groupe de collections
transactions
afin qu'un utilisateur ne puisse pas récupérer les documentstransaction
d'un autre utilisateur.
Nous appliquons cette restriction à nos règles de sécurité et nous incluons la validation des données pour le champ user
:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{path=**}/transactions/{transaction} {
// Authenticated users can retrieve only their own transactions
allow read: if resource.data.user == request.auth.uid;
}
match /users/{userid}/exchange/{exchangeid}/transactions/{transaction} {
// Authenticated users can write to their own transactions subcollections
// Writes must populate the user field with the correct auth id
allow write: if userid == request.auth.uid && request.data.user == request.auth.uid
}
}
}
Étapes suivantes
- Pour obtenir un exemple plus détaillé de contrôle des accès basé sur les rôles, consultez la section Sécuriser l'accès aux données pour les utilisateurs et les groupes.
- Consulter la documentation de référence sur les règles de sécurité.