I am using Cloud Firestore and I can't seem to get the "IN" operator to work with the security rules. I have tried using array and map but neither work. Of course when I set it to allow read, write; it works fine. What am I doing wrong?
Rules:
service cloud.firestore {
match /databases/{database}/documents {
match /rooms/{roomId=**} {
allow read, write: if request.auth.uid in resource.data.users;
allow read, write: if request.auth.uid in resource.data.users2;
allow create: if request.auth != null;
}
match /user-rooms/{userId} {
allow read, write: if userId == request.auth.uid;
}
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
allow get, create: if request.auth != null;
}
}
}
Client:
db.collection("rooms")
.document(self.room.getRoomId())
.collection("messages")
.addSnapshotListener { .....
//Room is: d6l946swspNSouANzVdZ
//Username is: eX8gkxJNDREv
data will return it's direct children not it's sub-children(users and users2) so you should use get and exists instead of in
match /rooms/{roomId=**} {
allow read, write: if request.auth.uid in get(/databases/$(database)/documents/rooms/$(roomId)/users/$(request.auth.uid)).data;
allow read, write: if exists(/databases/$(database)/documents/rooms/$(roomId)/users2/$(request.auth.uid));
allow create: if request.auth != null;
}
checkout the doc
You're trying to access a variable named "users" inside resource.data which doesn't exist. The resource variable contains data from the object that is currently being written to the database.
What you're probably trying to do is check if this users exist in the fields users and users2, which can be achieved with the rules:
match /rooms/{roomId=**}{
allow read, write: if (exists(/databases/$(database)/documents/rooms/$(roomId)/users2/$(request.auth.uid)) ||
request.auth.uid in get(/databases/$(database)/documents/rooms/$(roomId)).data.users);
allow create: if request.auth!=null;
}
Related
I want all authenticated users to read the collection but only the user with the uid that is specified in the field named uid should be able to write.
service cloud.firestore {
match /taken/{doc}{
allow read: if request.auth.uid != null;
allow read,write: if request.auth.uid == doc.id;
}
}
}
However, the above code does not allow write access even if the query has the right uid.
Try this:
service cloud.firestore {
match /taken/{doc}{
allow read: if request.auth != null;
allow create: if true; // resource.data is not defined on create, hence the separate case
allow update, delete: if request.auth.uid == resource.data.uid;
}
}
}
And you can even force the uid field to be set on create, depending on your logic:
service cloud.firestore {
match /taken/{doc}{
allow read: if request.auth != null;
allow create: if request.auth.uid == request.resource.data.uid; // Forces uid to be set to the user's uid
allow update, delete: if request.auth.uid == resource.data.uid;
}
}
}
You can read data of the document being access using resource object as explained in the documentation.
service cloud.firestore {
match /taken/{doc}{
allow read: if request.auth != null;
allow write: if request.auth.uid == resource.data.id;
}
}
}
The above rules allow any authenticated users to read the documents in 'taken' collection but only user with that UID in the documentation to write it.
So, for now im just trying to apply different rules to each one of the collections I have on my database but everytime i've tried this, it just rejects every operation no matter what, but accepts the operations if the rule is general for the whole database
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents{
match /users/{user} {
allow read, write, update, delete: if
request.auth !=null
}
match /productos/{product} {
allow read;
allow write, update, delete:
if request.auth !=null
}
match /business/{business} {
allow read;
allow write, update, delete: if
request.auth !=null
}
match /orders/{order} {
allow write, update, delete: if
request.auth !=null
allow read;
}
}
}
match /business/{business} {
allow read;
allow write, update, delete: if
request.auth !=null
}
match /orders/{order} {
allow write, update, delete: if
request.auth !=null
allow read;
}
}
}
this is what I'm trying and what I've seen on the internet but it just doesn't work
Matches are hierarchical, so they all need to be under match /databases/{database}/documents{ to match anything in your database. So the last two match statements in your rules won't work unless you prefix the path with /databases/{database}/documents.
I'm trying to write a rule in firebase security console with get() but I can't get resource data anyhow... I want documents and their subcollections to be readable to user, if user uid is in document field, array or map.
My collection structure:
/boards/(boardId)/...much more
Fields in (boardId) document:
name: "Board name"
ownerId: "MYID12345"
guestsId(array): ["MYID12345"]
guestsMap(map): [MYID12345: true]
Security rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /boards/{boardId=**} {
// rules here
}
}
}
What I have tried so far:
allow read: if get(/databases/$(database)/documents/boards/$(boardId)).data.guestsMap[request.auth.uid] == true;
allow read: if request.auth.uid in get(/databases/$(database)/documents/boards/$(boardId)).data.guestsMap;
allow read: if request.auth.uid in get(/databases/$(database)/documents/boards/$(boardId)).data.guestsMap[true];
allow read: if get(/databases/$(database)/documents/boards/$(boardId)).request.data.guestsId[request.auth.uid];
allow read: if request.auth.uid in get(/databases/$(database)/documents/boards/$(boardId)).data.guestsId;
allow read: if request.auth.uid in get(/databases/$(database)/documents/boards/$(boardId)).request.data.guestsId;
allow read: if get(/databases/$(database)/documents/boards/$(boardId)).data.ownerId == request.auth.uid;
allow read: if get(/databases/$(database)/documents/boards/$(boardId)).data.ownerId == "MYID12345";
allow read: if get(/databases/$(database)/documents/boards/$(boardId)).resource.data.ownerId == request.auth.uid;
allow read: if get(/databases/$(database)/documents/boards/$(boardId)).request.data.ownerId == request.auth.uid;
None of these worked, always getting:
ERROR FirebaseError: Missing or insufficient permissions.
allow read: if true, makes the application run normally.
I'm sticking to the documentation but it's not working for me...
#update
match /boards/{boardId=**} {
allow read: if resource.data.ownerId == request.auth.uid;
}
Also can't use it like this, because then, every subcollection is looking for ownerId field in it's documents, and ownerId or friend list array are only in board document.
#update
I tried to do like so, but it didn't help:
match /boards/{boardId} {
allow read, write: if request.auth.uid in resource.data.guestsId || request.auth.uid == resource.data.ownerId;
allow create: if exists(/databases/$(database)/documents/users/$(request.auth.uid));
function passResource() {
return request.auth.uid in resource.data.guestsId || request.auth.uid == resource.data.ownerId;
}
match /categoryList/{categoryId} {
allow read, write: if passResource();
}
...
}
What am I missing here?
Okay I found a working solution:
match /databases/{database}/documents {
match /boards/{boardId} {
allow read: if request.auth.uid in resource.data.guestsId || request.auth.uid == resource.data.ownerId;
allow write: if request.auth.uid == resource.data.ownerId;
allow create: if exists(/databases/$(database)/documents/users/$(request.auth.uid));
function isAllowed() {
return request.auth.uid in get(/databases/$(database)/documents/boards/$(boardId)).data.guestsId || request.auth.uid == get(/databases/$(database)/documents/boards/$(boardId)).data.ownerId;
}
match /categoryList/{category} {
allow read, write: if isAllowed();
match /taskList/{task} {
allow read, write: if isAllowed();
}
}
...
}
Also funny it didn't want to work like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /boards/{boardId=**} {
allow read, write: if request.auth.uid in get(/databases/$(database)/documents/boards/$(boardId)).data.guestsId || request.auth.uid == get(/databases/$(database)/documents/boards/$(boardId)).data.ownerId;
allow create: if exists(/databases/$(database)/documents/users/$(request.auth.uid));
}
}
because:
Error: simulator.rules line [5], column [49]. Property guestsId is undefined on object.
I have my firstore database with collections and documents structured like so:
Users -> Events -> Activities -> Streams
I want everyone to be able to read a document inside the Events collection and it's subcollection documment (activities + Streams) if the Events colleciton document has a property eg visibility to the string "public"
So if a document on Events collection has a field visibility to public any user should be able to read that document and it's subcollections.
So far I managed to make only the Document in the Events collection readable via:
service cloud.firestore {
match /databases/{database}/documents {
// Make sure the uid of the requesting user matches name of the user
// document. The wildcard expression {userId} makes the userId variable
// available in rules.
match /users/{userID} {
allow read, update, delete: if request.auth.uid == userID;
allow create: if request.auth.uid != null;
match /events/{eventID} {
allow read: if resource.data.visibility == 'public';
allow read, write, create, update, delete: if request.auth.uid == userID;
match /activities/{activitytID} {
allow read, write, create, update, delete: if request.auth.uid == userID;
match /streams/{streamID} {
allow read, write, create, update, delete: if request.auth.uid == userID;
}
}
}
}
}
}
How can I make when that visibility of one events document is public also the nested collections of activities and streams be also readable ?
I solved this via:
Adding a function to get the event data
function eventData() {
return get(/databases/$(database)/documents/users/$(userID)/events/$(eventID)).data
}
Complete rules:
service cloud.firestore {
match /databases/{database}/documents {
// Make sure the uid of the requesting user matches name of the user
// document. The wildcard expression {userId} makes the userId variable
// available in rules.
match /users/{userID} {
allow read, update, delete: if request.auth.uid == userID;
allow create: if request.auth.uid != null;
match /events/{eventID} {
allow read: if resource.data.visibility == 'public';
allow read, write, create, update, delete: if request.auth.uid == userID;
function eventData() {
return get(/databases/$(database)/documents/users/$(userID)/events/$(eventID)).data
}
match /activities/{activityID} {
allow read: if eventData().visibility == 'public'
allow read, write, create, update, delete: if request.auth.uid == userID;
match /streams/{streamID} {
allow read: if eventData().visibility == 'public'
allow read, write, create, update, delete: if request.auth.uid == userID;
}
}
}
}
}
}
I'm implementing a recipe book in Firestore where every user is able to see all the recipes all users created but only the original author of the recipe is allowed to edit or delete the recipe. Any user is also allowed to create a new recipe.
My problem is that I am unable to setup the permissions a subcollection to "listen" on a field of the subcollections parentdocument.
Each recipe document contains three things. A field called name where the name of the recipe is stored, a field called creatorUID where the request.auth.uid of the creators uid is stored and a subcollection called ingredients containing documents with some random fields.
service cloud.firestore {
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null;
}
match /ListOfRecipes/{recipe} {
allow read, create: if isSignedIn();
allow update, delete: if resource.data.creatorUID == request.auth.uid;
match /{list=**} {
allow read: if isSignedIn();
// Should return true if recipe.creatorUID has same value as request.auth.uid
allow write: if recipe.creatorUID == request.auth.uid;
}
}
}
}
The problem is that with these rules it only works to create the recipe document. The subcollection and it's documents are not created since the db says
FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
FirebaseError: Missing or insufficient permissions.
The calls is made from Angular client and it's official library.
Rules don't cascade, so you'll need to perform whatever checks you need for the document being captured by the Rules.
Generally speaking, {x=**} rules are more often a mistake and the usage of =** only for extremely specific use cases.
From your question, I'm assuming your data mode is something like this:
/ListofRecipes/{recipe_document}/List/{list_document}
In this case, you'll need your Rules to be configured something like this:
service cloud.firestore {
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null;
}
match /ListOfRecipes/{recipe} {
allow read, create: if isSignedIn();
allow update, delete: if resource.data.creatorUID == request.auth.uid;
function recipeData() {
return get(/databases/$(database)/documents/ListOfRecipes/$(recipe)).data
}
match /List/{list} {
allow read: if isSignedIn();
allow write: if recipeData().creatorUID == request.auth.uid;
}
}
}
}
Dan's answer above works great! Just for reference, in my case I only needed the root parent document ID, you can use the variable from the match statement above the nested one, like this:
service cloud.firestore {
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null;
}
match /ListOfRecipes/{recipeID} {
allow read, create: if isSignedIn();
allow update, delete: if resource.data.creatorUID == request.auth.uid;
match /List/{list} {
allow read: if isSignedIn();
allow write: if recipeID == 'XXXXX';
}
}
}
}
Building upon Dan's answer, you should be able to reduce the number of reads on your database for update and delete on the subcollection by adding the creatorUID to the subcollection document.
You'll have to restrict create to just the creator and make sure the creatorUID is set. Here's my modification of Dan's rules:
service cloud.firestore {
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null;
}
match /ListOfRecipes/{recipe} {
allow read, create: if isSignedIn();
allow update, delete: if resource.data.creatorUID == request.auth.uid;
function recipeData() {
return get(/databases/$(database)/documents/ListOfRecipes/$(recipe)).data
}
match /List/{list} {
allow read: if isSignedIn();
allow update, delete: if resource.data.creatorUID == request.auth.uid;
allow create: if recipeData().creatorUID == request.auth.uid
&& request.resource.data.creatorUID == request.auth.uid;
}
}
}
}