Kontrollieren Sie den Zugriff auf bestimmte Felder

Diese Seite baut auf den Konzepten in „Strukturieren von Sicherheitsregeln“ und „Schreiben von Bedingungen für Sicherheitsregeln“ auf und erläutert, wie Sie Cloud Firestore-Sicherheitsregeln verwenden können, um Regeln zu erstellen, die es Clients ermöglichen, Vorgänge für einige Felder in einem Dokument auszuführen, andere jedoch nicht.

Es kann vorkommen, dass Sie Änderungen an einem Dokument nicht auf Dokumentebene, sondern auf Feldebene steuern möchten.

Beispielsweise möchten Sie möglicherweise einem Kunden erlauben, ein Dokument zu erstellen oder zu ändern, ihm jedoch nicht erlauben, bestimmte Felder in diesem Dokument zu bearbeiten. Oder Sie möchten möglicherweise erzwingen, dass jedes Dokument, das ein Kunde erstellt, immer einen bestimmten Satz von Feldern enthält. In diesem Leitfaden erfahren Sie, wie Sie einige dieser Aufgaben mithilfe der Cloud Firestore-Sicherheitsregeln erledigen können.

Lesezugriff nur für bestimmte Felder zulassen

Lesevorgänge in Cloud Firestore werden auf Dokumentebene durchgeführt. Entweder rufen Sie das vollständige Dokument ab oder Sie rufen nichts ab. Es gibt keine Möglichkeit, ein Teildokument abzurufen. Es ist unmöglich, Sicherheitsregeln allein zu verwenden, um zu verhindern, dass Benutzer bestimmte Felder in einem Dokument lesen.

Wenn es in einem Dokument bestimmte Felder gibt, die Sie für einige Benutzer verborgen halten möchten, ist es am besten, sie in einem separaten Dokument unterzubringen. Sie könnten beispielsweise erwägen, ein Dokument in einer private Untersammlung wie folgt zu erstellen:

/employees/{emp_id}

  name: "Alice Hamilton",
  department: 461,
  start_date: <timestamp>

/employees/{emp_id}/private/finances

    salary: 80000,
    bonus_mult: 1.25,
    perf_review: 4.2

Anschließend können Sie Sicherheitsregeln hinzufügen, die unterschiedliche Zugriffsebenen für die beiden Sammlungen festlegen. In diesem Beispiel verwenden wir benutzerdefinierte Authentifizierungsansprüche , um zu sagen, dass nur Benutzer mit der benutzerdefinierten role Finance die Finanzinformationen eines Mitarbeiters anzeigen können.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow any logged in user to view the public employee data
    match /employees/{emp_id} {
      allow read: if request.resource.auth != null
      // Allow only users with the custom auth claim of "Finance" to view
      // the employee's financial data
      match /private/finances {
        allow read: if request.resource.auth &&
          request.resource.auth.token.role == 'Finance'
      }
    }
  }
}

Einschränken von Feldern bei der Dokumenterstellung

Cloud Firestore ist schemalos, was bedeutet, dass es auf Datenbankebene keine Einschränkungen hinsichtlich der Felder gibt, die ein Dokument enthält. Während diese Flexibilität die Entwicklung erleichtern kann, gibt es Zeiten, in denen Sie sicherstellen möchten, dass Kunden nur Dokumente erstellen können, die bestimmte Felder enthalten, oder keine anderen Felder.

Sie können diese Regeln erstellen, indem Sie die Methode keys des Objekts request.resource.data untersuchen. Dies ist eine Liste aller Felder, die der Client in dieses neue Dokument schreiben möchte. Durch die Kombination dieser Felder mit Funktionen wie hasOnly() oder hasAny() können Sie eine Logik hinzufügen, die die Arten von Dokumenten einschränkt, die ein Benutzer zu Cloud Firestore hinzufügen kann.

Erfordernis bestimmter Felder in neuen Dokumenten

Angenommen, Sie möchten sicherstellen, dass alle in einer restaurant erstellten Dokumente mindestens ein Feld name , location “ und city enthalten. Sie können dies tun, indem Sie hasAll() für die Liste der Schlüssel im neuen Dokument aufrufen.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document contains a name
    // location, and city field
    match /restaurant/{restId} {
      allow create: if request.resource.data.keys().hasAll(['name', 'location', 'city']);
    }
  }
}

Dadurch können Restaurants auch mit anderen Feldern erstellt werden, es wird jedoch sichergestellt, dass alle von einem Kunden erstellten Dokumente mindestens diese drei Felder enthalten.

Bestimmte Felder in neuen Dokumenten verbieten

Ebenso können Sie verhindern, dass Clients Dokumente erstellen, die bestimmte Felder enthalten, indem Sie hasAny() für eine Liste verbotener Felder verwenden. Diese Methode ergibt „true“, wenn ein Dokument eines dieser Felder enthält. Daher möchten Sie das Ergebnis wahrscheinlich negieren, um bestimmte Felder zu verbieten.

Im folgenden Beispiel ist es Clients beispielsweise nicht gestattet, ein Dokument zu erstellen, das ein Feld average_score “ oder rating_count enthält, da diese Felder zu einem späteren Zeitpunkt durch einen Serveraufruf hinzugefügt werden.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document does *not*
    // contain an average_score or rating_count field.
    match /restaurant/{restId} {
      allow create: if (!request.resource.data.keys().hasAny(
        ['average_score', 'rating_count']));
    }
  }
}

Erstellen einer Zulassungsliste mit Feldern für neue Dokumente

Anstatt bestimmte Felder in neuen Dokumenten zu verbieten, möchten Sie möglicherweise eine Liste nur der Felder erstellen, die in neuen Dokumenten ausdrücklich zulässig sind. Anschließend können Sie mit der Funktion hasOnly() sicherstellen, dass alle neu erstellten Dokumente nur diese Felder (oder eine Teilmenge dieser Felder) und keine anderen enthalten.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document doesn't contain
    // any fields besides the ones listed below.
    match /restaurant/{restId} {
      allow create: if (request.resource.data.keys().hasOnly(
        ['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

Kombination von Pflichtfeldern und optionalen Feldern

Sie können die Vorgänge hasAll und hasOnly in Ihren Sicherheitsregeln kombinieren, um einige Felder erforderlich zu machen und andere zuzulassen. In diesem Beispiel ist es beispielsweise erforderlich, dass alle neuen Dokumente die Felder „ name , location “ und city “ enthalten. Optional sind auch die Felder „ address , hours “ und cuisine zulässig.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document has a name,
    // location, and city field, and optionally address, hours, or cuisine field
    match /restaurant/{restId} {
      allow create: if (request.resource.data.keys().hasAll(['name', 'location', 'city'])) &&
       (request.resource.data.keys().hasOnly(
           ['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

In einem realen Szenario möchten Sie diese Logik möglicherweise in eine Hilfsfunktion verschieben, um eine Duplizierung Ihres Codes zu vermeiden und die optionalen und erforderlichen Felder einfacher in einer einzigen Liste zu kombinieren, etwa so:

service cloud.firestore {
  match /databases/{database}/documents {
    function verifyFields(required, optional) {
      let allAllowedFields = required.concat(optional);
      return request.resource.data.keys().hasAll(required) &&
        request.resource.data.keys().hasOnly(allAllowedFields);
    }
    match /restaurant/{restId} {
      allow create: if verifyFields(['name', 'location', 'city'],
        ['address', 'hours', 'cuisine']);
    }
  }
}

Einschränken von Feldern bei der Aktualisierung

Eine gängige Sicherheitspraxis besteht darin, Clients nur das Bearbeiten einiger Felder zu erlauben und andere nicht. Sie können dies nicht allein durch einen Blick auf die im vorherigen Abschnitt beschriebene Liste request.resource.data.keys() erreichen, da diese Liste das vollständige Dokument so darstellt, wie es nach der Aktualisierung aussehen würde, und daher Felder enthalten würde, die der Client nicht hatte ändern.

Wenn Sie jedoch die Funktion diff() verwenden würden, könnten Sie request.resource.data mit dem Objekt resource.data vergleichen, das das Dokument in der Datenbank vor der Aktualisierung darstellt. Dadurch wird ein mapDiff Objekt erstellt, das alle Änderungen zwischen zwei verschiedenen Karten enthält.

Durch Aufrufen der Methode affectedKeys() für dieses MapDiff können Sie eine Reihe von Feldern erstellen, die bei einer Bearbeitung geändert wurden. Dann können Sie Funktionen wie hasOnly() oder hasAny() verwenden, um sicherzustellen, dass dieser Satz bestimmte Elemente enthält (oder nicht).

Verhindert, dass einige Felder geändert werden

Indem Sie die hasAny() Methode auf den von affectedKeys() generierten Satz anwenden und dann das Ergebnis negieren, können Sie jede Client-Anfrage ablehnen, die versucht, Felder zu ändern, die Sie nicht ändern möchten.

Beispielsweise möchten Sie Ihren Kunden möglicherweise ermöglichen, Informationen über ein Restaurant zu aktualisieren, ihre durchschnittliche Punktzahl oder Anzahl der Bewertungen jedoch nicht zu ändern.

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
      // Allow the client to update a document only if that document doesn't
      // change the average_score or rating_count fields
      allow update: if (!request.resource.data.diff(resource.data).affectedKeys()
        .hasAny(['average_score', 'rating_count']));
    }
  }
}

Nur bestimmte Felder dürfen geändert werden

Anstatt Felder anzugeben, die Sie nicht ändern möchten, können Sie auch die Funktion hasOnly() verwenden, um eine Liste von Feldern anzugeben, die geändert werden sollen. Dies gilt im Allgemeinen als sicherer, da Schreibvorgänge in alle neuen Dokumentfelder standardmäßig nicht zulässig sind, bis Sie sie in Ihren Sicherheitsregeln ausdrücklich zulassen.

Anstatt beispielsweise die Felder average_score und „ rating_count zu verbieten, könnten Sie Sicherheitsregeln erstellen, die es Kunden ermöglichen, nur die Felder „ name “, „ location “, city “, address , hours “ und cuisine “ zu ändern.

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
    // Allow a client to update only these 6 fields in a document
      allow update: if (request.resource.data.diff(resource.data).affectedKeys()
        .hasOnly(['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

Das bedeutet, dass, wenn in einer zukünftigen Iteration Ihrer App Restaurantdokumente ein telephone enthalten, Versuche, dieses Feld zu bearbeiten, fehlschlagen würden, bis Sie zurückgehen und dieses Feld zur hasOnly() Liste in Ihren Sicherheitsregeln hinzufügen.

Feldtypen erzwingen

Ein weiterer Effekt der Schemalosigkeit von Cloud Firestore besteht darin, dass auf Datenbankebene keine Durchsetzung erfolgt, welche Datentypen in bestimmten Feldern gespeichert werden können. Dies können Sie jedoch in Sicherheitsregeln mit dem Operator is erzwingen.

Die folgende Sicherheitsregel erzwingt beispielsweise, dass das score einer Rezension eine Ganzzahl sein muss, die Felder „ headline “, content “ und „ author_name Zeichenfolgen sind und „ review_date “ ein Zeitstempel ist.

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
      // Restaurant rules go here...
      match /review/{reviewId} {
        allow create: if (request.resource.data.score is int &&
          request.resource.data.headline is string &&
          request.resource.data.content is string &&
          request.resource.data.author_name is string &&
          request.resource.data.review_date is timestamp
        );
      }
    }
  }
}

Gültige Datentypen für den is Operator sind bool , bytes , float , int , list , latlng , number , path , map , string und timestamp . Der is Operator unterstützt auch die Datentypen constraint “, duration “, set “ und „ map_diff “. Da diese jedoch von der Sicherheitsregelsprache selbst und nicht von Clients generiert werden, werden sie in den meisten praktischen Anwendungen selten verwendet.

list und map unterstützen keine Generika oder Typargumente. Mit anderen Worten: Sie können Sicherheitsregeln verwenden, um zu erzwingen, dass ein bestimmtes Feld eine Liste oder eine Karte enthält. Sie können jedoch nicht erzwingen, dass ein Feld eine Liste aller Ganzzahlen oder aller Zeichenfolgen enthält.

Ebenso können Sie Sicherheitsregeln verwenden, um Typwerte für bestimmte Einträge in einer Liste oder einer Karte zu erzwingen (unter Verwendung der Brakets-Notation bzw. Schlüsselnamen), aber es gibt keine Verknüpfung, um die Datentypen aller Mitglieder in einer Karte oder einer Liste zu erzwingen einmal.

Die folgenden Regeln stellen beispielsweise sicher, dass ein tags Feld in einem Dokument eine Liste enthält und dass der erste Eintrag eine Zeichenfolge ist. Es stellt außerdem sicher, dass das product eine Zuordnung enthält, die wiederum einen Produktnamen enthält, der eine Zeichenfolge ist, und eine Menge, die eine Ganzzahl ist.

service cloud.firestore {
  match /databases/{database}/documents {
  match /orders/{orderId} {
    allow create: if request.resource.data.tags is list &&
      request.resource.data.tags[0] is string &&
      request.resource.data.product is map &&
      request.resource.data.product.name is string &&
      request.resource.data.product.quantity is int
      }
    }
  }
}

Feldtypen müssen sowohl beim Erstellen als auch beim Aktualisieren eines Dokuments erzwungen werden. Daher sollten Sie die Erstellung einer Hilfsfunktion in Betracht ziehen, die Sie sowohl im Erstellungs- als auch im Aktualisierungsabschnitt Ihrer Sicherheitsregeln aufrufen können.

service cloud.firestore {
  match /databases/{database}/documents {

  function reviewFieldsAreValidTypes(docData) {
     return docData.score is int &&
          docData.headline is string &&
          docData.content is string &&
          docData.author_name is string &&
          docData.review_date is timestamp;
  }

   match /restaurant/{restId} {
      // Restaurant rules go here...
      match /review/{reviewId} {
        allow create: if reviewFieldsAreValidTypes(request.resource.data) &&
          // Other rules may go here
        allow update: if reviewFieldsAreValidTypes(request.resource.data) &&
          // Other rules may go here
      }
    }
  }
}

Erzwingen von Typen für optionale Felder

Es ist wichtig zu bedenken, dass der Aufruf von request.resource.data.foo für ein Dokument, in dem foo nicht existiert, zu einem Fehler führt und daher jede Sicherheitsregel, die diesen Aufruf durchführt, die Anfrage ablehnt. Sie können diese Situation bewältigen, indem Sie die get Methode für request.resource.data verwenden. Mit der get Methode können Sie ein Standardargument für das Feld angeben, das Sie aus einer Karte abrufen, wenn dieses Feld nicht vorhanden ist.

Wenn Überprüfungsdokumente beispielsweise auch ein optionales Feld photo_url “ und ein optionales Feld „ tags enthalten, bei denen Sie überprüfen möchten, dass es sich um Zeichenfolgen bzw. Listen handelt, können Sie dies erreichen, indem Sie die Funktion reviewFieldsAreValidTypes “ etwa wie folgt umschreiben:

  function reviewFieldsAreValidTypes(docData) {
     return docData.score is int &&
          docData.headline is string &&
          docData.content is string &&
          docData.author_name is string &&
          docData.review_date is timestamp &&
          docData.get('photo_url', '') is string &&
          docData.get('tags', []) is list;
  }

Dadurch werden Dokumente abgelehnt, in denen tags vorhanden sind, die aber keine Liste darstellen, während Dokumente, die kein Feld tags (oder photo_url “) enthalten, weiterhin zugelassen werden.

Teilweise Schreibvorgänge sind niemals zulässig

Ein letzter Hinweis zu den Cloud Firestore-Sicherheitsregeln ist, dass sie dem Client entweder erlauben, eine Änderung an einem Dokument vorzunehmen, oder dass sie die gesamte Bearbeitung ablehnen. Sie können keine Sicherheitsregeln erstellen, die Schreibvorgänge in einige Felder Ihres Dokuments akzeptieren, während andere im selben Vorgang abgelehnt werden.