Warunki zapisu reguł zabezpieczeń Cloud Firestore

Ten przewodnik jest rozwinięciem artykułu Strukturyzowanie reguł zabezpieczeń. Pokazuje, jak dodawać warunki do Cloud Firestore Security Rules. Jeśli nie znasz podstaw Cloud Firestore Security Rules, zapoznaj się z przewodnikiem dla początkujących.

Podstawowym elementem Cloud Firestore Security Rules jest warunek. Warunek to wyrażenie logiczne, które określa, czy dana operacja powinna być dozwolona czy zabroniona. Za pomocą reguł zabezpieczeń możesz pisać warunki, które sprawdzają uwierzytelnianie użytkownika, weryfikują dane przychodzące lub nawet uzyskują dostęp do innych części bazy danych.

Uwierzytelnianie

Jednym z najczęstszych wzorców reguł zabezpieczeń jest kontrolowanie dostępu na podstawie stanu uwierzytelniania użytkownika. Na przykład aplikacja może 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 typowym wzorcem jest zapewnienie użytkownikom możliwości odczytywania i zapisywania tylko własnych danych:

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 aplikacja korzysta z Firebase Authentication lub Google Cloud Identity Platform, zmienna request.auth zawiera informacje o uwierzytelnianiu klienta, który wysyła żądanie danych. Więcej informacji o request.auth znajdziesz w dokumentacji.

Walidacja danych

Wiele aplikacji przechowuje informacje o kontroli dostępu jako pola w dokumentach w bazie danych. Cloud Firestore Security Rules może dynamicznie zezwalać na dostęp lub go odmawiać na podstawie danych dokumentu:

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 przechowywanych w dokumencie. Więcej informacji o zmiennej resource znajdziesz w dokumentacji.

Podczas zapisywania danych możesz chcieć porównać dane przychodzące z danymi już istniejącymi. W takim przypadku, jeśli zestaw reguł zezwala na oczekujący zapis, zmienna request.resource zawiera przyszły stan dokumentu. W przypadku update operacji, które modyfikują tylko podzbiór pól dokumentu, zmienna request.resource będzie zawierać stan dokumentu oczekujący po operacji. Aby zapobiec niechcianym lub niespójnym aktualizacjom danych, możesz sprawdzić wartości pól w request.resource:

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()exists() reguły zabezpieczeń mogą oceniać przychodzące żądania w porównaniu z innymi dokumentami w bazie danych. Funkcje get() i exists() wymagają w pełni określonych ścieżek do dokumentów. Gdy używasz zmiennych do tworzenia ścieżek dla get()exists(), musisz jawnie zastosować znaki ucieczki w przypadku zmiennych za pomocą składni $(variable).

W przykładzie poniżej zmienna database jest przechwytywana przez instrukcję match /databases/{database}/documents i używana 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 operacji zapisu możesz użyć funkcji getAfter(), aby uzyskać dostęp do stanu dokumentu po zakończeniu transakcji lub partii operacji zapisu, ale przed zatwierdzeniem transakcji lub partii. Podobnie jak funkcja get(), funkcja getAfter() przyjmuje w pełni określoną ścieżkę dokumentu. Za pomocą getAfter() możesz definiować zestawy operacji zapisu, które muszą być wykonywane razem jako transakcja lub partia.

Limity dostępu do połączeń

Istnieje limit wywołań dostępu do dokumentu na ocenę zestawu reguł:

  • 10 w przypadku żądań dotyczących pojedynczego dokumentu i zapytań.
  • 20 w przypadku odczytów z wielu dokumentów, transakcji i zapisów zbiorczych. Poprzedni limit wynoszący 10 operacji dotyczy też każdej operacji.

    Załóżmy na przykład, że tworzysz zbiorczą prośbę o zapis z 3 operacjami zapisu, a Twoje reguły zabezpieczeń używają 2 wywołań dostępu do dokumentu, aby zweryfikować każdy zapis. W tym przypadku każde zapisanie wykorzystuje 2 z 10 wywołań dostępu, a żądanie zapisu wsadowego wykorzystuje 6 z 20 wywołań dostępu.

Przekroczenie któregokolwiek z tych limitów spowoduje błąd dotyczący braku uprawnień. Niektóre wywołania dostępu do dokumentu mogą być buforowane, a buforowane wywołania nie są wliczane do limitów.

Szczegółowe wyjaśnienie wpływu tych limitów na transakcje i zapisy zbiorcze znajdziesz w przewodniku dotyczącym zabezpieczania operacji niepodzielnych.

Dostęp do połączeń i cen

Użycie tych funkcji powoduje wykonanie operacji odczytu w bazie danych, co oznacza, że opłaty za odczyt dokumentów będą naliczane nawet wtedy, gdy reguły odrzucą żądanie. Więcej informacji o rozliczeniach znajdziesz w sekcji Cloud Firestore Ceny.

Funkcje niestandardowe

W miarę jak reguły zabezpieczeń stają się bardziej złożone, możesz umieszczać zestawy warunków w funkcjach, których możesz używać ponownie w całym zestawie reguł. Reguły zabezpieczeń obsługują funkcje niestandardowe. Składnia funkcji niestandardowych jest nieco podobna do JavaScriptu, ale funkcje reguł bezpieczeństwa są pisane w języku specyficznym dla domeny, który ma pewne istotne ograniczenia:

  • Funkcje mogą zawierać tylko 1 instrukcję return. Nie mogą one zawierać żadnej dodatkowej logiki. Nie mogą na przykład wykonywać pętli ani wywoływać usług zewnętrznych.
  • Funkcje mogą automatycznie uzyskiwać dostęp do funkcji i zmiennych z zakresu, w którym są zdefiniowane. Na przykład funkcja zdefiniowana w zakresie service cloud.firestore ma dostęp do zmiennej resource i wbudowanych funkcji, takich jak get()exists().
  • Funkcje mogą wywoływać inne funkcje, ale nie mogą być rekurencyjne. Łączna głębokość stosu wywołań jest ograniczona do 10.
  • W wersji reguł 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ą return.

Funkcja jest definiowana za pomocą słowa kluczowego function i przyjmuje zero lub więcej argumentów. Możesz na przykład połączyć 2 typy warunków użyte w przykładach powyżej 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żywanie funkcji w regułach bezpieczeństwa ułatwia ich utrzymanie w miarę wzrostu złożoności reguł.

Reguły nie są filtrami

Po zabezpieczeniu danych i rozpoczęciu pisania zapytań pamiętaj, że reguły zabezpieczeń nie są filtrami. Nie możesz napisać zapytania dotyczącego wszystkich dokumentów w kolekcji i oczekiwać, że Cloud Firestore zwróci tylko te dokumenty, do których bieżący klient ma uprawnienia dostępu.

Weźmy na przykład tę regułę 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 to zapytanie, ponieważ zestaw wyników może zawierać dokumenty, w których visibility nie jest równe public:

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

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

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

Cloud Firestore Reguły zabezpieczeń oceniają każde zapytanie pod kątem potencjalnego wyniku i odrzucają żądanie, jeśli może ono zwrócić dokument, do którego klient nie ma uprawnień do odczytu. Zapytania muszą być zgodne z ograniczeniami określonymi przez reguły zabezpieczeń. Więcej informacji o regułach zabezpieczeń i zapytaniach znajdziesz w artykule Bezpieczne wykonywanie zapytań dotyczących danych.

Dalsze kroki