realtime-database rule validation is not working when updating data with null - firebase-realtime-database

When I try to update data in realtime-database with null, ".validate" in database.rules.json seems to be ignored.
I have a database.rules.json in the following format (not the exact same way, but this shows what I expect at least).
database.rules.json
"data": {
".read": "auth != null",
".write": "auth != null",
".validate": "newData.val() != null"
}
When I update realtime-database with
frontend.js
import { set } from 'firebase/database';
export class PublishService {
...
setWithData(value) {
// suppose dbReference has ref to "data"
// something like ref(this.database, "data")
set(this.dbReference, value);
}
}
where value == null,
I can still update realtime-database with null even though this isn't what I expect.
Is this how realtime-database is supposed to work?
If that's the case, is there any documentation that says that?

The .validate rule is not triggered for data deletions. From the documentation on .validate rules:
the validate definitions are ignored when data is deleted (that is, when the new value being written is null).
So you'll want to check for newData.exists() in the .write rule.

Related

Firebase realtime DB rule based on data.child do not work

I like to retrieve the list of items only the ones its delete flag is false. but this setting does not work.
{
"rules": {
"items": {
".read": "data.child('isDelete').val() == false",
"$uid": {
".write": "auth != null && !data.exists()"
}
}
}
}
Here's a result from Rules playground request I tried.
Request details
{
"auth": null,
"resource": {
"key": "value"
},
"path": "/items",
"method": "get",
"time": "2022-07-07T09:24:31.042Z"
}
Result details
Line 4 (/items)
read: "data.child('isDelete').val() == false"
The data structure
items
- 1xxxxxxxxxx
title:"title text 1"
createdAt:"2022-06-05T04:21:57.322Z"
isDelete:false
- 2xxxxxxxxxxxxx
title:"title text 2"
createdAt:"2022-06-05T04:21:50.322Z"
isDelete:true
What is wrong?
I think you may be missing that rules are not filters on their own. All the rules do is check for any incoming operation whether it is allowed, and in your try from the playground you are trying to read all of /items, rather than just the items with isDelete set to false, to that isn't allowed.
There is no way to perform the necessary query from the playground, but in code you can get the items with this query:
ref.orderByChild("isDelete").equalTo(false)
Now the operation matches the condition in your rules, and is only trying to read data that it is allowed to, so the rules will allow the operation.
Update: I forgot that query-based rules in the Realtime Database actually require that you write the rule as this:
{
"rules": {
"items": {
".read": "query.orderByChild === 'isDelete' && query.equalTo === false",
...
Now the query only tries to retrieve non-deleted data, and the security rules correctly validate this.

How to make my Realtime database readable only by my users?

I am using the Firebase Realtime database only to know if I still have a connection to it like suggester here. So, there is nothing in it.
I thought the rules that I put was enough, but Google thinks it is not safe and I need to change it.
So, I went from:
{
"rules": {
".read": "auth != null",
".write": false
}
}
To this:
{
"rules": {
".read": false,
".write": false
}
}
That worked fine for my iOS app, but not with my Android app. Putting the 'read' to 'false' makes that solution not workable because of the solution suggested here.
What would you suggest me?
For those of you that had the same issue, here what Google tech support suggested me
You could modify this a bit like FirebaseDatabase.getInstance().getReference(“connect/” + (new Date()).toString()).keepSynced()
And in your Realtime Database rules, allow to read, write auth != null to this “connect” child.
It would look like this:
{
"rules": {
"connect":{
".read": "auth != null",
".write": "auth != null"
},
".write": false,
".read": false
}
}

Controlling data-update on Firebase

I am using Firebase for a small iOS project and I wonder if the following is possible.
Under a given node "myNode_123" I store data records; each record having the shape below:
[fieldStr: "ABC_xyz", fieldNum: 678]
A record is in fact a subnode.
Now my question is about updating the data, for already existing records.
Someone should be allowed to update a data record only if the new value for fieldNum is higher than the one already there.
And in any case the value for fieldStr should stay as it is.
I have already written some rules to make sure fieldNum is fullfilling my request.
But I still have the issue of fieldStr having the risk of being changed.
Here are my rules:
{
"rules": {
".read": true,
//".write": true
"myNode_123": {
"$Record": {
// Ensure that we are either creating a new record not yet existing,
// or that we are only updating for a higher fieldNum.
".write": "(!root.child('myNode_123/'+$Record).exists()) ||
(root.child('myNode_123/'+$Record).exists() && (newData.child('fieldNum').val() > data.child('fieldNum').val()))"
}
}
}
}
The control of fieldNum is working as I wish. But it is still possible to change fieldStr, which I do not want.
Any advice from a Firebase expert?
Add this under $Record.
{
"rules": {
".read": true,
//".write": true
"myNode_123": {
"$Record": {
// Ensure that we are either creating a new record not yet existing,
// or that we are only updating for a higher fieldNum.
".write": "!data.exists() || newData.child('fieldNum').val() > data.child('fieldNum').val()",
"fieldStr": {
".validate": "!data.exists() || data.val() == newData.val()"
}
}
}
}
}
The !data.exists() will make sure only new data can be written to this location. And data.val() == newData.val() will add an exception to allow writes when the new data is the same as the old data, just in case you want to write the entire object to the Database and include the fieldStr.

Firebase (Swift) runTransactionBlock: permission denied

May I ask what runTransactionBlock is doing behind the hood? When I run a simple setValue with the exact same rules it works, but not with runTransactionBlock. I suspect that behind the hood runTransactionBlock writes to paths outside of just the path I stated, which is causing my security rules to deny permission. Hence, I have to write a global ".write": "auth != null" and avoid doing stuff such as my wildcard ".validate": false.
My security rules are mapped out this way:
{
"rules": {
// NOTE: I NEED THIS GLOBAL WRITE ALLOW FOR TRANSACTION TO WORK
".write": "auth != null",
"real_db": {
// USERS
"users": {
"$user": {
".read": "auth != null",
"$other": {
// NOTE: I NEED TO COMMENT VALIDATE FOR OTHER FIELDS IN USER
// ".validate": false
},
"pushToken": {
".read": "auth != null",
".validate": "auth != null"
},
...
My runTransactionBlock is run on the path real_db/users/$uid and I am changing the value of pushToken. When setValue is run on this same path and modifying pushToken it works.

Firebase: How to enable users to decide on the privacy of their data?

Let's assume that users store some private data in /private/$userId which they can either share with others or not. The decision should be stored in /privacySettings/$userId/shareData which is of kind Bool. If the user sets its value to true others should be able to read the private data.
I have persistance enabled and tried to solve this with server rules:
"private": {
".read": false,
".write": false,
"$userId": {
".read": "auth != null && root.child('privacySettings/' + $userId + '/shareData').val() === true",
".write": "auth != null && auth.uid == $userId"
}
}
This works fine, but unfortunately a change in shareData does not raise an event when private/$userId is observed with .Value. So if the other user has observed this path before the change in the privacy, he will still see the data cached in the persistancy data store, which shouldn't be the case. When shareData is false all data should be hidden to others.
How to do this?
EDIT:
Just found out that once the data has been read, the observer will always return the cached data no matter if shareData has been set to false. This also happens when the app gets restarted.
EDIT 2:
After thinking more about it I came to the conclusion that this problem can easily be solved if the callback gave back a "permission denied" error.
I guess I found a reasonable workaround / solution for the problem:
Embedding the shareData in /private/$userId like so:
- private
- $userId
- shareData // Bool
- data // contains private data
Even if there is cached data, one can easily hide it according to the value of shareData without observing another node.
Server rules:
"private": {
".read": false,
".write": false,
"$userId": {
".read": "auth != null", //(*)
".write": false,
"shareData": {
".write": "auth != null && auth.uid == $userId",
".read": "auth != null",
},
"data": {
".read": "auth != null && root.child('private/' + $userId + '/shareData').val() === true",
".write": "auth != null && auth.uid == $userId",
}
}
}
EDIT:
Seems like the line marked with (*) overrides the child rules... Using "data.child('shareData').val() === true" there will cause the same effect as before: if there is cached data, it will be displayed.

Resources