【问题标题】:Firestore Security Rules: PERMISSION DENIED when BatchingFirestore 安全规则:批处理时 PERMISSION DENIED
【发布时间】:2019-11-27 16:27:32
【问题描述】:

考虑这个简单的 Firestore 数据库结构:

1:Firestore 结构

cities/
    city1/
        name:Beijing
        likes: 0
        dislikes: 0
        ... more fields 
    city2/
        name: New York
        likes: 21
        dislikes: 1
        ... more fields 

为了保护数据库免受意外操作,我添加了以下 Firestore 安全规则

2 Firestore 安全规则:

// Allow read/write access to all users under any conditions
service cloud.firestore {
  match /databases/{database}/documents {    
    match /cities/{cityId} {
      allow read: if request.auth.uid != null;
      allow update: if request.auth.uid != null && 
request.resource.data.keys().hasOnly(["likes", "dislikes"]); 
    }
  }
}

这基本上允许我执行这些要求:

  • 经过身份验证的用户可以读取城市数据。
  • 经过身份验证的用户可以更新城市的“喜欢”和“不喜欢”字段。

问题

为什么上面的设置给了我一个Exception: PERMISSION_DENIED: Missing or insufficient permissions.,当我尝试使用批量写入,但如果我单独写入更改就成功了?

例如以下代码(使用 Batch 写入)失败:PERMISSION_DENIED: 权限缺失或不足。

fun rate(likes: List<String>, dislikes: List<String>, done: (Boolean) -> Unit) {

    db.runBatch { batch ->
        likes.map { cityCollection.document(it) }.forEach { doc ->
            batch.update(doc,"likes", FieldValue.increment(1))
        }
        dislikes.map { cityCollection.document(it) }.forEach { doc ->
            batch.update(doc,"dislikes", FieldValue.increment(1))
        }
    }.addOnCompleteListener { task ->
        task.exception() // Exception: PERMISSION_DENIED: Missing or insufficient permissions.
        done(task.isSuccessful) // false
    }
}

没有批处理,代码可以正常工作。例如这完美无缺:

override fun rate(likes: List<String>, dislikes: List<String>, done: (Boolean) -> Unit) {

    val tasks = likes.map { cityCollection.document(it) }.map { doc ->
        doc.update("likes", FieldValue.increment(1))
    }.union(dislikes.map { cityCollection.document(it) }.map { doc ->
        doc.update("dislikes", FieldValue.increment(1))
    })

    Tasks.whenAllComplete(tasks).addOnCompleteListener { task ->
        done(task.isSuccessful) // true
    }
}

关于我的安全规则,我需要了解关于批量写入/事务的一些特殊情况吗?我希望这些更新以原子方式执行,因此我最初尝试使用批量写入。但是,我似乎无法让它们与我的安全规则结合使用。

【问题讨论】:

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


    【解决方案1】:

    您应该知道request.resource.data.keys() 包含现有文档中的所有键,而不仅仅是正在更新的那些。如果您允许写入完成,request.resource.data 表示文档的最终状态。因此,如果您要更新已包含其他字段的文档,您的规则将始终拒绝访问,因为 hasOnly 将返回 false。

    由于在这两种情况下您都没有显示现有文档的内容,因此实际上无法准确说明这会导致您的代码在哪里出错。但这几乎可以肯定是这里发生的事情。更新是否来自单个文档写入、批量更新或事务无关紧要。它们都遵循相同的规则。

    (您曾经能够使用名为 writeFields 的属性来仅找出正在更新的字段,但不推荐使用 - 不要使用它。)

    如果你想执行每个字段的限制,它实际上比你现在写的要复杂得多。您必须检查是否(且仅当)特定字段已被修改,同时还要检查其他字段是否已更改。有关详细信息,请参阅此问题:

    Cloud Firestore Security Rules - only allow write to specific key in document

    【讨论】:

    • 谢谢!我已经在考虑将likesdislikes 移到他们自己的子集合中(这可能更容易)。
    • 刚刚花了几个小时在这个问题上摸不着头脑。不断收到假阳性,模拟针对不存在的 UID 的“更新”;测试显然是针对空的resource 运行的,因此如果我的请求数据与hasOnly 字段中的任何一个匹配,则总是会返回ok。有没有办法模拟已经存在的数据?恕我直言,确实需要一种方法来仅针对正在更新的字段进行测试,否则通过管理界面添加任意字段(安全规则不适用)将破坏用户界面,除非安全规则也被更新。
    • 未来会有更简单的方法,但今天你必须检查所有已知的字段。
    • 现在多久了?
    • 严格来说,“现在”就是这一刻。 ;-) 但是如果你问事情什么时候会改变,我不知道未来。我只知道已经讨论过了。
    【解决方案2】:

    您的文档request.resource.data 对象可以(并且可能会)有更多的“喜欢”和“不喜欢”,这会导致规则失败。检查哪些字段已更改的新规范方法是将文档与现有文档进行比较(这将替换“writeFields”)。我已经编辑了您的规则以显示这一点:

    service cloud.firestore {
      match /databases/{database}/documents {    
        match /cities/{cityId} {
          function onlyLikedFieldUpdated() {
            return request.resource.data.diff(resource.data).affectedKeys().hasOnly(["likes", "dislikes"]);
          }
          allow read: if request.auth.uid != null;
          allow update: if request.auth.uid != null && onlyLikedFieldUpdated();
        }
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-10-19
      • 2019-07-05
      • 1970-01-01
      • 1970-01-01
      • 2020-06-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多