Korzystanie z warunków w regułach zabezpieczeń Cloud Storage Firebase

Ten przewodnik opiera się na poznawaniu podstawowej składni języka Firebase Security Rules i pokazuje, jak dodać warunki do Firebase Security Rules dla Cloud Storage.

Podstawowym elementem Cloud Storage Security Rules jest warunek. Warunek to wyrażenie logiczne określające, czy dana operacja powinna zostać dozwolona, czy odrzucona. W przypadku podstawowych reguł najlepiej sprawdza się użycie literałów true i false. Jednak Firebase Security Rules w języku Cloud Storage umożliwia tworzenie bardziej złożonych warunków, które mogą:

  • Sprawdzanie uwierzytelniania użytkowników
  • Weryfikuj dane przychodzące

Uwierzytelnianie

Firebase Security Rules dla Cloud Storage integruje się z Firebase Authentication, aby zapewnić Cloud Storage skuteczne uwierzytelnianie użytkowników. Umożliwia to szczegółową kontrolę dostępu na podstawie roszczeń tokena Firebase Authentication.

Gdy uwierzytelniony użytkownik wysyła żądanie do Cloud Storage, zmienna request.auth jest wypełniana wartością uid użytkownika (request.auth.uid), a także deklaracjami tokena JWT Firebase Authentication (request.auth.token).

Ponadto w przypadku uwierzytelniania niestandardowego w polu request.auth.token wyświetlają się dodatkowe oświadczenia.

Gdy nieuwierzytelniony użytkownik wysyła żądanie, zmienna request.auth ma wartość null.

Korzystając z tych danych, istnieje kilka popularnych sposobów uwierzytelniania do zabezpieczania plików:

  • Publiczna: ignoruj użytkownika request.auth
  • Uwierzytelniony dostęp prywatny: sprawdź, czy request.auth nie jest null
  • Prywatne informacje o użytkowniku: sprawdź, czy request.auth.uid jest równe ścieżce uid.
  • Grupa prywatna: sprawdź oświadczenia tokena niestandardowego, aby dopasować wybrane oświadczenie, lub odczytaj metadane pliku, aby sprawdzić, czy istnieje pole metadanych.

Publiczny

Każda reguła, która nie uwzględnia kontekstu request.auth, może być uznana za regułę public, ponieważ nie uwzględnia kontekstu uwierzytelniania użytkownika. Reguły te mogą być przydatne przy udostępnianiu danych publicznych, takich jak zasoby gry, pliki dźwiękowe lub inne treści statyczne.

// Anyone to read a public image if the file is less than 100kB
// Anyone can upload a public file ending in '.txt'
match /public/{imageId} {
  allow read: if resource.size < 100 * 1024;
  allow write: if imageId.matches(".*\\.txt");
}

Uwierzytelniona prywatna

W niektórych przypadkach możesz chcieć, aby dane były widoczne dla wszystkich uwierzytelnionych użytkowników aplikacji, ale nie dla niezalogowanych użytkowników. Ponieważ zmienna request.auth ma wartość null dla wszystkich niezaufanych użytkowników, wystarczy sprawdzić, czy zmienna request.auth istnieje, aby wymagać uwierzytelnienia:

// Require authentication on all internal image reads
match /internal/{imageId} {
  allow read: if request.auth != null;
}

Prywatny użytkownik

Najczęstszym przypadkiem użycia request.auth będzie przyznanie użytkownikom szczegółowych uprawnień do ich plików: od przesyłania zdjęć profilowych do czytania prywatnych dokumentów.

Ponieważ pliki w Cloud Storage mają pełną „ścieżkę” do pliku, wystarczy, że plik jest kontrolowany przez użytkownika, a w prefiksie nazwy pliku znajduje się unikalny element identyfikujący użytkownika (taki jak uid użytkownika), który można sprawdzić podczas oceny reguły:

// Only a user can upload their profile picture, but anyone can view it
match /users/{userId}/profilePicture.png {
  allow read;
  allow write: if request.auth.uid == userId;
}

Grupa prywatna

Innym równie często spotykanym przypadkiem użycia jest przyznawanie uprawnień grupie do obiektu, np. umożliwienie kilku członkom zespołu współpracy nad udostępnionym dokumentem. Można to zrobić na kilka sposobów:

  • Utwórz token niestandardowy Firebase Authentication, który zawiera dodatkowe informacje o członku grupy (np. identyfikator grupy).
  • Uwzględnij informacje o grupie (takie jak identyfikator grupy lub lista autoryzowanych uid) w metadanych pliku.

Gdy te dane zostaną zapisane w tokenie lub metadanych pliku, można się do nich odwołać z poziomu reguły:

// Allow reads if the group ID in your token matches the file metadata's `owner` property
// Allow writes if the group ID is in the user's custom token
match /files/{groupId}/{fileName} {
  allow read: if resource.metadata.owner == request.auth.token.groupId;
  allow write: if request.auth.token.groupId == groupId;
}

Poproś o ocenę

Przesyłanie, pobieranie, zmiany metadanych i usuwanie są oceniane za pomocą request wysłanego do Cloud Storage. Oprócz unikalnego identyfikatora użytkownika i ładunku Firebase Authentication w obiekcie request.auth, jak opisano powyżej, zmienna request zawiera ścieżkę do pliku, w którym wykonywane jest żądanie, czas otrzymania żądania i nową wartość resource, jeśli żądanie dotyczy zapisu.

Obiekt request zawiera też unikalny identyfikator użytkownika oraz dane Firebase Authentication w obiekcie request.auth, co zostanie wyjaśnione w sekcji Bezpieczeństwo oparte na użytkowniku w dokumentacji.

Pełna lista właściwości obiektu request znajduje się poniżej:

Właściwość Typ Opis
auth map<string, string> Gdy użytkownik jest zalogowany, przekazuje uid, czyli swój unikalny identyfikator, oraz token, czyli mapę Firebase Authentication deklaracji JWT. W przeciwnym razie będzie to:null.
params map<string, string> Mapa zawierająca parametry zapytania żądania.
path ścieżka path reprezentujący ścieżkę, na której wykonywane jest żądanie.
resource map<string, string> Nowa wartość zasobu, obecna tylko w przypadku żądań write.
time sygnatura czasowa Znak czasowy określający czas serwera, w którym żądanie zostało ocenione.

Ocena zasobów

Podczas oceny reguł możesz też ocenić metadane pliku, który jest przesyłany, pobierany, modyfikowany lub usuwany. Dzięki temu możesz tworzyć złożone i skuteczne reguły, które na przykład zezwalają na przesyłanie tylko plików z określonymi typami treści lub usuwanie tylko plików o większym rozmiarze.

Firebase Security Rules dla Cloud Storage udostępnia metadane pliku w obiekcie resource, który zawiera pary klucz-wartość metadanych wyświetlanych w obiekcie Cloud Storage. Te właściwości można sprawdzić w prośbach read lub write, aby zapewnić integralność danych.

W przypadku żądań write (takich jak przesyłanie, aktualizowanie metadanych i usuwanie) oprócz obiektu resource, który zawiera metadane pliku znajdującego się obecnie na ścieżce żądania, możesz też użyć obiektu request.resource, który zawiera podzbiór metadanych pliku do zapisania, jeśli zapis jest dozwolony. Możesz użyć tych 2 wartości, aby zapewnić integralność danych lub zastosować ograniczenia aplikacji, takie jak typ lub rozmiar pliku.

Pełna lista właściwości obiektu resource znajduje się poniżej:

Właściwość Typ Opis
name ciąg znaków Pełna nazwa obiektu
bucket ciąg znaków Nazwa zasobnika, w którym znajduje się ten obiekt.
generation int, Google Cloud Storage generacja obiektu tego obiektu.
metageneration int, Metageneracja obiektu Google Cloud Storage tego obiektu.
size int, Rozmiar obiektu w bajtach.
timeCreated sygnatura czasowa Sygnatura czasowa reprezentująca czas utworzenia obiektu.
updated sygnatura czasowa Sygnatura czasowa określająca, kiedy obiekt został ostatnio zaktualizowany.
md5Hash ciąg znaków Skrót MD5 obiektu.
crc32c ciąg znaków Hasz CRC32C obiektu.
etag ciąg znaków ETag powiązany z tym obiektem.
contentDisposition ciąg znaków Ustawienie treści powiązane z tym obiektem.
contentEncoding ciąg znaków Kodowanie treści powiązane z tym obiektem.
contentLanguage ciąg znaków Język treści powiązany z tym obiektem.
contentType ciąg znaków Typ treści powiązany z tym obiektem.
metadata map<string, string> Pary klucz-wartość dodatkowych metadanych niestandardowych określonych przez dewelopera.

request.resource zawiera wszystkie te opcje oprócz generation, metageneration, etag, timeCreated i updated.

Ulepsz za pomocą Cloud Firestore

Aby sprawdzić inne kryteria autoryzacji, możesz uzyskać dostęp do dokumentów w Cloud Firestore.

Dzięki funkcjom firestore.get() i firestore.exists() reguły bezpieczeństwa mogą sprawdzać przychodzące żądania pod kątem dokumentów w Cloud Firestore. Funkcje firestore.get() i firestore.exists() oczekują w pełni określonych ścieżek dokumentu. Jeśli do tworzenia ścieżek dla firestore.get() i firestore.exists() używasz zmiennych, musisz za pomocą składni $(variable) wyraźnie zmienić znaczenie zmiennych.

W przykładzie poniżej widzimy regułę, która ogranicza dostęp do odczytu plików do użytkowników należących do określonych klubów.

service firebase.storage {
  match /b/{bucket}/o {
    match /users/{club}/files/{fileId} {
      allow read: if club in
        firestore.get(/databases/(default)/documents/users/$(request.auth.id)).memberships
    }
  }
}
W następnym przykładzie tylko znajomi użytkownika mogą zobaczyć jego zdjęcia.
service firebase.storage {
  match /b/{bucket}/o {
    match /users/{userId}/photos/{fileId} {
      allow read: if
        firestore.exists(/databases/(default)/documents/users/$(userId)/friends/$(request.auth.id))
    }
  }
}

Gdy utworzysz i zapiszesz pierwszy Cloud Storage Security Rules, który używa tych funkcji Cloud Firestore, w konsoli Firebase lub w interfejsie wiersza poleceń Firebase pojawi się prośba o włączenie uprawnień umożliwiających połączenie tych dwóch usług.

Możesz wyłączyć tę funkcję, usuwając rolę uprawnień zgodnie z opisem w artykule Wdrażanie usługi Firebase Security Rules i zarządzanie nią.

Sprawdzanie danych

Firebase Security Rules dla Cloud Storage może też służyć do weryfikacji danych, w tym do weryfikacji nazwy i ścieżki pliku, a także właściwości metadanych pliku, np. contentType i size.

service firebase.storage {
  match /b/{bucket}/o {
    match /images/{imageId} {
      // Only allow uploads of any image file that's less than 5MB
      allow write: if request.resource.size < 5 * 1024 * 1024
                   && request.resource.contentType.matches('image/.*');
    }
  }
}

Funkcje niestandardowe

W miarę jak reguły Firebase Security Rules stają się bardziej złożone, warto umieszczać zbiory warunków w funkcjach, które można używać wielokrotnie w ramach reguł. Reguły zabezpieczeń obsługują funkcje niestandardowe. Składnia funkcji niestandardowych jest podobna do składni języka JavaScript, ale funkcje Firebase Security Rules 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. 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 są zdefiniowane. Na przykład funkcja zdefiniowana w zakresie service firebase.storage ma dostęp do zmiennej resource, a tylko w zakresie Cloud Firestore – do funkcji wbudowanych, takich jak get()exists().
  • Funkcje mogą wywoływać inne funkcje, ale nie mogą być rekurencyjne. Łączna głębokość wywołania zasobnika jest ograniczona do 10.
  • W wersji rules2 funkcje mogą definiować zmienne za pomocą słowa kluczowego let. Funkcje mogą zawierać dowolną liczbę instrukcji let, ale muszą kończyć się 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 rodzaje warunków użytych w powyższych przykładach w jedną funkcję:

service firebase.storage {
  match /b/{bucket}/o {
    // 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 /images/{imageId} {
      allow read, write: if signedInOrPublic();
    }
    match /mp3s/{mp3Ids} {
      allow read: if signedInOrPublic();
    }
  }
}

Używanie funkcji w definicji Firebase Security Rules ułatwia ich utrzymanie, gdy reguły stają się coraz bardziej złożone.

Dalsze kroki

Po tej dyskusji na temat warunków masz już większą wiedzę na temat reguł i możesz:

Dowiedz się, jak obsługiwać podstawowe przypadki użycia i jak wygląda proces tworzenia, testowania i wdrażania reguł: