Warunki zapisu reguł zabezpieczeń Cloud Firestore

Ten przewodnik opiera się na przewodniku na temat tworzenia reguł zabezpieczeń i pokazuje, jak dodawać warunki do reguły Cloud Firestore Security Rules. Jeśli nie znasz podstaw usługi Cloud Firestore Security Rules, przeczytaj przewodnik dla początkujących.

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

Uwierzytelnianie

Jednym z najczęstszych wzorów reguł zabezpieczeń jest kontrolowanie 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 typowym wzorcem jest zapewnienie, aby użytkownicy mogli odczytywać i zapisywać tylko 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 usługi uwierzytelniania Firebase lub Google Cloud Identity Platform, zmienna request.auth zawiera informacje uwierzytelniające klienta, który żąda danych. Więcej informacji o request.auth znajdziesz w dokumentacji referencyjnej.

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 blokować 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 wartość 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 porównać dane przychodzące z dotychczasowymi danymi. W tym przypadku, jeśli reguły zezwalają na oczekujące zapisywanie, zmienna request.resource zawiera przyszły stan dokumentu. W przypadku operacji update, które modyfikują tylko podzbiór pól dokumentu, zmienna request.resource będzie zawierać stan dokumentu oczekującego po operacji. Aby zapobiec niechcianym lub niespójnym aktualizacjom danych, możesz sprawdzić wartości pól w sekcji 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ą porównywać przychodzące żądania z innymi dokumentami w bazie danych. Funkcje get() i exists() oczekują pełnych ścieżek dokumentów. Jeśli używasz zmiennych do tworzenia ścieżek dla funkcji get()exists(), musisz jawnie ujęć zmienne w nawiasach kwadratowych za pomocą składni $(variable).

W tym przykładzie zmienna database jest przechwytywana przez instrukcję dopasowania 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 zbiorczego zapisu, ale przed zatwierdzaniem transakcji lub zbiorczego zapisu. Podobnie jak funkcja get(), funkcja getAfter() przyjmuje w parametrze ścieżkę dokumentu z pełną specyfikacją. Za pomocą getAfter() możesz definiować zestawy operacji zapisu, które muszą być wykonywane razem jako transakcja lub partia.

Dostęp do limitów 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 dla odczytu wielu dokumentów, transakcji i zbiorczego zapisu. Poprzedni limit 10 operacji obowiązuje również w przypadku każdej operacji.

    Załóżmy na przykład, że tworzysz zbiorcze żądanie zapisu z 3 operacjami zapisu i że Twoje reguły zabezpieczeń używają 2 wywołań dostępu do dokumentu, aby zweryfikować każdy zapis. W tym przypadku każde zapisywanie korzysta z 2 z 10 wywołań dostępu, a zbiórkowe żądanie zapisywania korzysta z 6 z 20 wywołań dostępu.

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

Szczegółowe informacje o tym, jak te limity wpływają na transakcje i zbiorowe zapisy, znajdziesz w przewodniku Bezpieczeństwo operacji atomowych.

Dostęp do rozmów i ceny

Użycie tych funkcji powoduje wykonanie operacji odczytu w Twojej bazie danych, co oznacza, że zostaniesz obciążony za odczytanie dokumentów, nawet jeśli Twoje reguły odrzucą żądanie. Więcej informacji o płatnościach znajdziesz w Cloud FirestoreCennik.

Funkcje niestandardowe

Gdy reguły zabezpieczeń staną się bardziej złożone, możesz umieścić zestawy warunków w funkcjach, które możesz ponownie wykorzystać w swojej liście reguł. Reguły zabezpieczeń obsługują funkcje niestandardowe. Składnia funkcji niestandardowych jest podobna do składni JavaScript, ale funkcje reguł zabezpieczeń są pisane w języku specyficznym dla danej dziedziny, który ma pewne ważne ograniczenia:

  • Funkcje mogą zawierać tylko jedno wyrażenie return. Nie mogą zawierać żadnej dodatkowej logiki. Na przykład nie mogą 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() i exists().
  • Funkcje mogą wywoływać inne funkcje, ale nie mogą być rekurencyjne. Łączna głębokość wywołania zasobnika jest ograniczona do 10.
  • W wersji reguł v2 funkcje mogą definiować zmienne za pomocą słowa kluczowego let. Funkcje mogą mieć maksymalnie 10 załączników, ale muszą się kończyć instrukcją return.

Funkcja jest definiowana za pomocą słowa kluczowego function i przyjmuje od 0 do nieograniczonej liczby argumentów. Możesz na przykład połączyć 2 typy warunków użytych w przykładach powyżej w jednej funkcji:

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, gdy ich złożoność rośnie.

Reguły nie są filtrami

Po zabezpieczeniu danych i rozpoczęciu tworzenia zapytań pamiętaj, że reguły bezpieczeństwa to nie filtry. Nie możesz utworzyć 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 taką 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';
    }
  }
}

Odrzucone: ta reguła odrzuca to zapytanie, ponieważ zbiór wyników może zawierać dokumenty, w których visibility nie jest public:

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

Dozwolone: to reguła zezwalająca na to 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 sprawdzają 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 Twoje reguły zabezpieczeń. Więcej informacji o regułach zabezpieczeń i zapytaniach znajdziesz w artykule Bezpieczne zapytania o dane.

Dalsze kroki