I trying to verify if an user profile has an specific property in order to allow .write data in a path of my Firebase, but I haven't found the way.
This is the user profiles structure in my Firebase:
root
|--user_profiles
| |--uid
| | |--name
| | |--email
| | |--invite // How could I reach the properties of this path with my .validate rules?
And these are the rules I'm trying to make it work:
"invitations": {
".write": true,
".validate": "auth.provider == 'provider' && auth.id == '123456'",
"$invitation": {
".read": true,
"used": {
".validate": "root.child('user_profiles').hasChild(auth.uid).(...)" // I'm stuck here.
}
}
}
The idea is to allow .write in "used" if, and only if, the property "invite" match with my requirements (Whether they are equal or not, for example).
I'm really stuck in this and I don't want to make it works without understand it.
You can simply concatenate the paths to the invite:
".validate": "root.child('user_profiles/'+auth.uid+'/invite').val() === ???"
Replacing ??? with your criterium, such as data.val() (for the current value at this path), or newData.val() (for the value being set at this path).
Related
The following is the database structure and i want to make sure the owner of the comment whose user_id is part of the comment object to have read and write access to the comment and all other users have read access to comment and the ability to like it to increase like_count:
The following is the security rule I came up with:
{
"rules": {
"comments": {
".read": "root.child('users').child(auth.uid).val() != null",
".write": "(newData.parent().child('users').child(auth.uid).val() != null && newData.parent().child('comments').hasChildren().hasChildren().child('user_id').val() == auth.uid)",
"$commentId": {
".read": "root.child('users').child(auth.uid).val() != null",
".write": "(newData.parent().parent().child('users').child(auth.uid).val() != null && !newData.parent().parent().child('comments').hasChildren().hasChildren().child('like_count'))"
}
}
}
}
So, for read better will be:
".read": "root.child('users').hasChild(auth.uid)"
".write" is more complicated:
You need to only allow edit in like_count, and you need to allow for only one like per user
Proper way to do this will be expand structure of your "post", something like like_list, here or in other place to protect data transfer from growing when post will be download by client
So in "comment" node you want to allow every auth user to .write and .read, they will be able to add new comment then (upper rules will be fine for this).
For $commentId node rules will look like:
".write":".data.child('user_id').val() == auth.uid"
You can link like_list to a clinet by adding user_id to the "like / $ commentId" node and setting listener in firebase funtions for this node. adding new user_id will fire "write" event and then call function to secure increase the value of like_count.
You can archive it with a firebase-function and transaction.
https://www.tutorialspoint.com/firebase/firebase_write_transactional_data.htm
If you really don`t want to change schema then you need to secure every single child of comment, like:
"$commentID":{
"comment":{ ".write":"".data.parent().child('user_id').val() == auth.uid" ),
...//things allowed to edit by owner
"like_count":{
".write":"root.child('users').hasChild(auth.uid)",
".validate":"newData.val() == data.val() + 1"
}
}
But this will not secure post from giving more than one like per user.
I have a database node called (people) that looks like this:
people
|
|
-------UserID1 //which is a random id
| |
| |
| ----UserId2 //which is a random id
| |
| |
| name:"some_name"
| id:"UserId2"
| image:"image_url"
|
|
|
-------UserId2
|
|
----UserId3
|
|
name:"some_name"
id:"UserId3"
image:"image_url"
If we look at the (people / UserID1 / UserId2) node :
Since UserId1 and UserId2 are 2 random ids, then if we want to write a rule to UserId2 we will notice that it is 2 random id level deep.
What I want is to write a rule at this specified path that says these:
1) people / UserId1 : can be written by (UserID1) and (UserId2).
2) people / UserId1 : can be read by (UserID1) and (UserId2).
3) people / UserId1 / UserId2 : must end up with a newData that has (name, id, image).
How do I do this?
Thanks.
Due to the way Firebase Realtime Database rules cascade into deeper keys, allowing people/UserId1 to be writable by UserId2 is not advised, as this would allow UserId2 write access to the data of other users stored under people/UserId1 like people/UserId1/UserId3.
But using this trait, we can "add" users that are allowed read & write permissions as we go deeper into the data structure.
So the new conditions are:
people/UserId1 - UserId1 has read & write access
people/UserId1/UserId2 - UserId2 has read & write access
people/UserId1/UserId2 - must always contain 'name', 'id' and 'image' keys
people/UserId1/UserId3 - cannot be read/written by UserId2
{
"rules": {
"people": {
"$userId1": {
"$userId2": {
".read": "auth.uid == $userId2", // add $userId2 to those granted read permission, cascades into deeper keys
".write": "auth.uid == $userId2", // add $userId2 to those granted write permission, cascades into deeper keys
".validate": "newData.hasChildren(['name', 'id', 'image'])" // any new data must have 'name', 'id' and 'image' fields.
},
".read": "auth.uid == $userId1", // add $userId1 to those granted read permission, cascades into deeper keys
".write": "auth.uid == $userId1" // add $userId1 to those granted write permission, cascades into deeper keys
}
}
}
Lastly, if it is also required that people/UserId1/UserId2/id is equal to UserId2, you can change the ".validate" rule to enforce this:
".validate": "newData.hasChildren(['name', 'id', 'image']) && newData.child('id').val() == $userId2" // any new data must have 'name', 'id' and 'image' fields and 'id' must have a value of $userId2
When user1 --> user2 (user1 sends a request to user2). I am trying to perform certain checks using firebase rules, which are:
Check if username2 exists
Check if both users are not already friends
for that I've written the following rule:
{
"rules":
{
"requests":
{
"$requestId":
{
".read": "auth != null",
".write": "auth!=null &&
data.child('username2').val() == true &&
root.child('usernames-list').child( data.child('username2') ).exists() &&
!root.child('user-requests').child( auth.uId ).child('accepted-pending').child($requestId2).child( data.child('username2') ).exists()"
}
}
}
}
But it returns the following errors at two areas:
data.child('username2') ----> Line 9: child() expects a string argument.
data.child($requestId2) ----> Line 9: Unknown variable $requestId2
Data Structure:
requests
---$requestId
---key: value
user-requests
---$userId
---pending
---$requestId
---key: value
---accepted
---$requestId
---key: value
---rejected
---$requestId
---key: value
---unfriend
---$requestId
---key: value
---accepted-pending
---$requestId
---key: value
Incoming json:
user1 --> authId of user1
username1 --> username of 1st user
username2 --> username of 2nd user
...
Note 1: I've tried using newData. instead of data. and still get the same errors. Also, newData('username2').isString() also returns the same error
Note 2: I could avoid the $requestId2 (error) by making a separate list only containing the usernames that user1 is friends with and compare it against that; but I'd still need to be able to use data.('username2') as a field withing the root.child()
Edit 1: The following rule publishes successfully, however I'm not sure if it still does what I've mentioned (need to check) :
".write": "auth!=null &&
newData.child('username2').val() == true &&
root.child('usernames-list').hasChild( newData.child('username2').val() ) &&
!root.child('user-requests').child( auth.uId ).child('accepted-pending-usernames').hasChild( data.child('username2').val() )"
Ref:Firebase security - newData() as a parameter of hasChildren() expression
Edit 2: This should be the solution:
.val() has been added within the brackets
.exists() remains at the end
I've created a separate list of usernames (/accepted-pending-usernames/) to overcome the wildcard error
data has been replaced with newData
".write": "auth!=null &&
newData.child('username2').val() == true &&
root.child('usernames-list').child( newData.child('username2').val() ).exists() &&
!root.child('user-requests').child( auth.uId ).child('accepted-pending').child('accepted-pending-usernames').child( data.child('username2').val() ).exists()"
Note: Question may still remain unsolved as its unclear if firebase allows one to use multiple wildcards in the rules
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'])"
}
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.