简体   繁体   中英

Firestore security rules document id causing Null value error

I am working on securing my Firestore database and am having trouble with a collection wildcard. I have a root-level collection called study , I want to allow reads only if the status field is set to "published".

My current security rules are as follows

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
   
  ...

  function isStudyPublished(studyId) {
    return get(/databases/$(database)/documents/study/$(studyId)).data.status == "published";
  }

  ...

  // Match for participant permissioning
  match /study/{studyId}/{document=**} {
    // Deny all requests to create, update, or delete the study
    allow create, update, delete: if false

    // Allow the requestor to read the study if it is published
    allow read: if isStudyPublished(studyId)
  }

  ...

  }
}

This security rule works great when requesting a single document, but not when doing a collection-level query. I get the error message: Null value error. for 'list' Null value error. for 'list' . I understand that Firestore rules do not act as a filter, however, I believe my query should only be requesting documents where the status field equals "published".

Here are my two queries:

// successfully retrieves a single document
export const getStudy: GetStudy = (studyId) => {
  return new Promise((resolve, reject) => {
    getDoc(doc(firestore, COLLECTION_KEYS.STUDY, studyId))
      .then((doc) => {
        resolve(dataToStudy(doc.id, doc.data()));
      })
      .catch((error) => reject(error));
  });
};
// fails to retrieve multiple documents
export const getStudies: GetStudies = (cursor) => {
  const studies: StudyDoc[] = [];
  return new Promise((resolve, reject) => {
    let q = query(collection(firestore, COLLECTION_KEYS.STUDY), where("status", "==", "published"), orderBy(FIELD_KEYS.UPDATED));

    if (cursor) q = query(q, startAfter(cursor));

    getDocs(q)
      .then((snapshot) => {
        snapshot.forEach((doc) => {
          studies.push(dataToStudy(doc.id, doc.data()));
        });

        return resolve([studies, snapshot.docs[snapshot.docs.length - 1]]);
      })
      .catch((error) => {
        return reject(error);
      });
  });
};

My initial thought is that my collection query was requesting documents that would fail the security rules, but after staring at the query I don't think that's true...

As a test, I changed up my security rules to test what would pass using the help of the debug function . I ended up realizing that the check fails every time I reference the studyId variable. As an example the following match statement fails.

match /study/{studyId}/{document=**} {
  // Deny all requests to create, update, or delete the study
  allow create, update, delete: if false

  // Allow the requestor to read the study if it is published
  allow read: if debug(debug(true) && (debug(studyId) != null));
}

When looking at the firestore-debug.log , if I run the query that requests a single document I see the following logged:

bool_value: true

string_value: "rMxuVTJ0O15qPjMbpEU1"

bool_value: true

however, when running the collection query the following is logged:

bool_value: true

I've never run into the document id wildcard throwing an error like this. I can't determine if this is an error with my match statement or my query, but any help is appreciated.

I'm not sure what your use-case really is but you don't have to use get() when trying to read data from the document being accessed or read. You should use resource.data instead. See Firestore Rule below:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    function isStudyPublished() {
      return resource.data.status == "published";
    }

    // Match for participant permissioning
    match /study/{studyId}/{document=**} {

      // Deny all requests to create, update, or delete the study
      allow create, update, delete: if false

      // Allow the requestor to read the study if it is published
      allow read: if isStudyPublished();
    }
  }
}

UPDATE:

When accessing your subcollection, your query should be matched with your security rules. You should have the document ID of the root collection to access your subcollection. Eg:

// Document-ID refers to study's document
let q = query(collection(db, "study", <DOCUMENT-ID>, <SUBCOLLECTION-NAME>), where(<FIELDNAME>, "==", <STRING>));

Security rules:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /study/{studyId} {
      // Deny all requests to create, update, or delete the study
      allow create, update, delete: if false

      // Do all restrictions here for the study collection.

      function isStudyPublished() {
        return get(/databases/$(database)/documents/study/$(studyId)).data.status == "published";
      }

      // Rules for accessing subcollection of study collection.
      match /subcollection-name/{docId} {
        // Allow read if the document referenced from the study collection is published.
        allow read: if isStudyPublished();

        // Do all restrictions here for study's subcollection.
      }
    }
  }
}

The sample rule above will only allow reads to the subcollection documents if the referenced document from the study collection is tagged as published.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM