Check out what’s new from Firebase at Google I/O 2022. Learn more

Безопасный запрос данных

Эта страница основана на концепциях структурирования правил безопасности и написания условий для правил безопасности , чтобы объяснить, как правила безопасности Cloud Firestore взаимодействуют с запросами. В нем более подробно рассматривается, как правила безопасности влияют на запросы, которые вы можете написать, и описывается, как обеспечить, чтобы ваши запросы использовали те же ограничения, что и ваши правила безопасности. На этой странице также описывается, как писать правила безопасности для разрешения или отклонения запросов на основе таких свойств запроса, как limit и orderBy .

Правила — это не фильтры

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

Запросы и правила безопасности

Как показывают приведенные ниже примеры, вы должны писать свои запросы так, чтобы они соответствовали ограничениям ваших правил безопасности.

Защищайте и запрашивайте документы на основе auth.uid

В следующем примере показано, как написать запрос для получения документов, защищенных правилом безопасности. Рассмотрим базу данных, содержащую коллекцию документов story :

/истории/{идентификатор истории}

{
  title: "A Great Story",
  content: "Once upon a time...",
  author: "some_auth_id",
  published: false
}

В дополнение к полям title и content в каждом документе хранятся поля author и published , которые используются для управления доступом. В этих примерах предполагается, что приложение использует аутентификацию Firebase для установки в поле author UID пользователя, создавшего документ. Аутентификация Firebase также заполняет переменную request.auth в правилах безопасности.

Следующее правило безопасности использует переменные request.auth и resource.data , чтобы ограничить доступ на чтение и запись для каждой story ее автором:

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Only the authenticated user who authored the document can read or write
      allow read, write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

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

Неверно : ограничения запроса не соответствуют ограничениям правил безопасности.

// This query will fail
db.collection("stories").get()

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

Напротив, следующий запрос выполняется успешно, поскольку он включает то же ограничение на поле author , что и правила безопасности:

Допустимо : ограничения запроса соответствуют ограничениям правил безопасности.

var user = firebase.auth().currentUser;

db.collection("stories").where("author", "==", user.uid).get()

Защита и запрос документов на основе поля

Чтобы еще больше продемонстрировать взаимодействие между запросами и правилами, приведенные ниже правила безопасности расширяют доступ для чтения к коллекции stories , позволяя любому пользователю читать документы story , в которых для published поля задано значение true .

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Anyone can read a published story; only story authors can read unpublished stories
      allow read: if resource.data.published == true || (request.auth != null && request.auth.uid == resource.data.author);
      // Only story authors can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

Запрос для опубликованных страниц должен включать те же ограничения, что и правила безопасности:

db.collection("stories").where("published", "==", true).get()

Ограничение запроса .where("published", "==", true) гарантирует, что resource.data.published true для любого результата. Таким образом, этот запрос удовлетворяет правилам безопасности и может считывать данные.

in и array-contains-any запросы

При оценке предложения in или array-contains-any относительно набора правил Cloud Firestore оценивает каждое значение сравнения отдельно. Каждое значение сравнения должно соответствовать ограничениям правила безопасности. Например, для следующего правила:

match /mydocuments/{doc} {
  allow read: if resource.data.x > 5;
}

Неверно : запрос не гарантирует, что x > 5 для всех потенциальных документов.

// This query will fail
db.collection("mydocuments").where("x", "in", [1, 3, 6, 42, 99]).get()

Действителен : запрос гарантирует, что x > 5 для всех потенциальных документов.

db.collection("mydocuments").where("x", "in", [6, 42, 99, 105, 200]).get()

Оценка ограничений на запросы

Ваши правила безопасности также могут принимать или отклонять запросы в зависимости от их ограничений. Переменная request.query содержит свойства limit , offset и orderBy запроса. Например, ваши правила безопасности могут отклонять любой запрос, который не ограничивает максимальное количество извлекаемых документов определенным диапазоном:

allow list: if request.query.limit <= 10;

В следующем наборе правил показано, как писать правила безопасности, оценивающие ограничения, накладываемые на запросы. Этот пример расширяет предыдущий набор правил stories со следующими изменениями:

  • Набор правил разделяет правило чтения на правила для get и list .
  • Правило get ограничивает получение отдельных документов общедоступными документами или документами, созданными пользователем.
  • Правило list применяет те же ограничения, что и get , но для запросов. Он также проверяет лимит запроса, а затем отклоняет любой запрос без ограничения или с ограничением больше 10.
  • Набор правил определяет функцию authorOrPublished() , чтобы избежать дублирования кода.
service cloud.firestore {

  match /databases/{database}/documents {

    match /stories/{storyid} {

      // Returns `true` if the requested story is 'published'
      // or the user authored the story
      function authorOrPublished() {
        return resource.data.published == true || request.auth.uid == resource.data.author;
      }

      // Deny any query not limited to 10 or fewer documents
      // Anyone can query published stories
      // Authors can query their unpublished stories
      allow list: if request.query.limit <= 10 &&
                     authorOrPublished();

      // Anyone can retrieve a published story
      // Only a story's author can retrieve an unpublished story
      allow get: if authorOrPublished();

      // Only a story's author can write to a story
      allow write: if request.auth.uid == resource.data.author;
    }

  }
}

Сбор групповых запросов и правил безопасности

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

Защита и запрос документов на основе групп коллекций

В ваших правилах безопасности вы должны явно разрешить запросы группы сбора, написав правило для группы сбора:

  1. Убедитесь, что rules_version = '2'; это первая строка вашего набора правил. Запросы группы сбора требуют нового поведения рекурсивных подстановочных знаков {name=**} правил безопасности версии 2.
  2. Напишите правило для своей группы коллекций, используя match /{path=**}/ [COLLECTION_ID] /{doc} .

Например, рассмотрим форум, организованный в виде документов forum , содержащих подколлекции posts :

/forums/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
}

В этом приложении мы делаем сообщения доступными для редактирования их владельцами и доступными для чтения аутентифицированным пользователям:

service cloud.firestore {
  match /databases/{database}/documents {
    match /forums/{forumid}/posts/{post} {
      // Only authenticated users can read
      allow read: if request.auth != null;
      // Only the post author can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

Любой аутентифицированный пользователь может получить сообщения любого отдельного форума:

db.collection("forums/technology/posts").get()

Но что, если вы хотите показать текущему пользователю его сообщения на всех форумах? Вы можете использовать запрос группы коллекций для получения результатов из всех коллекций posts :

var user = firebase.auth().currentUser;

db.collectionGroup("posts").where("author", "==", user.uid).get()

В ваших правилах безопасности вы должны разрешить этот запрос, написав правило чтения или списка для группы сбора posts :

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {
    // Authenticated users can query the posts collection group
    // Applies to collection queries, collection group queries, and
    // single document retrievals
    match /{path=**}/posts/{post} {
      allow read: if request.auth != null;
    }
    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth != null && request.auth.uid == resource.data.author;

    }
  }
}

Обратите внимание, однако, что эти правила будут применяться ко всем коллекциям с идентификаторами posts независимо от иерархии. Например, эти правила применяются ко всем следующим коллекциям posts :

  • /posts/{postid}
  • /forums/{forumid}/posts/{postid}
  • /forums/{forumid}/subforum/{subforumid}/posts/{postid}

Безопасные запросы группы сбора на основе поля

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

/forums/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
  published: false
}

Затем мы можем написать правила для группы сбора posts на основе published статуса и author сообщения:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    // Returns `true` if the requested post is 'published'
    // or the user authored the post
    function authorOrPublished() {
      return resource.data.published == true || request.auth.uid == resource.data.author;
    }

    match /{path=**}/posts/{post} {

      // Anyone can query published posts
      // Authors can query their unpublished posts
      allow list: if authorOrPublished();

      // Anyone can retrieve a published post
      // Authors can retrieve an unpublished post
      allow get: if authorOrPublished();
    }

    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth.uid == resource.data.author;
    }
  }
}

С помощью этих правил веб-клиенты, клиенты Apple и Android могут выполнять следующие запросы:

  • Любой может получить опубликованные сообщения на форуме:

    db.collection("forums/technology/posts").where('published', '==', true).get()
    
  • Любой может получить опубликованные сообщения автора на всех форумах:

    db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
    
  • Авторы могут получить все свои опубликованные и неопубликованные сообщения на всех форумах:

    var user = firebase.auth().currentUser;
    
    db.collectionGroup("posts").where("author", "==", user.uid).get()
    

Защита и запрос документов на основе группы сбора и пути к документу

В некоторых случаях может потребоваться ограничить запросы групп коллекций на основе пути к документу. Чтобы создать эти ограничения, вы можете использовать те же методы для защиты и запроса документов на основе поля.

Рассмотрим приложение, которое отслеживает транзакции каждого пользователя на нескольких фондовых и криптовалютных биржах:

/users/{userid}/exchange/{exchangeid}/transactions/{transaction}

{
  amount: 100,
  exchange: 'some_exchange_name',
  timestamp: April 1, 2019 at 12:00:00 PM UTC-7,
  user: "some_auth_id",
}

Обратите внимание на user поле. Несмотря на то, что мы знаем, какому пользователю принадлежит документ transaction из пути к документу, мы дублируем эту информацию в каждом документе transaction , потому что это позволяет нам делать две вещи:

  • Создавайте групповые запросы для коллекций, ограниченные документами, которые включают определенный /users/{userid} в путь к документу. Например:

    var user = firebase.auth().currentUser;
    // Return current user's last five transactions across all exchanges
    db.collectionGroup("transactions").where("user", "==", user).orderBy('timestamp').limit(5)
    
  • Примените это ограничение ко всем запросам в группе сбора transactions , чтобы один пользователь не мог получить документы transaction другого пользователя.

Мы применяем это ограничение в наших правилах безопасности и включаем проверку данных для user поля:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    match /{path=**}/transactions/{transaction} {
      // Authenticated users can retrieve only their own transactions
      allow read: if resource.data.user == request.auth.uid;
    }

    match /users/{userid}/exchange/{exchangeid}/transactions/{transaction} {
      // Authenticated users can write to their own transactions subcollections
      // Writes must populate the user field with the correct auth id
      allow write: if userid == request.auth.uid && request.data.user == request.auth.uid
    }
  }
}

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