Warunki zapisu reguł zabezpieczeń Cloud Firestore

Ten przewodnik uzupełnia przewodnik po tworzeniu struktury reguł zabezpieczeń i pokazuje, jak dodawać warunki do reguł zabezpieczeń Cloud Firestore. Jeśli nie znasz podstaw reguł zabezpieczeń Cloud Firestore, przeczytaj przewodnik dla początkujących.

Podstawowym elementem składowym reguł zabezpieczeń Cloud Firestore jest warunek. Warunek to wyrażenie logiczne, które określa, czy dana operacja powinna być dozwolona czy odrzucona. Reguły zabezpieczeń pozwalają tworzyć warunki, które sprawdzają uwierzytelnianie użytkowników, weryfikują dane przychodzące, a nawet umożliwiają dostęp do innych części bazy danych.

Uwierzytelnianie

Jednym z najczęstszych wzorców reguł zabezpieczeń jest kontrola dostępu na podstawie stanu uwierzytelniania użytkownika. Aplikacja może na przykład zezwalać na zapisywanie danych tylko zalogowanym użytkownikom:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to access documents in the "cities" collection
    // only if they are authenticated.
    match /cities/{city} {
      allow read, write: if request.auth != null;
    }
  }
}

Innym często spotykanym wzorcem jest zadbanie o to, aby użytkownicy mogli odczytywać i zapisywać tylko ich własne dane:

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches name of the user
    // document. The wildcard expression {userId} makes the userId variable
    // available in rules.
    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null;
    }
  }
}

Jeśli Twoja aplikacja korzysta z uwierzytelniania Firebase lub z Google Cloud Identity Platform, zmienna request.auth zawiera informacje uwierzytelniające klienta, który żąda danych. Więcej informacji na temat request.auth znajdziesz w dokumentacji referencyjnej.

Walidacja danych

Wiele aplikacji przechowuje informacje o kontroli dostępu jako pola dokumentów w bazie danych. Reguły zabezpieczeń Cloud Firestore mogą dynamicznie przyznawać i odbierać dostęp na podstawie danych dokumentów:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

Zmienna resource odnosi się do żądanego dokumentu, a resource.data to mapa wszystkich pól i wartości zapisanych w dokumencie. Więcej informacji o zmiennej resource znajdziesz w dokumentacji referencyjnej.

Podczas zapisywania danych możesz chcieć porównać dane przychodzące z danymi istniejącymi. W tym przypadku, jeśli Twój zestaw reguł zezwala na oczekujący zapis, zmienna request.resource zawiera przyszły stan dokumentu. W przypadku operacji update, które modyfikują tylko podzbiór pól dokumentu, zmienna request.resource po wykonaniu tych operacji będzie zawierać stan oczekującego dokumentu. Możesz sprawdzić wartości pól w request.resource, aby uniknąć niechcianych lub niespójnych aktualizacji danych:

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure all cities have a positive population and
    // the name is not changed
    match /cities/{city} {
      allow update: if request.resource.data.population > 0
                    && request.resource.data.name == resource.data.name;
    }
  }
}

Dostęp do innych dokumentów

Za pomocą funkcji get() i exists() reguły zabezpieczeń mogą oceniać żądania przychodzące względem innych dokumentów w bazie danych. Funkcje get() i exists() oczekują w pełni określonych ścieżek dokumentu. Jeśli do tworzenia ścieżek dla get() i exists() używasz zmiennych, musisz wyraźnie zmienić znaczenie zmiennych za pomocą składni $(variable).

W poniższym przykładzie zmienna database jest rejestrowana przez instrukcję dopasowania match /databases/{database}/documents i wykorzystywana do utworzenia ścieżki:

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      // Make sure a 'users' document exists for the requesting user before
      // allowing any writes to the 'cities' collection
      allow create: if request.auth != null && exists(/databases/$(database)/documents/users/$(request.auth.uid));

      // Allow the user to delete cities if their user document has the
      // 'admin' field set to 'true'
      allow delete: if request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true;
    }
  }
}

W przypadku zapisów możesz użyć funkcji getAfter(), aby uzyskać dostęp do stanu dokumentu po zakończeniu transakcji lub partii zapisów, ale przed zatwierdzeniem transakcji lub wsadu. Podobnie jak get(), funkcja getAfter() przyjmuje w pełni określoną ścieżkę dokumentu. Za pomocą getAfter() możesz zdefiniować zbiory zapisów, które muszą następować razem jako transakcja lub wsad.

Dostęp do limitów połączeń

Obowiązuje limit wywołań dostępu do dokumentu na ocenę zestawu reguł:

  • 10 w przypadku żądań pojedynczych dokumentów i zapytań.
  • 20 – odczyty, transakcje i zapisy wsadowe w wielu dokumentach. Do każdej operacji ma też zastosowanie poprzedni limit 10.

    Załóżmy na przykład, że tworzysz wsadowe żądanie zapisu z 3 operacjami zapisu i że Twoje reguły zabezpieczeń używają 2 wywołań dostępu do dokumentu do weryfikacji każdego zapisu. W tym przypadku każdy zapis używa 2 z 10 wywołań dostępu, a wsadowe żądanie zapisu używa 6 z 20 wywołań dostępu.

Przekroczenie dowolnego z limitów skutkuje błędem braku uprawnień. Niektóre wywołania dostępu do dokumentu mogą być przechowywane w pamięci podręcznej, a wywołania z tej pamięci nie wliczają się do limitów.

Szczegółowy opis wpływu tych limitów na transakcje i zapisy wsadowe znajdziesz w przewodniku na temat zabezpieczania operacji niepodzielnych.

Dostęp do połączeń i cen

Użycie tych funkcji powoduje wykonanie operacji odczytu w bazie danych, co oznacza, że za odczyt dokumentów zostanie naliczona opłata, nawet jeśli reguły odrzuci Twoje żądanie. Bardziej szczegółowe informacje rozliczeniowe znajdziesz w cenniku Cloud Firestore.

Funkcje niestandardowe

W miarę jak Twoje reguły zabezpieczeń stają się coraz bardziej złożone, być może warto połączyć zestawy warunków w funkcje, których możesz używać ponownie w zbiorze reguł. Reguły zabezpieczeń obsługują funkcje niestandardowe. Składnia funkcji niestandardowych przypomina JavaScript, z tą różnicą, że funkcje reguł zabezpieczeń są napisane w języku specyficznym dla danej domeny, który ma pewne ważne ograniczenia:

  • Funkcje mogą zawierać tylko jedną instrukcję return. Nie mogą zawierać żadnej dodatkowej logiki. Nie mogą np. uruchamiać pętli ani wywoływać usług zewnętrznych.
  • Funkcje mogą automatycznie uzyskiwać dostęp do funkcji i zmiennych z zakresu, w którym zostały zdefiniowane. Na przykład funkcja zdefiniowana w zakresie service cloud.firestore ma dostęp do zmiennej resource i funkcji wbudowanych, takich jak get() i exists().
  • Funkcje mogą wywoływać inne funkcje, ale nie mogą się powtarzać. Łączna głębokość stosu wywołań jest ograniczona do 10.
  • W regułach w wersji v2 funkcje mogą definiować zmienne za pomocą słowa kluczowego let. Funkcje mogą mieć maksymalnie 10 powiązań Let, ale muszą kończyć się instrukcją zwrotną.

Funkcja jest zdefiniowana za pomocą słowa kluczowego function i przyjmuje zero lub więcej argumentów. Możesz na przykład połączyć 2 rodzaje warunków użytych w powyższych przykładach w jedną funkcję:

service cloud.firestore {
  match /databases/{database}/documents {
    // True if the user is signed in or the requested data is 'public'
    function signedInOrPublic() {
      return request.auth.uid != null || resource.data.visibility == 'public';
    }

    match /cities/{city} {
      allow read, write: if signedInOrPublic();
    }

    match /users/{user} {
      allow read, write: if signedInOrPublic();
    }
  }
}

Użycie funkcji w regułach zabezpieczeń ułatwia ich obsługę wraz ze wzrostem złożoności reguł.

Reguły nie są filtrami

Gdy zabezpieczysz dane i zaczniesz tworzyć zapytania, pamiętaj, że reguły zabezpieczeń nie są filtrami. Nie możesz utworzyć zapytania dotyczącego wszystkich dokumentów w kolekcji. Cloud Firestore może zwrócić tylko te dokumenty, do których ma dostęp bieżący klient.

Na przykład użyj tej reguły zabezpieczeń:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

Odrzucono: ta reguła odrzuca następujące zapytanie, ponieważ zestaw wyników może zawierać dokumenty, w których visibility nie ma wartości public:

Sieć
db.collection("cities").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
    });
});

Dozwolone: ta reguła zezwala na poniższe zapytanie, ponieważ klauzula where("visibility", "==", "public") gwarantuje, że zbiór wyników spełnia warunek reguły:

Sieć
db.collection("cities").where("visibility", "==", "public").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
        });
    });

Reguły zabezpieczeń Cloud Firestore oceniają każde zapytanie pod kątem jego potencjalnego wyniku i odrzucają żądanie, jeśli może zwrócić dokument, do którego klient nie ma uprawnień do odczytu. Zapytania muszą spełniać ograniczenia ustawione przez reguły zabezpieczeń. Więcej informacji o regułach zabezpieczeń i zapytaniach znajdziesz w artykule o bezpiecznym tworzeniu zapytań o dane.

Dalsze kroki