Firebase Security Rules Nested $ Variables - firebase

In my Firebase database I have a node eventdata contains a list of data elements related to a certain event.
Each event item has an event_log node where all the logging of that certain event is done.
The goal is to allow users only access to part of the event_log items. (e.g. the items they created themselves)
When the respective part in the Security Rules looks like this, everything works fine, but no access control on individual items is possible.
"eventdata":{
"$event":{
"event_log":{
".read": " auth != null",
".write":" auth != null"
}
}
}
Changing the code such that it uses another $ variable (but still should allow access from all authenticated users) results in not being able to access the event_log.
"eventdata":{
"$event":{
"event_log":{
"$logitem":{
".read": " auth != null",
".write":" auth != null"
}
}
}
}
Isn't it possible to 'nest' $ variables in the Security API? I couldn't find an answer to this in the Firebase Docs. Is there any other solution other than having to restructure my code?
Thanks in advance

Related

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

How to only allow one field to be written in firebase realtime database security rules?

I want write access to only 1 field that I decide in security rules. I tried with this :
"users":{
".read":"auth !== null",
"$user_id" : {
".write":"newData.children.size() === 1 newData.hasChild('isConnected') && newData.child('isConnected').isBoolean()", // i know this is a little weird, but for prescence, it needs to be able to write to the user enpoint even if the token expired
}
But I get this error: No such method/property 'children'.
Define the rule on the specific field itself, not the parent. Make sure that the parent does not allow writes anywhere, then enable writes for individual fields on their own terms:
"users":{
".read": "auth !== null",
"$user_id": {
"isConnected": {
".write": "newData.isBoolean()"
}
}
}
If you're comparing security rules with Firestore (which does let you get a list of fields in a document), you should know that Firestore doesn't let you express individual rules per field, which is why you have to check for them individually in the rule for the overall document. Realtime Database does let you specify rules for arbitrarily nested children, so you can take advantage of that here.

Firebase Rules: Read restriction for dynamic child nodes

I'm trying to implement a Firebase rules read restriction in a data model that has a few nested dynamic child nodes.
I have the following data model:
/groupMessages/<groupId>/<messageId>/
{
"senderId": "<senderId>",
"recipientId": "<recipientId>",
"body": "..."
}
groupId, messageId, senderId and recipientId are dynamic ids. I would like to attach a listener to the /groudId node to listen to new messages. At the same time I only want users to read the message where the senderId or recipientId matches a corresponding auth.token value.
Due to Firebase cascading rules, if I allow the read at the groupId level without restrictions, I can't deny them on the message level.
{
"rules": {
"groupMessages"
"$groupId": {
".read": "auth != null"
}
}
}
}
I also haven't found a way to restrict the read rule on the groupId level to check for sender/recipientId of a message.
Any suggestions greatly appreciated.
As you've found, security rules cannot be used to filter data. But they can be used to restrict what queries can be performed on the data.
For example, you can query for all messages where the current user is the sender with:
var query = ref.child("groupMessages").child(groupId).orderByChild("senderId").equalTo(uid);
And you can secure access to the group's messages to only allow this query with:
{
"rules": {
"groupMessages": {
"$groupId": {
".read": "auth.uid != null &&
query.orderByChild == 'senderId' &&
query.equalTo == auth.uid"
}
}
}
}
The query and rules now exactly match, so the security rules will allow the query, while they'd reject a broader read operation. For more on this, see query based rules in the Firebase documentation
You'll note that this only works for a single field. Firebase Database queries can only filter on a single field. While there are workarounds by combining multiple values into a single property, I don't think those apply to your scenario, since they only work for AND queries, where you seem to want an OR.
You also seem to want to query on /groupMessages instead of on messages for a specific group. That also isn't possible: Firebase Database orders/filters on a property that is at a fixed path under each child of the node where you run the query. You cannot query across two dynamic levels, as you seem to be trying. For more on this see: Firebase Query Double Nested and Firebase query if child of child contains a value.
The common solution for your problem is to create a list of IDs for each user, which contains just the IDs of all messages (and/or the groups) they have access to.
userGroups: {
uid1: {
groupId1: true,
groupId2: true
},
uid2: {
groupId2: true,
groupId3: true
}
}
With this additional data structure (which you can much more easily secure), each user can simply read the groups they have access to, and your code then reads/queries the messages in each group. If necessary you can add a similar structure for the messages themselves too.
Finally: this type of recursive loading is not nearly as inefficient as many developers initially think, since Firebase pipelines the requests over an existing connection.

firebase security api read permission denied

I found a neat little example for permission based chat rooms using firebase security api here
Notice the "chat": {
// the list of chats may not be listed (no .read permissions here)
I actually need to list the chats a user belongs to when I load their inbox, however I can't seem to get the .read rule correctly.
Ive tried using the following rule which makes total sense but doesn't work:
"convos": {
".read" : "auth != null && data.child('users').hasChild(auth.id)",
I suspect the problem is that there is still a level between convo and users.. aka would make more sense to do:
"convos": {
".read" : "auth != null && data.child($key + '/users').hasChild(auth.id)",
$key : { ... }
But that's not allowed is complains about $key not existing yet.
How can I allow a user to pull all the convos they belongs to using this setup?
You can't use security rules to filter data. Generally, your data structure will be fairly dependent on your specific use case--most directly on how the data will be read back.
A general solution is to list the chats your user belongs to separate from the bulk chat data, i.e. to heavily denormalize, and access the chats individually.
/messages/$chat_id/... (messages chronologically ordered using push() ids)
/chats/$chat_id/... (meta data)
/my_chats/$user_id/$chat_id/true (the value here is probably not important)
Now to access all of my chats, I could do something like the following:
var fb = new Firebase(URL);
fb.child('my_chats/'+myUserId).on('child_added', function(snap) {
var chatID = snap.name());
loadChat(chatID);
});
function loadChat(chatID) {
fb.child('messages/'+chatID).on('child_added', function(snap) {
console.log('new message', chatID, snap.val());
});
}
You would still want security rules to validate the structure of chat messages, and access to a users' chat list, et al. But the functionality of filtering would be done with an index like this, or by another creative data structure.
I'm not completely sure how you're structuring your Firebase, but this might work:
"convos": {
$key : {
".read" : "auth != null && data.child('users').hasChild(auth.id)",
...
}

Unable to add new 'user' row to Firebase Users collecton using BackFire Collection

I'm using Backfire Collection trying to add a new user row to a custom "Users" collection in Firebase, after the user has registered successfully using FirebaseSimpleLogin.
Here are my security rules:
"users": {
"$user_id": {
".read": "auth.id === $user_id",
".write": "(!data.exists() && (newData.child('id').val() + '' === auth.id)) || ($user_id === auth.id)"
}
The security rule should accomplish the following:
Allow a newly registered user to create a new 'User' row, so his personal information is stored under the URL "http://myurl.firebase.com/users/1/".
A logged-in user can only read his own user row.
A logged-in user can only update his own user row.
However, I'm getting permission denied using the following code:
var FireUsers = Backbone.Firebase.Collection.extend({
model : Backbone.Model,
firebase: new Firebase(FirebaseURL + '/users/')
}),
fireUsers = new FireUsers();
fireUsers.add(newUserObj);
I tried it in the simulator and the only way I'm able to satisfied the above 3 conditions is to set the security rule to "auth != null", which is obviously not ideal.
Any help is appreciated!
-Tony
If a logged-in user can only read their row in the users table, then you cannot create a collection for the entire users collection (since that would mean that everyone could read the users table). Creating a new instance of Backbone.Firebase.Collection for a particular URL will immediately try to download all the data at that URL, which will fail because of the security rules.
I recommend using Backbone.Firebase.Model instead, and creating a new instance for the user, directly at the URL with the user ID included.

Resources