Johnny Oshika
Johnny Oshika

Reputation: 57502

Firestore security rule: query subcollection based on permission of parent document

Let's say I have a restaurants collection with menus subcollection. The restaurant permission is set inside the restaurant document and it looks like this:

Restaurant

{
 "roles": {
    "user123": "Owner"
  }
}

Anyone who has access to the restaurant also has access to any of its subcollections, including the menus subcollection. Here is the security rule for this:

Security Rule

match /restaurants/{restaurantId}/{document=**} {
  allow read: if resource.data.roles[request.auth.uid] > '' ;
}

Querying for the list of restaurants can be accomplished like this and it works well:

Query for restaurants

firebase
      .firestore()
      .collection('restaurants')
      .where(`roles.${uid}`, '>', '');

Now how do I query the menus subcollection? I understand that Firestore needs to infer the permission based on the query without looking at the underlying data. Logically this should be possible, as anyone who has access to the restaurant also has access to the menus subcollection. But how do I compose this menus subcollection query so that Firestore can infer this? The most logical query I can come up with is something like this:

Query for menus subcollection (doesn't work)

firebase
     .firestore()
     .doc(`restaurants/${restaurantId}`) // user has access to this restaurant
     .collection('menus');

...but that doesn't work with permission being denied.

Upvotes: 2

Views: 1273

Answers (1)

Johnny Oshika
Johnny Oshika

Reputation: 57502

Well it turns out that I need to create 2 security rules at the restaurant level to achieve what I want:

    match /restaurants/{restaurantId} {
      allow read: if resource.data.roles[request.auth.uid] > ''
    }

    match /restaurant/{restaurantId}/{document=**} {
      allow read: if get(/databases/$(database)/documents/restaurants/$(restaurantId)).data.roles[request.auth.uid] > '';
    }

The first rule is necessary in order to query for restaurants that the user has permission to. The second query alone won't work, although I'm not sure why. This is the query for the first rule:

firebase
      .firestore()
      .collection('restaurants')
      .where(`roles.${uid}`, '>', '');

The second rule is necessary for all subcollection queries. Here's an example:

firebase
      .firestore()
      .collection(`restaurants/${restaurantId}/menus`);

The fact that the second rule allows the subcollection query is very interesting, as Firestore needs to read the restaurant document in order to permit this query. This contradicts its claims that it doesn't look at the underlying data for queries, but it looks like it does if it needs to read just one document within a path.

Upvotes: 7

Related Questions