Sicherer Datenzugriff für Benutzer und Gruppen

Viele kollaborative Apps ermöglichen Benutzern das Lesen und Schreiben verschiedener Daten basierend auf einer Reihe von Berechtigungen. In einer App zum Bearbeiten von Dokumenten möchten Benutzer beispielsweise einigen Benutzern erlauben, ihre Dokumente zu lesen und zu schreiben und gleichzeitig unerwünschten Zugriff zu blockieren.

Lösung: Rollenbasierte Zugriffskontrolle

Sie können als individuelle Vorteil des Cloud Firestor des Datenmodelles nehmen sowie Sicherheitsregeln rollenbasierte Zugriffskontrolle in Ihrer Anwendung zu implementieren.

Angenommen, Sie erstellen eine kollaborative Schreibanwendung, in der Benutzer "Geschichten" und "Kommentare" mit den folgenden Sicherheitsanforderungen erstellen können:

  • Jede Geschichte hat einen Eigentümer und kann mit "Autoren", "Kommentatoren" und "Lesern" geteilt werden.
  • Die Leser können nur Geschichten und Kommentare sehen. Sie können nichts bearbeiten.
  • Commenters haben alle den Zugang der Leser, und sie können auch Kommentare zu einer Geschichte hinzufügen.
  • Autoren haben alle den Zugang von commenters , und sie können auch bearbeiten Geschichte Inhalt.
  • Eigentümer können einen Teil einer Geschichte bearbeiten sowie die Kontrolle des Zugangs von anderen Benutzern.

Datenstruktur

Angenommen , Ihre Anwendung hat eine stories Sammlung , wo jedes Dokument eine Geschichte darstellt. Jede Geschichte hat auch eine comments Untersammlung , wo jedes Dokument ist ein Kommentar zu dieser Geschichte.

Für eine Übersicht über Zugriffsrollen, ein hinzufügen roles - Feld , das eine Karte von Benutzer - IDs zu Rollen ist:

/stories/{storyid}

{
  title: "A Great Story",
  content: "Once upon a time ...",
  roles: {
    alice: "owner",
    bob: "reader",
    david: "writer",
    jane: "commenter"
    // ...
  }
}

Kommentare enthalten nur zwei Felder, die Benutzer-ID des Autors und einige Inhalte:

/stories/{storyid}/comments/{commentid}

{
  user: "alice",
  content: "I think this is a great story!"
}

Regeln

Nachdem Sie nun die Rollen der Benutzer in der Datenbank aufgezeichnet haben, müssen Sie Sicherheitsregeln schreiben, um sie zu validieren. Diese Regeln übernehmen die App nutzt Firebase Auth , so dass die request.auth.uid Variable ist die Benutzer - ID.

Schritt 1: Starten Sie mit einer Grundregel Datei, die für Geschichten und Kommentare leer Regeln enthält:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
         // TODO: Story rules go here...

         match /comments/{comment} {
            // TODO: Comment rules go here...
         }
     }
   }
}

Schritt 2: Fügen Sie eine einfache write , die Besitzer die vollständige Kontrolle über Geschichten gibt. Die definierten Funktionen helfen dabei, die Rollen eines Benutzers zu bestimmen und ob neue Dokumente gültig sind:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          // Read from the "roles" map in the resource (rsc).
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          // Determine if the user is one of an array of roles
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          // Valid if story does not exist and the new story has the correct owner.
          return resource == null && isOneOfRoles(request.resource, ['owner']);
        }

        // Owners can read, write, and delete stories
        allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner']);

         match /comments/{comment} {
            // ...
         }
     }
   }
}

Schritt 3: Schreibregeln , die ein Benutzer von jeder Rolle erlauben Geschichten und Kommentare zu lesen. Durch die Verwendung der im vorherigen Schritt definierten Funktionen bleiben die Regeln prägnant und lesbar:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          return resource == null
            && request.resource.data.roles[request.auth.uid] == 'owner';
        }

        allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner']);

        // Any role can read stories.
        allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);

        match /comments/{comment} {
          // Any role can read comments.
          allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                      ['owner', 'writer', 'commenter', 'reader']);
        }
     }
   }
}

Schritt 4: Lassen Sie Geschichte Schriftsteller, Kommentatoren und Besitzer um Kommentare zu schreiben. Beachten Sie, dass diese Regel bestätigt auch , dass der owner des Kommentars den anfordernden Benutzer übereinstimmt, die verhindert , dass Anwender aus über jeweils anderen Kommentare zu schreiben:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          return resource == null
            && request.resource.data.roles[request.auth.uid] == 'owner';
        }

        allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner'])
        allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);

        match /comments/{comment} {
          allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                      ['owner', 'writer', 'commenter', 'reader']);

          // Owners, writers, and commenters can create comments. The
          // user id in the comment document must match the requesting
          // user's id.
          //
          // Note: we have to use get() here to retrieve the story
          // document so that we can check the user's role.
          allow create: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                        ['owner', 'writer', 'commenter'])
                        && request.resource.data.user == request.auth.uid;
        }
     }
   }
}

Schritt 5: Geben Sie Autoren die Möglichkeit zu bearbeiten Geschichte Inhalt, aber nicht zu bearbeiten Geschichte Rollen oder andere Eigenschaften des Dokuments ändern. Dies erfordert Spaltung der Geschichten write Regel in separate Regeln für create , update und delete , da Autoren nur Geschichten aktualisieren:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          return request.resource.data.roles[request.auth.uid] == 'owner';
        }

        function onlyContentChanged() {
          // Ensure that title and roles are unchanged and that no new
          // fields are added to the document.
          return request.resource.data.title == resource.data.title
            && request.resource.data.roles == resource.data.roles
            && request.resource.data.keys() == resource.data.keys();
        }

        // Split writing into creation, deletion, and updating. Only an
        // owner can create or delete a story but a writer can update
        // story content.
        allow create: if isValidNewStory();
        allow delete: if isOneOfRoles(resource, ['owner']);
        allow update: if isOneOfRoles(resource, ['owner'])
                      || (isOneOfRoles(resource, ['writer']) && onlyContentChanged());
        allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);

        match /comments/{comment} {
          allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                      ['owner', 'writer', 'commenter', 'reader']);
          allow create: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                        ['owner', 'writer', 'commenter'])
                        && request.resource.data.user == request.auth.uid;
        }
     }
   }
}

Einschränkungen

Die oben gezeigte Lösung zeigt die Sicherung von Benutzerdaten mithilfe von Sicherheitsregeln, aber Sie sollten sich der folgenden Einschränkungen bewusst sein:

  • Granularität: In dem obigen Beispiel mehr Rollen (Schriftsteller und Eigentümer) haben Schreibzugriff auf das gleiche Dokument , aber mit verschiedenen Einschränkungen. Dies kann bei komplexeren Dokumenten schwierig zu handhaben sein, und es kann besser sein, einzelne Dokumente in mehrere Dokumente aufzuteilen, die jeweils einer einzelnen Rolle gehören.
  • Große Gruppen: Wenn Sie mit sehr großen oder komplexen Gruppen zu teilen müssen, sollten Sie ein System , in dem Rollen in ihrer eigenen Sammlung gespeichert werden und nicht als ein Feld auf dem Zieldokument.