Here are my firestore rules:
match /databases/{database}/documents {
match /customers/{customerId} {
allow write: if exists(/databases/$(database)/documents/$(request.resource.data.referred_by));
allow read: if true;
}
}
The following data does not pass this rule(I was able to create the data from client app by removing the exists(/databases/$(database)/documents/$(request.resource.data.referred_by)):
So how do we deal with the reference field so that I can validate whether this reference exists already or not?
EDIT:
The type of the referred_by field is reference.
It seems that its a limitation of firestore at this stage. We are not able to validate whether a reference exists or not.
I further did some digging using the rules playground. The request.resource.data.referred_by has a path child. So I used $(request.resource.data.referred_by.path). Still that did not work because the $(request.resource.data.referred_by.path) resolves to customers%2FB85CSuRJQZWUKt2jgxOy (url-encoded).
I don't know of a way to url decode this in firestore rules.
So at this time I am changing my data models to use referred_by as a 'string' instead of a reference.
I hope this thing is resolved in future by firestore team.
Related
I'm trying write rules for my firebase firestore. But it's not working and return 'Simulated write denied'. What sholud i do. I add screenshots below;
You have your location to create document set to /Users/{KullaniciId} and the UID is XnLOSW..... so that if statement is actually 'KullaniciId' == 'XnLOSW...'. Change your location to:
/Users/XnLOSW...
Now {KullaniciId} will have the value from your location and should pass the rule.
I have the following security rules:
match /collection1/{doc_id} {
allow read: if (get(/databases/$(database)/documents/collection2/$(doc_id)).author ==
request.auth.uid);
}
What I am doing is that I am trying to pass the wildcard variable from the parent path doc_id into the path of get method. The read access of this doc in collection1 depends on the author field of a document with the same id in another collection collection2. I don't believe that the way I am passing doc_id as $(doc_id) is correct, as I get an error of: Property author is undefined on object
I have also tried (doc_id) and \doc_id, but they are syntaxilly wrong. How do I pass a wildcard variable to a path then?
You're missing a data in there, which is needed to get at the fields of the document:
get(/databases/$(database)/documents/collection2/$(doc_id)).data.author
I'm trying to write a security rule for firestore DB and I'm having trouble for some reason. Does the syntax in the following code look wrong to anyone?
match /posts/{postType} {
allow read: if true;
allow write: if get(/databases/$(database)/documents/posts/$(postType)).data.creator_id ==
request.auth.uid;
}
I attached a screenshot of the path and the data structure. The problem I am getting is "get" is returning null.
Data & Path:
error:
You must change the rule.
Instead of:
allow write: if get(/databases/$(database)/documents/posts/$(postType)).data.creator_id == request.auth.uid;
You must write:
allow write: resource.data.creator_id == request.auth.uid;
request.resource.data.creator_id == request.auth.uid was the solution!
You're not using the simulator correctly. When asked to enter the path of the document to get, you must provide a path to an actual document. Do not type the wildcard from the match. The rules will determine the actual value of the wildcard at the time the rule is evaluated. If you enter "/posts/{postType}", you are telling it to literally load a document with the ID "{postType}", which doesn't exist.
I have a set of Collections whose names all start with ABC and I want to write a single rule that applies to all of them regardless of what follows ABC. Something like:
match /ABC*/{anyid} {
allow read, write;
}
Is this possible? In the Rules Console there are no syntax errors highlighted, but the Simulator won't allow me to access the table with:
GET /ABC123/456
Any ideas?
As far as I know it is not currently possible to match on a partial collection (or document) name. It sounds like an interesting feature request though, so I recommend filing a feature request.
In the meantime, the only thing I can think of is matching all collections, and then testing the path through resource['__name__']:
match /53829635/{document} {
match /{col}/{doc} {
allow read: if resource['__name__'][5].matches('ABC.*')
}
}
The resource['__name__'] expression returns a Path, which can be indexes as an array to get the path segments. It has a form /databases/(default)/documents/collection/document, so the subcollection is at index 5. Since that is just a string, we can use matches on it. In this case I allow reading from any subcollection whose name starts with ABC.
Update: it turns out that you can also simply access the col wildcard, instead of looking up from the path. So this would work the same:
allow read: if col.matches('ABC.*')
TLDR: What is request.resource.data.size() counting in the firestore rules when writing, say, some booleans and a nested Object to a document? Not sure what the docs mean by "entries in the map" (https://firebase.google.com/docs/reference/rules/rules.firestore.Resource#data, https://firebase.google.com/docs/reference/rules/rules.Map) and my assumptions appear to be wrong when testing in the rules simulator (similar problem with request.resource.data.keys().size()).
Longer version: Running into a problem in Firestore rules where not being able to update data as expected (despite similar tests working in the rules simulator). Have narrowed down the problem to point where can see that it is a rule checking for request.resource.data.size() equaling a certain number.
An example of the data being passed to the firestore update function looks like
Object {
"parentObj": Object {
"nestedObj": Object {
"key1": Timestamp {
"nanoseconds": 998000000,
"seconds": 1536498767,
},
},
},
"otherKey": true,
}
where the timestamp is generated via firebase.firestore.Timestamp.now().
This appears to work fine in the rules simulator, but not for the actual data when doing
let obj = {}
obj.otherKey = true
// since want to set object key name dynamically as nestedObj value,
// see https://stackoverflow.com/a/47296152/8236733
obj.parentObj = {} // needed for adding nested dynamic keys
obj.parentObj[nestedObj] = {
key1: fb.firestore.Timestamp.now()
}
firebase.firestore.collection('mycollection')
.doc('mydoc')
.update(obj)
Among some other rules, I use the rule request.resource.data.size() == 2 and this appears to be the rules that causes a permission denied error (since commenting out this rules get things working again). Would think that since the object is being passed with 2 (top-level) keys, then request.resource.data.size()=2, but this is apparently not the case (nor is it the number of keys total in the passed object) (similar problem with request.resource.data.keys().size()). So there's a long example to a short question. Would be very helpful if someone could clarify for me what is going wrong here.
From my last communications with firebase support around a month ago - there were issues with request.resource.data.size() and timestamp based security rules for queries.
I was also told that request.resource.data.size() is the size of the document AFTER a successful write. So if you're writing 2 additional keys to a document with 4 keys, that value you should be checking against is 6, not 2.
Having said all that - I am still having problems with request.resource.data.size() and any alternatives such as request.resource.size() which seems to be used in this documentation
https://firebase.google.com/docs/firestore/solutions/role-based-access
I also have some places in my security rules where it seems to work. I personally don't know why that is though.
Been struggling with that for a few hours and I see now that the doc on Firebase is clear: "the request.resource variable contains the future state of the document". So with ALL the fields, not only the ones being sent.
https://firebase.google.com/docs/firestore/security/rules-conditions#data_validation.
But there is actually another way to ONLY count the number of fields being sent with request.writeFields.size(). The property writeFields is a table with all the incoming fields.
Beware: writeFields is deprecated and may stop working anytime, but I have not found any replacement.
EDIT: writeFields apparently does not work in the simulator anymore...