1

A user needs to be able to add their uid to an array called 'users', within a document. They should only be able to add/remove their uid, and nothing else.

I need to write a security rule to allow for this, but can't figure out how to do array comparison in the security rules.

It needs to be something like this:

allow update: if request.resource.data.users == [...resource.data.users, request.auth.uid]

This is the query:

.update({
      users: firebase.firestore.FieldValue.arrayUnion(params.uid),
    })

Is this possible?

AJ Riley
  • 85
  • 6

4 Answers4

2

Yes, it's possible,:

allow update: if request.auth != null 
&& request.auth.uid in request.resource.data.users
&& request.resource.data.users.size() - resource.data.users.size() == 1

request.resource.data.users is the state of the users document AFTER the proposed change and resource.data.users is the state of the document BEFORE the proposed change. By checking if the difference is one, you guarantee that only one item was added to the array.

  • This is unsafe. Imagine a situation where `request.auth.uid=1`, `request.resource.data.users = [1,2,3]`, and `resource.data.users = [1,2]`. Our user (user 1) can add somebody else's ID (user 3) into the array. Also this doesn't give a way for the user to remove their own id. – akamichael Mar 30 '22 at 11:45
0

Yes. It is possible to check if a particular uid is present in an array field of the document in the security rules. You can use in condition to check the same.

So it would look a lot like this.

allow update: if (request.auth.uid in request.resource.data.users);

Hope this helps.

denniskbijo
  • 576
  • 3
  • 10
  • I need it to check that ONLY their uid has been added to the data. This would just check that their uid exists in the new data. They could have added more uids, which should not be allowed. Thanks though. Still haven't figured out how to do it! – AJ Riley Aug 25 '19 at 11:28
  • @AJRiley You cannot check the case of adding another uid using firestore rules. You can take care of it in your logic. However you can check if the current user's uid doesn't exist in the document and exists in the new document. – denniskbijo Aug 26 '19 at 04:42
0

There's a removeAll method on arrays for array exclusion. See this answer

Here's a little function checking that two arrays differ exactly by the provided element.

function arrDifferBy(arr1, arr2, el) {
            let diff12 = arr1.removeAll(arr2);
            let diff21 = arr2.removeAll(arr1);
            return diff12.length == 1 && diff21.length == 0 && diff12[0] == el || 
                diff12.length == 0 && diff21.length == 1 && diff21[0] == el
}

In your case it would be used like

allow update: if arrDifferBy(request.resource.data.users, resource.data.users, request.auth.uid)

Edit: after a few hours of battling with a similar task myself I've decided to do it in a cloud function. If this depends on a creation of some other document, or you can use callable cloud functions, it's much less hassle.

akamichael
  • 113
  • 7
0

I write this, I think its safe for arrayUnion, its for adding auth.uid but you can transform code for your targets

function isOwnerUnionedInArray(arrayName) {
  let isCreating = resource == null;
  let resourceArray = resource.data.get(arrayName, []);
  let requestArray = request.resource.data[arrayName];
  let arrayDiff = requestArray.removeAll(resourceArray);

  return ((
      isCreating &&
      requestArray.size() == 1
    ) || (
      requestArray.size() == resourceArray.size() &&
      arrayDiff.size() == 0
    ) || (
      requestArray.size() == resourceArray.size() + 1 &&
      arrayDiff[0] == request.auth.uid
    )
  ) && request.auth.uid in requestArray;
}