4
votes

So I have a query (that fails). It reads like this: "As I user I can list all the businesses, which I'm a part of, for an organization".

fs
    .collection('businesses')
    .where('organizationUid', isEqualTo: 'some-organization-id')
    .get();

And a security rule to protect it (the gist of it):

match /businesses/{businessId} {
  function isStaffOrHigher() {
    return get(/databases/$(database)/documents/businesses/$(businessId)/users/$(request.auth.uid)).data.role >= 50;
  }

  allow read: if isStaffOrHigher();

  match /orders/{orderId} {
    allow read, write: if isStaffOrHigher();
  }

  match /users/{userId} {
    allow read: if request.auth.uid == userId || isStaffOrHigher();
  }
}

Basically it looks up the user's role in his user document (that is owned by that business). This type of rule (that uses the get() operator) works for sub-collections (there's no problem querying orders, for example) of {businessId}, but not for queries that attempts to list the businesses.

Now I KNOW that the organizationUid is a valid constraint, but having read Rules are not filters, I can understand why Firestore can't validate this claim without reading a ton of data.

The question is just, then how do I solve this? And how does Firestore validate the constraint for sub-collections correctly?

1

1 Answers

1
votes

Security rules won't do what you want because it would involve reading another document for every document matched by the query. The rules can't know ahead of time what those documents are going to be, as there are variables (businessId) in the path. If this query would yield a millions of documents the businesses collection, you can see how it would be problematic (and expensive for you) to read each of the matching documents from /businesses/$(businessId)/users/$(request.auth.uid) to find out if the entire query should be allowed rejected.

Rules must operate extremely quickly in order to scale in the way that Firestore needs to scale. The limitation that rules can't be filters is part of that requirement for scalability. It's also why there is an overall limit of 10 documents read with get() per rule evaluation.

There is no workaround here from a rules perspective, other than to perform multiple queries, each within bounds of your rules for each collection, and merge the results in your client app.