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

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

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

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

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

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 Authentication или Google Cloud Identity Platform , переменная request.auth содержит информацию об аутентификации для клиента, запрашивающего данные. Дополнительные сведения о request.auth см. в справочной документации .

Проверка достоверности данных

Многие приложения хранят информацию об управлении доступом в виде полей документов в базе данных. Правила безопасности 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';
    }
  }
}

Переменная 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 также применяется к каждой операции.

    Например, представьте, что вы создаете пакетный запрос на запись с 3 операциями записи и что ваши правила безопасности используют 2 вызова доступа к документу для проверки каждой записи. В этом случае каждая запись использует 2 из 10 вызовов доступа, а пакетный запрос на запись использует 6 из 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 оценивают каждый запрос по его потенциальному результату и отклоняют запрос, если он может вернуть документ, на чтение которого у клиента нет разрешения. Запросы должны соответствовать ограничениям, установленным вашими правилами безопасности. Дополнительные сведения о правилах безопасности и запросах см. в разделе Безопасный запрос данных .

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