使用 Firestore 安全规则限制在嵌套对象上写入的字段

mdi*_*ker 3 firebase-security google-cloud-firestore

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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这很好用,但我认为安全规则不适用于写入。我希望允许用户修改某些字段 ( 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;
  }
}

Run Code Online (Sandbox Code Playgroud)

如果我们可以使用 MapDiff 来获取 Map 中发生更改的事务,这将非常有效container.transactions

let transactionId = request.resource.data.transactions
  .diff(resource.data.transactions).changedKeys()[0];
Run Code Online (Sandbox Code Playgroud)

关键缺失的部分是最后一位:[0]。目前,Set无法获取元素,这意味着将某些内容转换为 Set(以及使用 MapDiff 的任何内容)是一个死胡同:您实际上无法知道 Set 中的值是什么。它只能与其他套装进行比较。

否则……我是不是错过了什么?是否有另一种方法来限制嵌套更新的字段?


其他选项是:

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

Mar*_*ame 5

对于正在寻找嵌套对象和 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"]);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

在本例中,我希望用户能够在地图["bio", "displayName"]内进行编辑userData,但我也希望禁止编辑地图meta

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