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:
{age: 28}
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
}
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:
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.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)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With