Firestore security rules: what happens with reques

2019-03-18 23:44发布

问题:

My team has discussed this lately, and can't seem to determine for sure actual/intended behavior:

If you have a security rule like the following:

match /categories/{document=**} {
    allow update: if request.auth.uid != null
    && request.resource.data.firstName is string
    && request.resource.data.lastName is string;
}

And you create an update statement from the frontend to /categories/ with the following data:

{
   firstName: 'A valid firstName'
}

Is the security rule then supposed to pass or fail?

In the reference documentation, it says that

Developer provided data is surfaced in request.resource.data, which is a map containing the fields and values. Fields not provided in the request which exist in the resource are added to request.resource.data

Related questions:

  1. Does this mean that the success/fail depends on the existing data in the node?
  2. What happens if someone tries to update with data not specified in the security rule, i.e. {age: 28}
  3. What is the recommended way to validate update data?

Question 3 with more details (schema question) Suppose you have a model like this:

interface Category {
  firstName: string;
  lastName: string;
  age?: int;
  groupId?: string;
}

Now we create a security rule like this:

match /categories/{document=**} {
    allow update: if request.auth.uid != null
    && request.resource.data.firstName is string
    && request.resource.data.lastName is string;
    && request.resource.data.age is int;
    && request.resource.data.groupId is string;
}

Then we have the following scenario, as I understand:

None of those scenarios fits well with optional properties. Because if you have to provide all properties (like in scenario 1) it is not really optional properties. And if you don't provide them, like in scenario 2, it fails.

Maybe I am missing something here, a basic guide on how to validate data with optional properties being written to firestore?

A security rule for an optional parameter, something like this:

match /categories/{document=**} {
   allow update: if request.auth.uid != null
   && request.resource.data.firstName is string
   && request.resource.data.lastName is string;
   && request.resource.data.age is int; // ignore if NOT provided
   && request.resource.data.groupId is string; // ignore if NOT provided
}

回答1:

Is the security rule then supposed to pass or fail?

That update will succeed if the document being updated already has a lastName field and this field is a string. (I'm assuming you're running this update while authenticated so that request.auth.uid != null returns true)

Answering the Related questions:

  1. Yes, sometimes it might depend on the existing data on the node.
  2. If that document already has a firstName and lastName set, adding the age field will succeed. Note that the rule only checks if these 2 values are strings. It doesn't specify that the document can't have more than 2 fields.
  3. That question sounds a little broad (kinda like a XY problem I would say). Please explain what kind of update validation you're trying to do. Based on your questions I already have an idea of what you're trying, but I want to be 100% sure.

Update:

What I understood from your updated question 3 is that you want to only update the document if the user provided both first and last names. The age and groupId are optional.

To do that, you can check if this request.resource.data.firstName is not the one already on the database using: resource.data.firstName != request.resource.data.firstName. So your security rules would look like this:

match /categories/{document=**} {
   allow update: if request.auth.uid != null
   && (request.resource.data.firstName is string && resource.data.firstName != request.resource.data.firstName)
   && (request.resource.data.lastName is string && resource.data.firstName != request.resource.data.firstName)
   && request.resource.data.age is int
   && request.resource.data.groupId is string
}

Now with these rules, an update with this data will fail:

{
   firstName: 'A valid firstName'
}

While these 3 will succeed:

{
   firstName: 'A valid firstName',
   lastName: 'A valid lastName'
}

{
   firstName: 'A valid firstName',
   lastName: 'A valid lastName',
   age: 20
}

{
   firstName: 'A valid firstName',
   lastName: 'A valid lastName',
   age: 20,
   groupId: 'groupId'
}

Update 2: To have age and groupId as optional fields, use the OR operator and the hasAll() function to check if the request has these fields:

match /categories/{document=**} {
   allow update: if request.auth.uid != null
   && (request.resource.data.firstName is string && resource.data.firstName != request.resource.data.firstName)
   && (request.resource.data.lastName is string && resource.data.firstName != request.resource.data.firstName)
   || (request.resource.data.keys().hasAll(['age']) && request.resource.data.age is int)
   || (request.resource.data.keys().hasAll(['groupId']) && request.resource.data.groupId is string)
}