Search code examples
firebasegoogle-cloud-firestorefirebase-security

Can't access Collection Group resource data in Cloud Firestore rules


I have a collection /calendars with a subcollection /events. I'm trying to set up rules for querying /events as a Collection Group, but can't seem to access the resource.data fields. All documents in /calendars/events have a field "CalendarId" and for testing purposes I've setup the rules as can be seen below:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{path=**}/events/{eventId} {
      allow read: if 'CalendarId' in resource.data;
    }
  }
}

I then try to query the /events collection through a Collection Group query:

firestore.collectionGroup('events').where('UserId', '==', userId).get()

But the above query yields me the following error:

FirebaseError: Missing or insufficient permissions

However, if I change the "allow read" rules to the following, the query works and returns the correct documents as expected:

allow read: if !('CalendarId' in resource.data);

Why is it that the 'CalendarId' field is unavailable in the rules even though it is present in all of the /events documents?

For comparison, querying individual /calendars or /events document works great with use of the following rules:

match /calendars/{calendarId} {

  allow write: if resource == null || isOwner();
  allow read: if resource.data.IsPublic || isOwner();
  
  match /events/{eventId} {
  
    function calendarOwner() {
        return get(/databases/$(database)/documents/calendars/$(calendarId)).data.UserId
    }
  
    allow write: if request.auth != null && request.auth.uid == calendarOwner();
    allow read: if resource.data.IsPublic || (request.auth != null && request.auth.uid == calendarOwner());
  }
  
}

Solution

  • Security rules are not filters, but instead merely detect whether the access is permitted. Since your rule says:

    if 'CalendarId' in resource.data;
    

    And your code then does:

    firestore.collectionGroup('events').get().where('UserId', '==', userId)
    

    The code violates the rules, since it is requesting potential documents that don't have a CalendarId field.

    You will have to make sure the query has at least the same conditions as your rules check for, for example by checking whether the calendar (if it's a string) is >= to "".