Firebase multi location update handles security rules differently? - firebase

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

Related

Firebase Realtime Database Security Failing to Read Closure

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())

Firebase Securing Nodes in the Database

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"
}
}

Is this firebase rule redundant? When to use write vs validate?

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.

Understanding Firebase's rules for user-write, global-read

I am building a simple Firebase application with AngularJS. This app authenticates users through Google. Each user has a list of books. Anyone can see books, even if they are not authenticated. Only the creator of a book can edit it. However, individual users need to be able to record that they've read a book even if someone else added it.
I have rules.json like so:
{
"rules": {
".read": false,
".write": false,
"book": {
"$uid": {
".write": "auth !== null && auth.uid === $uid",
}
".read": true,
}
}
}
And I am trying to write a book simply with:
$firebaseArray(new Firebase(URL + "/book")).$add({foo: "bar"})
I get a "permission denied" error when trying to do this although I do seem to be able to read books I create manually in Forge.
I also think that the best way to store readers would be to make it a property of the book (a set of $uid for logged-in readers). ".write" seems like it would block this, so how would I do that?
"$uid": {
".write": "auth !== null && auth.uid === $uid",
"readers": {
".write": "auth !== null"
}
},
It seems like a validation rule would be appropriate here as well ... something like newData.val() == auth.uid, but I'm not sure how to validate that readers is supposed to be an array (or specifically a set) of these values.
Let's start with a sample JSON snippet:
"book": {
"-JRHTHaIs-jNPLXOQivY": { //this is the generated unique id
"title": "Structuring Data",
"url": "https://www.firebase.com/docs/web/guide/structuring-data.html",
"creator": "twiter:4916627"
},
"-JRHTHaKuITFIhnj02kE": {
"title": "Securing Your Data",
"url": "https://www.firebase.com/docs/security/guide/securing-data.html",
"creator": "twiter:209103"
}
}
So this is a list with two links to articles. Each link was added by a different user, who is identified by creator. The value of creator is a uid, which is a value that Firebase Authentication provides and that is available in your security rules under auth.uid.
I'll split your rule into two parts here:
{
"rules": {
".read": false,
"book": {
".read": true,
}
}
}
As far as I see your .read rule is correct, since your ref is to the /book node.
$firebaseArray(new Firebase(URL + "/book"))
Note that the ref below would not work, since you don't have read-access to the top-level node.
$firebaseArray(new Firebase(URL))
Now for the .write rules. First off is that you'll need to grant users write-access on the book level already. Calling $add means that you're adding a node under that level, so write-access is required.
{
"rules": {
"book": {
".write": "auth != null"
}
}
}
I leave the .read rules out here for clarity.
This allows any authenticated user to write to the book node. This means that they can add new books (which you want) and change existing books (which you don't want).
Your last requirement is most tricky. Any user can add a book. But once someone added a book, only that person can modify it. In Firebase's Security Rules, you'd model that like:
{
"rules": {
"book": {
".write": "auth != null",
"$bookid": {
".write": "!data.exists() || auth.uid == data.child('creator').val()"
}
}
}
}
In this last rule, we allow writing of a specific book if either there is no current data in this location (i.e. it's a new book) or if the data was created by the current user.
In the above example $bookid is just a variable name. The important thing is that the rule under it is applied to every book. If needed we could use $bookid in our rules and it would hold -JRHTHaIs-jNPLXOQivY or -JRHTHaKuITFIhnj02kE respectively. But in this case, that is not needed.
First off the "permission denied" error. You are getting this error because you are trying to write directly in the "book" node instead of "book/$uid".
Example of what you do now:
"book": {
"-JRHTHaIs-jNPLXOQivY": { //this is the generated unique id
"foo": "bar"
},
"-JRHTHaKuITFIhnj02kE": {
"foo": "bar"
}
}
In your rules you have a global rule for write set to false so that will be the default and next to that you have made a rule for the specific node book/$uid. So when trying to write directly in "book" it will take the default rule that was set to false. Have a look at Securing your data for more information about firebase rules.
And for the last part of your question i suggest you take a look at Structuring data for more information about the best ways to structure your data inside firebase.
So taka a good look at what and how you want to save and write in firebase and make sure your rules are structured accordingly.

Why don't .validate security rules get run when data is removed from Firebase?

My .validate rules don't seem to get executed when I'm deleting data from Firebase. Is this a bug or intentional behavior?
In general, .validate rules are used to define what the data should look like if it exists. They are intentionally not run when the data is deleted. You should instead use .write rules to make decisions about whether the write (or delete) should be allowed.
For example, consider these rules for securing a simple chat room, where users can create new chat rooms but only delete ones for which they are the owner:
{
"rules": {
".read": true,
"$room": {
".write": "auth != null && (
(!data.exists() && newData.child('owner').val() == auth.uid) ||
(!newData.exists() && data.child('owner').val() == auth.uid))",
".validate": "newData.hasChildren(['owner', 'name'])",
"name": {
".validate": "newData.val().length > 10"
},
"messages": {
".write": "auth != null"
}
}
}
}
The .write rule is used to decide if a user has permission to create / remove a chat room, and the .validate rules are used to define what a chat room should look like if it exists.
If the .validate rules were run on deletes, you could never delete a chat room (since both .validate rules would fail). Hence the decision not to run .validate rules on deletes.

Resources