Home > Enterprise >  Firestore rules | Allow to get docs only if doc ids is provided
Firestore rules | Allow to get docs only if doc ids is provided

Time:03-21

Im getting my documents based on a list of ids.

db.collection("fruits").where(db.FieldPath.documentId(), "in", fruitIds).get()

How should I write my security rules to allow the above call and to deny the below call

db.collection("fruits").get()

CodePudding user response:

It's not possible exactly as you require. What you can do is set your rules like this:

match /fruits/{id} {
  allow get: true;
  allow list: false;
}

This allows clients to get a document if they know the ID, but make it impossible to query documents in bulk.

You will have to then code your client app request each document individually with a DocumentReference get() (instead of a Query with a where clause). The performance hit for this is negligible (no, there is not any noticeable performance gain for using an "in" query the way that you show here - and you are limited to 10 documents per batch anyway).

CodePudding user response:

As @Doug covered in their answer, this is not currently supported in the way you expect.

However, by looking at the reference, you can at least limit list (query) operations by placing conditions on the orderBy and limit used by any queries to make it more difficult rather than outright blocking it.

Consider this answer educational, just fetch the items one-by-one instead and disable list/query access in your security rules. It is included here for those who simply want to obfuscate rather than outright block such queries.

This would mean changing your query to:

db.collection("fruits")
  .where(db.FieldPath.documentId(), "in", fruitIds)
  .orderBy(db.FieldPath.documentId()) // probably implicitly added by the where() above, but put here for good measure
  .limit(10) // this limit applies to `in` operations anyway, but for this to work needs to be added
  .get()
service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the cities collection as well as any document
    // in a subcollection.
    match /fruits/{fruit} {
      allow read: if <condition>
      // Limit documents per request to 10 and only if they provide an orderBy clause
      allow list: if <condition>
                  && request.query.limit <= 10
                  && request.query.orderBy = "__name asc" // __name is FieldPath.documentId()
      allow write: if <condition>;
    }
  }
}

Using those restrictions, this should no longer work:

db.collection("fruits").get()

But you could still scrape everything in smaller chunks using:

const fruits = [];
const baseQuery = db.collection("fruits")
    .orderBy(db.FieldPath.documentId())
    .limit(10);

while (true) {
  const snapshot = await (fruits.length > 0
    ? baseQuery.startAt(fruits[fruits.length-1]).get()
    : baseQuery.get())

  Array.prototype.push.apply(fruits, snapshot.docs);
  
  if (snapshot.empty) {
    break;
  }
}

// here, fruits now contains all snapshots in the collection
  • Related