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.
Related
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.
i want to write firebase realtime database rule where certain user can only write cetain key
ex: user with UID underlined in red can write value of key franchise_active only
user with UID underlined in green can write value of key vendor_active only
both users can read
Sounds possible. Something like this should work:
{
"rules": {
"application_status": {
"$uid1": {
"$uid2": {
"franchise_active": {
".write": "auth.uid === $uid1"
},
"vendor_active": {
".write": "auth.uid === $uid2"
}
}
}
}
}
}
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
}
}
So I have my Database structured like this, the owner gets set when the group is created and the owner the should have the permission to add other Users to allowed so they can access and edit the data too.
-Groups
|-Groupname
|- Owner: string
|- Allowed: List<string>
|- Data: all the data
So my attempt were these rules but they dont work when I use the playground feature with a saved uid under owner or allowed:
"Groups" : {
"$group": {
".read": "auth != null && (data.child('Owner').val() === auth.uid || data.child('Allowed').val() === auth.uid)",
".write": "auth != null && (data.child('Owner').val() === auth.uid || data.child('Allowed').val() === auth.uid)"
}
}
And would a User still be able to create a new group when these rules would work?
Pictures of the Database and Errors:
First, in the Realtime Database, avoid using arrays and use a map instead.
Change this:
"Allowed": {
"0": "8ZiQGBPFkiZOLgLJBgDeLw9ie9D3",
"1": "KEuhrxnAWXS0dnotjhjFAYUOcm42",
"2": "48yULftKSxgyS84ZJC4hs4ug4Ei2"
}
to this:
"Allowed": {
"8ZiQGBPFkiZOLgLJBgDeLw9ie9D3": true,
"KEuhrxnAWXS0dnotjhjFAYUOcm42": true,
"48yULftKSxgyS84ZJC4hs4ug4Ei2": true
}
Read that linked blog post for more info, but in short, it makes adding/removing users really simple:
const groupRef = firebase.database.ref(`Groups/${groupId}`);
// add a user
groupRef.child("E04HLbIjGDRUQxsRReHSKifaXIr2").set(true);
// remove a user
groupRef.child("KEuhrxnAWXS0dnotjhjFAYUOcm42").remove();
You can also change true to whatever you want. Here are some examples:
false = participant, true = moderator
false = read-only, true = can edit
Role names: "member", "admin", "moderator", etc.
Privilege levels: 0 (member), 500 (moderator), 1000 (owner), etc. (make sure to space these out, you don't want to have to add in a level between 0 and 1 and have to edit your entire database).
The most important point though, is that Realtime Database security rules don't know about arrays. data.val() won't return an array, it will just return a sentinel value that says "non-null object is here!". This means a map is necessary for security rules.
This reference document covers the structure and variables you can use in your Realtime Database Security Rules.
With your proposed rules, you attempt to allow any user in the group to be able to write to the group's data - but you don't manage what they can and can't write to. Any malicious member of a group could add/delete anyone else, make themselves the owner, or even delete the group entirely.
{
"rules": {
"Groups" : {
"$group": {
// If this group doesn't exist, allow the read.
// If the group does exist, only the owner & it's members
// can read this group's entire data tree.
".read": "!data.exists() || (auth != null && (data.child('Owner').val() === auth.uid || data.child('Allowed').child(auth.uid).val() === true))",
"Owner": {
// Only the current owner can write data to this key if it exists.
// If the owner is not yet set, they can only claim it for themselves.
".write": "auth != null && (data.val() === auth.uid || (!data.exists() && newData.val() === auth.uid))",
// Force this value to be a string
".validate": "newData.isString()"
},
"Allowed": {
// Only the owner can edit the entire member list
// For a new group, the owner is also granted write access
// for it's creation
".write": "auth != null && (data.parent().child('Owner').val() === auth.uid || (!data.exists() && newData.parent().child('Owner').val() === auth.uid))",
"$member": {
// Allows the user to remove themselves from the group
".write": "auth != null && auth.uid === $member && !newData.exists()",
// Force this value to be a boolean
".validate": "newData.isBoolean()"
}
},
"Data": {
// The owner and members can edit anything under "Data"
// Currently this includes deleting everything under it!
// For a new group, the owner is also granted write access
// for it's creation
// TODO: tighten structure of "Data" like above
".write": "auth != null && (data.parent().child('Owner').val() === auth.uid || data.parent().child('Allowed').child(auth.uid).val() === true || (!data.exists() && newData.parent().child('Owner').val() === auth.uid))"
}
}
}
}
}
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.