Firebase Data Connect zapewnia niezawodne zabezpieczenia po stronie klienta:
- Autoryzacja klienta mobilnego i internetowego
- Indywidualne mechanizmy autoryzacji na poziomie zapytania i mutacji
- Poświadczenie aplikacji z Firebase App Check.
Data Connect zapewnia dodatkowe zabezpieczenia:
- Autoryzacja po stronie serwera
- Bezpieczeństwo użytkowników projektu Firebase i Cloud SQL za pomocą IAM.
Autoryzowanie zapytań i mutacji klienta
Data Connect jest w pełni zintegrowana z Firebase Authentication, dzięki czemu możesz używać w swoim projekcie szczegółowych danych o użytkownikach, którzy uzyskują dostęp do Twoich danych (uwierzytelnianie), oraz o danych, do których mają oni dostęp (autoryzacja).
Data Connect udostępnia dyrektywę @auth
dotyczącą zapytań i mutacji, która pozwala ustawić poziom uwierzytelniania wymagany do autoryzacji operacji. W tym przewodniku znajdziesz opis dyrektywy @auth
z przykładami.
Ponadto Data Connect obsługuje wykonywanie zapytań zawartych w mutacjach, dzięki czemu możesz pobierać dodatkowe kryteria autoryzacji zapisane w bazie danych i używać ich w instrukcjach @check
, aby określić, czy otoczone mutacje są autoryzowane. W tym przypadku autoryzacji dyrektywa @redact
pozwala Ci określić, czy wyniki zapytań mają być zwracane do klientów w ramach protokołu sieciowego, a wbudowane zapytanie ma być pomijane w wygenerowanych pakietach SDK. Znajdź wprowadzenie do tych dyrektyw z przykładami.
Informacje o dyrektywie @auth
Możesz skonfigurować dyrektywę @auth
, aby korzystała z jednego z kilku wstępnie zdefiniowanych poziomów dostępu, które obejmują wiele typowych scenariuszy dostępu. Te poziomy sięgają od PUBLIC
(co pozwala na wykonywanie zapytań i mutacji przez wszystkich klientów bez uwierzytelniania) do NO_ACCESS
(co uniemożliwia wykonywanie zapytań i mutacji poza uprzywilejowanymi środowiskami serwerów korzystającymi z pakietu Admin SDK Firebase). Każdy z tych poziomów jest skorelowany z procesami uwierzytelniania udostępnianymi przez Firebase Authentication.
Poziom | Definicja |
---|---|
PUBLIC |
Operację może wykonać dowolna osoba z uwierzytelnieniem lub bez niego. |
PUBLIC |
Operację może wykonać dowolna osoba z uwierzytelnieniem lub bez niego. |
USER_ANON |
Każdy zidentyfikowany użytkownik, w tym użytkownicy, którzy zalogowali się anonimowo za pomocą Firebase Authentication, może wykonać zapytanie lub mutację. |
USER |
Każdy użytkownik zalogowany za pomocą Firebase Authentication może wykonać zapytanie lub mutację, z wyjątkiem użytkowników logujących się anonimowo. |
USER_EMAIL_VERIFIED |
Każdy użytkownik, który zalogował się za pomocą Firebase Authentication z weryfikowanym adresem e-mail, może wykonać zapytanie lub mutację. |
NO_ACCESS |
Tej operacji nie można wykonać poza kontekstem pakietu SDK Admin. |
Korzystając z tych wstępnie zdefiniowanych poziomów dostępu, możesz definiować złożone i solidne mechanizmy autoryzacji w instrukcji @auth
za pomocą filtrów where
i wyrażeń w języku CEL (Common Expression Language) ocenianych na serwerze.
Używanie dyrektywy @auth
do implementowania typowych scenariuszy autoryzacji
Wstępnie ustawione poziomy dostępu stanowią punkt wyjścia dla autoryzacji.
Poziom dostępu USER
jest najbardziej przydatnym poziomem podstawowym, od którego warto zacząć.
Pełna ochrona dostępu będzie opierać się na poziomie USER
oraz filtrach i wyrażeniach, które sprawdzają atrybuty użytkownika, atrybuty zasobów, role i inne aspekty. Poziomy USER_ANON
i USER_EMAIL_VERIFIED
to odmiany przypadku USER
.
Składnia wyrażeń umożliwia sprawdzanie danych za pomocą obiektu auth
, który reprezentuje dane uwierzytelniania przekazywane z operacjami, zarówno dane standardowe w tokenach uwierzytelniania, jak i dane niestandardowe w tokenach. Listę pól dostępnych w obiekcie auth
znajdziesz w sekcji z informacjami.
Oczywiście są przypadki, w których PUBLIC
jest odpowiednim poziomem dostępu na początek. Ponownie przypominamy, że poziom dostępu to zawsze punkt wyjścia, a do zapewnienia solidnej ochrony potrzebne są dodatkowe filtry i wyrażenia.
Ten przewodnik zawiera teraz przykłady wykorzystania funkcji USER
i PUBLIC
.
Motywujący przykład
Poniższe przykłady sprawdzonych metod odnoszą się do schematu dla platformy blogowej, w której niektóre treści są dostępne tylko w ramach planu płatnego.
Taka platforma prawdopodobnie modelowałaby Users
i Posts
.
type User @table(key: "uid") {
uid: String!
name: String
birthday: Date
createdAt: Timestamp! @default(expr: "request.time")
}
type Post @table {
author: User!
text: String!
# "one of 'draft', 'public', or 'pro'"
visibility: String! @default(value: "draft")
# "the time at which the post should be considered published. defaults to
# immediately"
publishedAt: Timestamp! @default(expr: "request.time")
createdAt: Timestamp! @default(expr: "request.time")
updatedAt: Timestamp! @default(expr: "request.time")
}
Zasoby należące do użytkownika
Firebase zaleca pisanie filtrów i wyrażeń, które sprawdzają, czy użytkownik jest właścicielem zasobu. W tych przypadkach chodzi o własność Posts
.
W podanych niżej przykładach dane z tokenów autoryzacji są odczytywane i porównywane za pomocą wyrażeń. Typowym wzorcem jest użycie wyrażenia takiego jak where: {authorUid:
{eq_expr: "auth.uid"}}
, aby porównać zapisany identyfikator authorUid
z identyfikatorem auth.uid
(identyfikator użytkownika) przekazanym w tokenie uwierzytelniania.
Utwórz
Ta metoda autoryzacji zaczyna się od dodania auth.uid
z tokena autoryzacji do każdego nowego Post
jako pola authorUid
, aby umożliwić porównanie w testach autoryzacji podrzędnych.
# Create a new post as the current user
mutation CreatePost($text: String!, $visibility: String) @auth(level: USER) {
post_insert(data: {
# set the author's uid to the current user uid
authorUid_expr: "auth.uid"
text: $text
visibility: $visibility
})
}
Zaktualizuj
Gdy klient próbuje zaktualizować Post
, możesz przetestować przekazaną wartość auth.uid
w porównaniu z zapisaną wartością authorUid
.
# Update one of the current user's posts
mutation UpdatePost($id: UUID!, $text: String, $visibility: String) @auth(level:USER) {
post_update(
# only update posts whose author is the current user
first: { where: {
id: {eq: $id}
authorUid: {eq_expr: "auth.uid"}
}}
data: {
text: $text
visibility: $visibility
# insert the current server time for updatedAt
updatedAt_expr: "request.time"
}
)
}
Usuń
Ta sama metoda jest używana do autoryzacji operacji usuwania.
# Delete one of the current user's posts
mutation DeletePost($id: UUID!) @auth(level: USER) {
post_delete(
# only delete posts whose author is the current user
first: { where: {
id: {eq: $id}
authorUid: {eq_expr: "auth.uid"}
}}
)
}
# Common display information for a post
fragment DisplayPost on Post {
id, text, createdAt, updatedAt
author { uid, name }
}
Lista
# List all posts belonging to the current user
query ListMyPosts @auth(level: USER) {
posts(where: {
userUid: {eq_expr: "auth.uid"}
}) {
# See the fragment above
...DisplayPost
# also show visibility since it is user-controlled
visibility
}
}
Get
# Get a post only if it belongs to the current user
query GetMyPost($id: UUID!) @auth(level: USER) {
post(key: {id: $id},
first: {where: {
id: {eq: $id}
authorUid: {eq_expr: "auth.uid"}}
}}, {
# See the fragment above
...DisplayPost
# also show visibility since it is user-controlled
visibility
}
}
Filtrowanie danych
System autoryzacji Data Connect umożliwia tworzenie zaawansowanych filtrów w połączeniu z wstępnie ustawionymi poziomami dostępu, takimi jak PUBLIC
, a także za pomocą danych z tokenów autoryzacji.
System autoryzacji umożliwia też używanie wyrażeń bez poziomu dostępu do bazy danych, jak pokazano w niektórych z następujących przykładów.
Filtrowanie według atrybutów zasobu
W tym przypadku autoryzacja nie jest oparta na tokenach uwierzytelniania, ponieważ poziom bezpieczeństwa podstawowy ma wartość PUBLIC
. Możemy jednak wyraźnie ustawić w naszej bazie danych rekordy jako odpowiednie do publicznego dostępu. Załóżmy, że mamy w naszej bazie danych Post
rekordów z visibility
ustawionym na „publiczne”.
# List all posts marked as 'public' visibility
query ListPublicPosts @auth(level: PUBLIC) {
posts(where: {
# Test that visibility is "public"
visibility: {eq: "public"}
# Only display articles that are already published
publishedAt: {lt_expr: "request.time"}
}) {
# see the fragment above
...DisplayPost
}
}
Filtrowanie według roszczeń użytkownika
Załóżmy, że masz skonfigurowane niestandardowe oświadczenia użytkownika, które przekazują tokeny uwierzytelniające, aby zidentyfikować użytkowników w ramach abonamentu „pro” w Twojej aplikacji. W polu auth.token.plan
w tokenie uwierzytelniającym jest oznaczony odpowiednim znacznikiem. Wyrażenia mogą być testowane w stosunku do tego pola.
# List all public or pro posts, only permitted if user has "pro" plan claim
query ProListPosts @auth(expr: "auth.token.plan == 'pro'") {
posts(where: {
# display both public posts and "pro" posts
visibility: {in: ['public', 'pro']},
# only display articles that are already published
publishedAt: {lt_expr: "request.time"},
}) {
# see the fragment above
...DisplayPost
# show visibility so pro users can see which posts are pro\
visibility
}
}
Filtrowanie według kolejności i limitu
Możesz też ustawić w rekordach visibility
wartość visibility
, aby wskazać, że są one dostępne dla użytkowników „pro”, ale w celu wyświetlenia podglądu lub listy danych w ramach teasera możesz ograniczyć liczbę zwracanych rekordów.Post
# Show 2 oldest Pro post as a preview
query ProTeaser @auth(level: USER) {
posts(
where: {
# show only pro posts
visibility: {eq: "pro"}
# that have already been published more than 30 days ago
publishedAt: {lt_time: {now: true, sub: {days: 30}}}
},
# order by publish time
orderBy: [{publishedAt: DESC}],
# only return two posts
limit: 2
) {
# See the fragment above
...DisplayPost
}
}
Filtrowanie według roli
Jeśli w Twoim roszczeniu niestandardowym zdefiniowana jest rola admin
, możesz przetestować i autoryzować operacje.
# List all posts unconditionally iff the current user has an admin claim
query AdminListPosts @auth(expr: "auth.token.admin == true") {
posts { ...DisplayPost }
}
Dodaj dyrektywy @check
i @redact
, aby sprawdzić dane autoryzacji.
Typowym przypadkiem użycia autoryzacji jest przechowywanie niestandardowych ról autoryzacji w bazie danych, na przykład w specjalnej tabeli uprawnień, i używanie tych ról do autoryzowania mutacji w celu tworzenia, aktualizowania lub usuwania danych.
Za pomocą wyszukiwania danych autoryzacyjnych możesz wysyłać zapytania o role na podstawie identyfikatora użytkownika i używać wyrażeń CEL, aby określić, czy mutacja jest autoryzowana. Możesz na przykład napisać funkcję UpdateMovieTitle
, która pozwala autoryzowanemu klientowi aktualizować tytuły filmów.
W dalszej części tej dyskusji zakładamy, że baza danych aplikacji do recenzowania filmów przechowuje rolę autoryzacji w tabeli MoviePermission
.
# MoviePermission
# Suppose a user has an authorization role with respect to records in the Movie table
type MoviePermission @table(key: ["movie", "user"]) {
movie: Movie! # implies another field: movieId: UUID!
user: User!
role: String!
}
Używanie w mutacjach
W tym przykładzie implementacji mutacja UpdateMovieTitle
zawiera pole query
, które służy do pobierania danych z tabeli MoviePermission
, oraz te dyrektywy, które zapewniają bezpieczeństwo i stabilność operacji:
- Dyrektywa
@transaction
, która zapewnia, że wszystkie zapytania i sprawdzenia autoryzacji są wykonywane lub nie są wykonywane w ramach jednej operacji. - Dyrektywa
@redact
służy do pomijania wyników zapytań w odpowiedzi, co oznacza, że weryfikacja autoryzacji jest wykonywana na serwerze Data Connect, ale dane wrażliwe nie są udostępniane klientowi. Para dyrektyw
@check
służących do oceny logiki autoryzacji na podstawie wyników zapytania, np. sprawdzania, czy dany identyfikator użytkownika ma odpowiednią rolę do wprowadzania modyfikacji.
mutation UpdateMovieTitle($movieId: UUID!, $newTitle: String!) @auth(level: USER) @transaction {
# Step 1: Query and check
query @redact {
moviePermission( # Look up a join table called MoviePermission with a compound key.
key: {movieId: $movieId, userId_expr: "auth.uid"}
# Step 1a: Use @check to test if the user has any role associated with the movie
# Here the `this` binding refers the lookup result, i.e. a MoviePermission object or null
# The `this != null` expression could be omitted since rejecting on null is default behavior
) @check(expr: "this != null", message: "You do not have access to this movie") {
# Step 1b: Check if the user has the editor role for the movie
# Next we execute another @check; now `this` refers to the contents of the `role` field
role @check(expr: "this == 'editor'", message: "You must be an editor of this movie to update title")
}
}
# Step 2: Act
movie_update(id: $movieId, data: {
title: $newTitle
})
}
this
.
Używanie w zapytaniach
Wyszukiwanie danych autoryzacji jest też przydatne do ograniczania zapytań na podstawie ról lub innych ograniczeń.
W tym przykładzie, który również korzysta ze schematu MoviePermission
, zapytanie sprawdza, czy żądający ma odpowiednią rolę „administratora”, aby wyświetlić użytkowników, którzy mogą edytować film.
query GetMovieEditors($movieId: UUID!) @auth(level: PUBLIC) {
moviePermission(key: { movieId: $movieId, userId_expr: "auth.uid" }) @redact {
role @check(expr: "this == 'admin'", message: "You must be an admin to view all editors of a movie.")
}
moviePermissions(where: { movieId: { eq: $movieId }, role: { eq: "editor" } }) {
user {
id
username
}
}
}
this
.
Nieprawidłowe rozwiązania dotyczące autoryzacji
W poprzedniej sekcji omówiliśmy wzorce, których należy używać podczas stosowania dyrektywy @auth
.
Warto też wiedzieć, jakich wzorców unikać.
Unikaj przekazywania identyfikatorów atrybutów użytkownika i parametrów tokena autoryzacji w argumentach zapytań i mutacji
Firebase Authentication to potężne narzędzie do prezentowania procesów uwierzytelniania i bezpiecznego rejestrowania danych uwierzytelniania, takich jak zarejestrowane identyfikatory użytkowników i liczne pola przechowywane w tokenach uwierzytelniania.
Nie zalecamy przekazywania identyfikatorów użytkowników ani danych tokena autoryzacji w argumentach zapytań i mutacji.
# Antipattern!
# This incorrectly allows any user to view any other user's posts
query AllMyPosts($userId: String!) @auth(level: USER) {
posts(where: {authorUid: {eq: $userId}}) {
id, text, createdAt
}
}
Unikaj używania poziomu dostępu USER
bez żadnych filtrów
Jak już kilkakrotnie wspominaliśmy w tym przewodniku, podstawowe poziomy dostępu, takie jak USER
, USER_ANON
i USER_EMAIL_VERIFIED
, stanowią punkt wyjścia dla kontroli autoryzacji, które można wzbogacić za pomocą filtrów i wyrażeń. Używanie tych poziomów bez odpowiedniego filtra lub wyrażenia, które sprawdza, który użytkownik wykonuje żądanie, jest zasadniczo równoznaczne z używaniem poziomu PUBLIC
.
# Antipattern!
# This incorrectly allows any user to view all documents
query ListDocuments @auth(level: USER) {
documents {
id
title
text
}
}
Unikaj korzystania z poziomu dostępu PUBLIC
lub USER
na potrzeby prototypowania
Aby przyspieszyć proces tworzenia, możesz zechcieć ustawić wszystkie operacje na poziomie dostępu PUBLIC
lub USER
bez dalszych ulepszeń, które autoryzują wszystkie operacje i umożliwiają szybkie testowanie kodu.
Gdy wykonasz w ten sposób wstępny prototyp, zacznij przechodzić z poziomu NO_ACCESS
do autoryzacji gotowej do wdrożenia z poziomami PUBLIC
i USER
.
Nie należy jednak stosować ich jako PUBLIC
ani USER
bez dodania dodatkowej logiki, jak w tym przewodniku.
# Antipattern!
# This incorrectly allows anyone to delete any post
mutation DeletePost($id: UUID!) @auth(level: PUBLIC) {
post: post_delete(
id: $id,
)
}
Unikaj autoryzacji na podstawie niepotwierdzonych adresów e-mail
Przyznawanie dostępu użytkownikom z konkretnej domeny to świetny sposób na ograniczenie dostępu. Podczas logowania się każdy może jednak twierdzić, że jest właścicielem danego adresu e-mail. Upewnij się, że przyznajesz dostęp tylko do adresów e-mail, które zostały zweryfikowane za pomocą usługi Uwierzytelnianie Firebase.
# Antipattern!
# Anyone can claim an email address during sign-in
mutation CreatePost($text: String!, $visibility: String) @auth(expr: "auth.token.email.endsWith('@example.com')") {
post_insert(data: {
# set the author's uid to the current user uid
authorUid_expr: "auth.uid"
text: $text
visibility: $visibility
})
}
Sprawdź też auth.token.email_verified
mutation CreatePost($text: String!, $visibility: String) @auth(expr: "auth.token.email_verified && auth.token.email.endsWith('@example.com')") {
post_insert(data: {
# set the author's uid to the current user uid
authorUid_expr: "auth.uid"
text: $text
visibility: $visibility
})
}
Sprawdzanie autoryzacji za pomocą wiersza poleceń Firebase
Jak już wspomnieliśmy, wstępnym punktem do zapewnienia solidnej autoryzacji są wstępnie zaprogramowane poziomy dostępu, takie jak PUBLIC
i USER
. Należy ich używać z dodatkowymi filtrami i kontrolami autoryzacji opartymi na wyrażeniach.
Nie należy ich używać bez dokładnego rozważenia przypadku użycia.
Data Connect pomaga Ci sprawdzić strategię autoryzacji. Analizuje ona kod łącznika podczas wdrażania na serwer za pomocą firebase deploy
w pliku Firebase CLI. Możesz użyć tego audytu, aby sprawdzić kod źródłowy.
Podczas wdrażania usług połączonych interfejs wiersza poleceń wyświetla oceny dla istniejącego, zmodyfikowanego i nowego kodu operacji w połączeniu.
W przypadku zmodyfikowanych i nowych operacji interfejs wiersza poleceń wyświetla ostrzeżenia i wymaga potwierdzenia, gdy używasz określonych poziomów dostępu w nowych operacjach lub gdy modyfikujesz istniejące operacje, aby używać tych poziomów dostępu.
Ostrzeżenia i komunikaty zawsze pojawiają się w przypadku:
PUBLIC
Ostrzeżenia i komunikaty wyświetlają się na tych poziomach dostępu, gdy nie rozszerzysz ich za pomocą filtrów za pomocą auth.uid
:
USER
USER_ANON
USER_EMAIL_VERIFIED
Blokowanie ostrzeżeń o niebezpiecznych operacjach za pomocą argumentu @auth(insecureReason:)
W wielu przypadkach okaże się, że stosowanie poziomów dostępu PUBLIC
i USER*
jest w pełni odpowiednie.
Jeśli Twój łącznik zawiera wiele operacji, możesz chcieć uzyskać bardziej przejrzyste i trafniejsze wyniki kontroli bezpieczeństwa, które pomija operacje, które normalnie wywoływałyby ostrzeżenie, ale wiesz, że masz odpowiedni poziom dostępu.
Możesz wyłączyć ostrzeżenia dotyczące takich operacji za pomocą @auth(insecureReason:)
.
Przykład:
query listItem @auth(level: PUBLIC, insecureReason: "This operation is safe to expose to the public.")
{
items {
id name
}
}
Używanie aplikacji Firebase App Check do weryfikacji aplikacji
Uwierzytelnianie i autoryzacja to kluczowe elementy bezpieczeństwaData Connect. Uwierzytelnianie i autoryzacja w połączeniu z potwierdzeniem aplikacji stanowią bardzo solidne rozwiązanie zabezpieczające.
W przypadku uwierzytelniania przez Firebase App Check urządzenia z Twoją aplikacją będą używać dostawcy uwierzytelniania aplikacji lub urządzenia, który potwierdza, że operacje Data Connect pochodzą z autentycznej aplikacji, a żądania pochodzą z autentycznego, nienaruszonego urządzenia. To zaświadczenie jest dołączane do każdego żądania wysyłanego przez Twoją aplikację do Data Connect.
Aby dowiedzieć się, jak włączyć App Check w przypadku Data Connect i umieścić w aplikacji pakiet klienta SDK App Check, zapoznaj się z ogólnym opisem App Check.
Poziomy uwierzytelniania w przypadku dyrektywy @auth(level)
Tabela poniżej zawiera wszystkie standardowe poziomy dostępu i ich odpowiedniki w CEL. Poziomy uwierzytelniania są wyświetlane od najszerszego do najwęższych – każdy poziom obejmuje wszystkich użytkowników, którzy pasują do podanych poziomów.
Poziom | Definicja |
---|---|
PUBLIC |
Operację może wykonać dowolna osoba z uwierzytelnieniem lub bez niego.
Uwagi: dane mogą być odczytywane i modyfikowane przez dowolnego użytkownika. Firebase zaleca ten poziom autoryzacji w przypadku danych publicznie dostępnych, takich jak informacje o produktach lub mediach. Zapoznaj się z przykładami sprawdzonych metod i alternatywami. Jest to równoznaczne z poziomem dostępu @auth(expr: "true") .Nie można używać filtrów i wyrażeń @auth w połączeniu z tym poziomem dostępu. Wszelkie takie wyrażenia spowodują błąd 400 „nieprawidłowe żądanie”.
|
USER_ANON |
Każdy zidentyfikowany użytkownik, w tym użytkownicy, którzy zalogowali się anonimowo za pomocą Firebase Authentication, może wykonać zapytanie lub mutację.
Uwaga: USER_ANON jest superzbiorem zbioru USER .
Uwagi: musisz dokładnie zaprojektować zapytania i mutacje pod kątem tego poziomu autoryzacji. Ten poziom umożliwia użytkownikowi zalogowanie się anonimnie (automatyczne logowanie powiązane tylko z urządzeniem użytkownika) za pomocą Authentication. Nie wykonuje on samodzielnie żadnych innych kontroli, na przykład czy dane należą do użytkownika. Zapoznaj się ze sprawdzonymi metodami i alternatywami. Ponieważ anonimowe procesy logowania Authentication generują odpowiedź uid , poziom USER_ANON jest równoważny poziomowi @auth(expr: "auth.uid != nil")
|
USER |
Każdy użytkownik zalogowany za pomocą Firebase Authentication może wykonać zapytanie lub mutację, z wyjątkiem użytkowników logujących się anonimowo.
Uwagi: musisz dokładnie zaprojektować zapytania i mutacje pod kątem tego poziomu autoryzacji. Ten poziom sprawdza tylko, czy użytkownik jest zalogowany na konto Authentication, i nie wykonuje samodzielnie innych kontroli, na przykład czy dane należą do tego użytkownika. Zapoznaj się ze sprawdzonymi metodami i alternatywami. Odpowiednik: @auth(expr: "auth.uid != nil &&
auth.token.firebase.sign_in_provider != 'anonymous'")"
|
USER_EMAIL_VERIFIED |
Każdy użytkownik, który zalogował się za pomocą Firebase Authentication z weryfikowanym adresem e-mail, może wykonać zapytanie lub mutację.
Uwagi: weryfikacja e-maila jest wykonywana za pomocą Authentication, co oznacza, że opiera się na bardziej niezawodnej metodzie Authentication, a więc zapewnia dodatkową ochronę w porównaniu z USER lub USER_ANON . Ten poziom sprawdza tylko, czy użytkownik jest zalogowany za pomocą Authentication z weryfikowanego adresu e-mail, ale nie wykonuje samodzielnie innych kontroli, na przykład czy dane należą do użytkownika. Zapoznaj się z przykładami sprawdzonych metod i alternatywami.
Odpowiednik: @auth(expr: "auth.uid != nil &&
auth.token.email_verified")" |
NO_ACCESS |
Tej operacji nie można wykonać poza kontekstem pakietu SDK Admin.
Odpowiednik: @auth(expr: "false") |
Dokumentacja wyrażeń CEL w przypadku @auth(expr)
Jak pokazano w przykładach w tym przewodniku, możesz i powinieneś używać wyrażeń zdefiniowanych w języku Common Expression Language (CEL) do kontrolowania autoryzacji Data Connect za pomocą dyrektyw @auth(expr:)
i @check
.
W tej sekcji omawiamy składnię CEL, która jest przydatna do tworzenia wyrażeń w przypadku tych dyrektyw.
Pełne informacje referencyjne dotyczące języka CEL znajdziesz w specyfikacji CEL.
Testowanie zmiennych przekazywanych w zapytaniach i mutacjach
Składnia @auth(expr)
umożliwia dostęp do zmiennych z zapytań i mutacji oraz ich testowanie.
Możesz na przykład użyć zmiennej operacji, takiej jak $status
, za pomocą funkcji vars.status
.
mutation Update($id: UUID!, $status: Any) @auth(expr: "has(vars.status)")
Dane dostępne dla wyrażeń: request, response, this
Dane są używane do:
- Ocena za pomocą wyrażeń CEL w regułach
@auth(expr:)
i@check(expr:)
- Przypisanie za pomocą wyrażeń serwera,
<field>_expr
.
Zarówno wyrażenia CEL @auth(expr:)
, jak i @check(expr:)
mogą oceniać:
request.operationName
vars
(alias dorequest.variables
)auth
(alias dorequest.auth
)
W mutacjach możesz uzyskać dostęp do tych treści i je przypisać:
response
(do sprawdzania częściowych wyników w logice wieloetapowej)
Dodatkowo wyrażenia @check(expr:)
mogą oceniać:
this
(wartość bieżącego pola)response
(do sprawdzania częściowych wyników w logice wieloetapowej)
Powiązanie request.operationName
Wiązanie request.operarationName
przechowuje typ operacji, czyli zapytanie lub zmianę.
Powiązanie vars
(request.vars)
Wiązanie vars
umożliwia wyrażeniom dostęp do wszystkich zmiennych przekazanych w zapytaniu lub mutacji.
Możesz użyć atrybutu vars.<variablename>
w wyrażeniu jako aliasu pełnego atrybutu request.variables.<variablename>
:
# The following are equivalent
mutation StringType($v: String!) @auth(expr: "vars.v == 'hello'")
mutation StringType($v: String!) @auth(expr: "request.variables.v == 'hello'")
Powiązanie auth
(request.auth)
Authentication identyfikuje użytkowników, którzy proszą o dostęp do Twoich danych, i przekazuje te informacje jako ograniczenie, na którym możesz oprzeć swoje wyrażenia.
W filtrach i wyrażeniach możesz używać wartości auth
jako aliasu wartości request.auth
.
Powiązanie autoryzacji zawiera te informacje:
uid
: unikalny identyfikator użytkownika przypisany do użytkownika przesyłającego żądanie.token
: mapa wartości zebranych przez Authentication.
Więcej informacji o treściach w polu auth.token
znajdziesz w artykule Dane w tokenach uwierzytelniania.
Powiązanie response
Wiązanie response
zawiera dane, które są tworzone przez serwer w odpowiedzi na zapytanie lub mutację w miarę ich tworzenia.
W miarę postępu operacji, po zakończeniu każdego etapu,
response
zawiera dane odpowiedzi z udanych etapów.
Wiązanie response
jest ustrukturyzowane zgodnie z formą powiązanej operacji, w tym (wielokrotnych) zagnieżdżonych pól i (w stosownych przypadkach) osadzonych zapytań.
Pamiętaj, że gdy uzyskujesz dostęp do danych odpowiedzi wbudowanego zapytania, pola mogą zawierać dowolny typ danych, w zależności od danych zapytanych wbudowanym zapytaniu. Gdy uzyskujesz dostęp do danych zwróconych przez pola mutacji, takie jak _insert
i _delete
, mogą one zawierać klucze UUID, liczbę usunięcia i wartości null (patrz dokumentacja mutacji).
Przykład:
- W zmianie, która zawiera osadzone zapytanie, parametr
response
zawiera dane wyszukiwania w miejscuresponse.query.<fieldName>.<fieldName>....
, w tym przypadkuresponse.query.todoList
iresponse.query.todoList.priority
.
mutation CheckTodoPriority(
$uniqueListName: String!
) {
# This query is identified as `response.query`
query @check(expr: "response.query.todoList.priority == 'high'", message: "This list is not for high priority items!") {
# This field is identified as `response.query.todoList`
todoList(where: { name: $uniqueListName }) {
# This field is identified as `response.query.todoList.priority`
priority
}
}
}
- W przypadku mutacji wieloetapowej, na przykład z wieloma polami
_insert
, elementresponse
zawiera częściowe dane w poluresponse.<fieldName>.<fieldName>....
, w tym przypadkuresponse.todoList_insert.id
.
mutation CreateTodoListWithFirstItem(
$listName: String!,
$itemContent: String!
) @transaction {
# Step 1
todoList_insert(data: {
id_expr: "uuidV4()",
name: $listName,
})
# Step 2:
todo_insert(data: {
listId_expr: "response.todoList_insert.id" # <-- Grab the newly generated ID from the partial response so far.
content: $itemContent,
})
}
Powiązanie this
Powiązanie this
zwraca pole, do którego jest dołączona dyrektywa @check
. W najprostszym przypadku możesz analizować wyniki zapytania o pojedynczej wartości.
mutation UpdateMovieTitle (
$movieId: UUID!,
$newTitle: String!)
@auth(level: USER)
@transaction {
# Step 1: Query and check
query @redact {
moviePermission( # Look up a join table called MoviePermission with a compound key.
key: {movieId: $movieId, userId_expr: "auth.uid"}
) {
# Check if the user has the editor role for the movie. `this` is the string value of `role`.
# If the parent moviePermission is null, the @check will also fail automatically.
role @check(expr: "this == 'editor'", message: "You must be an editor of this movie to update title")
}
}
# Step 2: Act
movie_update(id: $movieId, data: {
title: $newTitle
})
}
Jeśli zwracane pole występuje kilka razy, ponieważ każdy z jego przodków jest listą, każde wystąpienie jest testowane z użyciem this
powiązanego z każdą wartością.
Jeśli w przypadku dowolnej ścieżki dowolny z jej przodków to null
lub []
, to nie dojdzie do odwołania się do pola, a ocena CEL zostanie pominięta. Inaczej mówiąc,
sprawdzanie odbywa się tylko wtedy, gdy this
ma wartość null
lub nie-null
, ale nigdy undefined
.
Gdy samo pole jest listą lub obiektem, this
ma taką samą strukturę (w tym wszystkich potomków wybranych w przypadku obiektów), jak w tym przykładzie.
mutation UpdateMovieTitle2($movieId: UUID!, $newTitle: String!) @auth(level: USER) @transaction {
# Step 1: Query and check
query {
moviePermissions( # Now we query for a list of all matching MoviePermissions.
where: {movieId: {eq: $movieId}, userId: {eq_expr: "auth.uid"}}
# This time we execute the @check on the list, so `this` is the list of objects.
# We can use the `.exists` macro to check if there is at least one matching entry.
) @check(expr: "this.exists(p, p.role == 'editor')", message: "You must be an editor of this movie to update title") {
role
}
}
# Step 2: Act
movie_update(id: $movieId, data: {
title: $newTitle
})
}
Składnia złożonych wyrażeń
Aby tworzyć bardziej złożone wyrażenia, możesz łączyć operatory &&
i ||
.
mutation UpsertUser($username: String!) @auth(expr: "(auth != null) && (vars.username == 'joe')")
W sekcji poniżej opisujemy wszystkie dostępne operatory.
Operatory i pierwszeństwo operatorów
Poniższa tabela zawiera informacje o operatorach i odpowiednim dla nich priorytecie.
Załóżmy dowolne wyrażenia a
i b
, pole f
oraz indeks i
.
Operator | Opis | Związek |
---|---|---|
a[i] a() a.f |
Indeksowanie, wywoływanie i dostęp do pól | od lewej do prawej. |
!a -a |
Negacja unarna | od prawej do lewej |
a/b a%b a*b |
Operatory mnożenia | od lewej do prawej. |
a+b a-b |
Operatory addytywne | od lewej do prawej. |
a>b a>=b a<b a<=b |
Operatory relacji | od lewej do prawej. |
a in b |
występowanie na liście lub mapie; | od lewej do prawej. |
type(a) == t |
Porównanie typu, gdzie t może być wartością logiczną, liczbą całkowitą, liczbą zmiennoprzecinkową, liczbą, ciągiem znaków, listą, mapą, sygnaturą czasową lub czasem trwania. |
od lewej do prawej. |
a==b a!=b |
Operatory porównania | od lewej do prawej. |
a && b |
Warunkowe ORAZ | od lewej do prawej. |
a || b |
Warunkowe LUB | od lewej do prawej. |
a ? true_value : false_value |
Wyrażenie warunkowe 3-argumentowe | od lewej do prawej. |
Dane w tokenach uwierzytelniania
Obiekt auth.token
może zawierać te wartości:
Pole | Opis |
---|---|
email |
adres e-mail powiązany z kontem (jeśli istnieje). |
email_verified |
true , jeśli użytkownik potwierdził, że ma dostęp do adresu email . Niektórzy dostawcy automatycznie weryfikują należące do nich adresy e-mail. |
phone_number |
numer telefonu powiązany z kontem (jeśli istnieje); |
name |
Wyświetlana nazwa użytkownika (jeśli została ustawiona). |
sub |
Identyfikator Firebase użytkownika. Musi być unikalny w ramach projektu. |
firebase.identities |
Słownik wszystkich tożsamości powiązanych z kontem tego użytkownika. Klucze słownika mogą być dowolnymi z tych typów: email , phone , google.com , facebook.com , github.com , twitter.com . Wartości w słowniku to tablice unikalnych identyfikatorów dla każdego dostawcy tożsamości powiązanego z kontem. Na przykład auth.token.firebase.identities["google.com"][0] zawiera pierwsze identyfikator użytkownika Google powiązany z kontem. |
firebase.sign_in_provider |
Dostawca logowania użyty do uzyskania tego tokena. Może być dowolnym z tych ciągów: custom , password , phone , anonymous , google.com , facebook.com , github.com , twitter.com . |
firebase.tenant |
Identyfikator tenantId powiązany z kontem (jeśli występuje). na przykład tenant2-m6tyz . |
Dodatkowe pola w identyfikatorach JWT
Możesz też uzyskać dostęp do tych pól auth.token
:
Deklaracje tokenów niestandardowych | ||
---|---|---|
alg |
Algorytm | "RS256" |
iss |
Wystawca | adres e-mail konta usługi projektu, |
sub |
Temat | adres e-mail konta usługi projektu, |
aud |
Odbiorcy | "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit" |
iat |
Issued-at time | bieżący czas w sekundach od początku epoki UNIX |
exp |
Okres ważności |
Czas w sekundach od początku epoki UNIX, w którym token traci ważność. Może ona nastąpić maksymalnie 3600 sekund później niż iat .
Uwaga: to ustawienie określa tylko czas wygaśnięcia tokenu niestandardowego. Jednak po zalogowaniu użytkownika za pomocą opcji signInWithCustomToken() pozostanie on zalogowany na urządzeniu do czasu unieważnienia sesji lub wylogowania.
|
<claims> (opcjonalnie) |
Opcjonalne oświadczenia niestandardowe do uwzględnienia w tokenie, do których można uzyskać dostęp za pomocą funkcji auth.token (lub request.auth.token ) w wyrażeniach. Jeśli na przykład utworzysz roszczenie niestandardowe
adminClaim , możesz uzyskać do niego dostęp za pomocą
auth.token.adminClaim .
|
Co dalej?
- Firebase Data Connect udostępnia pakiet Admin SDK, który umożliwia wykonywanie zapytań i mutacji z otoczeń z podwyższonymi uprawnieniami.
- Więcej informacji o zabezpieczeniach w ramach uprawnień znajdziesz w przewodniku po zarządzaniu usługami i bazami danych.