I'm having some trouble finding the right Firestore security rules to match my use case.
The collection type is called Party. There are subcollections that are mostly irrelevant. Here's an example top-level record:
{
partyName: "Foo",
members: {
uid123: {
member: true
}
}
}
I have the following simplified security rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
allow read, write: if false;
match /parties/{partyId} {
// Define a helper
function isPartyMember() {
return request.auth != null &&
get(/databases/$(database)/documents/parties/$(partyId))
.data.get(['members', request.auth.uid, 'member'], false) == true;
}
// Top level collection
allow read, write: if isPartyMember();
// Subcollection prevents using "resource" variable in shared helper.
match /subcollection/{subId} {
allow read: if isPartyMember();
}
}
}
}
I am issuing the following query on web v9:
const resp = await getDocs(
query(
collection(this.firestore, "parties"),
where(`members.${this.auth.currentUser.uid}.member`, "==", true)
)
);
As far as I can tell, I am following the rules:
- The security rules exactly match the query.
- The function has access to the
partyId
variable. - This should only query for valid documents.
Notes:
- When I test the read rules via Rules Playground, it seems to work as I'd expect.
- Replacing the full
get(...)
call withresource
actually works (!), but I can't do this because of the subcollection. It has to be an explicit reference.
Unfortunately, when I run the query I get Missing or insufficient permissions. What am I missing? Can you not secure docs with a get()
operation in a list
query?
CodePudding user response:
For top level collections, you don't have to use get()
to read data of current document. Try refactoring the rules as shown below:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
allow read, write: if false;
match /parties/{partyId} {
// Define a helper
function isPartyMember() {
return request.auth != null &&
get(/databases/$(database)/documents/parties/$(partyId))
.data.get(['members', request.auth.uid, 'member'], false) == true;
}
// Top level collection
// Use resource.data instead of get();
allow read, write: if request.auth != null && resource.data.get(['members', request.auth.uid, 'member'], false) == true;
// Subcollection prevents using "resource" variable in shared helper.
match /subcollection/{subId} {
allow read: if isPartyMember();
}
}
}
}
I am not totally sure about this behaviour but using resource.data
instead of get()
for top level collection should work.
From what I can see, if you attempt to use get()
to get data of document being evaluated in a list
operation, the rule fails (though it works when you are fetching a single document by ID). For example:
match /subcollection/{subId} {
// Rule fails
allow read: if get(/databases/$(database)/documents/parties/$(partyId)/subcollection/$(subId)).data.field == 'value';
// Rule passes
allow read: if resource.data.field == 'value';
// Rule passes
allow read: if isPartyMember(); // reads parent document
}