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

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

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

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

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

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

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 , как описано ниже, эмулятор будет рассматривать все проекты как имеющие открытые правила.
  • Хотя большинство SDK Firebase работают с эмуляторами напрямую, только библиотека @firebase/rules-unit-testing поддерживает имитацию auth в правилах безопасности, что значительно упрощает модульное тестирование. Кроме того, библиотека поддерживает несколько специфических для эмуляторов функций, таких как очистка всех данных, как указано ниже.
  • Эмуляторы также будут принимать токены Firebase Auth, предоставленные через клиентские SDK, и соответствующим образом оценивать правила, что позволяет напрямую подключать ваше приложение к эмуляторам в интеграционных и ручных тестах.

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

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

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

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

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

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

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

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().doc('/users/alice'), { ... });

RulesTestEnvironment.unauthenticatedContext() => RulesTestContext

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

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

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

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 версии 9 .

RulesTestEnvironment.clearFirestore() => Promise<void>

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

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

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

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

Эмулятор 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, но общие подходы применимы к любому SDK Firebase.

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

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