0

I rearranged the security rules for cloud firestore to prevent the recreation of a document in a collection. Here is the rules I used :

match /UserData/{uid}/DAILY_USAGES/{day} {
  allow create: if !exists(/databases/$(database)/documents/UserData/$(request.auth.uid)/DAILY_USAGES/$(day));
  allow read, update: if request.auth.uid == uid;
}

I simulated this rules in console with these document path and user credentials

/UserData/UoeJtUhPpJTi78HouKLIqhcRpfs48/DAILY_USAGES/09-07-2020

If I create a document with the id 09-07-2020 and simulate with above path, it fails. When I delete the document 09-07-2020 then above simulation it works. So it works well in rules simulator.

But when I try in the app, every time when I try it creates the document again. So if the document content was different before, it is resetting to default value that I used to create document.

Here is the code I used in android studio to create this document only once

DailyUsage usage = new DailyUsage();
        usage.setUsage(0);
        usage.setTime(formatDate(date));
        FirebaseFirestore.getInstance().collection(Constants.USER_DATA_ROOT).document(uid)
                .collection(Constants.FIRESTORE_CHILD_DAILY_USAGES).document(today).set(usage);

I don't want to check that if document exists before, because it slows the ui and not works as I expect. So I tried to use the security rules to prevent recreation of documents but not working on application. Can anyone help for this ?

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
ertuzun
  • 153
  • 4
  • 15
  • I'm not sure what you're trying to do here. I think you misunderstand what `allow create` does. That rule only triggers when a document is actually being created. It doesn't trigger when a document already exists and is being updated. `set()` will fail without security rules if the document already exists. – Doug Stevenson Jul 09 '20 at 16:34
  • Is `set()` used for update, doesn't it create the document ? – ertuzun Jul 09 '20 at 16:42
  • I don't understand the question. `set()` is used for creating documents. It will only update an existing document if you also pass a parameter to tell it to merge contents into an existing document. – Doug Stevenson Jul 09 '20 at 16:43
  • I want to create a document for every logged in user for counting daily usages of them. When they logged in I am creating a document with id as date. For example a user logged in today first time and did something in app so a field of todays' document updated. If user exit from app and login again, my code triggers again so todays' document created again with the usage value 0. I am using `set()` to do this, so it updates the usage field as 0, but I don't want to update this value by `set()`. I am updating the field with `update()`. – ertuzun Jul 09 '20 at 16:58
  • 1
    I think you should focus on your attention on the `update` rule, so that it rejects updates that don't do what you want. It sounds like you might want to check that some fields don't change from their original values. Right now, your rule simply allows all updates with any field values, as long as the user is authenticated. – Doug Stevenson Jul 09 '20 at 17:00
  • Thank you for reply, I understand the situation now. I was thinking that set is related only with create rule but now it is clear. I changed the rules and now it is working as I expected. – ertuzun Jul 09 '20 at 20:03
  • 1
    Feel free to answer your own question if you have a solution. – Doug Stevenson Jul 09 '20 at 20:06

2 Answers2

1

The set() method has two behaviours depending of whether the document exists or not:

If the document does not exist, it will be created. If the document does exist, its contents will be overwritten with the newly provided data, unless you specify that the data should be merged into the existing document

This means that when the document already exists the rule that will be fired will be the update rule, so you will have to change your rule :

allow read, update: if request.auth.uid == uid;

to include the logic necessary when you're calling set and the document exists.

You might want to give a look at this answer, regarding the use of set and update. There's a mention to a create method that might suit your needs, but isn't part of the web API, don't know if it's available on the API you're using.

Emmanuel
  • 1,436
  • 1
  • 11
  • 17
  • 1
    Thank you so much Emmanuel I understand the situation. I was thinking set() is only works with create, but know it is clear. If there is a document it works with update. That is the important point. I changed the rules so if set() data contains 0 I am blocking. Therefore I can update the field with any numbers except 0. This solves my issue. – ertuzun Jul 09 '20 at 20:14
0

Thanks to @DougStevenson and @Emmanuel I clearly understood the situation. I was thinking set() triggers always allow create security rule because we are using set() to create documents and update() to update documents. However this was not completely true, set() triggers allow update rule if there is a document with the same id before. set() is also used to update with the merge options and I didn't know this.

My question was about creating firestore document for only once and preventing the recreate of it. However, on the one hand I am also updating this document in some situations. I wanted to prevent the overwrite of the document with the default value which is 0. So I update the rules, and know I am blocking the updates if the value is 0.

match /UserData/{uid}/DAILY_USAGES/{day} {
      allow create: if !exists(/databases/$(database)/documents/UserData/$(request.auth.uid)/DAILY_USAGES/$(day));
                allow read: if request.auth.uid == uid; 
        allow update: if request.auth.uid == uid && request.resource.data.usage != 0;
    }

In the app I didn't change anything and using like that:

DailyUsage usage = new DailyUsage();
        usage.setUsage(0);
        usage.setTime(formatDate(date));
        FirebaseFirestore.getInstance().collection(Constants.USER_DATA_ROOT).document(uid)
                .collection(Constants.FIRESTORE_CHILD_DAILY_USAGES).document(today).set(usage);

This code triggers when app started, if there is not a document with the same id allow create security rule triggers and creates the document. But if there is a document allow update security rule triggers and checks the coming resource data and blocks the update if the usage value 0. In the codeblock of app it sends the value as 0 when app started so it is not allows to update anything in the document.

ertuzun
  • 153
  • 4
  • 15