Bezpiecznie przeszukuj dane

Ta strona opiera się na koncepcjach z Strukturyzacji reguł bezpieczeństwa i Pisania warunków dla reguł bezpieczeństwa , aby wyjaśnić, w jaki sposób reguły bezpieczeństwa Cloud Firestore wchodzą w interakcję z zapytaniami. Przyjrzyjmy się bliżej, jak reguły bezpieczeństwa wpływają na zapytania, które możesz pisać, i opisano, w jaki sposób zapewnić, że zapytania będą używać tych samych ograniczeń, co reguły bezpieczeństwa. Na tej stronie opisano również sposób pisania reguł zabezpieczeń zezwalających na zapytania lub blokujących je na podstawie właściwości zapytania, takich jak limit i orderBy .

Reguły to nie filtry

Pisząc zapytania w celu pobrania dokumentów, należy pamiętać, że reguły bezpieczeństwa to nie filtry — zapytania oznaczają wszystko albo nic. Aby zaoszczędzić czas i zasoby, Cloud Firestore ocenia zapytanie na podstawie potencjalnego zestawu wyników, a nie rzeczywistych wartości pól dla wszystkich Twoich dokumentów. Jeśli zapytanie mogłoby potencjalnie zwrócić dokumenty, do których klient nie ma uprawnień, całe żądanie kończy się niepowodzeniem.

Zapytania i zasady bezpieczeństwa

Jak pokazują poniższe przykłady, musisz pisać zapytania tak, aby pasowały do ​​ograniczeń reguł bezpieczeństwa.

Zabezpiecz i przeszukuj dokumenty w oparciu o auth.uid

Poniższy przykład ilustruje sposób napisania zapytania w celu pobrania dokumentów chronionych regułą zabezpieczeń. Rozważmy bazę danych zawierającą zbiór dokumentów story :

/historie/{historiaid}

{
  title: "A Great Story",
  content: "Once upon a time...",
  author: "some_auth_id",
  published: false
}

Oprócz pól title i content w każdym dokumencie przechowywane są pola author i published , które można wykorzystać do kontroli dostępu. W tych przykładach założono, że aplikacja korzysta z uwierzytelniania Firebase , aby ustawić pole author na UID użytkownika, który utworzył dokument. Uwierzytelnianie Firebase wypełnia również zmienną request.auth w regułach bezpieczeństwa.

Następująca reguła bezpieczeństwa wykorzystuje zmienne request.auth i resource.data w celu ograniczenia dostępu do odczytu i zapisu każdej story jego autorowi:

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Only the authenticated user who authored the document can read or write
      allow read, write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

Załóżmy, że Twoja aplikacja zawiera stronę wyświetlającą użytkownikowi listę dokumentów z story , których jest autorem. Można się spodziewać, że do wypełnienia tej strony można użyć następującego zapytania. Jednak to zapytanie zakończy się niepowodzeniem, ponieważ nie zawiera tych samych ograniczeń, co reguły bezpieczeństwa:

Nieprawidłowe : ograniczenia zapytania nie są zgodne z ograniczeniami reguł bezpieczeństwa

// This query will fail
db.collection("stories").get()

Zapytanie kończy się niepowodzeniem , nawet jeśli bieżący użytkownik jest faktycznie autorem każdego dokumentu story . Powodem tego zachowania jest to, że gdy Cloud Firestore stosuje reguły bezpieczeństwa, ocenia zapytanie pod kątem potencjalnego zestawu wyników, a nie rzeczywistych właściwości dokumentów w Twojej bazie danych. Jeśli zapytanie może potencjalnie zawierać dokumenty naruszające zasady bezpieczeństwa, zapytanie zakończy się niepowodzeniem.

Natomiast poniższe zapytanie powiodło się, ponieważ zawiera takie samo ograniczenie pola author , jak reguły bezpieczeństwa:

Prawidłowe : ograniczenia zapytań są zgodne z ograniczeniami reguł bezpieczeństwa

var user = firebase.auth().currentUser;

db.collection("stories").where("author", "==", user.uid).get()

Zabezpieczaj i wysyłaj zapytania do dokumentów w oparciu o pola

Aby jeszcze bardziej zademonstrować interakcję między zapytaniami i regułami, poniższe reguły bezpieczeństwa rozszerzają dostęp do odczytu kolekcji stories , aby umożliwić każdemu użytkownikowi czytanie dokumentów story , w których published pole ma wartość true .

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Anyone can read a published story; only story authors can read unpublished stories
      allow read: if resource.data.published == true || (request.auth != null && request.auth.uid == resource.data.author);
      // Only story authors can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

Zapytanie o opublikowane strony musi zawierać te same ograniczenia, co reguły bezpieczeństwa:

db.collection("stories").where("published", "==", true).get()

Ograniczenie zapytania .where("published", "==", true) gwarantuje, że resource.data.published ma true dla każdego wyniku. Dlatego to zapytanie spełnia zasady bezpieczeństwa i może odczytać dane.

OR zapytania

Podczas oceniania zapytania logicznego OR ( or , in lub array-contains-any ) względem zestawu reguł Cloud Firestore ocenia każdą wartość porównania osobno. Każda wartość porównawcza musi spełniać ograniczenia reguły bezpieczeństwa. Na przykład dla następującej reguły:

match /mydocuments/{doc} {
  allow read: if resource.data.x > 5;
}

Nieprawidłowy : zapytanie nie gwarantuje, że x > 5 dla wszystkich potencjalnych dokumentów

// These queries will fail
query(db.collection("mydocuments"),
      or(where("x", "==", 1),
         where("x", "==", 6)
      )
    )

query(db.collection("mydocuments"),
      where("x", "in", [1, 3, 6, 42, 99])
    )

Ważne : zapytanie gwarantuje, że x > 5 dla wszystkich potencjalnych dokumentów

query(db.collection("mydocuments"),
      or(where("x", "==", 6),
         where("x", "==", 42)
      )
    )

query(db.collection("mydocuments"),
      where("x", "in", [6, 42, 99, 105, 200])
    )

Ocena ograniczeń zapytań

Reguły bezpieczeństwa mogą także akceptować lub odrzucać zapytania w oparciu o swoje ograniczenia. Zmienna request.query zawiera właściwości limit , offset i orderBy zapytania. Na przykład reguły bezpieczeństwa mogą odrzucić każde zapytanie, które nie ogranicza maksymalnej liczby pobieranych dokumentów do określonego zakresu:

allow list: if request.query.limit <= 10;

Poniższy zestaw reguł pokazuje, jak napisać reguły bezpieczeństwa, które oceniają ograniczenia nałożone na zapytania. Ten przykład rozszerza poprzedni zestaw reguł stories o następujące zmiany:

  • Zestaw reguł dzieli regułę read na reguły get i list .
  • Reguła get ogranicza pobieranie pojedynczych dokumentów do dokumentów publicznych lub dokumentów, których autorem jest użytkownik.
  • Reguła list stosuje te same ograniczenia, co get , ale w przypadku zapytań. Sprawdza również limit zapytań, a następnie odrzuca każde zapytanie bez limitu lub z limitem większym niż 10.
  • Zestaw reguł definiuje funkcję authorOrPublished() , aby uniknąć powielania kodu.
service cloud.firestore {

  match /databases/{database}/documents {

    match /stories/{storyid} {

      // Returns `true` if the requested story is 'published'
      // or the user authored the story
      function authorOrPublished() {
        return resource.data.published == true || request.auth.uid == resource.data.author;
      }

      // Deny any query not limited to 10 or fewer documents
      // Anyone can query published stories
      // Authors can query their unpublished stories
      allow list: if request.query.limit <= 10 &&
                     authorOrPublished();

      // Anyone can retrieve a published story
      // Only a story's author can retrieve an unpublished story
      allow get: if authorOrPublished();

      // Only a story's author can write to a story
      allow write: if request.auth.uid == resource.data.author;
    }

  }
}

Zapytania dotyczące grup kolekcji i reguły bezpieczeństwa

Domyślnie zapytania są ograniczone do pojedynczej kolekcji i pobierają wyniki tylko z tej kolekcji. Za pomocą zapytań o grupę kolekcji można pobrać wyniki z grupy kolekcji składającej się ze wszystkich kolekcji o tym samym identyfikatorze. W tej sekcji opisano, jak zabezpieczyć zapytania dotyczące grup kolekcji za pomocą reguł bezpieczeństwa.

Zabezpiecz dokumenty i przeszukuj je w oparciu o grupy kolekcji

W regułach bezpieczeństwa musisz jawnie zezwolić na zapytania dotyczące grup kolekcji, pisząc regułę dla grupy kolekcji:

  1. Upewnij się rules_version = '2'; to pierwsza linia zestawu reguł. Zapytania dotyczące grup kolekcji wymagają nowego, rekurencyjnego zachowania symboli wieloznacznych {name=**} reguł zabezpieczeń w wersji 2.
  2. Napisz regułę dla swojej grupy kolekcji, używając match /{path=**}/ [COLLECTION_ID] /{doc} .

Rozważmy na przykład forum zorganizowane w dokumenty forum zawierające podzbiory posts :

/forums/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
}

W tej aplikacji umożliwiamy edytowanie postów przez ich właścicieli i odczytywanie przez uwierzytelnionych użytkowników:

service cloud.firestore {
  match /databases/{database}/documents {
    match /forums/{forumid}/posts/{post} {
      // Only authenticated users can read
      allow read: if request.auth != null;
      // Only the post author can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

Każdy uwierzytelniony użytkownik może pobrać posty z dowolnego forum:

db.collection("forums/technology/posts").get()

Ale co, jeśli chcesz pokazać aktualnemu użytkownikowi jego posty na wszystkich forach? Możesz użyć zapytania grupy kolekcji , aby pobrać wyniki ze wszystkich kolekcji posts :

var user = firebase.auth().currentUser;

db.collectionGroup("posts").where("author", "==", user.uid).get()

W swoich regułach bezpieczeństwa musisz zezwolić na to zapytanie, pisząc regułę odczytu lub listy dla grupy kolekcji posts :

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {
    // Authenticated users can query the posts collection group
    // Applies to collection queries, collection group queries, and
    // single document retrievals
    match /{path=**}/posts/{post} {
      allow read: if request.auth != null;
    }
    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth != null && request.auth.uid == resource.data.author;

    }
  }
}

Pamiętaj jednak, że te zasady będą miały zastosowanie do wszystkich kolekcji z posts identyfikacyjnymi, niezależnie od hierarchii. Na przykład te reguły dotyczą wszystkich następujących kolekcji posts :

  • /posts/{postid}
  • /forums/{forumid}/posts/{postid}
  • /forums/{forumid}/subforum/{subforumid}/posts/{postid}

Bezpieczne zapytania dotyczące grup kolekcji na podstawie pola

Podobnie jak zapytania dotyczące pojedynczego zbioru, zapytania dotyczące grup kolekcji muszą również spełniać ograniczenia określone przez reguły zabezpieczeń. Na przykład możemy dodać published pole do każdego wpisu na forum, tak jak to zrobiliśmy w powyższym przykładzie stories :

/forums/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
  published: false
}

Następnie możemy napisać reguły dla grupy kolekcji posts w oparciu o status published i author posta:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    // Returns `true` if the requested post is 'published'
    // or the user authored the post
    function authorOrPublished() {
      return resource.data.published == true || request.auth.uid == resource.data.author;
    }

    match /{path=**}/posts/{post} {

      // Anyone can query published posts
      // Authors can query their unpublished posts
      allow list: if authorOrPublished();

      // Anyone can retrieve a published post
      // Authors can retrieve an unpublished post
      allow get: if authorOrPublished();
    }

    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth.uid == resource.data.author;
    }
  }
}

Dzięki tym regułom klienci sieci Web, Apple i Android mogą wykonywać następujące zapytania:

  • Każdy może odzyskać opublikowane posty na forum:

    db.collection("forums/technology/posts").where('published', '==', true).get()
    
  • Każdy może przeglądać posty autora opublikowane na wszystkich forach:

    db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
    
  • Autorzy mogą odzyskać wszystkie swoje opublikowane i niepublikowane posty na wszystkich forach:

    var user = firebase.auth().currentUser;
    
    db.collectionGroup("posts").where("author", "==", user.uid).get()
    

Zabezpieczaj dokumenty i przeszukuj je w oparciu o grupę kolekcji i ścieżkę dokumentu

W niektórych przypadkach możesz chcieć ograniczyć zapytania dotyczące grup kolekcji na podstawie ścieżki dokumentu. Aby utworzyć te ograniczenia, można użyć tych samych technik zabezpieczania dokumentów i wysyłania zapytań na podstawie pól.

Rozważ aplikację, która śledzi transakcje każdego użytkownika na kilku giełdach i giełdach kryptowalut:

/users/{userid}/exchange/{exchangeid}/transactions/{transaction}

{
  amount: 100,
  exchange: 'some_exchange_name',
  timestamp: April 1, 2019 at 12:00:00 PM UTC-7,
  user: "some_auth_id",
}

Zwróć uwagę na pole user . Mimo że ze ścieżki dokumentu wiemy, który użytkownik jest właścicielem dokumentu transaction , powielamy tę informację w każdym dokumencie transaction , ponieważ pozwala nam to na dwie rzeczy:

  • Twórz zapytania dotyczące grup kolekcji, które są ograniczone do dokumentów zawierających określony /users/{userid} w ścieżce dokumentu. Na przykład:

    var user = firebase.auth().currentUser;
    // Return current user's last five transactions across all exchanges
    db.collectionGroup("transactions").where("user", "==", user).orderBy('timestamp').limit(5)
    
  • Wymuś to ograniczenie dla wszystkich zapytań w grupie gromadzenia transactions , aby jeden użytkownik nie mógł pobrać dokumentów transaction innego użytkownika.

Egzekwujemy to ograniczenie w naszych zasadach bezpieczeństwa i uwzględniamy weryfikację danych w polu user :

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    match /{path=**}/transactions/{transaction} {
      // Authenticated users can retrieve only their own transactions
      allow read: if resource.data.user == request.auth.uid;
    }

    match /users/{userid}/exchange/{exchangeid}/transactions/{transaction} {
      // Authenticated users can write to their own transactions subcollections
      // Writes must populate the user field with the correct auth id
      allow write: if userid == request.auth.uid && request.data.user == request.auth.uid
    }
  }
}

Następne kroki