Check References in Array - Firestore Security Rules - firebase

I am building firestore security rules for my app and in the events collection I have a property called members that has an array of references from the users collection. How would I go about making sure that the user that has sent the request is in that collection? I know I am able to get the userId through request.auth.uid but I'm unaware of how to get the document reference in firestore rules and make sure that the reference is in the array.

The answer that I have found is this:
match /events/{eventId} {
allow read: if /databases/$(database)/documents/users/$(request.auth.uid) in resource.data.members;
}
Looks like the in keyword allows me to check if a value is inside an array and /databases/$(database)/documents/users/$(request.auth.uid) creates a DocumentReference which is the data type stored in the array.

As clarified in this other post here, similar to yours, Firestore rules are not used for filtering data, but to set which data are accessible for which users and which queries can be performed.
Considering that, you will need to write code that will query and compare the datas from your request.auth.uid, with the ids from your subcollection. This way, you will be able to confirm the data you want, that is the user requesting being authorized to access the information. This would be the correct way to handle the request and return the information or not from your database.
A simple example of code that will confirm that the requesting users is in the members subcollection is similar to the following lines:
var user = firebase.auth().currentUser;
db.collection("events").where("members", "array-contains", user.uid).get()
While this code is untested, is a starting point for what you will need to do, to guarantee that the user requesting is allowed to retrieve the information. You can get more information on what you need here.
Let me know if the information helped you!

Related

Firestore rules validate query field value

I have collection of users and every user can search for others by their publicId
query(
collection(db, "users"),
where("publicId", "==", "..."),
limit(1)
);
and I want to allow users to regenerate their `publicId"s so others won't be able to find them by the old ones.
The problem is that if someone finds a user once and get their doc id they could potentially get the user by that doc("users", "docId") regardless of their "publicId".
I tried to use request.query.publicId == resource.data.publicId, but query seems to only provide limit, offset and orderBy.
Is there a different way to access the query field value or a different way to mitigate the issue?
For the public profile, it might be best to create another collection e.g. "public_users" where the document ID is user's publicId. So when a user regenerates their ID, you can just create another document with new publicId and then delete the previous one.
Do not store a reference to user's UID in this document if you want to keep that a secret. Instead, store this public ID in the "users" collection so that can be read by user only.
Alternatively, you can make your requests through a Cloud Function and block direct requests to Firestore. So there's no way anyone can query by user ID.
For the main collection, you can add a rule that allows users to write their own document only like this:
match /users/{userId} {
allow write: if request.auth.uid == userId;
}

Firestore Security Rules Shared Document by Group

I have a situation where a user can create a doc and then share it with a group of other users. They could share it to multiple different groups. I don't know how to set a rule for this.
Here is the database structure:
So in the group you have a list of docs that have been shared to it. My app loads the group that a user is in, then wants to load all the docs in the documents array. I need a way server side to say that this is OK. Up until now only the owner of the doc can read it.
I put a field in each doc that contains ids for each group its shared to. I think I want to say "check if the user is a member of any groups in the sharedToGroups" list but I can't work out how to do that unless I maintain another list somewhere say in the userProfile doc that has a list of circles the user is a member of. Even then I'd be trying to compare 2 lists and I'm not sure I can do that client side.
It would be nice to be able to get the group Id somehow from where the request is being issued from and just see if that is in the sharedToGroups array.
Any help or comments on how this can be achieved would be greatly appreciated, maybe it needs a different db structure.
You can try an approach of this sort:
I am not sure if this will help you but off the top of my head maybe you could enable permissions on firestore for the group document. As in, in the rules, for the group set up a function that validates the user with the user ID stored in the document with the ID attached in the auth via the firebase auth
Therefore, rather than trying to restrict access per document, restrict access per group.
I'm going to answer my own question. Not sure if its the correct protocol here (not a professional programmer or experienced Stack Overflower) but it might help someone.
I ended up adding a field in the user_profiles document that has a list of each group they are in. This list needs to be maintained as I create and add / remove people from groups along with the members list in the group itself.
The benefit of this is that I can use the users id from the request object to get that document from the data base in the security rule. I then have a 'sharedToGroup' array in the doc I'm trying to access and a "inGroups" array in the user_profile that I can access also. Then I use the hasAny operator to compare the two arrays and allow access if the sharedToGroup array has any values from the inGroups array.
My rule becomes:
match /_group/{groupId}{
allow create: if isSignedIn();
allow read: if isOwner()
|| resource.data.sharedToGroup.hasAny(get(/databases/$(database)/documents/user_profiles/$(request.auth.uid)).data['inGroups']);
allow write: if isOwner();
}
Only thing left to do is to secure the user_profiles doc to make sure not even the user can write to it since I don't want someone manually adding groups into their array.
I hope this might help someone someday - like I said I'm a not a pro here so take it with a grain of salt.

Can firebase security rules be used to deliver the right data or its only here for malicious users?

Imagine the case I want to request a chat (a document) from two user Ids (user1 and user2).
Chat document contains an array with participants ids so users1 and users2 are both in particiants array.
If I request everything that contains user1 or user2 in participant array I may get user1 chats with other users too, not only user1 with user2.
Code for the example :
ref.where('participantsIds', 'array-contains-any', [u1.id , u2.id])
Now imagine I add a security rule that only allow getting chats if the user making the request is in participants array.
Rule for example :
allow read, write: if request.auth!=null && request.auth.uid in resource.data.participantsId
Is this a bad practice? Can I use rules to control data behaviors for each instance of the app?
The rule will probably add some milliseconds (or not even?) to the request no matter if the result is true or false.
And yes I know will affect cost and request quotas.
Thanks !!
Firebase security rules are not filters. They can't change the data that would be returned from a query. All they do is stop people from accessing data based on the logic you supply.
If you apply the rule shown above, then any query where the user requests any document where their uid is not in participantsId, or they make a query that does not use their uid as a required filter on participantsId, then the query will simply fail. This is something that you should pretty easily be able to observe for yourself.
I suggest reading: What does it mean that Firestore security rules are not filters?

Trying to separate the data by user uid firebase?

I have this in my rules but it's not working, just started using firebase a few weeks ago
match /pets/{owner} {
allow read, write: if request.auth.uid == owner
}
this is the code im using to get the data from the collection:
Stream<List<Pet>> get pets{
print(userUid);
return petsCollection.where('owner', isEqualTo:
userUid).snapshots().map(_petListFromSnapshot);
}
im using the print to see if i get the uid from the current user for that query, but it comes null. In a diffrent part of the code i already add the uid of the person that creates a document to the collection.
what im trying to say is that i need advice on how to only get the current user data.
You cannot use security rules to filter results
Once you secure your data and begin to write queries, keep in mind that security rules are not filters. You cannot write a query for all the documents in a collection and expect Cloud Firestore to return only the documents that the current client has permission to access.
https://firebase.google.com/docs/firestore/security/rules-conditions?authuser=0#rules_are_not_filters
Use filter to get current user related documents.

Firestore rule to determine if the resource has attributes with certain value

I have three types of collections in my database.
User
Post
Like
I make a 'like' document such that it has a _user and _post attribute that stores the user and post id respectively.
Currently I am writing a database rule such that a 'like' document is not created if there already exist a document which has the attribute value _user with the user trying to create the object and the _post which has the id the user is liking.
How do I write a rule for this situation ?
I don't think Firestore has any rules that can enforce this at the moment. But I do have some suggestions for alternative approaches:
1. Replicate the like user id to the Post with a cloud function:
You can set up a Firebase Cloud Function that triggers on Create operations of the Like collection. This Cloud Function writes the id of the user into an array on the post called like_users. Then you can have a rule that say:
allow create: if request.auth.uid not in get(/databases/$(database)/documents/Post/$( request.resource.data._post).data.like_users;
(Only problem with this solution, I don't think there is such a thing as "not in", this needs some research)
2. Check if there is a duplicate with a Cloud Function:
You can set up a Firebase Cloud Function that triggers on Create operations of the Like collection. In the function you query for Like documents with the same user and post id. If a document already exists, you delete the most recent one.
3. Restructure your database:
You could remove the Like collection, and instead have an array called "like_users" on the Post collection. This will need you to put a rule that checks if the user exists in the "like_users" array, so I think we might have the same problem as in alternative 1. You will also need to have rules saying that the user is not allowed to update the other fields of the document, which is possible.

Resources