Правила безопасности Firestore получают поле/идентификатор ссылки

У меня есть две коллекции - аренда и пользователи.

В документе об аренде есть поле "landlordID" типа REFERENCE (не String).

Теперь в моих правилах безопасности Firestore я хочу разрешить обновление аренды, ТОЛЬКО ЕСЛИ поле LandlordID этой аренды совпадает с uid пользователя, делающего запрос, а именно request.auth.uid.

Прочитайте это как «разрешить обновление документа об аренде, если пользователь, создающий пользователя, аутентифицирован, поэтому request.auth.uid != null, а идентификатор поля landlordID должен быть равен идентификатору request.auth.uid.

Следовательно, код должен выглядеть примерно так:

    service cloud.firestore {

      match /databases/{database}/documents {

        match /tenancies/{tenancyID}{

            allow update: if request.auth.uid != null && 
                        request.auth.uid == get(resource.data.landlordID).id
    }

}

Я тоже пробовал get(/databases/$(database)/documents/users/$(resource.data.landlordID)).data.id

Поддерживающий скриншот моей базы данных

Правила безопасности Firestore получают поле/идентификатор ссылки

Это должно быть очень просто, но get() просто не работает. Документы Firebase, прокрутите до «Доступ к другим документам». совсем не помог в моей ситуации, и я не знаю, как заставить его работать.

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

Вы показываете настоящие правила, которые не работают так, как вы ожидаете? Если нет, отредактируйте вопрос, чтобы точно показать, что вы делаете. Как видно, я ожидаю, что они не будут компилироваться, потому что нет nill. Вы имели в виду null?

Doug Stevenson 14.03.2019 17:07

Это просто опечатка, в правилах безопасности это "ноль". И да, я показываю автономное правило, которое не выполняет запрос на симуляцию, который должен пройти. Прочитайте это как «разрешить обновление документа об аренде, если пользователь, создающий пользователя, аутентифицирован, следовательно, request.auth.uid != null, а идентификатор поля landlordID должен быть равен идентификатору request.auth.uid.

John Dough 15.03.2019 03:04

Ваш get() определенно неверен. Вам нужно передать ему полный спецификатор документа, как вы можете видеть в документации.

Doug Stevenson 15.03.2019 03:16

Смотрите второй ответ и мой ответ. Я пробовал различные версии get с полными путями и т. д., включая версию во втором ответе, и это не сработало. Второй ответ также указывает на то, что, возможно, нельзя использовать такие ссылки, что меня шокирует.

John Dough 15.03.2019 03:28
Интеграция Angular - Firebase Analytics
Интеграция Angular - Firebase Analytics
Узнайте, как настроить Firebase Analytics и отслеживать поведение пользователей в вашем приложении Angular.
8
4
2 930
7

Ответы 7

Я вижу здесь пару проблем. Первая проблема заключается в том, что функция get() ожидает полностью указанный путь к документу, например:

get(/databases/$(database)/documents/users/$(resource.data.landlordID)).data.id

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

Ссылочный тип в Firestore не очень полезен (пока), я думаю, что вы должны хранить идентификатор лендлорда в виде строки, тогда вы можете просто сделать что-то вроде:

service cloud.firestore {

      match /databases/{database}/documents {

        match /tenancies/{tenancyID}{

            allow update: if request.auth.uid != resource.data.landlordID;

    }

}

Я знаю это и пробовал несколько версий того, что я добавлял в get(), включая построение путей с помощью path() и т. д. Это позор, поскольку в этом нет никакого смысла, чтобы не быть полезным. Ссылка аналогична любому типу, и логика моего приложения построена вокруг того, что она не является строковым полем. Трудно понять, какой смысл иметь ссылку на тип, если вы не можете использовать ее в правилах безопасности.

John Dough 15.03.2019 03:13

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

SnorreDan 15.03.2019 05:06

Я просто подумал, что размещение «строки», представляющей ссылку на другой документ/подколлекцию в другой коллекции, должно быть выполнено с типом ссылки, я считаю, что это логично, иначе в этом нет никакого смысла. В заключение, в этой ситуации нет обходного пути, кроме превращения landlordID в строку в моей «схеме»?

John Dough 15.03.2019 06:01

Да, я думаю, что это обходной путь, который вам придется использовать. Я много использую Firestore и всегда использую строки для создания отношений между документами.

SnorreDan 15.03.2019 07:07

У меня была та же проблема, на которую мне нужен был ответ. См. этот гугл-тред с ответом от кого-то из Google. Цитирую:

You can get an id out of a path using the "index" operator:

some_document_ref should look like /databases/(default)/documents/foo/bar

which has 5 segments: ["databases", "(default)", ...]

some_document_ref[4] should be "bar" allow create: if request.resource.data.some_document_ref[4] == "bar";

You can also use the normal get and exists functions on them.

A few difficult aspects of this that you may run into:

  • There's no way to retrieve the number of segments in a path at the moment (we're adding this soon), so you'll need to know some information about the reference ahead of time

  • There's not great support for writing references using the simulator in the Firebase Console. I used the Firestore emulator to test out this behavior (gist1, gist2)

Вот функция, которую я сделал, которая работает для меня. Я предполагаю, что у вас есть коллекция пользователей, в которой пользователи имеют такие же id, как и их auth.uid.

    function isUserRef(field) {
      return field in resource.data
        && resource.data[field] == /databases/$(database)/documents/users/$(request.auth.uid)
    }

Приспосабливаясь к вашему варианту использования, вы бы назвали функцию так: isUserRef('landlordID') хотя идентификатор в конце немного вводит в заблуждение, поскольку это поле на самом деле является ссылкой.

Для других, которым необходимо получить идентификатор из поля типа ссылки, вам нужно сначала получить документ: get(/databases/$(database)/documents/$(request.resource.data‌​['owner'].path)).id == request.auth.uid Здесь владелец — это ссылка пользователя.

Gerardlamo 15.10.2019 12:41

может быть слишком поздно, но я смог собрать воедино (несмотря на отсутствие документов), что ссылка на документ — это просто путь, а полный путь можно создать с помощью

/databases/$(database)/documents/users/$(request.auth.uid)

Затем у меня есть массив/список в firestore ссылок, называемый reads, с помощью которого я могу получить:

get(/databases/$(database)/documents/users/$(userId)/userinfo/granted_users).data.reads

Оставив мне возможность создать логическое значение и правило с:

/databases/$(database)/documents/users/$(request.auth.uid) in get(/databases/$(database)/documents/users/$(userId)/userinfo/granted_users).data.reads

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

Мне пришлось немного поэкспериментировать, чтобы заставить это работать. Вот функция, которая сработала для меня

function isUserRef(database, userId) {
  return 'user' in resource.data
    && resource.data.user == /databases/$(database)/documents/users/$(userId);
}

И я называю это так:

match /answers/{answer} {
   allow read:
        if isUserRef(database, request.auth.uid);
}

Как упоминалось в некоторых других ответах, ссылка имеет свойство path, которое представляет собой просто строку, которая будет выглядеть примерно так users/randomuserid123. Вы можете разделить это на массив и сопоставить его с пользователем, делающим запрос на обновление.

...

match /tenancies/{tenancyID}{

 allow update: if request.auth.uid != null && 
   resource.data.landlordID.path.split('/') == ['users', request.auth.uid]
 }

...

Также возникли проблемы с обработкой этой проблемы, но в моем случае мне нужно было разрешить пользователю добавлять сообщение в чат, только если он является владельцем этого чата. Есть 2 "таблицы" - chats и chat_messages, причем chat_messages относятся к конкретному чату через поле chatId. chats объекты имеют ownerId поле. Правило, которое я использовал, звучит так:

    // Allow adding messages into a chat if the user is an owner of the chat room
    match /chat_messages/{itemId} {
      function isOwner() {
          return get(/databases/$(database)/documents/chats/$(request.resource.data.chatId)).data.ownerId == request.auth.uid;
      }
      
      allow read: if true;
      allow create: if isOwner();
    }

Другие вопросы по теме