4

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.

lampShadesDrifter
  • 3,925
  • 8
  • 40
  • 102

2 Answers2

10

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.

Zeeshan Adil
  • 1,937
  • 5
  • 23
  • 42
Dore mee
  • 116
  • 1
  • 4
  • 1
    Do you know of any other way to check that data **only** contains certain keys (eg. I was hoping to check `data.keys().hasAll(['expectedKey']) && data.size() == 1`)? – lampShadesDrifter Sep 11 '18 at 05:40
  • Not that I know of. Ideally I wish they would just add something like hasOnly(). If you manage to figure out what is needed to get the data size to work please report back. – Dore mee Sep 11 '18 at 08:28
  • 1
    @Doremee is this what you seek? https://firebase.google.com/docs/reference/rules/rules.List#hasOnly – jimmont Oct 22 '18 at 21:46
  • @jimmont finally! – Dore mee Oct 23 '18 at 12:42
  • 5
    For posterity, you want something like `request.resource.data.keys().hasOnly(['userId', 'anotherField'])` – Bartholomew Furrow Aug 21 '19 at 03:33
  • This worked for me - `return request.resource.data.keys().hasAll(['listeners']) && request.resource.size() == resource.size()` That checks that I'm sending `listeners` and that it is only this field I am sending – Gal Bracha Oct 15 '19 at 23:33
  • Eventually - this was the solution I was looking for - https://stackoverflow.com/a/59019850/395804 – Gal Bracha Nov 24 '19 at 16:55
  • @BartholomewFurrow although doing this `request.resource.data.keys().hasOnly(['userId', 'anotherField'])` may work, `.hasOnly()` fn will check if any of the items in the supplied list is in the list you're checking against regardless of how many items it may have, which may not be what you want if expecting exactly what you supplied, in this case `[userId, anotherField]`. It could have `[userId, anotherField, yetAnotherField]` and still be `true` – Ahsath Dec 04 '19 at 23:19
  • @Ahsath, I'm not sure I understand your concern. I'm also a little confused by your terminology. If I say a.hasOnly(b), then I'm saying "Do all of a's elements appear in b?" which is exactly what's needed here. – Bartholomew Furrow Dec 06 '19 at 02:29
  • @BartholomewFurrow what `hasOnly()` is doing; "Do all b's elements appear in a" regardless of what "a" may have, it does not check for exactly `n` numbers of elements within "a". Like I said "a" can have 3 elements and you expect it to have 2 instead of 3, well that's not the case but the fn will still evaluate to `true` – Ahsath Dec 06 '19 at 21:20
  • @Ahsath You're describing the behaviour of hasAll. – Bartholomew Furrow Dec 09 '19 at 00:22
0

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...

l1b3rty
  • 3,333
  • 14
  • 32