Search code examples
google-cloud-firestorefirebase-security

How to properly grant permission to all subcollections based off parent document


I'm struggling to figure out how to write my Firestore rules for a sub-collection, and I think this should be doable, but I keep getting permission denied.

I have the following structure

/mycollection/{mainDocument}/subcollection/{subDocument}

Inside {mainDocument} is a members list. If you're UID is in that list, you have permission to read/write in that document and all the sub-collection documents

To verify my if condition works works at all, I have the following rule which works for the main document but not sub-collections. I can read and write to this document and alter the members within it

match /mycollection/{mainDocument} {
     allow read, write: if request.auth.uid in resource.data.members;
}

The documentation says that if you use recursive wildcards on rule_version=2 it will work on any sub-collection and they give this example.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the cities collection as well as any document
    // in a subcollection.
    match /cities/{city}/{document=**} {
      allow read, write: if <condition>;
    }
  }
}

When I try to use a recursive wildcard though, i get permission denied when I try to read or write to the sub-collection?

rules_version = '2';
service cloud.firestore {
    match /databases/{database}/documents {

        match /mycollection/{mainDocument}/{document=**} {
            allow read, write: if request.auth.uid in resource.data.members;
        }
    }
}

or

rules_version = '2';
service cloud.firestore {
    match /databases/{database}/documents {

        match /mycollection/{mainDocument=**} {
            allow read, write: if request.auth.uid in resource.data.members;
        }
    }
}

If add an explicit rule for the sub-collection though, then it works, but I can't use my if statement in there as the members list isn't in that sub-collection.

rules_version = '2';
    service cloud.firestore {
        match /databases/{database}/documents {

            match /mycollection/{mainDocument} {
                allow read, write: if request.auth.uid in resource.data.members;
            }

            match /mycollection/{mainDocument}/subcollection/{subDocment} {
                allow read, write
            }
        }
    }
}

So I know my rules if open can work on each collection, but I can't get the permission to apply on the sub-collection and only allow it like I can on the {mainDocument}.


Solution

  • It's important to remember that resource in a rule refers to the specific document being read or written. So when this rule triggers for a document in a subcollection:

    match /mycollection/{mainDocument}/{document=**} {
        allow read, write: if request.auth.uid in resource.data.members;
    }
    

    resource refers to the document in the subcollection, not the one matched by {mainDocument}. It's not considering the contents of {mainDocument} at all.

    What you should do is use get() to access the specific document whose contents must be used to evaluate the rule. Something like this:

    match /mycollection/{mainDocument}/{document=**} {
        allow read, write: if request.auth.uid in get(/databases/$(database)/documents/mycollection/$(mainDocument)).data.members;
    }