Правила безопасности Firebase

Firebase — это платформа, которая обеспечивает легкую интеграцию широко используемых сервисов для мобильных и веб-приложений.
Это такие сервисы, как:
- Firestore
- Cloud Functions
- Authentication
- Cloud Storage
- Realtime Database
- Hosting
- Firebase ML
Как вступают в игру правила безопасности?
Традиционно доступ к базе данных контролируется с помощью кода на стороне сервера. Однако такие сервисы Firebase, как Firestore, позволяют клиентскому коду получить прямой доступ к базе данных. Это представляет собой риск для безопасности, так как клиентский код может быть просмотрен любым человеком.
Именно здесь в игру вступает концепция правил безопасности Firebase. Правила безопасности Firebase контролируют доступ к определенным ресурсам. Другими словами, мы можем избежать сложностей с написанием, обслуживанием и установкой кода на стороне сервера, который контролирует доступ к ресурсам. Правила безопасности можно писать только для Realtime Database, Firestore и Cloud Storage.
Написание правил безопасности
Правила безопасности действуют как промежуточное программное обеспечение при использовании службы (например, чтение документа в Firestore). Когда запрос на доступ к сервису отклонен по правилам безопасности, весь запрос не удается.
Правила могут быть написаны через консоль Firebase или IDE, использующую Firebase CLI. Написание правил безопасности в консоли дает преимущество использования Rules Playground перед развертыванием правил.
Правила безопасности Firestore
Правила безопасности Firestore Security Rules состоят из операторов сопоставления и разрешенных выражений. Операторы соответствия идентифицируют документы в базе данных и позволяют выражениям контролировать доступ к этим документам.
Базовая структура правила безопасности:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /<some_path>/ {
allow read: if <some_condition>;
allow write: if <some_condition>;
}
}
}
В приведенном выше примере мы сопоставляем некоторый путь в базе данных и даем разрешение на чтение и запись на основе условия. В ситуациях, когда нужен более тонкий контроль доступа, мы можем разделить права на чтение и запись на более детальные операции.
Чтение (read) можно разбить на:
get
(относится к заявкам на однократное прочтение документов)
list
(относится к запросам и requests на чтение коллекции)
Запись (write) можно разбить на:
create
update
delete
Мы также можем определить пользовательские функции для улучшения читабельности и возможности повторного использования.
Рассмотрим пример:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isAdmin() {
let userDoc = get(/databases/$(database)/documents/users/$(request.auth.uid));
return userDoc == null ? false : userDoc.data.isAdmin;
}
function isAuthenticated(){
return request.auth != null;
}
match /users/{userId}{
allow read, delete: if isAuthenticated()
&& request.auth.uid == resource.id;
allow create: if request.resource.data.keys().hasAll(['name', 'email', 'isAdmin'])
&& request.resource.data.keys().hasOnly(['name', 'email', 'isAdmin'])
&& request.resource.data.isAdmin == false;
allow update: if isAuthenticated()
&& request.auth.uid == resource.id
&& request.resource.data.isAdmin == resource.data.isAdmin;
}
}
}
На основе приведенного выше примера мы написали следующие правила для коллекции пользователей:
- чтение пользователя(ов) и удаление пользователя разрешено, если пользователь аутентифицирован, а читаемый/удаляемый документ пользователя принадлежит аутентифицированному пользователю
- создание пользователя разрешено, если тело запроса имеет форму
{
name,
email,
isAdmin}
&& поле isAdmin ложно (для предотвращения создания учетных записей администратора со стороны клиента) - обновление пользователя разрешено, если пользователь аутентифицирован, документ этого пользователя принадлежит аутентифицированному пользователю, а поле isAdmin остается без изменений
Вам может быть интересно, откуда берутся переменные и такие методы, как get()
, request
, resource
. Любые пользовательские функции или операторы сопоставления в пространстве service cloud.firestore
имеют доступ к следующему:
Свойства
request
(контекст запроса)- Содержит информацию об автозапросе и входящем запросе через свойство auth & resource
request.resource
используется для обеспечения выполнения операций записи (создание, обновление, удаление), имеющих определенную структуру и проверку полей (например, возраст >= 15)request.auth
используется для предоставления доступа на его основе, если запрос авторизован.- Дополнительную информацию можно найти на сайте https://firebase.google.com/docs/reference/rules/rules.firestore.Request
resource
- resource написан или прочитан (т.е. документ из коллекции)
- Доступ к данным и идентификатору документа можно получить через
resource.data
&resource.id
- Полезные для принудительного выполнения определенных полей не могут быть изменены. В приведенном выше примере мы не позволяем полю
isAdmin
изменять запросы пользователей поrequest.resource.data.isAdmin == resource.data.isAdmin
- Дополнительную информацию можно найти на сайте https://firebase.google.com/docs/reference/rules/rules.firestore.Resource
Методы
get(path)
— путь должен быть абсолютным путем к документу.- Получает содержание документа
- Полезно, когда модификация документа зависит от другого документа. Например, функция isAdmin() в приведенном выше примере получает документ пользователя на основе
request.auth.uid
, после чего получает доступ к полюisAdmin
для проверки того, является ли пользователь администратором.
Полный список методов и свойств, предоставляемых в пространстве service cloud.firestore
, можно найти на сайте https://firebase.google.com/docs/reference/rules/rules.firestore.
Firebase также предоставляет функции для работы с различными типами свойств (например, list, boolean, map…и т.д.) при написании правил. В приведенном выше примере мы используем hasOnly()
и hasAll()
для принудительного исполнения пользовательского документа, имеющего только поля ‘name’, ’email’ и ‘isAdmin’.
- Дополнительную информацию можно найти на сайте https://firebase.google.com/docs/reference/rules
Правила безопасности Cloud Storage
Правила безопасности Cloud Storage похожи на правила Firestore, за исключением того, что вместо совпадения документов в коллекции мы сопоставляем каталоги или пути к файлам.
Рассмотрим пример:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /product_images/{imageName}
{
allow read, delete: if request.auth != null;
allow create: if request.auth != null
&& request.resource.contentType.matches('image/.*')
&& request.resource.size < 100000;
}
}
}
Обратите внимание на сходство между правилами Firestore и Cloud Storage. Основное отличие заключается в том, что пространство service firebase.storage
предоставляет нам различные свойства в переменной request
.
- Полный список свойств можно найти на сайте https://firebase.google.com/docs/storage/security/rules-conditions
На основе приведенного выше примера мы написали следующие правила для директории product_images:
- чтение и удаление файлов разрешено, если пользователь аутентифицирован
- файлы могут быть добавлены, если пользователь аутентифицирован, а файл представляет собой файл изображения размером менее 100 000 байт
Правила безопасности Realtime Database
Realtime Database от Firebase хранит данные в виде большого JSON-объекта. Это означает, что при написании правил безопасности мы сопоставляем ключи в объекте, а не в документах из коллекции (как в Firestore).
Основная структура правила безопасности:
{
"rules": {
"parent_node": {
"child_node": {
".read": <condition>,
".write": <condition>,
".validate": <condition>,
}
}
}
}
Прежде чем писать правила, важно помнить о том, что правила чтения и записи идут каскадом. Другими словами, если правило предоставляет доступ на чтение и запись по определённому пути, то оно предоставляет доступ ко всем дочерним узлам. Более того, более мелкие правила отменяют более глубокие.
Давайте рассмотрим пример:
{
"rules": {
"foo": {
// allows read to /foo/*
".read": "true",
"bar": {
/* bar can be read so this rule is ignored since read
was allowed already */
".read": false
}
}
}
}
Firebase Realtime Database предоставляет предопределенные переменные, аналогичные тому, как пространство service firebase.storage
предоставляет глобальные переменные и методы.
- Полный список переменных и методов можно найти на сайте: https://firebase.google.com/docs/rules/rules-language#building_conditions
Более практический пример:
{
"rules": {
"users": {
"$uid": {
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid",
".validate": "newData.hasChildren(['name', 'age'])",
"age": {
".validate": "newData.isNumber() &&
newData.val() > 0"
}
}
}
}
}
На основе вышеприведенного примера мы написали следующие правила для узла «Пользователи»:
- доступ на запись и чтение разрешен, узел пользователя принадлежит аутентифицированному пользователю
- дочерний узел в узле пользователей должен обладать свойством «name» и «age»
- принадлежность «age» должна быть числом больше 0
Заключение
Хотя различные сервисы (Firestore, Cloud Storage, Realtime Database) имеют немного разный синтаксис при написании правил, на высоком уровне мы делаем то же самое. Точнее, мы подбираем путь к ресурсу и контролируем доступ к этому ресурсу через условия чтения и записи.
Тем не менее, правила безопасности Firebase служат цели защиты данных от вредоносных пользователей. Таким образом, мы надеемся, что эта статья помогла вам понять основные понятия при написании правил безопасности! А теперь идите и защищайте свои данные!
Источник: dev.to