Catch up on everything announced at Firebase Summit, and learn how Firebase can help you accelerate app development and run your app with confidence. Learn More

Interroger les données en toute sécurité

Restez organisé à l'aide des collections Enregistrez et classez les contenus selon vos préférences.

Cette page s'appuie sur les concepts des sections Structuration des règles de sécurité et Conditions d'écriture des règles de sécurité pour expliquer comment les règles de sécurité Cloud Firestore interagissent avec les requêtes. Il examine de plus près comment les règles de sécurité affectent les requêtes que vous pouvez écrire et décrit comment vous assurer que vos requêtes utilisent les mêmes contraintes que vos règles de sécurité. Cette page décrit également comment écrire des règles de sécurité pour autoriser ou refuser 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 sont tout ou rien. Pour vous faire gagner du temps et des ressources, Cloud Firestore évalue une requête par rapport à son ensemble de résultats potentiels au lieu des valeurs de champ réelles de tous vos documents. Si une requête est susceptible de 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écurisez et interrogez des documents basés sur 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é. Considérez une base de données qui contient une collection de documents d' story :

/histoires/{idhistoire}

{
  title: "A Great Story",
  content: "Once upon a time...",
  author: "some_auth_id",
  published: false
}

En plus des champs de title et content , chaque document stocke l' author et les champs published à utiliser pour le contrôle d'accès. Ces exemples supposent que l'application utilise Firebase Authentication pour définir le champ de l' author sur l'UID de l'utilisateur qui a créé le document. Firebase Authentication renseigne é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 restreindre l'accès en lecture et en écriture de chaque 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 inclue une page qui affiche à l'utilisateur une liste de documents de story qu'il a créés. Vous pouvez vous attendre à utiliser la requête suivante pour remplir cette page. Cependant, 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 la 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 en fait l'auteur de chaque document de story . La raison de ce comportement est que lorsque Cloud Firestore applique vos règles de sécurité, il évalue la requête par rapport à son ensemble de résultats potentiels , et non par rapport aux propriétés réelles des documents de votre base de données. Si une requête est susceptible d' inclure des documents qui enfreignent vos règles de sécurité, la requête échouera.

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 la 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 basés sur un champ

Pour démontrer davantage l'interaction entre les requêtes et les règles, les règles de sécurité ci-dessous étendent l'accès en lecture pour la collection d' stories afin de permettre à tout utilisateur de lire des documents d' story où 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 des pages publiées doit comporter 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 true pour tout résultat. Par conséquent, cette requête satisfait aux règles de sécurité et est autorisée à lire des données.

in et array-contains-any requêtes

Lors de l'évaluation d'une clause de requête in ou array-contains-any par rapport à un ensemble de règles, Cloud Firestore évalue chaque valeur de comparaison séparément. Chaque valeur de comparaison doit répondre aux contraintes des règles 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 que x > 5 pour tous les documents potentiels

// This query will fail
db.collection("mydocuments").where("x", "in", [1, 3, 6, 42, 99]).get()

Valide : la requête garantit que x > 5 pour tous les documents potentiels

db.collection("mydocuments").where("x", "in", [6, 42, 99, 105, 200]).get()

Évaluation des 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 placées sur les requêtes. Cet exemple étend l'ensemble de règles des stories précédentes avec les modifications suivantes :

  • L'ensemble de règles sépare la règle de lecture en règles pour get et list .
  • La règle get limite la récupération de documents uniques aux documents publics ou aux documents créés par l'utilisateur.
  • La règle de list applique les mêmes restrictions que get mais pour les requêtes. Il vérifie également la limite de 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 récupèrent uniquement les résultats de cette collection. Avec les requêtes de groupe de collections , vous pouvez récupérer les résultats d'un groupe de collections composé de toutes les collections avec le même ID. Cette section décrit comment sécuriser vos requêtes de groupe de collecte à l'aide de règles de sécurité.

Sécurisez et interrogez les documents en fonction des groupes de collections

Dans vos règles de sécurité, vous devez explicitement autoriser les requêtes de groupe de collecte en écrivant une règle pour le groupe de collecte :

  1. Assurez-vous que rules_version = '2'; est la première ligne de votre ensemble de règles. Les requêtes de groupe de collections nécessitent le nouveau comportement de caractère générique récursif {name=**} des règles de sécurité version 2.
  2. Écrivez une règle pour votre groupe de collecte en utilisant match /{path=**}/ [COLLECTION_ID] /{doc} .

Par exemple, considérons un forum organisé en documents de forum contenant des sous-collections d' 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 publications 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 messages de n'importe quel forum :

db.collection("forums/technology/posts").get()

Mais que se passe-t-il si vous souhaitez montrer à l'utilisateur actuel ses messages 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 d' 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 collecte des 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 cependant que ces règles s'appliqueront à toutes les collections avec des posts d'ID, quelle que soit la hiérarchie. Par exemple, ces règles s'appliquent à toutes les collections de posts suivantes :

  • /posts/{postid}
  • /forums/{forumid}/posts/{postid}
  • /forums/{forumid}/subforum/{subforumid}/posts/{postid}

Sécuriser les requêtes de groupe de collecte basées sur un champ

Comme les requêtes de collection unique, 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 message du forum comme nous l'avons fait dans l'exemple des 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 collecte des posts en fonction du statut published et de l' author de l'article :

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;
    }
  }
}

Avec ces règles, les clients Web, Apple et Android peuvent effectuer les requêtes suivantes :

  • Tout le monde peut récupérer les messages publiés dans un forum :

    db.collection("forums/technology/posts").where('published', '==', true).get()
    
  • Tout le monde peut récupérer les messages publiés d'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 messages publiés et non publiés sur tous les forums :

    var user = firebase.auth().currentUser;
    
    db.collectionGroup("posts").where("author", "==", user.uid).get()
    

Sécurisez et interrogez les documents en fonction du groupe de collections et du chemin d'accès au document

Dans certains cas, vous souhaiterez peut-être 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 de sécurisation et d'interrogation de documents basés sur un champ.

Considérez une application qui garde une trace des transactions de chaque utilisateur entre plusieurs bourses d'actions et de crypto-monnaie :

/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",
}

Remarquez le champ user . Même si nous savons à quel utilisateur appartient un document de transaction à partir du chemin du document, nous dupliquons ces informations dans chaque document de transaction car cela nous permet de faire deux choses :

  • Écrivez des requêtes de groupe de collections qui sont limitées aux documents qui incluent un /users/{userid} spécifique dans leur chemin de document. Par 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)
    
  • Appliquez cette restriction pour toutes les requêtes sur le groupe de collecte de transactions afin qu'un utilisateur ne puisse pas récupérer les documents de transaction d'un autre utilisateur.

Nous appliquons cette restriction dans nos règles de sécurité et 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
    }
  }
}

Prochaines étapes