Firebase Cloud Firestore Security Rules for updateData FieldValue.arrayUnion([value]) [swift]

Viewed 467

I have a document in Cloud Firestore.

The structure of the "events" field is as follows:

{
"events" : [ 
    "eventID-1": [
        "value1",
        "value2"    
        ],
    "eventID-2": [
        "value2",
        "value3"    
        ],
    "eventID-3": [
        "value3",
        "value4"    
        ]
    ]
}

enter image description here

Updating [swift]:

self.db.collection("collection").document(document).updateData([
  "events.eventID-1": FieldValue.arrayUnion([value2])
...

How do I make up Firebase Cloud Firestore Security Rules:

  1. value were unique. That is, when writing, check if the value does not exist in arrays eventID-1, eventID-2, eventID-3 ....

  2. Check (length) the value that comes for recording

I just figured out how to check the incoming data, that it's a map and that we only get one value.

match /databases/{database}/documents {
    match /collection/{documentID} {
      allow write: if request.resource.data is map
                   && request.writeFields.size() == 1
    }
}

UPD : It's been dealt with:

  1. Checking the string length in the query
allow write : if request.resource.data.events.values()[0].size() < 100
  1. Checking uniqueness in the list
allow write : if (request.resource.data.events.keys()[0] in resource.data.events.keys()) == false

But I have not figured out how to solve my problems (((( Help!!!

3 Answers

Here is a try:

allow write: if request.writeFields.size() == 1
    && geteventarray_after().size() == geteventarray_before().size() + 1 // Making sure that the size increased so that is is unique
    && getnewelement().size() < 100     // Making sure the new element size is < 100
    && getnewelement() is string        // Other valdiations...

    function getsubstr(str, from, to) {
      return str[from:to];
    }
    function geteventarrayname() {
      return getsubstr(input, "events.".size(), request.writeFields[0].size());
    }
    function geteventarray(map) {
      return map.get(["events", geteventarrayname()], [])
    }
    function geteventarray_after() {
      return geteventarray(request.resource.data);
    }
    function geteventarray_before() {
      return geteventarray(resource.data);
    }
    function getnewelement() {
      return geteventarray_after()[geteventarray_after().size()-1];  // It is added at the end
    }

EDIT: this is untested but should be a good start

In order to check if there are existing values you could use array-contains or something similar, here is the documentation where there are a few examples, and would look similar to this:

let docRef = db.collection("events");
let query = docRef.whereField("events", arrayContains: "newValue")
query.getDocuments() { (querySnapshot, err) in
    if let err = err {
        print("Error getting documents: \(err)")
    } else {
        if (querySnapshot!.isEmpty) {
           //do what have to if the value do not exist
        } else {
           //do what have to if the value exists
        }
    }
}

Of course this is only an example but should be a good starting point for what you need, also important to note that this would be on your app's side, not on the firebase rules.

For the Length measurement, since you only add 1 value at a time, you could the following on your Firebase Rule:

allow write: if request.resource.data is map
               && request.writeFields.size() == 1 
               && request.resource.data.map.values[0].length < 100 //whatever value you want

Hope this Helped you.

IMO checking only the size is not enough...

To permit only update of a field using arrayUnion, i use two things:

  • .size() to check if the user is updating the array adding only one element, so i check if the size of the new array is equal to the size of the old array + 1

request.resource.data.offers.size() == resource.data.offers.size() + 1

  • .hasAll to be sure that the user is only adding a new value without editing existing ones (really important point ⚠️)

request.resource.data.offers.hasAll(resource.data.offers)

And the final version where offers is my array who accept only arrayUnion as update

match /swaps/{swapId} {
      allow create: if request.auth != null;
      allow read: if request.auth != null;
      allow update: if (
          request.resource.data.offers.size() == resource.data.offers.size() + 1
          && request.resource.data.offers.hasAll(resource.data.offers)
      );
      allow delete: if false;
}
Related