node.jsfirebasegoogle-cloud-firestorefirebase-security

How do I specify Firebase Rules to allow an Admin user, in the same organization, be able to read documents belonging to other users


I have collections:

users
files 

(though there could be many different collections beyond files).

Both have an email field, both have an organization field.

I am looking to allow Admin users to read documents in the files collection, belonging to other users, provided they are in the same organization (as specified on the organizationId field of the file itself).

The server side call to Fire base looks like:

const db = getFirestoreInstance();
...
const = await db.collection("files")
  .where("email", "==", email)
  .where("organizationId", "==", organizationId)
  .orderBy("timestamp", "desc")
  .limit(filesLimit)
  .get();


...
let myApp;
if (!myApp){ myApp = initializeApp();}
let fireStoreInstance;

export const getFirestoreInstance = () => {
    if (!firestoreInstance){
       firestoreInstance = getFirestore(myApp);
    }
    return firestoreInstance;
}

The Firebase rules look like:

  match /databases/{database}/documents {
    function isUserAdminInSameOrg(request, resource, database){
        let userObj = get(/databases/$(database)/documents/users/$(request.auth.token.email)).data;
        return userObj.role == 'admin' //this line works
    && userObj.organizationId == resource.data.organizationId;  //userObj.org has the correct value, resource.data.organizationId does not, despite resource.data.email working in the next function
    }

    //this function works
    function doesResourceBelongToUser(resource, request){
        return resource.data.email == request.auth.token.email;
    }
    function Rules(collection, resource, request){
        let collectionsAdminsCanRead = [
        'files'
        ];
        return (collection in collectionsAdminsCanRead && 
                    (doesResourceBelongToUser(resource, request) || isUserAdminInSameOrg(request, resource, database))) 
      || !(collection in collectionsAdminsCanRead );
    }
    match /{collection}/{document} {
      allow read: if request.auth != null && Rules(collection, resource, request);
    }
  }

The goal is to check the authenticated user against the users collection (hence the get), ensure the matching document from the users collection has the field role equaling admin (again verified this works), that it has organizationId equaling the initial collection (in this case files) organizationId.

I was under the impression that resource.data allowed you to access the fields from the document of the collection being queried prior to any execution (in the event it was an create/update/delete) of the query. And in fact this works for checking if the document belongs to the logged in user, checking resource.data.email against request.auth.token.email

I do know that the rules aren't filters, which is why there's where clauses when making the calls. I also know that the rules will reject requests not based on what's actually in the DB, but on all hypothetical scenarios. What am I missing? Is there something in my isUserAdminInSameOrg function that would reject?


Solution

  • I was able to trace the root cause of this issue to the organizationId param not coming across the wire to Firebase correctly, which ultimately led to userObj.organizationId == resource.data.organizationId failing as resource.data.organizationId wasn't what it should have been.