I am writing a security rule in firebase to validate that either a post exists or a group exists. I fully understand that security rules are not filters and I am not using it as a filter, but as a validation metric. This is the rule I am trying to implement...
"notifications": {
".read": "auth !== null",
".write": "auth !== null",
".indexOn": ["post_ID", "group_ID"],
"$userID": {
"$notifID": {
".validate": "newData.hasChildren(['has-seen', 'end-time', 'time', 'user_ID', 'username']) && $userID !== newData.child('sender_ID').val() && (root.child('groups').child(newData.child('group_ID').val()).exists() || root.child('follower-feed-storage').child(newData.child('post_ID').val()).exists())",
".write": "auth.uid !== $userID && !data.exists()",
}
}
},
Clearly, the rule should evaluate the hasChildren and $userID !== newData.child('user_ID').val() condition properly which it does. However, when it should evaluate the last expression after the && and inside the parenthesis, it will either succeed or fail based on the first condition inside the parenthesis and not perform the or operator and evaluate the second expression. Clearly I am doing something wrong with the syntax but I cant figure it out. Any help is appreciated in advance.
You must pass a string to .child so if newData.child('group_ID').val() evaluates to null or undefined, then it will fail. Try casting the result of that to a string or test whether that exists before looking it up. (I recommend the latter.)
(newData.child('group_ID').exists() && root.child('groups').child(newData.child('group_ID').val()).exists())
Related
I've stopped to receive emails from Firebase telling me that my realtime database has insecure rules. Here is the beginning of the rules I have set:
{
"rules": {
"aaa":{
".read": "auth != null",
".write": "auth != null",
},
"bbb":{
".read": "auth != null",
".write": "auth != null",
},
//..... rest of the rules.
}
}
Here "aaa" and "bbb" are the nodes I use in my Firebase realtime databse. So you should mention all of yours.
Is this solution suitable?
Your security rules should only allow what your application code does, and nothing more.
So if your code writes directly to aaa and/or bbb and writes whatever data it wants there, then your rules match that.
But typically your code will write data of a specific structure, in which case you should validate that structure with validation rules.
Also: does your code really need to delete or replace the entire aaa and/or bbb node? Or does it only need to append new child nodes to it? Because right now, your rules allow any user to take the configuration from your application and then do firebase.database().ref("aaa").remove() and wipe whatever is under it. Again: if that matches with what your application does, then the rules match that. But... it seem unlikely.
Always try to include User UID for more security.
"aaa":{
"$uid": {
".read": "auth != null && auth.uid === $uid",".write": "auth != null && auth.uid === $uid"
}
}
EDIT: Figured it out. Multi-location updates work not like updates as far as they seem to overwrite pre-existing values.
I have a hard time figuring out security rules for multi location updates. Until now I had this code
return this.userProfileRef.child(firebase.auth().currentUser.uid).update(profileData);
to update a user profile in fb, where profileData is an object containing some fields.
when I try to do the same operation, but written in a way that I can add more write operations (multi-location update), I get a validation error.
var updateData = {};
updateData['users/' + firebase.auth().currentUser.uid] = profileData;
return firebase.database().ref().update(updateData);
my security rules are
{
"rules": {
".write": "true",
".read": "true",
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "!data.exists() || ( data.exists() && auth.uid === $uid )",
"profileStatus": {
".validate": "!data.exists() || (newData.parent().hasChildren(['firstName', 'dateOfBirth', 'gender', 'lookingForGender', 'lookingForAgeMin', 'lookingForAgeMax', 'lookingForRadius']) && data.val() === 'incomplete' && newData.val() === 'awaitingVerification')"
}
}
}
}
Validation fails in profileStatus, when I do the multi-location update as written above, but passes when I do a 'normal' update.
Can someone help out and tell me what I'm missing here. Does fb handle multi-location updates differently when it comes to security rules?
Thanks.
Okay, to answer my own question. It seems that multi-loation update works more like a multi-lcoation set, so it overwrites existing values which caused my validation fail.
I'm still confused by this beviour… following the firebase blog here I thought, it would wor like an update.
In my example, i have a pre-existing key called dateOfBirth. This key gets deletd when I run the multi-update
I have this data structure.
root
-foo
-key0
-bar1:baz1
-bar2:baz2
-key1
-bar1:baz1
-bar2:baz2
And I have this rules structure.
"foo":{
".read":true,
".write":"auth != null",
".validate":"newData.hasChildren(['bar1', 'bar2'])"
}
But when I tried to do this firebase.database().ref('/foo').remove() or this firebase.database().ref('/foo').set(null); on the javascript console, the .validate on rules is not being respected and the data on the foo node is being deleted.
Hod do I secure database nodes that are not associated to a certain user?
To prevent /foo from being removed, you can check that newData.val() is not null:
"foo": {
...
".write": "(auth != null) && (newData.val() != null)",
...
}
Validation step is completely skipped because write was allowed.
Note: The .validate rules are only evaluated for non-null values and do not cascade.
Read the following section with great useful video from firebase documentation for securing your data.
https://firebase.google.com/docs/database/security/
You should modify your rules.
"foo":{
".read":true,
".write":"auth != null && newData.exists()",
".validate":"newData.hasChildren(['bar1', 'bar2'])"
}
I use Firebase. Here is what one of my nodes looks like:
--Users
----RandomUID1
----RandomUID2
----RandomUID3
For Rules, this is what I have:
--"users" : {
----".read": "auth != null && !root.child('blockedUsers').hasChild(auth.uid)",
----".write": "auth != null && !root.child('blockedUsers').hasChild(auth.uid)"
--}
What I would like is for reading from the users node to stay the same, but I would like for writing to only be allowed on children of the users node. The reason for this is it would be catastrophic if I accidentally wrote code one night that deleted the entire users node. Is it possible to make a rule so that writing on the node itself is not allowed but writing to child nodes is allowed?
Is there a way to secure myself from such an accident that might occur because of human error?
You can use variables, Firebase variable rules
.At the users node i didn't add a write rule, which doesn't allow for data modification.
"users":{
".read": "auth != null && !root.child('blockedUsers').hasChild(auth.uid)",
"$uid": { //where $uid is child
".read": "auth!==null && auth.uid == $uid",
".write": ""auth!==null && auth.uid === $uid"
}
}
I read through the docs, and I'm not sure of the difference between the write-rule and validation-rule section. Is this code redundant? Any point of using one or the other, or both?
Specifically:
"validates" to say "user must be logged in, and the value written must be the uid.
"write" permission says you can only write to the $user_id section if the value matches your uid.
{
"rules": {
"users": {
".validate": "auth != null && newData.val() === auth.uid",
"$user_id": {
".write": "$user_id === auth.uid"
}
}
}
}
The only difference is that .validate doesn't propagate to its children.
Answering to your question, in your example you could use just ".write".
".write": "auth != null && $user_id === auth.uid"
Technically there might not be (except propagation) any difference but in my opinion there is a difference.
For example Security is the primary concern of the .write while data integrity is the primary concern of the .validate.
For example:
Consider we are writing to /books/{bookKey}/name path.
Is user allowed to write to this path is .write's concern. Checks like user logged in can be performed here.
Is user is entering the right name for the book? Is .validate's concern. For example name should be more than 3 characters long, must start with characters...
P.S.
Also considering this issue with multi-line, it is good to have two separate methods for more readability.