4
votes

I'm attempting to setup security rules that allow access to a collection, based on the value of a document field in a subcollection.

This works as expected when retrieving an individual document by id, which is a get operation. However, when querying main_collection (a list operation), this fails with a "permission denied" error. Since there is only a single document in the collection, this is not a case where I don't have permission to some of the documents being queried, such as on this question.

My database structure looks like the following. It contains the collection being listed (main_collection), which has a single document (some_doc), which has a single subcollection (sub_collection), which has a single document (another_doc).

/main_collection/some_doc/sub_collection/another_doc

another_doc has one string field someFieldValue.

For this example, my query is of the entire collection, which is the single document. In my actual application it only queries the documents it expects to have access to, but the end result here is the same because I cannot filter against a document's subcollection from the client library.

firestore.collection('main_collection').get()

These are my security rules.

service cloud.firestore {
  match /databases/{database}/documents {
    match /main_collection/{mainColDoc} {
      // This operation works
      allow get: if subCollectionDocumentHasField('someFieldValue');
      // This operation fails with permission denied
      allow list: if subCollectionDocumentHasField('someFieldValue');

      // This checks for the existence of a field on the subcollection's document
      function subCollectionDocumentHasField(fieldName) {
        return get(/databases/$(database)/documents/main_collection/$(mainColDoc)/sub_collection/another_doc).data.keys().hasAny([fieldName]);
        //return get(/databases/$(database)/documents/main_collection/some_doc/sub_collection/another_doc).data.keys().hasAny([fieldName]);
      }
    }
  }
}

The subCollectionDocumentHasField function checks for the existence of the someFieldValue field on the document another_doc. In this function, if I replace the $(mainColDoc) variable with the hard-coded document id some_doc, the list operation is successful. Since the $(database) path variable can be used in this context, I would expect that others could be as well.

Is this a bug or expected behavior?

3

3 Answers

1
votes

This is actually the expected behavior, you can't use Firebase's rules to filter the results of your query.


A typical scenario would be to have collection of messages, where each message refers to its creator.

You can't simply add a rule where reading is only allowed on messages for which creator is the authenticated user, to filter automatically the messages of the current authenticated user.

The only way to go is to query with filter on the client side (or through a Cloud function).


The documentation is very clear about this :

When writing queries to retrieve documents, keep in mind that security rules are not filters—queries are all or nothing. To save you time and resources, Cloud Firestore evaluates a query against its potential result set instead of the actual field values for all of your documents. If a query could potentially return documents that the client does not have permission to read, the entire request fails.

From Firebase's documentation

1
votes

I opened a ticket with Google and confirmed effectively what @José inferred from usage, which is that a security rule "is only checked once per query".

For clarification, while a security rule on a list operation will typically not query the contents of a document (to avoid potenitally-poor performance), there is at least one condition when it will query the contents of a document. This is when the security rule is guaranteed to return only one document. When this guarantee is met, the single document's contents will be queried because high performance can be maintained; the same as on a get operation.

So, in the linked example in my question where the list operation's rule is referencing a parent document, this guarantee is met and the parent document's contents will get queried.

Also, in my example where the list operation's rule is referencing a hard-coded document id, this guarantee is met and the hard-coded document's contents will get queried.

For the sake of stating it explicitly, for a list operation, in any case where Firestore cannot guarantee that its rule will only query a single document, access will be automatically denied, by design.

0
votes

To reiterate what the other answers say, but stated in a slightly different way: The query must be consistent with the security rules, before any query documents are looked at, or it will fail with permission denied.

For example, if all of the documents in a sub-collection happen to match the security rule (e.g., your create and list rules both require the owner field is "X"), the query still must match the security rules (e.g., the query must also filter on owner is "X") or it will fail with a permission denied error, independent of the actual content of the sub-collection.