Bezpieczne zapytania o dane

Ta strona rozszerza koncepcje opisane w artykułach Strukturyzowanie reguł zabezpieczeńPisanie warunków dla reguł zabezpieczeń, aby wyjaśnić, jak Cloud Firestore Security Rules wchodzą w interakcje z zapytaniami. Wyjaśnia, jak reguły zabezpieczeń wpływają na zapytania, które możesz pisać, i opisuje, jak zapewnić, że zapytania będą używać tych samych ograniczeń co reguły zabezpieczeń. Na tej stronie znajdziesz też informacje o tym, jak pisać reguły zabezpieczeń, aby zezwalać na zapytania lub je odrzucać na podstawie właściwości zapytań, takich jak limitorderBy.

Reguły nie są filtrami

Podczas pisania zapytań o pobieranie dokumentów pamiętaj, że reguły zabezpieczeń nie są filtrami – zapytania zwracają wszystkie wyniki lub nie zwracają żadnych. Aby zaoszczędzić czas i zasoby, usługa Cloud Firestore ocenia zapytanie na podstawie potencjalnego zbioru wyników zamiast rzeczywistych wartości pól we wszystkich dokumentach. Jeśli zapytanie może zwrócić dokumenty, do których klient nie ma uprawnień do odczytu, całe żądanie zakończy się niepowodzeniem.

Zapytania i reguły zabezpieczeń

Jak pokazują przykłady poniżej, zapytania muszą być zgodne z ograniczeniami określonymi w regułach zabezpieczeń.

Zabezpieczanie dokumentów i tworzenie zapytań na ich podstawie zgodnie z auth.uid

W przykładzie poniżej pokazujemy, jak napisać zapytanie, aby pobrać dokumenty chronione przez regułę zabezpieczeń. Rozważmy bazę danych, która zawiera zbiór dokumentów:story

/stories/{storyid}

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

Oprócz pól titlecontent każdy dokument zawiera pola authorpublished, które służą do kontroli dostępu. W tych przykładach zakłada się, że aplikacja używa Uwierzytelniania Firebase do ustawiania pola author na identyfikator UID użytkownika, który utworzył dokument. Usługa Firebase Authentication wypełnia też zmienną request.auth w regułach zabezpieczeń.

Ta reguła zabezpieczeń używa zmiennych request.auth i resource.data, aby ograniczyć dostęp do odczytu i zapisu dla każdego dokumentu story do jego autora:

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ę, na której użytkownik widzi listę storydokumentów, których jest autorem. Możesz oczekiwać, że do wypełnienia tej strony możesz użyć tego zapytania: To zapytanie nie zostanie jednak wykonane, ponieważ nie zawiera tych samych ograniczeń co reguły zabezpieczeń:

Nieprawidłowe: ograniczenia zapytania nie pasują do ograniczeń reguł bezpieczeństwa.

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

Zapytanie zakończy się niepowodzeniem nawet wtedy, gdy bieżący użytkownik jest autorem wszystkich dokumentów.story Dzieje się tak, ponieważ gdy usługaCloud Firestore stosuje reguły zabezpieczeń, ocenia zapytanie na podstawie potencjalnego zbioru wyników, a nie rzeczywistych właściwości dokumentów w bazie danych. Jeśli zapytanie może potencjalnie zawierać dokumenty, które naruszają reguły zabezpieczeń, zapytanie nie powiedzie się.

Natomiast to zapytanie zakończy się powodzeniem, ponieważ zawiera to samo ograniczenie w polu author co reguły zabezpieczeń:

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

var user = firebase.auth().currentUser;

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

Zabezpieczanie dokumentów i tworzenie zapytań na podstawie pola

Aby dokładniej zademonstrować interakcję między zapytaniami a regułami, poniższe reguły zabezpieczeń rozszerzają uprawnienia do odczytu w przypadku kolekcji stories, aby umożliwić każdemu użytkownikowi odczytywanie dokumentów story, w których pole published 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 dotyczące opublikowanych stron 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 wartość true w przypadku każdego wyniku. Dlatego to zapytanie spełnia reguły zabezpieczeń i może odczytywać dane.

Zapytania: OR

Podczas sprawdzania zapytania logicznego OR (or, in lub array-contains-any) w odniesieniu do zestawu reguł usługa Cloud Firestore ocenia każdą wartość porównania oddzielnie. Każda wartość porównania musi spełniać ograniczenia reguły bezpieczeństwa. Na przykład w przypadku tej reguły:

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

Nieprawidłowe: zapytanie nie gwarantuje, że x > 5 w przypadku 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])
    )

Prawidłowe: zapytanie gwarantuje, żex > 5 w przypadku 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 zabezpieczeń mogą też akceptować lub odrzucać zapytania na podstawie ich ograniczeń. Zmienna request.query zawiera właściwości limit, offset i orderBy zapytania. Na przykład reguły zabezpieczeń mogą odrzucać każde zapytanie, które nie ogranicza maksymalnej liczby pobieranych dokumentów do określonego zakresu:

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

Ten zestaw reguł pokazuje, jak pisać reguły zabezpieczeń, które oceniają ograniczenia nałożone na zapytania. Ten przykład rozszerza poprzedni zestaw reguł stories o te zmiany:

  • Zbiór reguł dzieli regułę odczytu na reguły dla get i list.
  • Reguła get ogranicza pobieranie pojedynczych dokumentów do dokumentów publicznych lub dokumentów utworzonych przez użytkownika.
  • Reguła list stosuje te same ograniczenia co reguła get, ale w przypadku zapytań. Sprawdza też limit zapytań, a potem odrzuca wszystkie zapytania bez limitu lub z limitem większym niż 10.
  • Zbiór reguł definiuje funkcję authorOrPublished(), aby uniknąć duplikowania 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 zabezpieczeń

Domyślnie zapytania są ograniczone do jednej kolekcji i pobierają wyniki tylko z niej. Za pomocą zapytań dotyczących grupy kolekcji możesz pobierać wyniki z grupy kolekcji składającej się ze wszystkich kolekcji o tym samym identyfikatorze. W tej sekcji opisujemy, jak zabezpieczyć zapytania do grupy zbiorów za pomocą reguł zabezpieczeń.

Zabezpieczanie dokumentów i wysyłanie zapytań dotyczących dokumentów na podstawie grup kolekcji

W regułach zabezpieczeń musisz wyraźnie zezwolić na zapytania dotyczące grupy zbiorów, pisząc regułę dla grupy zbiorów:

  1. Sprawdź, czy rules_version = '2'; jest pierwszym wierszem zestawu reguł. Zapytania dotyczące grup kolekcji wymagają nowego rekursywnego symbolu wieloznacznego {name=**} w przypadku reguł zabezpieczeń w wersji 2.
  2. Napisz regułę dla grupy kolekcji, używając match /{path=**}/[COLLECTION_ID]/{doc}.

Rozważmy na przykład forum podzielone na forum dokumenty zawierająceposts podzbiory:

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

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

W tej aplikacji umożliwiamy właścicielom edytowanie postów, a uwierzytelnionym użytkownikom – ich odczytywanie:

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()

A co, jeśli chcesz wyświetlić posty bieżącego użytkownika na wszystkich forach? Aby pobrać wyniki ze wszystkich kolekcji posts, możesz użyć zapytania dotyczącego grupy kolekcji:

var user = firebase.auth().currentUser;

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

W regułach zabezpieczeń musisz zezwolić na to zapytanie, pisząc regułę odczytu lub listowania 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 reguły będą stosowane do wszystkich kolekcji o identyfikatorze posts niezależnie od hierarchii. Na przykład te zasady mają zastosowanie do wszystkich tych kolekcji:posts

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

Zabezpieczanie zapytań do grupy zbierania na podstawie pola

Podobnie jak zapytania dotyczące pojedynczych kolekcji, zapytania dotyczące grup kolekcji muszą spełniać ograniczenia określone przez reguły zabezpieczeń. Możemy na przykład dodać pole published do każdego posta na forum, tak jak w stories przykładzie powyżej:

/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 zbiorów posts na podstawie stanu published i postu author:

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 internetowi, Apple i Android mogą wykonywać te zapytania:

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

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

    db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
    
  • Autorzy mogą pobierać wszystkie opublikowane i nieopublikowane posty ze wszystkich forów:

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

Zabezpieczanie dokumentów i wysyłanie zapytań dotyczących dokumentów na podstawie grupy kolekcji i ścieżki dokumentu

W niektórych przypadkach możesz chcieć ograniczyć zapytania do grup kolekcji na podstawie ścieżki dokumentu. Aby utworzyć te ograniczenia, możesz użyć tych samych technik zabezpieczania dokumentów i wykonywania na nich zapytań na podstawie pola.

Rozważmy aplikację, która śledzi transakcje każdego użytkownika na kilku giełdach akcji i 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. Chociaż wiemy, który użytkownik jest właścicielem transactiondokumentu, na podstawie ścieżki dostępu do niego, powielamy te informacje w każdym dokumencie, ponieważ umożliwia nam to transaction2 rzeczy:

  • Pisz zapytania do grup kolekcji, które są ograniczone do dokumentów zawierających określony /users/{userid} w ścieżce dokumentu. 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 w przypadku wszystkich zapytań dotyczących kolekcji transactions, aby jeden użytkownik nie mógł pobierać dokumentów transaction innego użytkownika.

Wymuszamy to ograniczenie w naszych regułach zabezpieczeń i uwzględniamy weryfikację danych w przypadku pola 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
    }
  }
}

Dalsze kroki