Firebase Realtime database Rules Groups with shared data - firebase

I need to create a system where there are many groups.
The group can have multiple users.
The users can be part of multiple groups.
The users can have different permissions: read other members data, write other members data, change other members permissions
All member can read data only from the members of the group, only if they have the read other data permission (Groups/$group_id/Members/uid/canReadOthersData = true).
Every user it's already storing own data inside UserData path
I have written the following rules, but my problem is that i can't give Group member permissions to read other users data with the or ending part:
root.child('Group').child($group_id).child('Members').hasChild(auth.uid)
simply because it can't work with $group_id .
I did not found any function that give me the possibility to check if there is a uid inside Groups from the UserData group.
My constraint is that i already have UserData part and i can't change it, because already populated, but i need to define a Group parts around it.
Should i structure this in an other way? Like having an other path that it's defining for every user, the groups that it may be in?
{
"rules": {
"Groups": {
"$group_id": {
".read": "data.child('Members').child(auth.uid).exists()",
".write": "data.child(auth.uid).child('canChangeRegistry').val() == true",
"Members": {
".read": "data.child(auth.uid).exists()",
".write": "data.child(auth.uid).child('canChangeRegistry').val() == true",
"$uid": {
".read": "data.child(auth.uid).exists()",
".write": "auth.uid === $uid || data.child('canChangeRegistry').val() == true"
}
}
}
},
"UserData": {
"$uid": {
".read": "auth.uid === $uid || root.child('Groups').child($group_id).child('Members').hasChild(auth.uid)",
".write": "auth.uid === $uid "
}
},
}
}

There is no way to have the rules search through all groups for a membership match, as that wouldn't scale.
The only option I see is to store an additional list for each user with all other users that they share a group with, and then update that whenever you add users to groups and remove them.
With such a list in place, the lookup in security rules only depends on auth.uid and $uid, so can be something like:
root.child('UserBuddies').child(auth.uid).hasChild($uid)

Related

How to write Rules condition based on input data in Firebase Realtime Database

I have a Firebase Realtime Database which gets written to in a predicted structure.
Is there a way to verify using rules that the authenticated user's UID matches one of the data input values?
I know it is possible to validate this using values from the tree structure, but in my case I want to compare the UID to data in the input JSON.
For example, in my case, the input data is in the following JSON data structure:
{ "UserID": "SOME_UID", "UserName": "SOME_USERNAME", "CompletionMoves": 3, "CompletionTime": 12.345 }
And it is posted to a location which includes a level name and then the user's UID, akin to /Level3/SOME_UID.
I want to verify the user's UID equals the value of UserID in the data.
I thought of trying the following rule logic, but it fails on tests for some reason on the uid comparison line:
{
"rules": {
"$level": {
"$uid" : {
".read": "auth != null",
".write": "auth != null
&& newData.child($level).child($uid).child('UserID').val() == auth.uid",
}
}
}
}
Does anybody know what I am missing? Thanks in advance!
The newData and data variables in security are snapshots of the (new) data at the node where the rule is declared. So you don't need to build the entire path to UserID, and can instead refer to it directly.
So instead of:
&& newData.child($level).child($uid).child('UserID').val() == auth.uid",
Use:
&& newData.child('UserID').val() == auth.uid",
Instead of '.child($uid)', try '.child(auth.uid)'.
This works for me (if I've understood your question correctly).

firebase rule - retrieve only items where child has certain value

Is there a way to add a firebase security rule that prevents certain items in a collection from being read based on a value within each child item?
My example:
JSON:
orders{
orderA: {
name: x,
company:a
isDeleted: true
}
orderB: {
name: y,
company:a
isDeleted: false
}
}
It would be great to restrict users to be only able to read all orders where isDeleted === false
My Rule as I currently have (NOT WORKING):
"rules": {
"orders": {
".indexOn": "companyId",
".read": "auth !== null && data.child('isDeleted').val() === false",
"$ord": {
".write": etc
}
},...
The above doesnt work because "data" doesnt represent the right object - I can only use data inside the "$res" area.
If I remove "&& data.child('isDeleted').val() === false" it works but of course brings back both records.
My request is something like this, so the $res doesn't apply - as I'm getting ALL orders by companyId
http://mysite.firebase.io/orders?auth="xyz"&orderBy="companyId"&equalTo="a"
Is it even possible for a "retrieve all" type REST call like this and to filter out certain values via the firebase security rules? Am I just as well to retrieve all and then filter them out once I get them back in the front end??
Firebase's server-side security rules don't filter data. I highly recommend checking out the documentation, and some previous questions on this topic, as it's a very common misconception.
Instead the rules merely ensure that any read (in this case) operation, adhere to your requirements. So for your ".read": "auth !== null && data.child('isDeleted').val() === false", rule that means that the server checks if the user is logged in (they are), and that the node they are reading has a child isDeleted that is false. And since /orders/isDeleted does not exist, the read gets rejected.
You can securely allow access to only undeleted data by combining a query that only selects undeleted nodes with security rules that validate this query. Based on the example in the documentation on query based rules that'd look something like:
"rules": {
"orders": {
".indexOn": "companyId",
".read": "auth !== null &&
query.orderByChild == 'isDeleted' &&
query.equalTo == false"
}
}
This will work to get only non-deleted nodes. But since you can only order/filter on one property, you can't then also filter on companyId. You could allow that by introducing a synthesized isDeleted_companyId property, as shown in my answer here: Query based on multiple where clauses in Firebase

Firebase database rules, differentiate between create and update

I want to differentiate somehow between creating or updating a list with write rules. Any user can create a new chat, while only a user inside that chat should be able to update it. So basically, I want to have an update rule that checks in another denormalized list if that user is inside that chat before being able to update (similar if not equal to the read rule which works fine) without breaking the auth != null rule for create new chat.
"chats": {
"$chat": {
".write": "auth != null",
".read": "root.child('chats_by_user').child(auth.uid).child($chat).exists()"
}
}
Is there a way to do this?
An update usually means that newData will be different from existing data.
So the rule to only let the user update if he is inside the chat would be:
"newData.val() != data.val() && root.child('chats_by_user').child(auth.uid).child($chat).exists()"
And a create means that there is currently no data under that node.
So the rule to only allow create operations would be:
"!data.exists()"
Now putting it all together:
"chats":{
"$chat":{
".write":"auth!=null && ((newData.val() != data.val() && root.child('chats_by_user').child(auth.uid).child($chat).exists()) || !data.exists() )",
".read":"root.child('chats_by_user').child(auth.uid).child($chat).exists()"
}
}

Grant access to a user only if the node contains their uid?

So, I have an app that has a messaging feature, where two users can message each other. I am structuring my data in the "messages" node, where each node are the message threads between two users. Each node is named after the two uid's of the two users who are communicating, sorted alphabetically.
For example,
if user (dd77gg) and user (zz22ss) are in a conversation, the node would be named "dd77ggzz22ss". I know you can grant access in Security Rules by doing
{
"rules": {
"messages": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
But, in my case, the nodes are not simply named $uid, but rather two uid's merged together. So, my question is, how would I simply grant access, only if the current user's uid is found somewhere in the node name?
First of all do not sort your uids alphabetically, this may seem like a good idea but it is not deterministic. For example. Say the two uids were 'cat' and 'bca'. Sorting them alphabetically would give you 'aabcct'. Now suppose you have another two uids: 'cat' and 'cba'. If you were to sort them you will get the same concatenation of uids, you will get aabbcct. This will cause previous conversation data to be overwritten. Concatenating two uids is actually the best way to go. You do not need to sort them, you just have to have a determenistic way of concatenating them. Ie: who's uid should I place first.
Now to answer your question you could just use the contains method in the firebase rule.
{
"rules": {
"messages": {
"$uid": {
".read": "$uid.contains(auth.uid),
".write": "$uid.contains(auth.uid)"
}
}
}
}
You just check if it contains one of the users id, if it does then they can read it of course. I hope this helps.

Preventing child nodes from being removed in Firebase

I am running into a painful issue with Firebase security.
I would like an authenticated user to create children under a child node however not be allowed to delete any of the children.
Please see comments in 'used' node
security rules below:
"users": {
"$userid":{
".read": "$userid === auth.uid",
".write":" $userid === auth.uid && newData.exists()",
//writeable by user
"qrcodevalue":{},
"datesubscribed":{},
//not writeable by user
"confirmed":{".validate":false},
"issubscribed":{".validate":false},
"periodend":{".validate":false},
"stripeid":{".validate":false},
"stripesubscription":{".validate":false},
"subscriptionstatus":{".validate":false},
//user should be able to create children under this node but not delete
"used":{
"$promotionid":{
"dateused":{}
}
},
}
},
Any help would be greatly appreciated.
From the Firebase security documentation on new and existing data:
The predefined data variable is used to refer to the data before a write operation takes place. Conversely, the newData variable contains the new data that will exist if the write operation is successful. newData represents the merged result of the new data being written and existing data.
To illustrate, consider a rule that would allow us to create new records or delete existing ones, as long as data does not already exist at a given path, but not to make changes to the data:
// we can write as long as old data or new data does not exist
// in other words, if this is a delete or a create, but not an update
".write": "!data.exists() || !newData.exists()"
So for you that would translate to something like this:
//user should be able to create children under this node but not delete
"used":{
"$promotionid":{
"dateused":{
".write": "newData.exists()"
}
}
},
This allows the user to write any data to the node, but not delete it.
If you want them to only create but not change the data, it becomes:
".write": "!data.exists() && newData.exists()"

Resources