Écrire des conditions pour les règles de sécurité Cloud Firestore

Ce guide s'appuie sur le guide Structurer les règles de sécurité pour montrer comment ajouter des conditions à votre Cloud Firestore Security Rules. Si vous ne maîtrisez pas les principes de base de Cloud Firestore Security Rules, consultez le guide de démarrage.

La condition représente le composant fondamental de Cloud Firestore Security Rules. Une condition est une expression booléenne qui détermine si une opération spécifique doit être autorisée ou refusée. Utilisez des règles de sécurité pour écrire des conditions permettant de vérifier l'authentification des utilisateurs, de valider les données entrantes ou même d'accéder à d'autres parties de votre base de données.

Authentification

L'un des modèles de règles de sécurité les plus courants consiste à contrôler l'accès en fonction de l'état d'authentification de l'utilisateur. Par exemple, votre application peut autoriser uniquement les utilisateurs connectés à écrire des données :

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to access documents in the "cities" collection
    // only if they are authenticated.
    match /cities/{city} {
      allow read, write: if request.auth != null;
    }
  }
}

Un autre modèle courant consiste à s'assurer que les utilisateurs peuvent uniquement lire et écrire leurs propres données :

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches name of the user
    // document. The wildcard expression {userId} makes the userId variable
    // available in rules.
    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null;
    }
  }
}

Si votre application utilise Firebase Authentication ou Google Cloud Identity Platform, la variable request.auth contient les informations d'authentification du client demandant des données. Pour plus d'informations sur request.auth, consultez la documentation de référence.

Validation des données

De nombreuses applications stockent des informations de contrôle des accès sous forme de champs dans les documents de la base de données. Cloud Firestore Security Rules peut autoriser ou refuser l'accès de manière dynamique en fonction des données du document:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

La variable resource fait référence au document demandé et resource.data correspond à une carte de tous les champs et valeurs stockés dans le document. Pour plus d'informations sur la variable resource, consultez la documentation de référence.

Lorsque vous écrivez des données, vous pouvez comparer les données entrantes aux données existantes. Dans ce cas, si votre ensemble de règles autorise l'écriture en attente, la variable request.resource contient l'état futur du document. Pour les opérations update qui ne modifient qu'un sous-ensemble de champs du document, la variable request.resource contient l'état du document en attente après l'opération. Vous pouvez vérifier les valeurs des champs dans request.resource pour éviter les mises à jour de données non souhaitées ou incohérentes :

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure all cities have a positive population and
    // the name is not changed
    match /cities/{city} {
      allow update: if request.resource.data.population > 0
                    && request.resource.data.name == resource.data.name;
    }
  }
}

Accéder à d'autres documents

Grâce aux fonctions get() et exists(), vos règles de sécurité peuvent évaluer les requêtes entrantes par rapport aux autres documents de la base de données. Les fonctions get() et exists() exigent des chemins d'accès de document complets. Lorsque vous utilisez des variables pour créer des chemins d'accès pour get() et exists(), vous devez explicitement échapper les variables à l'aide de la syntaxe $(variable).

Dans l'exemple ci-dessous, la variable database est capturée par l'instruction de correspondance match /databases/{database}/documents et utilisée pour former le chemin d'accès :

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      // Make sure a 'users' document exists for the requesting user before
      // allowing any writes to the 'cities' collection
      allow create: if request.auth != null && exists(/databases/$(database)/documents/users/$(request.auth.uid));

      // Allow the user to delete cities if their user document has the
      // 'admin' field set to 'true'
      allow delete: if request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true;
    }
  }
}

Pour les écritures, vous pouvez utiliser la fonction getAfter() pour accéder à l'état d'un document après l'exécution d'une transaction ou d'un lot d'écritures, mais avant la validation de la transaction ou du lot. Comme get(), la fonction getAfter() exige un chemin d'accès de document complet. Vous pouvez utiliser getAfter() pour définir des ensembles d'écritures qui doivent se dérouler conjointement en tant que transaction ou lot.

Accéder aux limites d'appels

Les appels d'accès aux documents via une évaluation d'ensemble de règles sont limités :

  • 10 pour les requêtes de documents uniques et les requêtes de type "query".
  • 20 pour les lectures de plusieurs documents, les transactions et les écritures par lot. La limite précédente de 10 s'applique également à chaque opération.

    Par exemple, imaginons que vous créez une requête d'écriture par lot comprenant trois opérations, et que vos règles de sécurité utilisent deux appels d'accès au document pour valider chaque écriture. Dans ce cas, chaque écriture utilise deux de ses 10 appels d'accès et la requête d'écriture par lot utilise six de ses 20 appels d'accès.

Le dépassement de l'une ou l'autre limite entraîne une erreur de type "permission refusée". Certains appels d'accès aux documents peuvent être mis en cache, et les appels en cache ne sont pas pris en compte dans les limites.

Pour plus d'informations sur l'incidence de ces limites sur les transactions et les écritures par lot, consultez le guide de sécurisation des opérations atomiques.

Appels d'accès et tarification

En utilisant ces fonctions, vous exécutez une opération de lecture dans votre base de données, ce qui signifie que vous serez facturé pour la lecture des documents, même si vos règles rejettent la requête. Pour en savoir plus sur la facturation, consultez la page Tarifs de Cloud Firestore.

Fonctions personnalisées

À mesure que vos règles de sécurité deviennent plus complexes, vous voudrez peut-être regrouper des ensembles de conditions dans des fonctions réutilisables dans votre ensemble de règles. Les règles de sécurité sont compatibles avec les fonctions personnalisées. La syntaxe des fonctions personnalisées ressemble à JavaScript ; cependant, les fonctions de règles de sécurité sont écrites dans un langage propre au domaine, qui présente certaines limitations importantes :

  • Les fonctions ne peuvent contenir qu'une seule instruction return. Elles ne peuvent pas contenir de logique supplémentaire. Par exemple, ils ne peuvent pas exécuter de boucles ni appeler de services externes.
  • Les fonctions peuvent accéder automatiquement aux fonctions et aux variables du champ d'application dans lequel elles sont définies. Par exemple, une fonction définie dans le champ d'application service cloud.firestore a accès à la variable resource et aux fonctions intégrées telles que get() et exists().
  • Les fonctions peuvent appeler d'autres fonctions, mais peuvent ne pas les inclure. La profondeur totale de la pile d'appel est limitée à 10.
  • Dans la version de règles v2, les fonctions peuvent définir des variables à l'aide du mot clé let. Les fonctions peuvent avoir jusqu'à 10 liaisons "let", mais elles doivent se terminer par une instruction "return".

Une fonction est définie avec le mot clé function et accepte zéro ou plusieurs arguments. Par exemple, vous pouvez combiner les deux types de conditions utilisés dans les exemples ci-dessus en une seule fonction :

service cloud.firestore {
  match /databases/{database}/documents {
    // True if the user is signed in or the requested data is 'public'
    function signedInOrPublic() {
      return request.auth.uid != null || resource.data.visibility == 'public';
    }

    match /cities/{city} {
      allow read, write: if signedInOrPublic();
    }

    match /users/{user} {
      allow read, write: if signedInOrPublic();
    }
  }
}

L'utilisation de fonctions dans vos règles de sécurité les rend plus faciles à gérer à mesure que la complexité de vos règles augmente.

Les règles ne sont pas des filtres

Une fois que vous avez sécurisé vos données et que vous commencez à écrire des requêtes, gardez à l'esprit que les règles de sécurité ne sont pas des filtres. Vous ne pouvez pas écrire une requête pour tous les documents d'une collection et vous attendre à ce que Cloud Firestore ne renvoie que les documents auxquels le client actuel a accès.

Prenons l'exemple de la règle de sécurité suivante :

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

Refusé : cette règle rejette la requête suivante, car l'ensemble de résultats peut inclure des documents où le champ visibility ne contient pas la valeur public :

Web
db.collection("cities").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
    });
});

Autorisé : cette règle autorise la requête suivante, car la clause where("visibility", "==", "public") garantit que l'ensemble de résultats répond à la condition de la règle :

Web
db.collection("cities").where("visibility", "==", "public").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
        });
    });

Les règles de sécurité Cloud Firestore évaluent chaque requête par rapport à son résultat potentiel ; la requête échoue si elle peut renvoyer un document que le client n'est pas autorisé à lire. Les requêtes doivent respecter les contraintes définies par vos règles de sécurité. Pour en savoir plus sur les règles de sécurité et les requêtes, consultez l'article Interroger des données de manière sécurisée.

Étapes suivantes