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

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

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

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

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

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

Правила безопасности Cloud Firestore состоят из двух частей:

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

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

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

Узнайте больше о правилах безопасности Cloud Firestore в разделе «Начало работы с правилами безопасности Cloud Firestore» .

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

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

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

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

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

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

Используйте модуль @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 .

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

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

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>

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

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

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

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

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

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

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. Вместо этого в Firebase Test SDK мы предоставили метод initializeTestApp() в библиотеке rules-unit-testing , который принимает поле auth . Дескриптор Firebase, созданный с помощью этого метода, будет вести себя так, как будто он успешно прошел аутентификацию как любой предоставленный вами объект. Если вы передадите null , он будет вести себя как неаутентифицированный пользователь (например, правила auth != null не будут работать).

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

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

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

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

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

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

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

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

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

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

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

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