Conditions d'écriture des règles de sécurité Cloud Firestore

Ce guide s'appuie sur le guide des règles de sécurité structurantes pour montrer comment ajouter des conditions à vos règles de sécurité Cloud Firestore. Si vous n'êtes pas familier avec les bases des règles de sécurité Cloud Firestore, consultez le guide de démarrage .

Le principal élément constitutif des règles de sécurité Cloud Firestore est la condition. 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 rédiger des conditions qui vérifient l'authentification des utilisateurs, valident les données entrantes ou même accèdent à 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 souhaiter 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 ne peuvent lire et écrire que 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 .

La validation des données

De nombreuses applications stockent les informations de contrôle d'accès sous forme de champs sur les documents de la base de données. Les règles de sécurité Cloud Firestore peuvent 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 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 de données, vous souhaiterez peut-être 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 modifient uniquement un sous-ensemble des champs du document, la variable request.resource contiendra 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 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

À l'aide des fonctions get() et exists() , vos règles de sécurité peuvent évaluer les requêtes entrantes par rapport à d'autres documents de la base de données. Les fonctions get() et exists() attendent toutes deux des chemins de document entièrement spécifiés. Lorsque vous utilisez des variables pour construire des chemins pour get() et exists() , vous devez échapper explicitement aux variables en utilisant la syntaxe $(variable) .

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

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 la fin d'une transaction ou d'un lot d'écritures, mais avant la validation de la transaction ou du lot. Comme get() , la fonction getAfter() prend un chemin de document entièrement spécifié. Vous pouvez utiliser getAfter() pour définir des ensembles d'écritures qui doivent avoir lieu ensemble sous forme de transaction ou de lot.

Accéder aux limites d'appels

Il existe une limite aux appels d'accès aux documents par évaluation d'ensemble de règles :

  • 10 pour les demandes de document unique et les demandes de requête.
  • 20 pour les lectures, transactions et écritures par lots de plusieurs documents. La limite précédente de 10 s'applique également à chaque opération.

    Par exemple, imaginez que vous créez une demande d'écriture par lots avec 3 opérations d'écriture et que vos règles de sécurité utilisent 2 appels d'accès aux documents pour valider chaque écriture. Dans ce cas, chaque écriture utilise 2 de ses 10 appels d'accès et la demande d'écriture par lots utilise 6 de ses 20 appels d'accès.

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

Pour une explication détaillée de la manière dont ces limites affectent les transactions et les écritures par lots, consultez le guide de sécurisation des opérations atomiques .

Accès aux appels et tarifs

L'utilisation de ces fonctions exécute 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 demande. Consultez la tarification de Cloud Firestore pour obtenir des informations de facturation plus spécifiques.

Fonctions personnalisées

À mesure que vos règles de sécurité deviennent plus complexes, vous souhaiterez peut-être regrouper des ensembles de conditions dans des fonctions que vous pourrez réutiliser dans votre ensemble de règles. Les règles de sécurité prennent en charge les fonctions personnalisées. La syntaxe des fonctions personnalisées ressemble un peu à JavaScript, mais les fonctions de règles de sécurité sont écrites dans un langage spécifique au domaine qui présente certaines limitations importantes :

  • Les fonctions ne peuvent contenir qu'une seule instruction return . Ils ne peuvent contenir aucune logique supplémentaire. Par exemple, ils ne peuvent pas exécuter de boucles ni appeler des services externes.
  • Les fonctions peuvent accéder automatiquement aux fonctions et aux variables à partir de la portée dans laquelle elles sont définies. Par exemple, une fonction définie dans la portée du 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 récidiver. 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 avoir jusqu'à 10 liaisons let, mais doivent se terminer par une instruction return.

Une fonction est définie avec le mot-clé function et prend zéro ou plusieurs arguments. Par exemple, vous souhaiterez peut-être 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 à maintenir à 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 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 Cloud Firestore renvoie uniquement les documents auxquels le client actuel est autorisé à accéder.

Par exemple, prenons 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 le jeu de résultats peut inclure des documents dont visibility n'est pas public :

la toile
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 le jeu de résultats satisfait à la condition de la règle :

la toile
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é de Cloud Firestore évaluent chaque requête par rapport à son résultat potentiel et échouent si la requête peut renvoyer un document que le client n'est pas autorisé à lire. Les requêtes doivent suivre les contraintes fixées par vos règles de sécurité. Pour en savoir plus sur les règles et requêtes de sécurité, consultez interroger des données en toute sécurité .

Prochaines étapes