Firestore security rules - request.auth.uid in array

1,001 views
Skip to first unread message

Jannica Thun

unread,
May 20, 2020, 8:18:32 AM5/20/20
to Firebase Google Group

Hi,


I've been struggling with this problem for many days now. I've done my research and if I cannot make this work I don't know what to do.

From my Swift application I want to retrieve all documents from a collection based on the criterias below (I'm using Firestore).



Preferred approach


The preferred approach is to store the parents as a map.


Database

/familyMembers/{familyMemberId}

{

parents 

{

userId1

userId: "userId1"

userId2

userId: "userId2"

}

}


Security rules

match /anotherCollection/{familyMemberId}

{

allow read, write: ?

}


Query from Swift code

db.collection("familyMembers").whereField("parents.\(userId).userId", isEqualTo: userId).getDocuments()



Second approach


Since the first approach might not be feasible due to difficulties finding a proper security rule, I was hoping that this approach could work.


Database

/familyMembers/{familyMemberId}

{

parents: ["userId1", "userId2"]

}


Security rules

match /anotherCollection/{familyMemberId}

{

allow read, write: if request.auth.uid in get(/databases/$(database)/documents/familyMembers/$(familyMemberId)).data.parents;

// note, I'm not intereseted in solutions relative to the resource such as "if request.auth.uid in resource.data.parents"

}    


Queries tried out from Swift code

db.collection("familyMembers").whereField("parents", in: [userId]).getDocuments()

db.collection("familyMembers").whereField("parents", arrayContains: userId).getDocuments()



Research


According to my research, what I'm trying above SHOULD work, but it's not working.


Some findings

I've read all firestore security rules related documentation (such as "rules are not filters", the query has to match the rule).

https://stackoverflow.com/questions/46835481/firestore-security-rules-searching-for-a-users-id-in-array-in-a-document

https://medium.com/firebase-developers/patterns-for-security-with-firebase-group-based-insuffions-for-cloud-firestore-72859cdec8f6



Final


None of the above approaches works. I've tried it out multiple times from the actual project and not only the online emulator. Apart from above I've also tried numerous other ways and still get Permission Denied.


This seems to me like a common case, and it's crucial for it to work for the project I'm working on. I really hope someone could help me on this.

Jannica Thun

unread,
May 26, 2020, 9:54:36 AM5/26/20
to Firebase Google Group
Bump. Anyone?

Sam Stern

unread,
May 26, 2020, 1:17:14 PM5/26/20
to Firebase Google Group
Hi Jannica,

In your second case I still think the issue comes down to "rules are not filters".  Remember that for a query (a "list" operation) to work the rules engine has to be able to prove, without touching the database, that all possible results will be allowed by the rule.  Because your rule has a get() request in it this is automatically impossible.  I would expect that rule to work for a single document get operation, but it won't work for a query that can return multiple documents.

- Sam

--
You received this message because you are subscribed to the Google Groups "Firebase Google Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to firebase-tal...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/firebase-talk/8b3487b2-99f0-4c0e-92ed-37f33ca5ce26%40googlegroups.com.

Jannica Thun

unread,
May 27, 2020, 9:49:07 AM5/27/20
to Firebase Google Group
Hi Sam,

Thanks so much for helping out!

I wonder, do you know if this is possible somehow? The use case is, in its simplest form, I need to restrict access to documents based on a list of users in another document.

Any suggestions would be very helpful.

Best regards,
Jannica

Den tisdag 26 maj 2020 kl. 19:17:14 UTC+2 skrev Samuel Stern:
Hi Jannica,

In your second case I still think the issue comes down to "rules are not filters".  Remember that for a query (a "list" operation) to work the rules engine has to be able to prove, without touching the database, that all possible results will be allowed by the rule.  Because your rule has a get() request in it this is automatically impossible.  I would expect that rule to work for a single document get operation, but it won't work for a query that can return multiple documents.

- Sam

To unsubscribe from this group and stop receiving emails from it, send an email to fireba...@googlegroups.com.

Sam Stern

unread,
May 27, 2020, 9:56:53 AM5/27/20
to Firebase Google Group
Hey Jannica,

My previous response was a bit too broad.  You can use get() in a query rule but the reason it's a problem for you is because your get() path is being built from the match { } path.  So each result needs to make a different get() to know if it can be read, and that is not something we can allow in the engine.

Could you keep a list of accessible documents per user?  Then your rule would look more like this:

match /anotherCollection/{familyMemberId} {
  allow read, write: if request.auth.uid in get(/databases/$(database)/documents/userAccess/$(request.auth.uid)).data.parents;
}

- Sam

To unsubscribe from this group and stop receiving emails from it, send an email to firebase-tal...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/firebase-talk/4ba58ff2-9b52-4176-aa0f-7ba10192bca4%40googlegroups.com.

Kato Richardson

unread,
May 27, 2020, 4:49:33 PM5/27/20
to Firebase Google Group
If docs are accessed by family, then you can probably avoid a lot of complexity by just flattening your family map and structuring your docs by family. So something like the following. The rules would become fairly trivial at this point:
/family/{familyId}/members/{userId} => { isParent: boolean, name: ... }
/family/{familyId}/docs/{docId} => { ... }

A simple way to use the current setup would be to create functions that trigger whenever you add/remove a member in /familyMembers/{familyMemberId} and create a map of permissions for use in rules. Although it's very unclear how you link a parent to their docs right now, so I left that part up to you. Here's an example with rules and functions code to support. Here's an unrelated example I wrote some time back, that is likely interesting for ideas.

All of these are valid and as a general rule, structuring your data appropriately for how it will be accessed and used is the key. You may want to check out Todd's series on Firestore, which includes some great videos on data structures and rules.

☼, Kato



--

Kato Richardson | Developer Programs Eng | kato...@google.com | 775-235-8398

Jannica Thun

unread,
May 31, 2020, 10:46:06 AM5/31/20
to Firebase Google Group
Hi Sam and Kato,

While testing out your approaches I actually found a way to make my preferred approach above work! Apparently the familyMembers path in the rules couldn't have the get to itself, it had to have the resource solution. But the other collections could have the get to the familyMembers path.

match /familyMembers/{familyMemberId}/{document=**} {
    allow read, write: if resource.data.parents[request.auth.uid] != null;
}

match /anotherCollection/{familyMemberId}/{document=**} {
    allow read, write: if request.auth.uid in get(/databases/$(database)/documents/familyMembers/$(familyMemberId)).data.parents;
}

Thank you for all your help!

/Jannica

Den onsdag 27 maj 2020 kl. 22:49:33 UTC+2 skrev Kato Richardson:
If docs are accessed by family, then you can probably avoid a lot of complexity by just flattening your family map and structuring your docs by family. So something like the following. The rules would become fairly trivial at this point:
/family/{familyId}/members/{userId} => { isParent: boolean, name: ... }
/family/{familyId}/docs/{docId} => { ... }

A simple way to use the current setup would be to create functions that trigger whenever you add/remove a member in /familyMembers/{familyMemberId} and create a map of permissions for use in rules. Although it's very unclear how you link a parent to their docs right now, so I left that part up to you. Here's an example with rules and functions code to support. Here's an unrelated example I wrote some time back, that is likely interesting for ideas.

All of these are valid and as a general rule, structuring your data appropriately for how it will be accessed and used is the key. You may want to check out Todd's series on Firestore, which includes some great videos on data structures and rules.

☼, Kato

On Wed, May 27, 2020 at 6:56 AM 'Sam Stern' via Firebase Google Group <fireba...@googlegroups.com> wrote:
Hey Jannica,

My previous response was a bit too broad.  You can use get() in a query rule but the reason it's a problem for you is because your get() path is being built from the match { } path.  So each result needs to make a different get() to know if it can be read, and that is not something we can allow in the engine.

Could you keep a list of accessible documents per user?  Then your rule would look more like this:

match /anotherCollection/{familyMemberId} {
  allow read, write: if request.auth.uid in get(/databases/$(database)/documents/userAccess/$(request.auth.uid)).data.parents;
}

- Sam

--
You received this message because you are subscribed to the Google Groups "Firebase Google Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to fireba...@googlegroups.com.

Sam Stern

unread,
Jun 2, 2020, 12:07:20 PM6/2/20
to Firebase Google Group
Thanks for following up!  Glad it works for you.  The reason that second rule works is because {familyMemberId} is static for the query, it doesn't change for each document.  Therefore it's not a "filter".

- Sam

To unsubscribe from this group and stop receiving emails from it, send an email to firebase-tal...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/firebase-talk/91a41865-4f67-4466-a528-02a305461758%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages