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

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

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

For a few basic test cases with simple rules, try out the quickstart sample .

Разберитесь Cloud Firestore Security Rules

Implement Firebase Authentication and Cloud Firestore Security Rules for serverless authentication, authorization, and data validation when you use the mobile and web client libraries.

Cloud Firestore Security Rules include two pieces:

  1. A match statement that identifies documents in your database.
  2. An allow expression that controls access to those documents.

Firebase Authentication verifies users' credentials and provides the foundation for user-based and role-based access systems.

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

Learn more about Cloud Firestore Security Rules in Get started with Cloud Firestore Security Rules .

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

To install the Cloud Firestore emulator, use the Firebase CLI and run the command below:

firebase setup:emulators:firestore

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

Begin by initializing a Firebase project in your working directory. This is a common first step when using the Firebase CLI .

firebase init

Start the emulator using the following command. The emulator will run until you kill the process:

firebase emulators:start --only firestore

In many cases you want to start the emulator, run a test suite, and then shut down the emulator after the tests run. You can do this easily using the emulators:exec command:

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

When started the emulator will attempt to run on a default port (8080). You can change the emulator port by modifying the "emulators" section of your firebase.json file:

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

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

Before you start using the emulator, keep in mind the following:

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

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

Запускайте локальные модульные тесты с помощью JavaScript SDK версии 9.

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

Use the @firebase/rules-unit-testing module to interact with the emulator that runs locally. If you get timeouts or ECONNREFUSED errors, double-check that the emulator is actually running.

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

The v9 Rules Unit Testing library is always aware of the emulators and never touches your production resources.

You import the library using v9 modular import statements. For example:

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.

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

  • Creating and configuring a RulesTestEnvironment with a call to initializeTestEnvironment .
  • Setting up test data without triggering Rules, using a convenience method that allows you to temporarily bypass them, RulesTestEnvironment.withSecurityRulesDisabled .
  • Setting up test suite and per-test before/after hooks with calls to clean up test data and environment, like RulesTestEnvironment.cleanup() or RulesTestEnvironment.clearFirestore() .
  • Implementing test cases that mimic authentication states using RulesTestEnvironment.authenticatedContext and RulesTestEnvironment.unauthenticatedContext .

Общие методы и вспомогательные функции

Also see emulator-specific test methods in the v9 SDK .

initializeTestEnvironment() => RulesTestEnvironment

This function initializes a test environment for rules unit testing. Call this function first for test setup. Successful execution requires emulators to be running.

The function accepts an optional object defining a TestEnvironmentConfig , which can consist of a project ID and emulator configuration settings.

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

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

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

Use the returned test context object in your tests to access any emulator instances configured, including those configured with 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().doc('/users/alice'), { ... });

RulesTestEnvironment.unauthenticatedContext() => RulesTestContext

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

Use the returned test context object in your tests to access any emulator instances configured, including those configured with 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()

Run a test setup function with a context that behaves as if Security Rules were disabled.

This method takes a callback function, which takes the Security-Rules-bypassing context and returns a promise. The context will be destroyed once the promise resolves / rejects.

RulesTestEnvironment.cleanup()

This method destroys all RulesTestContexts created in the test environment and cleans up the underlying resources, allowing a clean exit.

This method does not change the state of emulators in any way. To reset data between tests, use the application emulator-specific clear data method.

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

Это вспомогательная функция для тестового примера.

The function asserts that the supplied Promise wrapping an emulator operation will be resolved with no Security Rules violations.

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

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

Это вспомогательная функция для тестового примера.

The function asserts that the supplied Promise wrapping an emulator operation will be rejected with a Security Rules violation.

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

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

Also see common test methods and utility functions in the v9 SDK .

RulesTestEnvironment.clearFirestore() => Promise<void>

This method clears data in the Firestore database that belongs to the projectId configured for the Firestore emulator.

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

This method gets a Firestore instance for this test context. The returned Firebase JS Client SDK instance can be used with the client SDK APIs (v9 modular or v9 compat).

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

The Cloud Firestore emulator lets you visualize client requests in the Emulator Suite UI, including evaluation tracing for Firebase Security Rules.

Open the Firestore > Requests tab to view the detailed evaluation sequence for each request.

Firestore Emulator Requests Monitor showing Security Rules evaluations

Создание отчетов о тестировании

After running a suite of tests, you can access test coverage reports that show how each of your security rules was evaluated.

To get the reports, query an exposed endpoint on the emulator while it's running. For a browser-friendly version, use the following URL:

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

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

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

Различия между эмулятором и производственной средой

  1. You do not have to explicitly create a Cloud Firestore project. The emulator automatically creates any instance that is accessed.
  2. Эмулятор Cloud Firestore не работает со стандартным процессом Firebase Authentication . Вместо этого в Firebase Test SDK мы предоставили метод initializeTestApp() в библиотеке rules-unit-testing , который принимает поле auth . Созданный с помощью этого метода дескриптор Firebase будет вести себя так, как если бы он успешно аутентифицировался как предоставленная вами сущность. Если вы передадите null , он будет вести себя как неаутентифицированный пользователь (например, правила, auth != null , завершатся ошибкой).

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

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

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

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

In particular, review the following async operations:

  • Setting security rules, with, for example, initializeTestEnvironment .
  • Reading and writing data, with, for example, db.collection("users").doc("alice").get() .
  • Operational assertions, including assertSucceeds and assertFails .

Tests only pass the first time you load the emulator

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

  • Используйте уникальные идентификаторы проектов для каждого теста. Обратите внимание, что если вы решите это сделать, вам потребуется вызывать initializeTestEnvironment в рамках каждого теста; правила автоматически загружаются только для идентификатора проекта по умолчанию.
  • Restructure your tests so they don't interact with previously written data (for example, use a different collection for each test).
  • Удалите все данные, записанные во время тестирования.

Настройка тестовой установки очень сложная.

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

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