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

Ce guide s'appuie sur le guide Structurer les règles de sécurité pour montrer comment ajouter des conditions à vos règles de sécurité Firestore. Si vous ne connaissez pas les principes de base des règles de sécurité Firestore, consultez le guide Premiers pas.

La condition est le composant fondamental des règles de sécurité Firestore. Une condition est une expression booléenne qui détermine si une opération particulière 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 les 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 le fichier request.auth, consultez la documentation de référence.

Validation des données

De nombreuses applications stockent les informations de contrôle d'accès sous forme de champs dans les documents de la base de données. Les règles de sécurité Firestore peuvent autoriser ou refuser dynamiquement l'accès en fonction des données de 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 est 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.

Lors de l'écriture 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 de document, la variable request.resource contient l'état du document en attente après l'opération. Vous pouvez vérifier les valeurs de champ dans request.resource pour empêcher les mises à jour de données indésirables 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

Les fonctions get() et exists() permettent à vos règles de sécurité d'évaluer les requêtes entrantes par rapport aux autres documents de la base de données. Les fonctions get() et exists() s'attendent à des chemins de document entièrement spécifiés. Lorsque vous utilisez des variables pour créer des chemins d'accès pour get() et exists(), vous devez les échapper explicitement à 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 une transaction ou un lot d'écritures, mais avant la validation de la transaction ou du lot. Comme get(), la fonction getAfter() emprunte un chemin de document entièrement spécifié. Vous pouvez utiliser getAfter() pour définir des ensembles d'écritures qui doivent avoir lieu conjointement sous forme de transaction ou de lot.

Limites d'appels d'accès

Le nombre d'appels d'accès aux documents par évaluation de l'ensemble de règles est limité comme suit:

  • 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 de 10 précédente s'applique également à chaque opération.

    Par exemple, imaginez que vous créez une requête d'écriture par lot comprenant trois opérations d'écriture et que vos règles de sécurité utilisent deux appels d'accès aux documents 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 obtenir une explication détaillée de l'impact de ces limites sur les transactions et les écritures par lot, consultez le guide sur la sécurisation des opérations atomiques.

Appels et tarifs d'accès

L'utilisation de ces fonctions exécute une opération de lecture dans votre base de données, ce qui signifie que la lecture des documents vous sera facturée, même si vos règles refusent la requête. Consultez la page Tarifs de Firestore pour en savoir plus sur la facturation.

Fonctions personnalisées

À mesure que vos règles de sécurité deviennent plus complexes, vous pouvez encapsuler des ensembles de conditions dans des fonctions que vous pouvez réutiliser dans votre ensemble de règles. Les règles de sécurité acceptent les fonctions personnalisées. La syntaxe des fonctions personnalisées est un peu semblable à JavaScript, à ceci près que les fonctions des règles de sécurité sont écrites dans un langage spécifique à un domaine présentant quelques limitations importantes:

  • Les fonctions ne peuvent contenir qu'une seule instruction return. Elles ne peuvent pas contenir de logique supplémentaire. Par exemple, elles ne peuvent pas exécuter des boucles ni appeler des 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 ne peuvent pas être répétées. La profondeur totale de la pile d'appels est limitée à 10.
  • Dans la version v2 des règles, les fonctions peuvent définir des variables à l'aide du mot clé let. Les fonctions peuvent comporter jusqu'à 10 liaisons de lettres, mais elles doivent se terminer par une instruction de retour.

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 leur complexité augmente.

Les règles ne sont pas des filtres

Une fois que vous avez sécurisé vos données et commencé à é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 Firestore ne renvoie que les documents auxquels le client actuel a accès.

Prenons par exemple 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';
    }
  }
}

Demande refusée : cette règle rejette la requête suivante, car l'ensemble de résultats peut inclure des documents où visibility n'est pas public :

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

Autorisée: cette règle autorise la requête suivante, car la clause where("visibility", "==", "public") garantit que l'ensemble de résultats remplit 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é Firestore évaluent chaque requête par rapport à son résultat potentiel et échoue à la requête 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 la page Interroger des données de manière sécurisée.

Étapes suivantes