Zugriff auf bestimmte Felder steuern

Diese Seite basiert auf den Konzepten in den Abschnitten Sicherheitsregeln strukturieren und Schreibbedingungen für Sicherheitsregeln, um zu erläutern, wie Sie mit Cloud Firestore Security Rules Regeln festlegen, die Clients Vorgänge in manchen Feldern eines Dokuments erlauben, in anderen jedoch nicht.

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

Angenommen, Sie möchten einem Client das Erstellen oder Ändern eines Dokuments erlauben, die Bearbeitung bestimmter Felder in diesem Dokument aber nicht . Oder Sie wollen erzwingen, dass jedes Dokument, das ein Client erstellt, eine bestimmte Gruppe von Feldern enthält. In diesem Leitfaden wird beschrieben, wie Sie einige dieser Aufgaben mit Cloud Firestore Security Rules ausführen können.

Lesezugriff nur für bestimmte Felder zulassen

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

Enthält ein Dokument bestimmte Felder, die Sie für manche Nutzer ausblenden möchten, empfiehlt es sich, diese in ein separates Dokument einzufügen. Beispielsweise können Sie ein Dokument in einer private-Untersammlung 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 je Sammlung Sicherheitsregeln mit unterschiedlichen Zugriffsebenen hinzufügen. In diesem Beispiel verwenden wir benutzerdefinierte Auth-Anforderungen um zu bestimmen, dass nur Nutzer, deren benutzerdefinierte Auth-Anforderung role Finance entspricht, die Finanzdetails eines Mitarbeiters aufrufen 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'
      }
    }
  }
}

Felder beim Erstellen von Dokumenten einschränken

Cloud Firestore ist schemalos. Das bedeutet, dass auf Datenbankebene keine Einschränkungen für die in einem Dokument enthaltenen Felder bestehen. Diese Flexibilität kann Entwicklungen vereinfachen. Manchmal wollen Sie allerdings garantieren, dass Clients nur Dokumente erstellen können, die bestimmte Felder oder keine anderen Felder enthalten.

Um diese Regeln zu erstellen prüfen Sie die Methode keys des request.resource.data-Objekts. Dies ist eine Liste aller Felder, die der Client in dieses neue Dokument schreiben möchte. Durch Kombination dieses Satzes an Feldern mit Funktionen wie hasOnly() oder hasAny() können Sie eine Logik erstellen, die bestimmt, welche Dokumententypen Nutzer zu Cloud Firestore hinzufügen können.

Bestimmte Felder in neuen Dokumenten erforderlich machen

Angenommen, Sie möchten sicherstellen, dass alle in einer restaurant-Sammlung erstellten Dokumente mindestens ein Feld der Typen name, location und city enthalten. Dazu können Sie auf die Liste der Schlüssel im neuen Dokument beispielsweise hasAll() anwenden.

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']);
    }
  }
}

So können Restaurants mit abweichenden Feldern erstellt werden, wobei sichergestellt bleibt, dass alle von einem Client erstellten Dokumente mindestens diese drei Felder enthalten.

Bestimmte Felder in neuen Dokumenten ausschließen

Ebenso können Sie verhindern, dass Clients Dokumente erstellen, die bestimmte Felder enthalten, indem Sie mit hasAny() eine Liste verbotener Felder erstellen. Diese Methode wird bestätigt, wenn ein Dokument eines dieser Felder enthält. Daher sollten Sie das Ergebnis negieren, um bestimmte Felder auszuschließen.

Im folgenden Beispiel dürfen Clients kein Dokument erstellen, das ein average_score- oder rating_count-Feld enthält, da diese Felder später 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']));
    }
  }
}

Zulassungsliste für Felder neuer Dokumente erstellen

Anstatt bestimmte Felder in neuen Dokumenten zu verbieten können Sie eine Liste mit den Feldern erstellen, die in neuen Dokumenten explizit zugelassen sind. Sie können dann mit der Funktion hasOnly() dafür sorgen, dass alle neu erstellten Dokumente nur diese oder nur eine Teilmenge dieser Felder 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']));
    }
  }
}

Erforderliche und optionale Felder kombinieren

Sie können hasAll- und hasOnly-Vorgänge in Ihren Sicherheitsregeln kombinieren, um manche Felder zu erzwingen und andere zuzulassen. In diesem Beispiel müssen alle neuen Dokumente die Felder name, location und city enthalten. Optional sind die Felder address, hours und cuisine-erlaubt.

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 der Praxis können Sie diese Logik in eine Hilfsfunktion verschieben, um das Duplizieren von Code zu vermeiden und die optionalen und erforderlichen Felder einfacher in einer Liste zu kombinieren:

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']);
    }
  }
}

Felder bei der Aktualisierung einschränken

Eine gängige Sicherheitspraxis besteht darin, Clients nur die Bearbeitung einiger Felder zu erlauben. Dies ist nicht allein anhand der im vorherigen Abschnitt beschriebenen Liste request.resource.data.keys() möglich, da diese Liste das vollständige Dokument so darstellt, wie es nach der Aktualisierung aussehen würde und entsprechend Felder einschließt, die der Kunde nicht geändert hat.

Wenn Sie jedoch die Funktion diff() verwendet haben, können Sie request.resource.data mit dem Objekt resource.data vergleichen, das das Dokument in der Datenbank vor dem Update darstellt. Dadurch wird ein mapDiff-Objekt erstellt, ein Objekt, das alle Änderungen zwischen zwei verschiedenen Zuordnungen enthält.

Wenn Sie die Methode affectedKeys() für diese mapDiff aufrufen, erhalten Sie eine Reihe Felder, die während einer Bearbeitung geändert wurden. Anschließend können Sie Funktionen wie hasOnly() oder hasAny() verwenden, um dafür zu sorgen, dass dieser Satz bestimmte Elemente enthält oder nicht.

Verhindern, dass bestimmte Felder geändert werden

Wenn Sie die Methode hasAny() auf den von affectedKeys() generierten Satz anwenden und dann das Ergebnis negieren, können Sie Clientanfragen ablehnen, die versuchen, Felder zu ändern, die nicht geändert werden sollen.

Beispiel: Sie können Kunden die Möglichkeit geben, Informationen zu einem Restaurant zu aktualisieren, die Bearbeitung der durchschnittlichen Bewertung oder der Anzahl der Rezensionen aber verbieten.

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']));
    }
  }
}

Bearbeitungen nur für bestimmte Felder zulassen

Anstatt Felder anzugeben, die nicht geändert werden dürfen, können Sie mit der Funktion hasOnly() eine Liste an Feldern angeben, die geändert werden sollen. Dies gilt im Allgemeinen als sicherer, da Schreibvorgänge in alle neuen Feldern eines Dokuments standardmäßig verboten sind, bis Sie diese ausdrücklich in Ihren Sicherheitsregeln zulassen.

Beispiel: Sie können Sicherheitsregeln erstellen, die Clients nur die Bearbeitung der Felder name, location city, address, hours und cuisine erlauben, statt die Felder average_score und rating_count zu verbieten.

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']));
    }
  }
}

Würde in einer zukünftigen Iteration Ihrer Anwendung z. B. ein Restaurantdokument mit einem Feld telephone eingefügt, so würde der Versuch, dieses Feld zu bearbeiten, fehlschlagen, bis Sie das Feld in die hasOnly()-Liste in Ihren Sicherheitsregeln aufnehmen.

Feldtypen erzwingen

Ein weiterer Effekt der schemalosen Cloud Firestore besteht darin, dass auf Datenbankebene nicht durchgesetzt wird, welche Datentypen in bestimmten Feldern gespeichert werden können. Das können Sie jedoch in den Sicherheitsregeln mit dem Operator is erzwingen.

Beispiel: Folgende Sicherheitsregel erzwingt, dass das Feld score einer Rezension eine Ganzzahl enthalten muss, die Felder headline, content und author_name stellen dagegen Strings dar, review_date ist ein Zeitstempel.

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 Operator is sind bool, bytes, float, int, list, latlng, number, path, map, string und timestamp. Der Operator is unterstützt auch die Datentypen constraint, duration, set und map_diff. Da diese jedoch von der Sprache der Sicherheitsregeln selbst und nicht von Clients generiert werden, werden sie in praktischen Anwendungen selten verwendet.

list- und map-Datentypen unterstützen keine generischen oder Typenargumente. Anders gesagt: Sie können mit Sicherheitsregeln erzwingen, dass ein bestimmtes Feld eine Liste oder eine Zuordnung enthält. Sie können jedoch nicht erzwingen, dass ein Feld eine Liste aller Ganzzahlen oder aller Strings enthält.

Ebenso können Sie Sicherheitsregeln verwenden, um Typenwerte für bestimmte Einträge in einer Liste oder einer Zuordnung (durch Klammernotation oder Schlüsselnamen respektive) zu erzwingen. Es gibt aber keine Verknüpfung, um die Datentypen aller Mitglieder einer Zuordnung oder Liste gleichzeitig zu erzwingen.

Mit folgenden Regeln wird beispielsweise sichergestellt, dass das Feld tags in einem Dokument eine Liste enthält und der erste Eintrag ein String ist. Außerdem wird sichergestellt, dass das Feld product eine Zuordnung enthält, die wiederum einen Produktnamen enthält, der aus einem String und einer Menge als Ganzzahl besteht.

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 erzwungen werden, wenn Dokumente erstellt oder aktualisiert werden. Daher sollten Sie eine Hilfsfunktion erstellen, die Sie in den Abschnitten "create" und "update" 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
      }
    }
  }
}

Typen für optionale Felder erzwingen

Wichtig: Wenn Sie request.resource.data.foo für ein Dokument aufrufen, in dem foo nicht vorhanden ist, wird ein Fehler ausgegeben. Daher wird jede Sicherheitsregel, die diesen Aufruf durchführt, die Anfrage ablehnen. Um dieses Problem zu umgehen, können Sie die Methode get in request.resource.data verwenden. Mit der Methode get können Sie ein Standardargument für ein von einer Zuordnung abgerufenes Feld angeben, für den Fall, dass dieses Feld nicht vorhanden ist.

Beispiel: Wenn Rezensionsdokumente ein optionales photo_url-Feld und ein optionales tags-Feld enthalten und Sie prüfen wollen, dass es sich um einen String und eine Liste handelt, so können Sie die reviewFieldsAreValidTypes-Funktion in etwa so 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, bei denen tags vorhanden, aber keine Liste ist. Dokumente, die kein Feld vom Typ tags (oder photo_url) enthalten, werden dagegen zugelassen.

Teilweise Schreibvorgänge sind nie zulässig

Ein letzter Hinweis zu Cloud Firestore Security Rules: Sie erlauben Kunden entweder eine Änderung an einem Dokument, oder sie lehnen die gesamte Änderung ab. Sie können keine Sicherheitsregeln erstellen, die Schreibvorgänge in einige Felder Ihres Dokuments akzeptieren, während sie andere Änderungen in demselben Vorgang ablehnen.