Home > Enterprise >  Firestore Security Rules get() call on list operation
Firestore Security Rules get() call on list operation

Time:12-28

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 with resource 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
}
  • Related