Can you control individual properties in firebase realtime database security rules? - firebase

I need to control access to individual properties under a user object in the users directory in the firebase rtdb. My current rules are as follows:
{
"rules": {
"users": {
"$uid": {
"name": {
".write": "auth.uid === 'adminID' || $uid === auth.uid",
".read": "auth.uid === 'adminID' || $uid === auth.uid"
},
"plans": {
".write": "auth.uid === 'adminID' || $uid === auth.uid",
".read": "auth.uid === 'adminID' || $uid === auth.uid"
},
"survey": {
".write": "auth.uid === 'adminID' || $uid === auth.uid",
".read": "auth.uid === 'adminID' || $uid === auth.uid"
},
"survey_done": {
".write": "auth.uid === 'adminID' || $uid === auth.uid",
".read": "auth.uid === 'adminID' || $uid === auth.uid"
},
"notes": {
".write": "auth.uid === 'adminID'",
".read": "auth.uid === 'adminID'"
},
"status": {
".write": "auth.uid === 'adminID'",
".read": "auth.uid === 'adminID'"
}
}
},
"sending": {
".write": "auth.uid === 'adminID'",
".read": "auth.uid === 'adminID'"
}
}
}
The admin should be able to access all the data, but when I login as him on my web app, I get a permission_denied error.
Also, the user (not admin) should be able to see their name, plans, survey and survey_done properties but none of the others that are admin protected. When a user makes a read request to users/their-user-id I get the same error as above. Ideally it would just return all the properties the user does have access to, but the only way the user can access these are to read request to them individually.
The query I am using is:
firebase.database().ref("users").child(id).once("value")

When you query for the contents of a specific node in the database, security rules with either allow access to the entire contents of that node and all of its children, or reject all access. You can't use security rules to filter some of the children from view.
Typically what people do is split the node into two children, and protect each child separated, based on the roles required of the user. So, in your case, there would be one node that contains name, plans, survey, and survey_done. Then there would be another node for notes and status. Each container node would have different permissions.

Related

Firebase Realtime Database Insecure Rule Warning

I keep getting emails saying my database is not secure after implementing my security rules. The emails specify that any authenticated user can read/write to my database but I implemented specific access rules:
{
"rules": {
"posts": {
".read": "auth.uid !== null",
".write": "auth.uid !== null && newData.hasChildren(['score', 'quote',
'description', 'source', 'sourceType', 'ownerID', 'ownerImageURl', 'ownerUsername', 'timestamp', 'usersVoted'])",
".indexOn":["sourceType", "ownerID"],
"$postID": {
".write": "!data.hasChild('ownerID')",
"score": {
".write": "newData.isNumber() && (newData.val() === data.val() + 1 || newData.val() === data.val() - 1) && !root.child('posts').child('$postID').child('usersVoted').hasChild(auth.uid)"
},
"usersVoted": {
".write": "!data.hasChild(auth.uid)",
"$userID": {
".write": false
}
}
}
},
"users": {
".write":"auth.uid !== null && !data.hasChild(auth.uid)",
"$userID": {
".read": "auth.uid === $userID",
".write": "auth.uid === $userID"
}
},
"comments": {
".read": "auth.uid !== null",
"$postID": {
".write": "auth.uid !== null",
"$commentID": {
".write": false
}
}
}
}
}
Why does Firebase think that any user can read/write to any location in my database?
EDIT: I haven't gotten the email in a while so I think my rules are secure.
For example this rule for users node, is not secure:
"users": {
".write":"auth.uid !== null && !data.hasChild(auth.uid)",
"$userID": {
".read": "auth.uid === $userID",
".write": "auth.uid === $userID"
}
}
Because, this rule allows any authenticated user and non-existing user to write to your users node (not secure):
".write":"auth.uid !== null && !data.hasChild(auth.uid)",
and it overwrites this rule (as if this is meaningless now):
".write": "auth.uid === $userID"
To make it secure, users rule must look like this:
"users": {
"$userID": {
".write":"auth.uid !== null && !data.hasChild(auth.uid) && auth.uid === $userID",
".read": "auth.uid === $userID"
}
}
So fix your rules, and be careful of RULES CASCADING.

Firebase Database rules - wildcard for dynamic key

I have this usecase where I want to give permission to other users read data under my ID key only if there is a key === to their Id.
Let’s say I have user 123 and 456 who writes json object under “
orders-
I
-123
I
-456
And I want only user 123 and 456 to be able to read the 456 nodes. With the rules that I provide only 123 is able to read the node:
"orders": {
"$uid": {
".read": "auth.uid === $uid || root.child('orders').child($uid).child(auth.uid).exists()",
".write": "auth.uid != null",
}
}
What am I doing wrong here? Can anyone help me with this, please?
EDIT: Two screenshots of the simulator
I solved it setting these rules (note $id is nested in $uid):
{
"rules": {
"orders": {
"$uid": {
".read": "auth.uid === $uid",
".write": "auth.uid === $uid",
"$id": {
".read": "auth.uid === $id",
".write": "auth.uid === $id"
},
}
}
}
}
Previously I tought this line should do the work:
".read": "auth.uid === $uid || root.child('orders').child($uid).child(auth.uid).exists()",

Firebase Database Rules to match row

I am using a Firebase Realtime Database. I have the following data:
I also have the rules:
{
"rules": {
".read": "auth != null",
".write": "auth != null",
"chat": {
"$key": {
".read": "data.child('memberId1').val() === auth.uid && data.child('memberId2').val() === auth.uid",
".write": "data.child('memberId1').val() === auth.uid || data.child('memberId2').val() === auth.uid"
}
},
The initial rule works perfectly:
".read": "auth != null",
".write": "auth != null",
Problem
The following 2 rules have no effect.
"chat": {
"$key": {
".read": "data.child('memberId1').val() === auth.uid && data.child('memberId2').val() === auth.uid",
".write": "data.child('memberId1').val() === auth.uid || data.child('memberId2').val() === auth.uid"
}
},
As you can see, in order to test these rules, in the first rule, I have made an impossible condition of memberId1 and memberId2 both equal to the users uid. As a result I would expect it to fail.
If I remove:
".read": "auth != null",
".write": "auth != null",
and just have:
"chat": {
"$key": {
".read": "data.child('memberId1').val() === auth.uid || data.child('memberId2').val() === auth.uid",
".write": "data.child('memberId1').val() === auth.uid || data.child('memberId2').val() === auth.uid"
}
},
Then access is denied. Even if I change it to:
"data.child('memberId1').val() === 'h6qQg5YfQveTaCyBEXwDMSJPqwk1'
The following is also denied:
"chat": {
"Ko7w9XTtuRVN4p6CMp7": {
".read": true,
Question
How should I structure the rules to allow that a user may only access a row where their uid matches either memberId1 or memberId2?
Thanks
UPDATE
I have the following code:
findChats(): Observable<any[]> {
return this.af.database.list('/chat/', {
query: {
orderByChild: 'negativtimestamp'
}
}).map(items => {
const filtered = items.filter(
item => (item.memberId1 === this.me.uid || item.memberId2 === this.me.uid)
);
return filtered;
});
}
My question is similar to this one. I try the following with no success:
{
"rules": {
"chat": {
"$id": {
".read": true
}
},
Firebase rules are atomic. So if you try to read /chat (and thats what you are currently doing) it will only check the /chat branch rules. Since you dont have any rule in /chat it goes for the default thats is not giving access. Therefore, your rules would only be evaluated in case you were trying to read /chat/chatId.
One possible solution you could go for is to store a list of chats which each user is part of. So you can keep your current chat branch but store another branch in the database with the following structure:
user_chats: {
uid1: {
chatId1: true,
chatId2: false
}
uid2: ...
}
And rules:
"user_chats": {
"$uid": {
".read": "auth.uid === $uid",
".write": "auth.uid === $uid"
}
}
Then you could keep your chat rules like you already have them but first get the data from /user_chats/uid and then for each chatId retrieved you you will need to read on chat/chatId.

How prevent retrieve a child in same parent node?

I want to read all usernames from users parent node, because I'm using search feature in my app (if provided searchActive: true child in users node). But email need to be reachable only by owner.
I have just tried like below, but I'm still getting email. I'm worried about security not only email, What I'm missing and How Can I organize all of them?
"rules": {
"users": {
".read": "auth !== null",
"$uid": {
".write": "auth !== null && auth.uid === $uid",
".read": "auth !== null && auth.uid === $uid",
"username": {
".validate": "
!root.child('usernames').child(newData.val()).exists() ||
root.child('usernames').child(newData.val()).val() == $uid"
},
"email": {
".read": "auth.uid == 'facebook:'+$uid || auth.uid == $uid"
}
}
},
"usernames": {
".write": "auth !== null",
".read": "auth !== null"
},
First the reason why you can still access email is because rules cascade meaning when you set read to true for the parent node all the children can also be read. In your case:
"users": {
//Read is being set to true here for everything in this node
".read": "auth !== null",
"$uid": {
".write": "auth !== null && auth.uid === $uid",
//This will be ignored, since read was allowed already
".read": "auth !== null && auth.uid === $uid",
"username": {
".validate": "
!root.child('usernames').child(newData.val()).exists() ||
root.child('usernames').child(newData.val()).val() == $uid"
},
"email": {
//This will be ignored, since read was allowed already
".read": "auth.uid == 'facebook:'+$uid || auth.uid == $uid"
}
}
},
"usernames": {
".write": "auth !== null",
".read": "auth !== null"
},
I suggest you take some time to read all the documentation about firebase security. It can really help you avoid situations like this and perhaps give you some idea's about implementing a good security for your case.
A possible solution is to use a seperate username node where you store all the usernames for your search feature. You can use the rules to make sure everyone can read it but only the owner of a specific username can change it.

Access a node/path by two specific users only

I've read up and tried denormalization and while it makes perfect sense for cases like comments/messages that are accessible publicly while a user can only write to his own path/node, I'm having a hard time restricting ".read" rule to the user/owner AND to another user/admin. My use case doesn't publicly post all messages. To expound, for example, messages by user1 are only readable by user1 and admin, while still retaining write-only to user1.
How is this achieved? In security rules, I tried:
"messages": {
".read": "auth !== null",
".write": "auth !== null",
"$message": {
".read": "data.child('userID').val() === auth.uid"
}
}
OR
"messages": {
".write": "auth !== null",
"$message": {
".read": "data.child('userID').val() === auth.uid"
}
}
OR
"messages": {
"$user_id": {
".read": "auth.uid === $user_id"
".write": "auth.uid === $user_id"
}
}
While the last one does restrict reading and writing to the authenticated user, say user1, I had no luck getting the admin user to get all users' messages. The first, I can't circumvent the cascading/top-down rule.
I'm a firebase newbie so if this is really simple to do, I appreciate any helping hand.
Thanks!
This depends on how you define an administrator.
Let's first say that your admin is a known user with uid SOF.
"messages": {
"$user_id": {
".read": "auth.uid === $user_id || auth.uid == 'SOF'"
".write": "auth.uid === $user_id"
}
}
If you want the administrator to be configurable, you'll probably store their ID somewhere else, say in a node called administrators:
administrators
SOF: true
Frank: true
messages
...
In that case your can check if the current uid is either in the current node or it exists in the list of administrators:
"messages": {
"$user_id": {
".read": "auth.uid === $user_id || root.child('administrators/'+auth.uid).exists()"
".write": "auth.uid === $user_id"
}
}
Update
Since you want administrators to be able to read all messages, you'll end up with:
"messages": {
".read": "root.child('administrators/'+auth.uid).exists()",
"$user_id": {
".read": "auth.uid === $user_id",
".write": "auth.uid === $user_id"
}
}

Resources