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

Ten przewodnik opiera się na poznawaniu podstawowej składni języka reguł zabezpieczeń Firebase i pokazuje, jak dodawać warunki do reguł zabezpieczeń Firebase dla Cloud Storage.

Podstawowym elementem składowym reguł zabezpieczeń Cloud Storage jest warunek. Warunek to wyrażenie logiczne, które określa, czy dana operacja powinna być dozwolona czy odrzucona. W przypadku podstawowych reguł najlepiej sprawdza się użycie literałów true i false. Jednak reguły zabezpieczeń Firebase dla Cloud Storage pozwalają na pisanie bardziej złożonych warunków, które:

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

Uwierzytelnianie

Reguły zabezpieczeń Firebase dla Cloud Storage integrują się z Uwierzytelnianiem Firebase, aby zapewnić zaawansowane uwierzytelnianie oparte na użytkownikach w Cloud Storage. Zapewnia to szczegółową kontrolę dostępu na podstawie deklaracji tokena uwierzytelniania Firebase.

Gdy uwierzytelniony użytkownik wysyła żądanie do Cloud Storage, zmienna request.auth jest zapełniana identyfikatorem uid(request.auth.uid) użytkownika oraz deklaracji JWT (request.auth.token) uwierzytelniania Firebase.

Dodatkowo podczas korzystania z niestandardowego uwierzytelniania w polu request.auth.token wyświetlane są dodatkowe deklaracje.

Gdy nieuwierzytelniony użytkownik wykonuje żą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 prywatny: sprawdź, czy request.auth nie jest typu null
  • Prywatny użytkownik: sprawdź, czy request.auth.uid równa się ścieżce uid
  • Grupa prywatna: sprawdź, czy deklaracje tokena niestandardowego pasują do wybranego żądania, lub odczytaj metadane pliku, aby sprawdzić, czy istnieje pole metadanych.

Publiczny

Reguła, która nie uwzględnia kontekstu request.auth, może być uznawana 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");
}

Uwierzytelniony prywatny

W niektórych przypadkach możesz chcieć, aby dane były dostępne dla wszystkich uwierzytelnionych użytkowników aplikacji, ale nie dla nieuwierzytelnionych. Zmienna request.auth ma wartość null w przypadku wszystkich nieuwierzytelnionych użytkowników, więc wystarczy, że sprawdzisz, 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 jest udzielanie poszczególnym użytkownikom szczegółowych uprawnień do ich plików: od przesyłania zdjęć profilowych po czytanie dokumentów prywatnych.

Pliki w Cloud Storage mają pełną „ścieżkę” do pliku, dlatego aby plik sterowany przez użytkownika był unikalnym elementem w prefiksie nazwy pliku (takim jak uid użytkownika), który można sprawdzić, gdy reguła jest sprawdzana:

// 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;
}

Prywatna grupa

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:

  • utworzyć token niestandardowy uwierzytelniania Firebase, który zawiera dodatkowe informacje o członku grupy (np. identyfikator grupy);
  • Dołącz informacje o grupie (takie jak jej identyfikator 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ę

Przesłane, pobrane pliki, zmiany w metadanych i usunięcia są oceniane przy użyciu request wysłanego do Cloud Storage. Oprócz unikalnego identyfikatora użytkownika i ładunku uwierzytelniania Firebase w obiekcie request.auth, jak opisano powyżej, zmienna request zawiera ścieżkę pliku, w której jest wykonywane żądanie, czas otrzymania żądania i nową wartość resource, jeśli żądanie dotyczy zapisu.

Obiekt request zawiera też unikalny identyfikator użytkownika i ładunek uwierzytelniania Firebase w obiekcie request.auth, co wyjaśniamy dokładniej w sekcji Bezpieczeństwo oparte na użytkownikach w tej dokumentacji.

Pełną listę właściwości obiektu request znajdziesz poniżej:

Właściwość Typ Opis
auth mapa<ciąg, ciąg> Po zalogowaniu się użytkownik udostępnia identyfikator uid, jego unikalny identyfikator, oraz token mapę deklaracji JWT uwierzytelniania Firebase. W przeciwnym razie będzie to null.
params mapa<ciąg, ciąg> Mapa zawierająca parametry zapytania żądania.
path ścieżka path reprezentujący ścieżkę, na której wykonywane jest żądanie.
resource mapa<ciąg, ciąg> Nowa wartość zasobu, obecna tylko w żądaniach write.
time sygnatura czasowa Sygnatura czasowa określająca czas oceny żądania przez serwer.

Ocena zasobów

Przy ocenie reguł warto też ocenić metadane przesyłanego, pobieranego, zmodyfikowanego lub usuniętego pliku. Dzięki temu można tworzyć złożone, zaawansowane reguły, które na przykład zezwalają na przesyłanie tylko plików o określonych typach treści lub usuwanych tylko pliki o określonym rozmiarze.

Reguły zabezpieczeń Firebase dla Cloud Storage udostępniają 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 sprawdzać po otrzymaniu żądań read lub write, aby zapewnić integralność danych.

W przypadku żądań write (np. przesyłania, aktualizowania i usuwania) oprócz obiektu resource, który zawiera metadane pliku istniejącego obecnie w ścieżce żądania, możesz też używać obiektu request.resource, który zawiera podzbiór metadanych pliku do zapisania, o ile zapis jest dozwolony. Możesz użyć tych 2 wartości, aby zapewnić integralność danych lub egzekwować ograniczenia aplikacji, takie jak typ lub rozmiar pliku.

Pełną listę właściwości obiektu resource znajdziesz poniżej:

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

request.resource zawiera wszystkie te elementy z wyjątkiem generation, metageneration, etag, timeCreated i updated.

Większe możliwości dzięki Cloud Firestore

Możesz uzyskać dostęp do dokumentów w Cloud Firestore, aby ocenić inne kryteria autoryzacji.

Za pomocą funkcji firestore.get() i firestore.exists() reguły zabezpieczeń mogą oceniać żądania przychodzące względem 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 widać 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))
    }
  }
}

Po utworzeniu i zapisaniu pierwszych reguł zabezpieczeń Cloud Storage korzystających z tych funkcji Cloud Firestore w konsoli Firebase lub w interfejsie wiersza poleceń Firebase pojawi się prośba o przyznanie uprawnień do połączenia 2 usług.

Możesz wyłączyć tę funkcję, usuwając rolę uprawnień zgodnie z opisem w artykule Wdrażanie reguł zabezpieczeń Firebase i zarządzanie nimi.

Zweryfikuj dane

Reguł zabezpieczeń Firebase dla Cloud Storage można też używać do weryfikacji danych, w tym do weryfikacji nazwy i ścieżki pliku oraz 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 zabezpieczeń Firebase stają się coraz bardziej złożone, być może warto połączyć zestawy warunków w funkcje, których możesz używać w zestawie reguł. Reguły zabezpieczeń obsługują funkcje niestandardowe. Składnia funkcji niestandardowych przypomina JavaScript, ale funkcje reguł zabezpieczeń Firebase 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 firebase.storage ma dostęp do zmiennej resource, a w przypadku Cloud Firestore dostępne są funkcje wbudowane, takie 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 wersji rules2 funkcje mogą definiować zmienne za pomocą słowa kluczowego let. Funkcje mogą mieć dowolną liczbę 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 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();
    }
  }
}

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

Dalsze kroki

Po omówieniu warunków stajemy się bardziej skomplikowani w zasadach i możemy:

Dowiedz się, jak obsługiwać podstawowe przypadki użycia i jak wygląda przepływ pracy przy tworzeniu, testowaniu i wdrażaniu reguł: