Android Firestore安全规则:批处理时拒绝权限

Android Firestore安全规则:批处理时拒绝权限,android,firebase,google-cloud-firestore,firebase-security,Android,Firebase,Google Cloud Firestore,Firebase Security,考虑以下简单的Firestore数据库结构: 1:Firestore结构 cities/ city1/ name:Beijing likes: 0 dislikes: 0 ... more fields city2/ name: New York likes: 21 dislikes: 1 ... more fields 为了保护数据库不受意外操作

考虑以下简单的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"]); 
    }
  }
}
这基本上允许我执行这些要求:

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

为什么当我尝试使用批写入时,上面的设置会给我一个
异常:权限\u拒绝:权限缺失或不足。
,但如果我单独写入更改,则会成功

例如,以下代码(使用批处理写入)失败:权限被拒绝:权限缺失或不足

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
    }
}
fun rate(喜欢:列表,不喜欢:列表,完成:(布尔)->单位){
db.runBatch{batch->
likes.map{cityCollection.document(it)}.forEach{doc->
批处理更新(文档“likes”,FieldValue.increment(1))
}
dislikes.map{cityCollection.document(it)}.forEach{doc->
批处理更新(文件“不喜欢”,字段值。增量(1))
}
}.addOnCompleteListener{task->
task.exception()//异常:权限\u被拒绝:缺少权限或权限不足。
完成(task.isSuccessful)//错误
}
}
没有批处理,代码运行良好。e、 g.这一点非常有效:

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
    }
}
覆盖有趣率(喜欢:列表,不喜欢:列表,完成:(布尔)->单位){
val tasks=likes.map{cityCollection.document(it)}.map{doc->
文档更新(“likes”,FieldValue.increment(1))
}.union(dislikes.map{cityCollection.document(it)}.map{doc->
文档更新(“不喜欢”,FieldValue.increment(1))
})
Tasks.whenAllComplete(Tasks).addOnCompleteListener{task->
完成(task.isSuccessful)//true
}
}

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

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

因为在这两种情况下都没有显示现有文档的内容,所以实际上不可能确切地说出这是在哪里让代码出错的。但这几乎肯定就是这里发生的事情。更新是否来自单个文档写入、批处理更新或事务都无关紧要。他们都受到同样的规则限制

(过去,您可以使用名为writeFields的属性来查找只更新了哪些字段,但这是不推荐使用的-不要使用它。)

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


您的文档
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();
    }
  }
}

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