My Goal
I want to ONLY allow users to update a specific field in the user documents of others.
My user document
/* BEFORE */
{
id: 'uid1',
profile: { /* a map of personal info */ },
connectedUsers: {
uid2: true,
uid3: true,
}
}
/* AFTER */
{
id: 'uid1',
profile: { /* a map of personal info */ },
connectedUsers: {
uid2: true,
uid3: true,
uid4: true, // <--- added.
}
}
The request
const selfUserId = 'uid4';
db.runTransaction(function(transaction) {
return transaction.update(userDocRef).then(function(userDoc) {
if (!userDoc.exists) { throw "Document does not exist!"; }
transaction.update(userDocRef, 'connectedUsers.${selfUserId}', true);
});
}
My understanding of how rules work:
request.resource.darais theentiretarget documentafterthe change.For
updateoperation, the above remains true. I don't quite understand what the Docs mean by:
For update operations that only modify a subset of the document fields, the
request.resourcevariable will contain the pending document state after the operation.
My rules: (See update below)
function existingData() { return resource.data }
function expectedData() { return request.resource.data }
- check if the requester's
uidis added after the update.
function isAddingRequester() {
return expectedData().connectedUsers[requesterId()] != null
}
- check if only
1or0item is added toconnectedUsersafter the update.0for if the requester is already in the list.
function isAddingOneAtMost() {
return expectedData().connectedUsers.size() == existingData().connectedUsers.size() + 1
|| expectedData().connectedUsers.size() == existingData().connectedUsers.size()
}
- check if all other fields of the user document are not changed after the update.
function isNotChangingOtherFields() {
return expectedData().id == existingData().id
&& expectedData().profile == existingData().profile
}
My questions
Are my understandings of how Firestore rules work correct? What does the doc referenced above mean by
pending document state?Are my rule implementations reflecting my intentions? I am confused after searching around and learned that the simulator may have bug.
in my
isNotChangingOtherFieldsfunciton, am I able to compare theprofileobject directly with the==operator?
Update - 2018/01/17 3PM
Removed existingData() and expectedData().
function isAddingRequester() {
return request.resource.data.connectedUsers[requesterId()] != null
}
function isAddingOneAtMost() {
return (request.resource.data.connectedUsers.size() == resource.data.connectedUsers.size() + 1)
|| (request.resource.data.connectedUsers.size() == resource.data.connectedUsers.size()) // NOTE: if the requester is already in the list.
}
function isNotChangingOtherFields() {
return request.resource.data.profile == resource.data.profile
&& request.resource.data.id == resource.data.id
}
function isNotAddingOtherFields() {
return request.resource.data.size() == resource.data.size()
}
Debugging results
Interestingly, the results are NOT the same in the simlator and in production.
// PASSED in simulator & production:
allow update: if isAddingRequester();
// PASSED in simulator but NOT production:
allow update: if isNotChangingOtherFields();
// PASSED in simulator but NOT production:
allow update: if isNotAddingOtherFields();
// FAILED in both simulator AND production:
allow update: if isAddingOneAtMost();
// NOTE: inserted 2 mock data before update.
// PASSED in simulator:
allow update: if resource.data.connectedUsers.size() == 2;
// FAILED in simulator:
allow update: if request.resource.data.connectedUsers.size() == 3;
// PASSED in simulator:
allow update: if request.resource.data.connectedUsers.size() == 1;
Question
If request.resource is the document after the update, why is request.resource.data.connectedUsers.size() 1 instead of 3 (2 existing + 1 the new added)?
Related finding (from simulator)
If I have a function:
expectedData() { return request.resource.data }
And I got such unexpected results:
// PASSED:
allow update: if request.resource.data.id == expectedData().id;
// FAILED if the order is changed.
allow update: if expectedData().id == request.resource.data.id;