Grzegorz D.
Grzegorz D.

Reputation: 1806

Firestore Security Rules - how to prevent modification of a certain field

Let's assume we have a Firestore collection called todos, where each todo will look something like this:

{
    name: "Buy milk",
    completed: false,
    user: "eYtGDHdfgSERewfqwEFfweE" // some user's uid
}

Now, I want to prevent modification of the user field during updates (in other words - make this field read-only).

Believe me, I've done my research. My update rule looks like this:

allow update: if request.auth.uid == resource.data.user
              //&& !request.writeFields.hasAny(["user"]);
              //&& !(request.writeFields.hasAny(["user"]));
              //&& !request.resource.data.keys().hasAny(["user"]);
              //&& !('user' in request.resource.data);
              //&& ('user' in request.resource.data) == false;
              //&& !('user' in request.writeFields);

None of the above (commented out) work. They all result in an error: Error: Missing or insufficient permissions..

But...

It gets even more interesting! Because if we compare some of the above rules for positive outcome (aka true) they will work!

For example, this one works perfectly (assuming we include user field in our request):

allow update: if request.resource.data.keys().hasAny(["user"]) == true;

BUT this one doesn't work (assuming we do NOT include the user field in the request):

allow update: if request.resource.data.keys().hasAny(["user"]) == false;

Can anyone explain me what is going on here? Is it possible this is actually a bug in Firestore?

Upvotes: 4

Views: 4023

Answers (2)

yman
yman

Reputation: 71

When you update a document the security rules compare the document that results from the update with the conditions of the security rules. This means that if a field exists in the document on the server but not in the data you push in the update, the security rules will see that field from the old data in the update. For example:

In the document store on the server:

{
  reviewerID: sam123,
  title: "It sucks",
  description: "Because it does",
  rating: 1
}

and then your update is:

{
  description: "but actually it's not so bad",
  rating: 3
}

Then what the security rules see in request.resource.data is:

{
  reviewerID: sam123,
  title: "It sucks",
  description: "but actually it's not so bad",
  rating: 3
}

Which means that even though the update didn't push a change to reviewerID or title those fields do exist in the data you're evaluating.

To make sure that the data is unchanged you need to compare the new data with the old data:

request.resource.data.reviewerID == resource.data.reviewerID

the line:

!request.resource.data.keys().hasAny(["user"]);

Would be correct in preventing the data from existing in the document. It is not something that would allow it to exist but make it immutable.

See:

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure all cities have a positive population and
    // the name is not changed
    match /cities/{city} {
      allow update: if request.resource.data.population > 0
                    && request.resource.data.name == resource.data.name;
    }
  }
}

From:

https://firebase.google.com/docs/firestore/security/rules-conditions#data_validation

and:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document does *not*
    // contain an average_score or rating_count field.
    match /restaurant/{restId} {
      allow create: if (!request.resource.data.keys().hasAny(
        ['average_score', 'rating_count']));
    }
  }
}

From:

https://firebase.google.com/docs/firestore/security/rules-fields#forbidding_specific_fields_in_new_documents

Upvotes: 0

Itang Sanjana
Itang Sanjana

Reputation: 717

At "Writing conditions for Cloud Firestore Security Rules" section "Data validation" example #2

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure all cities have a positive population and
    // the name is not changed
    match /cities/{city} {
      allow update: if request.resource.data.population > 0
                    && request.resource.data.name == resource.data.name;
    }
  }
}

So request.resource.data.user == resource.data.user should work for you? CMIIW

Ref: https://firebase.google.com/docs/firestore/security/rules-conditions#data_validation

Upvotes: 4

Related Questions