Firestore安全规则 - 如何检查某个字段是否未被修改?

时间:2018-01-09 21:11:47

标签: firebase firebase-security google-cloud-firestore

对于我的生活,我无法理解为什么以下内容导致false允许写入。假设我的users集合是空的,我正在从我的Angular前端编写以下表单的文档:

{
  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());
    }
  }
}

一般来说,我希望没有用户能够编辑自己的角色。普通用户可以编辑他们自己的文档,管理员可以编辑任何人。

isEditingRole()存储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.fooresource.data.foo进行比较来测试字段是否已被修改,因为resource知道request.resource中的每个字段也会出现在request.resource.data.role != resource.data.role中,即使它未在写请求。

根据这一点,我认为来自"编辑1"被排除在外。我确实尝试了version的建议,而且这种建议也没有...我不知所措,我开始怀疑Firestore中是否真的存在错误。

9 个答案:

答案 0 :(得分:4)

我使用writeFields解决了这个问题。请尝试这条规则。

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

就我而言,我使用list来限制更新字段。它也有效。

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

答案 1 :(得分:3)

如果您创建自定义函数来检查更新,那么您的规则将更具可读性和可维护性。例如:

service cloud.firestore {
  match /databases/{database}/documents {
    function isUpdatingField(fieldName) {
      return (!fieldName in resource.data && fieldName in request.resource.data) || resource.data[fieldName] != request.resource.data[fieldName];
    }

    match /users/{userId} {
      //read rules here...
      allow write: if !isUpdatingField("role") && !isUpdatingField("adminOnlyAttribute");
    }
  }
}

答案 2 :(得分:2)

所以最后,似乎我假设$bookings = $wpdb->get_var("SELECT COUNT(*) FROM wp_frm_item_metas WHERE field_id=12 AND meta_value='$select_date'"); 会返回resource.data.nonExistentField == null,当它实际返回false时(根据this和我的测试)。所以我原来的解决方案可能已经遇到了。这是令人费解的,因为相反应该根据the docs起作用,但也许文档指的是一个值不存在"而不是键 - 一个微妙的区别。

我仍然没有100%的清晰度,但这是我最终的结果:

Error

另一件让我感到困惑的事情是,根据文档,我不应该需要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部分,因为它应该由Firestore自动插入。虽然这似乎并非如此,但删除它会导致我的写入因权限问题而失败。

可以通过将写入分成isChangingRole()createupdate部分而不仅仅是delete来澄清/改进。

答案 3 :(得分:2)

由于文档中对writeFields的引用已经消失,所以我不得不想出一种新方法来完成我们对writeFields的处理。

function isSameProperty(request, resource, key) {
    return request.resource.data[key] == resource.data[key]
}

match /myCollection/{id} {
    // before version !request.writeFields.hasAny(['property1','property2','property3', 'property4']);
  allow update: isSameProperty(request, resource, 'property1')
    && isSameProperty(request, resource, 'property2')
    && isSameProperty(request, resource, 'property3')
    && isSameProperty(request, resource, 'property4')
  }

答案 4 :(得分:2)

汤姆·贝利(https://stackoverflow.com/a/48177722/5727205)的解决方案确实很有希望。

但是在我的情况下,我需要防止对字段进行编辑,并且可能会有这样的情况,即该字段根本不存在于现有数据中。因此,我添加了一个检查字段是否存在。

此解决方案会检查两项检查:

  1. 如果该字段不在请求中且不在现有数据中(等于字段未修改)
  2. 或请求和现有数据相同(等于字段未修改)
ALTER DATABASE mydatabase SET MULTI_USER

答案 5 :(得分:1)

使用此单一功能,您可以检查是否/未创建/修改字段。

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

答案 6 :(得分:1)

这看起来似乎是致命的,但是对于更新文档,您可能拥有其他非用户生成的字段,例如。角色,创建的角色等。您需要可以测试这些字段不变的功能。因此,满足这三个FN。

function hasOnlyFields(fields) {
  if request.resource.data.keys().hasOnly(fields) 
}
function hasNotChanged(fields) {
  return (fields.size() < 1 || equals(fields[0]))
    && (fields.size() < 2 || equals(fields[1]))
    && (fields.size() < 3 || equals(fields[2]))
    && (fields.size() < 4 || equals(fields[3]))
    && (fields.size() < 5 || equals(fields[4]))
    && (fields.size() < 6 || equals(fields[5]))
    && (fields.size() < 7 || equals(fields[6]))
    && (fields.size() < 8 || equals(fields[7]))
    && (fields.size() < 9 || equals(fields[8]))
}
function equals(field) {
  return field in request.resource.data && field in resource.data && request.resource.data[field] == request.resource.data[field]
}

因此,在说用户文档更新时,用户只能更新其姓名,年龄和地址,而不能更新角色和电子邮件,您可以这样做:

allow update: if hasOnlyFields(['name', 'age', 'address']) && hasNotChanged(['email', 'roles'])

请注意hasNotChanged最多可以检查9个字段。同样,这些也不是您要做的唯一检查。您还需要检查文档的类型和所有权。

答案 7 :(得分:1)

发现此规则非常有效:

function propChanged(key) {
  // Prop changed if key in req but not res, or if key req and res have same value
  return (
    (key in request.resource.data) && !(key in resource.data)
  ) || (
    (key in request.resource.data) && request.resource.data[key] != resource.data[key]
  );
}

答案 8 :(得分:1)

这是一个不会触发安全规则错误的函数,例如“对象上的属性名称未定义”。

功能/属性:

  • 如果请求或现有资源均不包含该字段,请允许更新。
  • 如果现有资源包含该字段,但用户未提供该字段,则允许更新。
  • 如果现有资源包含该字段,并且用户提供了该字段,请拒绝更新。
  • 如果现有资源包含该字段,并且用户为其提供了相同的值,则允许更新。如果它们不相等,请拒绝更新。
function notUpdated(key) {
  return !(key in request.resource.data)
         || (
           (key in resource.data)
           && request.resource.data[key] == resource.data[key]
         );
}

说明

1:如果request.resource.data上不存在该字段,则意味着该字段既不在请求中也不在现有资源中。 (请记住,request.resource.data代表成功写入操作之后的资源,即“未来”文档。)如果该字段在任何地方都不存在,则允许写入。

2:如果输入资源或现有资源上都存在字段 ,则需要再次检查。首先,检查现有资源上是否存在该字段。如果没有,更新将被拒绝。如果是这样,请继续检查请求字段是否等于现有字段。如果它们相等,则允许写入。此时request.resource.data[field]不可能触发参考错误;如果该字段出现在resource.data上,那么它也出现在request.resource.data上。