Проверьте свои правила безопасности Cloud Firestore

При разработке приложения вам может потребоваться ограничить доступ к базе данных Cloud Firestore . Однако перед запуском вам понадобятся более детальные Cloud Firestore Security Rules . С помощью эмулятора Cloud Firestore , помимо создания прототипа и тестирования основных функций и поведения приложения, вы можете писать модульные тесты, проверяющие поведение Cloud Firestore Security Rules .

Быстрый старт

Для нескольких базовых тестовых случаев с простыми правилами попробуйте пример быстрого старта .

Понимание Cloud Firestore Security Rules

Реализуйте Cloud Firestore Security Rules Firebase Authentication и безопасности Cloud Firestore для бессерверной аутентификации, авторизации и проверки данных при использовании библиотек мобильных и веб-клиентов.

Cloud Firestore Security Rules состоят из двух частей:

  1. Оператор match , который идентифицирует документы в вашей базе данных.
  2. Выражение allow , которое управляет доступом к этим документам.

Firebase Authentication проверяет учетные данные пользователей и обеспечивает основу для систем доступа на основе пользователей и ролей.

Каждый запрос к базе данных из библиотеки мобильного/веб-клиента Cloud Firestore проверяется на соответствие вашим правилам безопасности перед чтением или записью данных. Если правила запрещают доступ к любому из указанных путей к документам, весь запрос завершается ошибкой.

Дополнительную информацию о Cloud Firestore Security Rules можно найти в статье Начало работы с Cloud Firestore Security Rules .

Установить эмулятор

Чтобы установить эмулятор Cloud Firestore , используйте Firebase CLI и выполните следующую команду:

firebase setup:emulators:firestore

Запустить эмулятор

Начните с инициализации проекта Firebase в рабочем каталоге. Это стандартный первый шаг при использовании Firebase CLI .

firebase init

Запустите эмулятор с помощью следующей команды. Эмулятор будет работать, пока вы не завершите процесс:

firebase emulators:start --only firestore

Во многих случаях требуется запустить эмулятор, выполнить набор тестов и затем завершить работу эмулятора после их выполнения. Это можно легко сделать с помощью команды emulators:exec :

firebase emulators:exec --only firestore "./my-test-script.sh"

При запуске эмулятор попытается работать на порту по умолчанию (8080). Вы можете изменить порт эмулятора, изменив раздел "emulators" файла firebase.json :

{
  // ...
  "emulators": {
    "firestore": {
      "port": "YOUR_PORT"
    }
  }
}

Перед запуском эмулятора

Прежде чем начать использовать эмулятор, имейте в виду следующее:

  • Эмулятор изначально загрузит правила, указанные в поле firestore.rules вашего файла firebase.json . Он ожидает имя локального файла, содержащего Cloud Firestore Security Rules , и применяет эти правила ко всем проектам. Если вы не укажете путь к локальному файлу или не используете метод loadFirestoreRules , как описано ниже, эмулятор будет считать все проекты имеющими открытые правила.
  • Хотя большинство Firebase SDK работают с эмуляторами напрямую, только библиотека @firebase/rules-unit-testing поддерживает auth в правилах безопасности, что значительно упрощает модульное тестирование. Кроме того, библиотека поддерживает несколько функций, специфичных для эмуляторов, например, очистку всех данных, перечисленных ниже.
  • Эмуляторы также будут принимать производственные токены Firebase Auth, предоставляемые через клиентские SDK, и соответствующим образом оценивать правила, что позволяет подключать ваше приложение напрямую к эмуляторам для интеграционных и ручных тестов.

Запуск локальных модульных тестов

Запускайте локальные модульные тесты с помощью JavaScript SDK v9

Firebase распространяет библиотеку модульного тестирования Security Rules как с JavaScript SDK версии 9, так и с SDK версии 8. API библиотек существенно различаются. Мы рекомендуем библиотеку тестирования версии 9, которая более оптимизирована и требует меньше настроек для подключения к эмуляторам, что позволяет избежать случайного использования ресурсов производства. Для обеспечения обратной совместимости мы по-прежнему предоставляем библиотеку тестирования версии 8 .

Используйте модуль @firebase/rules-unit-testing для взаимодействия с эмулятором, работающим локально. Если возникают тайм-ауты или ошибки ECONNREFUSED , ещё раз проверьте, запущен ли эмулятор.

Мы настоятельно рекомендуем использовать последнюю версию Node.js, чтобы вы могли использовать нотацию async/await . Практически всё поведение, которое вы хотите протестировать, включает асинхронные функции, и модуль тестирования разработан для работы с кодом на основе Promise.

Библиотека модульного тестирования правил v9 всегда знает об эмуляторах и никогда не трогает ваши производственные ресурсы.

Библиотека импортируется с помощью операторов модульного импорта версии 9. Например:

import {
  assertFails,
  assertSucceeds,
  initializeTestEnvironment
} from "@firebase/rules-unit-testing"

// Use `const { … } = require("@firebase/rules-unit-testing")` if imports are not supported
// Or we suggest `const testing = require("@firebase/rules-unit-testing")` if necessary.

После импорта реализация модульных тестов включает в себя:

  • Создание и настройка RulesTestEnvironment с помощью вызова initializeTestEnvironment .
  • Настройка тестовых данных без запуска правил с использованием удобного метода, позволяющего временно их обходить, RulesTestEnvironment.withSecurityRulesDisabled .
  • Настройка тестового набора и перехватов «до/после» для каждого теста с вызовами для очистки тестовых данных и среды, такими как RulesTestEnvironment.cleanup() или RulesTestEnvironment.clearFirestore() .
  • Реализация тестовых случаев, имитирующих состояния аутентификации, с использованием RulesTestEnvironment.authenticatedContext и RulesTestEnvironment.unauthenticatedContext .

Общие методы и функции полезности

См. также методы тестирования, специфичные для эмулятора, в SDK v9 .

initializeTestEnvironment() => RulesTestEnvironment

Эта функция инициализирует тестовую среду для модульного тестирования правил. Вызовите эту функцию сначала для настройки теста. Для успешного выполнения требуется запуск эмуляторов.

Функция принимает необязательный объект, определяющий TestEnvironmentConfig , который может состоять из идентификатора проекта и параметров конфигурации эмулятора.

let testEnv = await initializeTestEnvironment({
  projectId: "demo-project-1234",
  firestore: {
    rules: fs.readFileSync("firestore.rules", "utf8"),
  },
});

RulesTestEnvironment.authenticatedContext({ user_id: string, tokenOptions?: TokenOptions }) => RulesTestContext

Этот метод создаёт RulesTestContext , который ведёт себя как аутентифицированный пользователь аутентификации. К запросам, созданным через возвращаемый контекст, будет прикреплён фиктивный токен аутентификации. При необходимости можно передать объект, определяющий пользовательские утверждения или переопределения для полезных данных токена аутентификации.

Используйте возвращенный объект контекста теста в своих тестах для доступа к любым настроенным экземплярам эмулятора, включая настроенные с помощью initializeTestEnvironment .

// Assuming a Firestore app and the Firestore emulator for this example
import { setDoc } from "firebase/firestore";

const alice = testEnv.authenticatedContext("alice", {  });
// Use the Firestore instance associated with this context
await assertSucceeds(setDoc(alice.firestore(), '/users/alice'), { ... });

RulesTestEnvironment.unauthenticatedContext() => RulesTestContext

Этот метод создаёт RulesTestContext , который ведёт себя как клиент, не прошедший аутентификацию. К запросам, созданным через возвращаемый контекст, не будут прикреплены токены аутентификации Firebase.

Используйте возвращенный объект контекста теста в своих тестах для доступа к любым настроенным экземплярам эмулятора, включая настроенные с помощью initializeTestEnvironment .

// Assuming a Cloud Storage app and the Storage emulator for this example
import { getStorage, ref, deleteObject } from "firebase/storage";

const alice = testEnv.unauthenticatedContext();

// Use the Cloud Storage instance associated with this context
const desertRef = ref(alice.storage(), 'images/desert.jpg');
await assertSucceeds(deleteObject(desertRef));

RulesTestEnvironment.withSecurityRulesDisabled()

Запустите функцию настройки теста с контекстом, который ведет себя так, как будто правила безопасности отключены.

Этот метод принимает функцию обратного вызова, которая принимает контекст обхода правил безопасности и возвращает обещание. Контекст будет уничтожен после завершения/отклонения обещания.

RulesTestEnvironment.cleanup()

Этот метод уничтожает все RulesTestContexts , созданные в тестовой среде, и очищает базовые ресурсы, обеспечивая чистый выход.

Этот метод никак не изменяет состояние эмуляторов. Для сброса данных между тестами используйте метод очистки данных, специфичный для эмулятора приложения.

assertSucceeds(pr: Promise<any>)) => Promise<any>

Это функция полезности тестового случая.

Функция утверждает, что предоставленное Promise, оборачивающее операцию эмулятора, будет разрешено без нарушений правил безопасности.

await assertSucceeds(setDoc(alice.firestore(), '/users/alice'), { ... });

assertFails(pr: Promise<any>)) => Promise<any>

Это функция полезности тестового случая.

Функция утверждает, что предоставленное Promise, оборачивающее операцию эмулятора, будет отклонено из-за нарушения правил безопасности.

await assertFails(setDoc(alice.firestore(), '/users/bob'), { ... });

Методы, специфичные для эмулятора

См. также общие методы тестирования и служебные функции в v9 SDK .

RulesTestEnvironment.clearFirestore() => Promise<void>

Этот метод очищает данные в базе данных Firestore, принадлежащие projectId настроенному для эмулятора Firestore.

RulesTestContext.firestore(settings?: Firestore.FirestoreSettings) => Firestore;

Этот метод получает экземпляр Firestore для данного тестового контекста. Возвращаемый экземпляр Firebase JS Client SDK можно использовать с API клиентского SDK (модульным или совместимым с v9).

Визуализация оценок правил

Эмулятор Cloud Firestore позволяет визуализировать клиентские запросы в пользовательском интерфейсе Emulator Suite, включая трассировку оценки для правил безопасности Firebase.

Откройте вкладку Firestore > Запросы , чтобы просмотреть подробную последовательность оценки для каждого запроса.

Монитор запросов эмулятора Firestore, показывающий оценки правил безопасности

Создание отчетов об испытаниях

После запуска набора тестов вы можете получить доступ к отчетам о покрытии тестами, в которых показано, как оценивалось каждое из ваших правил безопасности.

Чтобы получить отчёты, отправьте запрос к открытой конечной точке эмулятора во время его работы. Для браузерной версии используйте следующий URL:

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage.html

Это разобьёт ваши правила на выражения и подвыражения, на которые можно навести курсор, чтобы получить дополнительную информацию, включая количество вычислений и возвращаемых значений. Чтобы получить необработанную версию этих данных в формате JSON, включите в запрос следующий URL:

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage

Различия между эмулятором и продакшеном

  1. Вам не нужно явно создавать проект Cloud Firestore . Эмулятор автоматически создаёт любой экземпляр, к которому осуществляется доступ.
  2. Эмулятор Cloud Firestore не работает с обычной процедурой Firebase Authentication . Вместо этого в Firebase Test SDK мы предоставили метод initializeTestApp() в библиотеке rules-unit-testing , который принимает поле auth . Дескриптор Firebase, созданный с помощью этого метода, будет вести себя так, как будто он успешно прошёл аутентификацию в качестве любого указанного вами объекта. Если передать null , он будет вести себя как неаутентифицированный пользователь (например, правила auth != null приведут к ошибке).

Устранение известных проблем

При использовании эмулятора Cloud Firestore вы можете столкнуться со следующими известными проблемами. Следуйте приведенным ниже инструкциям для устранения любых нестандартных проблем. Эти заметки написаны с учётом библиотеки модульного тестирования Security Rules, но общие подходы применимы к любому Firebase SDK.

Поведение теста непоследовательно

Если ваши тесты периодически проходят и не проходят, даже без внесения каких-либо изменений в сами тесты, вам может потребоваться проверить их последовательность. Большинство взаимодействий с эмулятором происходит асинхронно, поэтому дважды проверьте последовательность всего асинхронного кода. Вы можете исправить последовательность, либо объединив обещания в цепочку, либо свободно используя нотацию await .

В частности, рассмотрите следующие асинхронные операции:

  • Установка правил безопасности, например, с помощью initializeTestEnvironment .
  • Чтение и запись данных, например, с помощью db.collection("users").doc("alice").get() .
  • Операционные утверждения, включая assertSucceeds и assertFails .

Тесты проходят только при первой загрузке эмулятора.

Эмулятор сохраняет состояние. Он хранит все записанные в него данные в памяти, поэтому при завершении работы эмулятора все данные теряются. Если вы запускаете несколько тестов с одним и тем же идентификатором проекта, каждый тест может генерировать данные, которые могут повлиять на последующие тесты. Вы можете обойти это поведение любым из следующих способов:

  • Используйте уникальные идентификаторы проектов для каждого теста. Обратите внимание: в этом случае вам потребуется вызывать initializeTestEnvironment в рамках каждого теста; правила автоматически загружаются только для идентификатора проекта по умолчанию.
  • Измените структуру своих тестов так, чтобы они не взаимодействовали с ранее записанными данными (например, используйте отдельную коллекцию для каждого теста).
  • Удалите все данные, записанные во время теста.

Тестовая установка очень сложная

При настройке теста вам может потребоваться изменить данные способом, который не допускается Cloud Firestore Security Rules . Если ваши правила усложняют настройку теста, попробуйте использовать RulesTestEnvironment.withSecurityRulesDisabled в настройках, чтобы чтение и запись не вызывали ошибок PERMISSION_DENIED .

После этого ваш тест сможет выполнять операции от имени аутентифицированного или неаутентифицированного пользователя, используя RulesTestEnvironment.authenticatedContext и unauthenticatedContext соответственно. Это позволяет проверить, что ваши Cloud Firestore Security Rules корректно разрешают/запрещают различные ситуации.