Условия написания правил безопасности Cloud Firestore

Это руководство основано на руководстве по структурированию правил безопасности и показывает, как добавлять условия в Cloud Firestore Security Rules . Если вы не знакомы с основами Cloud Firestore Security Rules , см. руководство по началу работы .

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

Аутентификация

Один из наиболее распространённых шаблонов правил безопасности — управление доступом на основе состояния аутентификации пользователя. Например, ваше приложение может разрешить запись данных только вошедшим в систему пользователям:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to access documents in the "cities" collection
    // only if they are authenticated.
    match /cities/{city} {
      allow read, write: if request.auth != null;
    }
  }
}

Другой распространенный шаблон — гарантировать, что пользователи могут читать и записывать только свои собственные данные:

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches name of the user
    // document. The wildcard expression {userId} makes the userId variable
    // available in rules.
    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null;
    }
  }
}

Если ваше приложение использует аутентификацию Firebase или Google Cloud Identity Platform , переменная request.auth содержит информацию об аутентификации клиента, запрашивающего данные. Подробнее о request.auth см. в справочной документации .

Проверка данных

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

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

Переменная resource ссылается на запрошенный документ, а resource.data — это карта всех полей и значений, хранящихся в документе. Подробнее о переменной resource см. в справочной документации .

При записи данных может потребоваться сравнить входящие данные с существующими. В этом случае, если ваш набор правил допускает отложенную запись, переменная request.resource будет содержать будущее состояние документа. Для операций update , которые изменяют только подмножество полей документа, переменная request.resource будет содержать отложенное состояние документа после операции. Вы можете проверить значения полей в request.resource , чтобы предотвратить нежелательные или несогласованные обновления данных:

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure all cities have a positive population and
    // the name is not changed
    match /cities/{city} {
      allow update: if request.resource.data.population > 0
                    && request.resource.data.name == resource.data.name;
    }
  }
}

Доступ к другим документам

Используя функции get() и exists() , ваши правила безопасности могут сравнивать входящие запросы с другими документами в базе данных. Функции get() и exists() ожидают полностью заданных путей к документам. При использовании переменных для построения путей для get() и exists() необходимо явно экранировать переменные с помощью синтаксиса $(variable) .

В примере ниже переменная database захватывается оператором сопоставления match /databases/{database}/documents и используется для формирования пути:

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      // Make sure a 'users' document exists for the requesting user before
      // allowing any writes to the 'cities' collection
      allow create: if request.auth != null && exists(/databases/$(database)/documents/users/$(request.auth.uid));

      // Allow the user to delete cities if their user document has the
      // 'admin' field set to 'true'
      allow delete: if request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true;
    }
  }
}

Для операций записи можно использовать функцию getAfter() для доступа к состоянию документа после завершения транзакции или пакета записей, но до их фиксации. Как и get() , функция getAfter() принимает полностью указанный путь к документу. С помощью getAfter() можно определить наборы операций записи, которые должны выполняться вместе как транзакция или пакет.

Лимиты вызовов доступа

Существует ограничение на количество вызовов доступа к документу на оценку набора правил:

  • 10 для запросов на отдельные документы и запросов.
  • 20 для многодокументного чтения, транзакций и пакетной записи. Предыдущее ограничение в 10 также применяется к каждой операции.

    Например, представьте, что вы создаёте пакетный запрос на запись с тремя операциями записи, и ваши правила безопасности используют два вызова доступа к документу для проверки каждой записи. В этом случае каждая запись использует два из 10 вызовов доступа, а пакетный запрос на запись использует шесть из 20 вызовов доступа.

Превышение любого из этих ограничений приводит к ошибке «Отказано в доступе». Некоторые вызовы доступа к документам могут кэшироваться, и кэшированные вызовы не учитываются при расчете ограничений.

Подробное объяснение того, как эти ограничения влияют на транзакции и пакетные записи, см. в руководстве по обеспечению безопасности атомарных операций .

Доступ к звонкам и ценам

Использование этих функций запускает операцию чтения в вашей базе данных, что означает, что с вас будет взиматься плата за чтение документов, даже если ваши правила отклонят запрос. Более подробную информацию о тарифах см. в разделе «Цены Cloud Firestore .

Пользовательские функции

По мере усложнения правил безопасности может возникнуть необходимость обернуть наборы условий в функции, которые можно будет повторно использовать в вашем наборе правил. Правила безопасности поддерживают пользовательские функции. Синтаксис пользовательских функций немного похож на JavaScript, но функции правил безопасности написаны на предметно-ориентированном языке, который имеет ряд важных ограничений:

  • Функции могут содержать только один оператор return . Они не могут содержать никакой дополнительной логики. Например, они не могут выполнять циклы или вызывать внешние сервисы.
  • Функции могут автоматически получать доступ к функциям и переменным из области действия, в которой они определены. Например, функция, определённая в области действия service cloud.firestore , имеет доступ к переменной resource и встроенным функциям, таким как get() и exists() .
  • Функции могут вызывать другие функции, но не могут быть рекурсивными. Общая глубина стека вызовов ограничена 10.
  • В правилах версии v2 функции могут определять переменные с помощью ключевого слова let . Функции могут иметь до 10 привязок let, но должны завершаться оператором return.

Функция определяется ключевым словом function и принимает ноль или более аргументов. Например, вы можете объединить два типа условий, использованных в примерах выше, в одну функцию:

service cloud.firestore {
  match /databases/{database}/documents {
    // True if the user is signed in or the requested data is 'public'
    function signedInOrPublic() {
      return request.auth.uid != null || resource.data.visibility == 'public';
    }

    match /cities/{city} {
      allow read, write: if signedInOrPublic();
    }

    match /users/{user} {
      allow read, write: if signedInOrPublic();
    }
  }
}

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

Правила не являются фильтрами

Защитив данные и начав писать запросы, помните, что правила безопасности не являются фильтрами. Нельзя написать запрос ко всем документам в коллекции и ожидать, что Cloud Firestore вернёт только те документы, к которым у текущего клиента есть разрешение на доступ.

Например, возьмем следующее правило безопасности:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

Отклонено : это правило отклоняет следующий запрос, поскольку набор результатов может включать документы, visibility которых не является public :

Интернет
db.collection("cities").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
    });
});

Разрешено : это правило разрешает следующий запрос, поскольку предложение where("visibility", "==", "public") гарантирует, что результирующий набор удовлетворяет условию правила:

Интернет
db.collection("cities").where("visibility", "==", "public").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
        });
    });

Правила безопасности Cloud Firestore оценивают каждый запрос с точки зрения его потенциального результата и отклоняют запрос, если он может вернуть документ, на чтение которого у клиента нет прав. Запросы должны соответствовать ограничениям, установленным вашими правилами безопасности. Подробнее о правилах безопасности и запросах см. в разделе «Безопасные запросы к данным» .

Следующие шаги