Проверьте свои правила безопасности 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 Mobile/Web оценивается на предмет соответствия вашим правилам безопасности перед чтением или записью любых данных. Если правила запрещают доступ к любому из указанных путей документа, весь запрос завершается неудачей.

Подробнее о 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 как с версией 9 JavaScript SDK, так и с версией 8 SDK. API библиотеки существенно различаются. Мы рекомендуем библиотеку тестирования v9, которая более оптимизирована и требует меньше настроек для подключения к эмуляторам, что позволяет безопасно избегать случайного использования производственных ресурсов. Для обратной совместимости мы продолжаем предоставлять библиотеку тестирования v8 .

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

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

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

Вы импортируете библиотеку с помощью операторов импорта v9 modular. Например:

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 , который ведет себя как аутентифицированный пользователь Authentication. Запросы, созданные через возвращенный контекст, будут иметь прикрепленный фиктивный токен Authentication. При желании передайте объект, определяющий пользовательские утверждения или переопределения для полезных нагрузок токена Authentication.

Используйте возвращаемый объект контекста теста в своих тестах для доступа к любым настроенным экземплярам эмулятора, включая настроенные с помощью 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 , который ведет себя как клиент, не вошедший в систему через Authentication. Запросы, созданные через возвращаемый контекст, не будут иметь прикрепленных токенов Firebase Auth.

Используйте возвращаемый объект контекста теста в своих тестах для доступа к любым настроенным экземплярам эмулятора, включая настроенные с помощью 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()

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

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

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'), { ... });

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

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

RulesTestEnvironment.clearFirestore() => Promise<void>

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

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

Этот метод получает экземпляр Firestore для этого тестового контекста. Возвращенный экземпляр Firebase JS Client SDK может использоваться с API клиентского SDK (модульный v9 или совместимый с 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 разрешают/запрещают различные случаи правильно.