【问题标题】:Limit fields written on nested object with Firestore security rules使用 Firestore 安全规则限制写入嵌套对象的字段
【发布时间】:2020-05-23 20:33:39
【问题描述】:

tl;dr: 我认为Set 需要一种获取元素的方法 (set.toList()[0]),但也许我遗漏了什么!


你好!我正在使用带有大量小对象(信用卡交易)的 Firestore 开发预算应用程序。为了限制读取次数,将每个事务存储为单独的文档是没有意义的,因为用户可能一次需要大约数百个事务。

相反,我有一个容器来保存许多事务,如下所示:

/user/{user_id}/transactions/{container_id}

container: {
  transactions: {
    transaction_id_1: {
      amount: 8.25,
      note: 'chipotle lunch'
    },
    transaction_id_2: {
      amount: 12.01
    }
  }
}

这很好用,但我认为安全规则不适用于写入。我想允许用户修改某些字段(note),但不允许修改其他字段(amount)。如果每个事务都是一个文档,我们可以用MapDiff 来做这件事,但是嵌套让它变得更难了。

由于我们不能编写 for 循环,如果我们将自己限制为每次写入一个更新的事务,这应该完全可以使用这样的嵌套 MapDiff:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents/{document=**} {

    function allowTransactionUpdate() {
      let transactionId = <transaction ID of the single transaction being updated>;

      // Limit fields updated for container.
      return request.resource.data.diff(resource.data).changedKeys()
        .hasOnly(['transactions']) &&

      // Make sure only one transaction changed.
      request.resource.data.transactions.diff(resource.data.transactions)
        .changedKeys().hasOnly([transactionId]) &&

      // Verify the transaction already exists.
      transactionId in resource.data.transactions &&

      // Only allow certain fields to be updated on that transaction.
      request.resource.data.transactions[transactionId]
        .diff(resource.data.transactions[transactionId]).affectedKeys()
        .hasOnly(['note']);
    }

    match /transactions/{transMonthId} {
      allow update: if allowTransactionWrite();
    }

    allow read, write: if false;
  }
}

这会很好用...如果我们可以使用 MapDiff 来获取在 container.transactions 映射中更改的事务:

let transactionId = request.resource.data.transactions
  .diff(resource.data.transactions).changedKeys()[0];

关键缺失部分是最后一位:[0]。目前,Sets 没有提供获取元素的方法,这意味着将某些内容转换为 Set(以及因此使用 MapDiff 进行的任何内容)是一个死胡同:您永远无法真正知道 Set 中的值是什么。它只能与其他集合进行比较。

否则……我错过了什么吗?还有其他方法可以限制嵌套更新中的字段吗?


其他选项是:

  • 使用自定义后端进行此写入,这是可行的,但很遗憾,因为 Firestore 的一大优势是后端最少 + 安全规则的强制执行。
  • 将用户可编辑的属性放在一个容器文档中,将不可编辑的属性放在另一个容器文档中,但这会增加读取次数并增加客户端订阅的烦人复杂性。
  • 接受这是不可能的并在每个事务中使用一个文档,这将导致 100 倍以上的读取。 ;)

【问题讨论】:

    标签: google-cloud-firestore firebase-security


    【解决方案1】:

    你并没有错过什么。安全规则无法实现您尝试做的事情。

    如果您打算收集数据项,并且希望引用这些数据项并使用安全规则进行保护,则它们应该是集合或子集合中的单个文档。试图将它们全部塞进一个文档中是不可取的,也不可扩展。如果您这样做是为了节省文档读取,您很快就会发现这种“优化”在涉及安全规则和管理这些单个项目时实际上并不是很有帮助。将数据项作为单独的文档进行保护比在单个文档中管理它们更容易和直接。

    如果您确实必须将所有内容存储在一起,我建议通过一些您可以编写自定义逻辑的后端来限制写入访问,并让您的客户在需要执行写入时调用后端。请记住,这是不可扩展的,您可能会遇到 1MB 的最大文档大小,这是一个比您开始解决的问题更昂贵的问题。

    【讨论】:

      【解决方案2】:

      对于正在寻找嵌套对象和 MapDiff 示例的其他人

      rules_version = '2';
      service cloud.firestore {
        match /databases/{database}/documents {
      
          function affectedKeys(keys){
            return request.resource.data.diff(resource.data).affectedKeys().hasOnly(keys)
          }
      
          function affectedKeysObj(obj1Key, obj2Key, keys){
            return request.resource.data[obj1Key].diff(resource.data[obj2Key]).affectedKeys().hasOnly(keys)
          }
      
          match /{document=**} {
            allow read, write: if false;
          }
          match /users/{uid}{
            allow get: if request.auth.uid == uid;
            allow update: if request.auth.uid == uid 
              && ! affectedKeys(["meta"]) 
              && affectedKeys(["userData"]) 
              && affectedKeysObj("userData", "userData", ["bio", "displayName"]);
          }
        }
      }
      

      在这种情况下,我希望用户能够在 userData 映射中编辑 ["bio", "displayName"],但我也希望禁止编辑 meta 映射。

      然而,关于这个问题,Doug Stevensons 是对的,我只是补充一点,这就是我将 MapDiff 与嵌套对象一起使用的方式。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多