Правила безопасности Firestore - как я могу проверить, что поле/не изменяется?

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

{
  displayName: 'FooBar',
  email: 'foo.bar@example.com'
}

мой правила безопасности:

service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      function isAdmin() {
        return resource.data.role == 'ADMIN';
      }

      function isEditingRole() {
        return request.resource.data.role != null;
      }

      function isEditingOwnRole() {
        return isOwnDocument() && isEditingRole();
      }

      function isOwnDocument() {
        return request.auth.uid == userId;
      }

      allow read: if isOwnDocument() || isAdmin();
      allow write: if !isEditingOwnRole() && (isOwnDocument() || isAdmin());
    }
  }
}

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

Stubbing isEditingRole() на false дает ожидаемый результат, поэтому я сузил его до этого выражения.

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

изменить 1

вещи, которые я пробовал:

function isEditingRole() {
  return request.resource.data.keys().hasAny(['role']);
}

и

function isEditingRole() {
  return 'role' in request.resource.data;
}

и

function isEditingRole() {
  return 'role' in request.resource.data.keys();
}

Изменить 2

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

поля, не указанные в запросе, которые существуют в ресурсе, добавляются в request.resource.data. Правила могут проверить, изменяется ли поле путем сравнения request.resource.data.foo to resource.data.foo зная, что каждое поле в resource также будет присутствовать в request.resource даже если он не был представлен в записи запроса.

в соответствии с этим, я думаю, что три варианта из "Edit 1" исключены. Я попробовал предложение request.resource.data.role != resource.data.role и это не работает... Я в недоумении и начинаю задаваться вопросом, есть ли на самом деле ошибка в Firestore.

5 ответов


Я решил это с помощью writeFields. Пожалуйста, попробуйте это правило.

allow write: if !('role' in request.writeFields);

в моем случае, я использую list для ограничения полей обновления. Это тоже работает.

allow update: if !(['leader', '_created'] in request.writeFields);

запрос.ресурс.ключи.hasAny() - вероятно, ваш лучший выбор здесь. Это позволяет проверить, если конкретный запрос.ресурс имеет любой из указанных ключей. Отрицая логику, вы можете убедиться, что запросы на запись не имеют ваших ключей в черном списке. Например:

service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      //read rules here...
      allow write: if !request.resource.keys().hasAny(["role", "adminOnlyAttribute"]);
    }
  }
}

Итак, в конце концов, кажется, я предполагал, что resource.data.nonExistentField == null вернутся false, когда он фактически возвращает Error (по этой и мои тесты). Таким образом, мое первоначальное решение, возможно, столкнулось с этим. Это озадачивает, потому что противоположное должно работать в соответствии с документы, но, возможно, документы ссылаются на значение, которое "не существует", а не на ключ-тонкое различие.

у меня все еще нет 100% ясности, но это то, что я закончил с этим работал:

function isAddingRole() {
  return !('role' in resource.data) && 'role' in request.resource.data;
}

function isChangingRole() {
  return 'role' in resource.data && 'role' in request.resource.data && resource.data.role != request.resource.data.role;
}

function isEditingRole() {
  return isAddingRole() || isChangingRole();
}

еще одна вещь, которая все еще озадачивает меня, это то, что, согласно документам, мне не нужно && 'role' in request.resource.data входит в isChangingRole(), потому что он должен быть вставлен автоматически Firestore. Хотя это не так, поскольку удаление этого приводит к сбою моей записи для проблем с разрешениями.

это, вероятно, может быть уточнено / улучшено путем разрыва записи в create, update и delete запасные части, а не просто allow write: if !isEditingOwnRole() && (isOwnDocument() || isAdmin());.


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

function incomingDataHasFields(fields) {
    return (( 
        request.writeFields == null
        && request.resource.data.keys().hasAll(fields)
    ) || (
        request.writeFields != null
        && request.writeFields.hasAll(fields)
    ));
}

использование:

match /xxx/{xxx} {    
    allow create:
        if incomingDataHasFields(['foo'])              // allow creating a document that contains 'foo' field
           && !incomingDataHasFields(['bar', 'baz']);  // but don't allow 'bar' and 'baz' fields to be created

в целях обеспечения соблюдения полей только для чтения в клиенте, используйте writeFields (https://firebase.google.com/docs/reference/rules/rules.firestore.Request#writeFields) - ... гекиджин предложил это, но синтаксис был немного неправильным.

match /myCollection/{id}
  // Readonly fields
  allow update: if !(request.writeFields.hasAny(['field1', 'field2']));
}

Firestore будет заполнять поля записи для вас, поэтому вышеуказанная проверка всегда безопасна в вашем update & create правила.

EDIT 9 Oct 2018

@dls101 был достаточно любезно сообщить, что Google, похоже, удалил любое упоминание writeFields документы. Поэтому будьте осторожны с этим решением.