Consider this simple Firestore database structure:
1: Firestore Structure
cities/
city1/
name:Beijing
likes: 0
dislikes: 0
... more fields
city2/
name: New York
likes: 21
dislikes: 1
... more fields
To protect the database from unintended operations, I added the following Firestore Security Rules
2 Firestore Security Rules:
// 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"]);
}
}
}
Which basically allows me to enforce these requirements:
- Authenticated users can READ a city's data.
- Authenticated users can UPDATE the 'likes' and 'dislikes' fields of a city.
Question
How come, the above setup gives me a Exception: PERMISSION_DENIED: Missing or insufficient permissions., when I try to use batch writes, but succeeds if I write changes individually?
E.g. below code (using Batch write) fails: PERMISSION_DENIED: Missing or insufficient permissions.
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
}
}
Whereas without Batching, the code works fine. e.g. this works perfectly:
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
}
}
Is there something special about batch writes/transactions that I need to know with respect to my security rules? I would like these updates to execute atomically, hence I tried to use batch writes initially. However, i cannot seem to get them working in combination with my security rules.