We wpisie Cloud Firestore w Firebase pokazałem, w jaki sposób można wykorzystać usługę Cloud Firestore do przechowywania danych, wprowadzanych przez użytkowników. Gromadząc dane, należy zadbać o to, aby były bezpieczne i nikt poza uprawnionymi osobami nie mógł się do nich dostać. W kolejnych częściach tego wpisu omówię podstawową wiedzę związaną z regułami bezpieczeństwa Cloud Firebase.
Podczas tworzenia reguł będziemy wykorzystywać przedstawioną poniżej strukturę bazy danych.
{ users: { idUser: { username, firstName, lastName } }, movies: { idMovie: { title rating, comments: [ idComments: { user, body }, ... ] } } }
Podstawy
Zacznijmy od tego, czym w ogóle są reguły bezpieczeństwa?
Reguły zapewniają kontrolę dostępu i sprawdzają poprawność danych. Składają się z dopasowań, które identyfikują dokumenty. One zaś mogą być odczytywane (read) oraz zapisywane (write).
Od maja 2019 roku możemy korzystać z reguł w jednej z dwóch wersji. Domyślnie wykorzystywane są reguły w wersji 1. Jeżeli chcemy zmienić na wersję 2, należy na początku dodać rules_version = ‚2’;. Przy niektórych zagadnieniach będę zaznaczał, że są wymagane reguły w wersji 2.
Tworząc bazę danych Cloud Firestore jesteśmy pytani o to, czy tworzona baza ma być publiczna, czy prywatna. Wybierając typ publiczny wyświetla się komunikat informujący, że każdy użytkownik będzie miał dostęp do danych. To właśnie w tym miejscy mamy po raz pierwszy do czynienia z regułami, choć możemy sobie nie zdawać z tego sprawy. Poniżej znajdują się reguły dla trybu prywatnego, jak również publicznego.
Prywatny
service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if false; } } }
Publiczny
service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if true; } } }
Jak możemy zobaczyć, każdy zestaw reguł zaczyna się od service cloud.firestore. Deklaracja ta pozwala określić zakres reguł, które dotyczą tylko Cloud Firestore. Dzięki temu unika się konfliktów pomiędzy innymi usługami, takimi jak Realtime Database czy Cloud Storage.
Następnie mamy match /databases/{database}/document Odpowiada za dopasowanie reguł dla dowolnej bazy danych. Obecnie każdy projekt ma jedną bazę danych, która nazywa się default.
match – słowo kluczowe, które odpowiada za deklarację reguły – dopasowanie. Zawsze wskazuje na kolekcję, nigdy zaś na dokument.
allow – słowo kluczowe, które pozwala na dostęp. Po nim zawsze pojawia się typ operacji oraz warunek.
Poniżej znajduje się schemat tworzenia reguł
match (ścieżka do kolekcji) { allow (typ operacji): if warunek }
Podstawowe typy operacji:
- read – odpowiada za odczyt dokumentu, jak również całej kolekcji,
- write – odpowiada za utworzenie, modyfikowanie oraz usuwanie dokumentu.
Mamy również możliwość sprawdzenia, czy zapytanie do bazy jest wysłane od zalogowanego użytkownika. Poniżej znajduje się reguła sprawdzająca to.
service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if request.auth.uid != null; } } }
Dzięki wykorzystaniu obiektu request, mamy dostęp do pola auth.uid, które zawiera identyfikator użytkownika. Obiekt request zawiera następujące informacje:
- auth – zawiera informacje o uwierzytelnieniu takie jak: uid i token,
- method – metoda, która zostanie wykonana,
- path – ścieżka do katalogu na którym zostanie wykonana wybrana metoda,
- query – mapa właściwości zapytanie, jeżeli są wykorzystane,
- resource – przy zapisie przechowuje nową wartość zasobu,
- time – czas otrzymania żądania.
Granulacja
Cloud Firestore daje nam również możliwość podzielenia dopasowań na odczytywanie, zapisywanie, edycje oraz usuwanie. Dzięki temu mamy jeszcze większy wpływ na poszczególne operacje wykonywane przez użytkownika. Do tego celu wykorzystywane są dodatkowe metody.
Metody odpowiadające za odczyt:
- get – metoda odpowiada za pobranie pojedynczego dokumentu,
- list – metoda odpowiada za pobranie całej listy
Metody odpowiadające za zapis:
- create – metoda odpowiadająca za tworzenie nowego dokumentu
- update – metoda odpowiadająca za aktualizowanie istniejącego dokumentu
- delete – metoda odpowiadająca za usuwanie dokumentu
Poniżej znajduje się kod pokazujący sposób użycia omówionych metod.
service cloud.firestore { match /databases/{database}/documents { match /movies/{movie} { allow get: if <condition>; allow list: if <condition>; } match /movies/{movie} { allow create: if <condition>; allow update: if <condition>; allow delete: if <condition>; } } }
Dane hierarchiczne
Jak już wspomniałem wcześniej, ścieżki zawsze wskazują na kolekcję, a nie na dokumenty. W przypadku gromadzenia danych w strukturze hierarchicznej, wymagane jest jawne deklarowanie reguł dla podkolekcji. Wynika to z tego, że w przypadku kiedy utworzymy regułę dla kolekcji nadrzędnej, nie ma ona wpływu na kolekcję podrzędną. Należy także pamiętać, że ścieżka zewnętrzna zawarta jest w ścieżce wewnętrznej. Poniżej znajduje się kod prezentujący jawne deklarowanie dopasowań do podkategorii.
service cloud.firestore { match /databases/{database}/documents { match /movies/{movie} { allow read, write: if <condition>; match /comments/{idComment} { allow read, write: if <condition>; } } } }
Mając zewnętrzne dopasowanie match /movies/{movie} { } i wewnętrzne dopasowanie match /comments/{idComment} { }, w efekcie uzyskujemy taką ścieżkę: match /movies/comments/{idComment}.
Symbole wieloznaczne
Podczas tworzenia reguł do kolekcji, wykorzystywane są również symbole wieloznaczne. Dzięki temu utworzona reguła dotyczy nie tylko dokumentów w kolekcji, ale także podkolekcji. Można je stosować do dowolnej głębokości w strukturze hierarchicznej. Poniżej znajduje się kod przedstawiający działanie omawianych symboli.
service cloud.firestore { match /databases/{database}/documents { match /movies/{document=**} { allow read, write: if <condition>; } } }
Jeżeli korzystamy z reguł w wersji 1, musimy pamiętać o tym, by {name=**} znajdowało się na końcu ścieżki. W przypadku korzystania z reguł w wersji 2, możemy symbole wieloznaczne wykorzystywać w dowolnym miejscu ścieżki.
service cloud.firestore { match /databases/{database}/documents { match /movies/{idMovie=**} { allow read, write: if <condition>; } } }
Tworząc reguły możemy spotkać się z nakładaniem dopasowań. Dostęp dozwolony będzie, jeżeli zostanie spełniony dowolny z warunków. Poniżej znajduję się kod przedstawiający nakładanie się dopasowań.
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /users/{idUser=**}/movies/{movie} { allow read, write: if <condition>; } } }
Testowanie i publikowanie
Firebase udostępnia użytkownikowi w panelu zarządzającym bazą Cloud Firestore symulator reguł, dzięki czemu przed opublikowaniem reguł możemy je przetestować, aby sprawdzić, na pewno są odpowiednio bezpieczne. Symulator posiada własny edytor reguł, dlatego przed testowaniem nie musimy zmieniać aktualnych reguł bazy. Symulator pozwala nam na wykonywanie odczytu, zapisu oraz usuwania danych.
Opublikować nowe reguły możemy za pomocą panelu zarządzania bazą danych. Wystarczy, że wejdziemy w zakładkę Reguły, w polu tekstowym wprowadzimy nowe reguły i potwierdzimy je klikając w przycisk Opublikuj.
Podsumowanie
Bezpieczeństwo danych jest bardzo ważnym zagadnieniem, o którym nie można zapomnieć podczas tworzenia aplikacji. Wpis ten pokazuje podstawowe możliwości, jakie daje nam usługa Firebase do zabezpieczania danych. Temat ten nie został jednak jeszcze w pełni wyczerpany. Uznałem, że pisząc o wszystkich możliwościach powstałby zbyt obszerny tekst jak na jeden wpis, dlatego w kolejnych miesiącach możecie spodziewać się kontynuacji tego tematu.