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

97

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’.

Правила безопасности 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.

На основе приведенного выше примера мы написали следующие правила для директории 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 предоставляет глобальные переменные и методы.

Более практический пример:

{
  "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

Поделитесь в соц.сетях
  • 1
  •  
  •  
  •  
  •  
  •  
  •  

Оставить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *