The Anatomy of Firestore's get(/databases/) call ... What do the various aspects mean?

30 views
Skip to first unread message

M Mathems

unread,
May 4, 2023, 7:08:40 AMMay 4
to google-cloud-firestore-discuss

Hi

I am looking over a get(/databases/) call I recently made within my Firestore security rules to try and understand why what I wrote did not offer the result I expected, and in this close look, I'd like to understand how the call works so that in future the expected result can be the result.

The call I wrote was:

match /users/{docId}/userA/{docId2}/exclusiveA/{docId3} {
   // allow read if user: (1) has a uid, (2) has creditcard = false
   allow get: if request.auth.uid != null && get(/databases/$(database)/documents/exclusiveB/$(request.auth.uid)).data.creditCard == false;
  }

In this call, I have imagined that the '/databases/$(database)/documents' aspect is a mandatory feature of the call, however, more recently I noticed a get(/databases/) call that did not feature this aspect, and instead the get call was expressed as get(/collectionName/ ...).

A question I have is, when is it acceptable by Firestore for the get(/databases/) call to omit the '/databases/$(database)/documents' aspect and begin with the relevant collection's name?


I have understood the '/users/$(request.auth.uid)' aspect of the call I made, to be incorrect, because in my case, the 'users' collection was an empty collection, and so the $(request.auth.uid) tag was being expressed across a collection where no field or value could confirm a reference to the current user.

A question I have is, what does the $(request.auth.uid) tag within the get(/databases/) call mean?  For example, it is set to match the document-id of the relevant collection.  So, is it seeking a document with the same iD as the current user?  And if "yes", then under what circumstances would Firestore create a document-id with exactly the same iD as a user?

A question I have is, if the $(request.auth.uid) tag within a get(/databases/) call is not seeking a document-id with the same iD as the current user, then, is the tag seeking a field or a field-value that is equal to the identity of the current user?  In which case, the collection to which the $(request.auth.uid) tag refers needs to have a field or a field value that is equal to user identities before a match is possible, right?

A question I have is, in the Firestore documentation, the term 'fully specified path' is stated as being a requirement for get(/databases/) calls.  What is meant by 'fully specified path' and is there an example of a 'fully specified path' that is illustrated with a photographic reference to Firestore?  For example, how would the 'fully specified path' within the get(/databases/) call differ where a sub-collection is relevant from when a sub-collection is not relevant? (When the relevant data is within a sub-collection versus when the relevant data is within a parent collection)


The '.data' aspect of the get(/databases/) call seems to mean that the preceding aspect of the call has referred to a specific document, so that the '.data' aspect then taps into a map of every field concerning that specific document, where the value of a field can then be accessed. 

A question I have is, is my interpretation of the '.data' aspect of the get(/databases/) call accurate, and how would you explain its meaning if "no" please?

With thanks.



Denver Coneybeare

unread,
May 13, 2023, 1:19:09 AMMay 13
to google-cloud-firestore-discuss
Here are some answers to your questions.

> when is it acceptable by Firestore for the get(/databases/) call to omit the '/databases/$(database)/documents' aspect and begin with the relevant collection's name?
It is never acceptable. It is always required to use that prefix. I cannot explain why you saw this "working" before.

> what does the $(request.auth.uid) tag within the get(/databases/) call mean?
It means that when the security rule is evaluated during a client request, $(request.auth.uid) will be replaced by the UID of the user who is making the request. For example, if a user with UID "user123" logs into your app, then when $(request.auth.uid) is encountered during evaluation of security rules for requests from that user, it will be replaced by the 7-character string "user123". See https://firebase.google.com/docs/reference/rules/rules.firestore.Request#auth for details.

> if the $(request.auth.uid) tag within a get(/databases/) call is not seeking a document-id with the same iD as the current user, ...
I think the most helpful way to think about this is to choose a concrete UID and replace $(request.auth.uid) with that UID. For example, if the UID of the user issuing the request is "user123", and the name of the database is "my-database", then get(/databases/$(database)/documents/exclusiveB/$(request.auth.uid)) will load the document /databases/my-database/documents/exclusiveB/user123.

> What is meant by 'fully specified path' and is there an example?
A "fully specified path" is a path that begins with "/databases/$(database)/documents/" and has no wildcard placeholders. For example, "/databases/$(database)/documents/MyCollection/{docId}" is not a fully-specified path because it contains a "wildcard", {docId}. This is likely a typo and should probably by $(docId).

>  is my interpretation of the '.data' aspect of the get(/databases/) call accurate?
Yes, it is. The get() call loads a single document. So the .data "property" gives access to all key/value pairs contained in that document.

I hope this helps.

M Mathems

unread,
May 15, 2023, 9:20:20 AMMay 15
to google-cloud-firestore-discuss
Thank you for your answers.  I understand all except one because it interferes with the logic requirement for writing code.

"> if the $(request.auth.uid) tag within a get(/databases/) call is not seeking a document-id with the same iD as the current user, ...
I think the most helpful way to think about this is to choose a concrete UID and replace $(request.auth.uid) with that UID. For example, if the UID of the user issuing the request is "user123", and the name of the database is "my-database", then get(/databases/$(database)/documents/exclusiveB/$(request.auth.uid)) will load the document /databases/my-database/documents/exclusiveB/user123."

What you have explained is clear, however the underlying logic is unclear, and in order to write some code, there's an imagination of what you are asking Firestore coupled with an expectation - what result should occur.

So, how could I imagine that the get(/databases/) call is ever going to work if the document-ID of documents is never going to equal the UID of the current user?  I mean, I understand what has been explained, but because the underlying idea is not logical, I imagined that a more logical explanation was correct.  This could explain why my get(/databases/) calls are never going to work, and, WHY is the Firebase team using a bad/misleading example as its main/default example of the get(/databases/) function please?

I'm aware that there are other variables that can be placed within the get(/databases/) call instead of $(request.auth.uid), could you lead me to Firebase materials that highlight the alternatives please?

"> when is it acceptable by Firestore for the get(/databases/) call to omit the '/databases/$(database)/documents' aspect and begin with the relevant collection's name?
It is never acceptable. It is always required to use that prefix. I cannot explain why you saw this "working" before."

A call omitting the '/databases/$(database)/documents' aspect is something I saw watching a tutorial authored by the Firebase team.  It was a blackboard example and not a working example. 

Denver Coneybeare

unread,
May 26, 2023, 2:38:12 AMMay 26
to google-cloud-firestore-discuss
> So, how could I imagine that the get(/databases/) call is ever going to work if the document-ID of documents is never going to equal the UID of the current user?

In that case, it won't work. In order for the rule to work you must incorporate the UID of the current user in the path of the document. You can retrieve the UID of the current user from the "auth" API at runtime.

I don't know Flutter personally, but the JavaScript code below shows how you might include the UID of the current user in the path of a document:

const user = await signInWithEmailAndPassword(auth, 'f...@bar.com', 'abc123');
const uid = user.user.uid;

const permissionsDocRef = doc(db, "users", uid, "info", "permissions");
const myPermissions = await getDoc(permissionsDocRef);
log(`${permissionsDocRef.path}: ${JSON.stringify(myPermissions.data())}`);

const postUid = doc(collection(db, "foo")).id; // generate a unique ID
const postDocRef = doc(db, "posts", postUid);
log(`Creating document: ${postDocRef.path}`);
await setDoc(postDocRef, { text: "This is the post" });


In this case, for each user with UID [uid] there exists a document /users/[uid]/info/permissions. For example, if the UID of the currently-logged-in user is Sk5R5XBnatgPWMIKYvAAIqOcW3v2 then the document's path would be /users/Sk5R5XBnatgPWMIKYvAAIqOcW3v2/info/permissions. This document is not writable by anyone, and, therefore, requires some other means to create and maintain the file, such as using the server SDK or editing it via the Firestore console.

Now suppose this "permissions" document has a field named canPost, which is true if, and only if, the user is allowed to add "posts" to the /posts/ collection. Then the security rules below would ensure that any user whose "permissions" document does not contain the field canPost=true is disallowed from performing any write operations in the /posts/ collection:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read: if true;
    }
    match /posts/{postId} {
      allow create:
        if request.auth != null
          && get(/databases/$(database)/documents/users/$(request.auth.uid)/info/permissions).data.canPost == true;
    }
  }
}

I hope this explanation helps.

M Mathems

unread,
May 26, 2023, 6:05:17 AMMay 26
to google-cloud-firestore-discuss

Good day Denver

Thank you for your response above.

With your response, you have answered numerous of the questions I have needed to ask, with the correct convention of the get(/databases/) call being the best of them.

I am not familiar with Javascript but I am aware that it is possible for a document-iD to be set manually in Flutter, and now I can understand that when wanting to make a get(/databases/) call, that setting the root-collection or a higher sub-collection as the user's iD would be the convention to follow (it being okay for lower sub-collections to be randomly generated).

So yes, I do believe that your explanation has helped to resolve my enquiries, and that I might now be able to make use of the Firestore function.

Many thanks for your time and for your assistance.

Kind regards

M Mathems

Denver Coneybeare

unread,
May 26, 2023, 11:15:29 PMMay 26
to google-cloud-firestore-discuss
Oh I'm glad to hear. Thank you for your patience as we figured out how to adequately answer your question. Good luck with Firestore!
Reply all
Reply to author
Forward
0 new messages