Allow delete and write with Firebase Rules - firebase

I am using Firebase Realtime Database. And I have a structure to prevent to many children. I have 2 keys. One for the max(max_people), and one that contains how many children there are (registered_people).
My structure:
I have the max_people key. This is the max children that can be in the approved key. Registered_people is counting how many childs there in the approved key.
This is my security rule:
"registrations": {
"approved": {
".write": "(root.child('/agenda/activitys').child($room_id).child('max_people').val() > root.child('/agenda/activitys').child($room_id).child('registered_people').val())"
}
What I do at my firebase rules, is that I check if there not are to many rows. If there are to many I block the writing. This works as expected.
But now the problem, the max_people is for example 20, and the registered_people is also 20. No writes can happen. Because I have declared that. But how can I allow a delete than. Because I want to delete at every moment.
So in short, I want to delete no matter what. And I want to write with my current rule.
Some more information:
I add data to the key with the following code:
let data = [
"full_name": "test",
"email_addres": "test",
]
Database.database().reference().child("agenda/activitys/" + event + "/registrations/approved").child(Auth.auth().currentUser!.uid).setValue(data) { (error, ref) in
print("got from subscribing \(String(describing: error))")
if((error) != nil){
completionHandler(Result.failure(ApiError.noMorePlaces))
return
} else {
completionHandler(Result.success("succes \(event)"))
return
}
}
Adding works as expected.
And I delete data with the following code:
Database.database().reference().child("agenda/activitys/" + event + "/registrations/approved").child(Auth.auth().currentUser!.uid).removeValue { (error, ref) in
if((error) != nil){
completionHandler(Result.failure(ApiError.unknownError))
return
} else {
completionHandler(Result.success("succes \(event)"))
return
}
}
Deleting does not work as expected. I get a permission_denied error when there are for example a max of 20 keys. And that max is fully used (so I have 20 keys with data under the approved child).
Error:
[Firebase/Database][I-RDB038012] setValue: or removeValue: at /agenda/activitys/Jumping Fitness/registrations/approved/exCnADF43AdFUzGsi0GllEsZJZY2 failed: permission_denied
However deleting works when there a less than the max amount of keys.
Edit:
I have tested the suggested solution on the firebase rules simulator. I got the simulated write error there also.
I think that I have the solution. I delete on the key, so on the rules I need the key also. My solution:
"registrations": {
"approved": {
".write": "(root.child('/agenda/activitys').child($room_id).child('max_people').val() > root.child('/agenda/activitys').child($room_id).child('registered_people').val())",
"$key" : {
".write" : "!newData.exists()"
}
}

When data is being deleted, the newData variable will be null. So you can check if it exists using the exists() method:
"registrations": {
"approved": {
".write": "(root.child('/agenda/activitys').child($room_id).child('max_people').val() > root.child('/agenda/activitys').child($room_id).child('registered_people').val()) || !newData.exists()"
}
Update: After looking at the code you provided, I can see that you do the write operations against the child key. But have in mind that, according to the documentation:
Shallower security rules override rules at deeper paths.
This means that the write rule under "approved" will overwrite the write rule at "$key". So you should join both rules and keep them under the "$key" rule. Like this:
"registrations": {
"approved": {
"$key" : {
".write" : "(root.child('/agenda/activitys').child($room_id).child('max_people').val() > root.child('/agenda/activitys').child($room_id).child('registered_people').val()) || !newData.exists()"
}
}

Related

Accessing object property in Firebase Realtime Database ruleset

I store data about trips in a Firebase Realtime Database. I have defined some user groups. I want only users from group "vips" to read trips that have freePlaces == 0 and all the others to read the remaining trips.
I tried to set such access rules:
{
"rules": {
"trips":{
"$trip":{
".read": "root.child('users').child('vips').child(auth.uid).exists() || $trip.child('freePlaces') > 0"
}
}
}
}
but Firebase tells me $trip has no method/property 'child'.
I tried to do it in a different way
{
"rules": {
"trips":{
"$trip":{
".read": "root.child('users').child('vips').child(auth.uid).exists() || root.child('trips').child($trip).child('freePlaces') > 0"
}
}
}
}
but Firebase says Invalid > expression: left operand must be a number or string.
What went wrong?
I recommend playing close attention to the type of each expression in your rules:
$trip is a string, which explains why you can't call .child on it. If you want to get a child of the node, you can just do: data.child('freePlaces').
root.child('trips').child($trip).child('freePlaces') is a location in the database, which can't be compared with >. If you want the value of the node, use .val().
So combining these, your first snippet should be:
data.child('freePlaces').val() > 0

How do you get the top value of newData in Firebase Realtime Database rules?

New to firebase rules. I may be thinking about this all wrong, but anyway, I am trying to create a rule that makes sure that the newData being written to the database does not exist under the node it is being written. To write to the database I am using this code:
self.reference?.child("users").child(userid).setValue(data) {
(error, result) in
if (error != nil) {
completion(true, error!.localizedDescription)
} else {
completion(false, "")
}
}
Here is where I'd like to create the rule:
{
"rules": {
".read": false,
"users": {
".write": //allow write if the newData's keyBasedOnAuthID is not under students already
"$student_id": {
//more rules...
}
},
My incoming newData looks something like this:
{ keyBasedOnAuthID {
uid: auth.uid
name: xxxx
other:{
key: xxx
key: xxx
}
} }
I am writing this data to a node named 'users,' so the final path would be root.child('users').child(newData). The rule I would like to make would prevent users from writing duplicate data to the 'users' node. So I want to get the keyBasedOnAuthID value from the newData and check that it does not already exist.
Is this possible? I've tried many different ways to retrieve the keyBasedOnAuthID from the newData snapshot, but none have worked. Maybe I need a different approach? Any help would be appreciated!
Unfortunately, I'm not sure which language your using.
However, setValue doesn't create duplicates: either creates or updates data for the given path.
So, if you don't want to update existing data, you need to check for existence before calling your setValue method in your application code.

Firebase Database Delete Security Rules

I am currently creating Firebase security rules to prevent a user from setting a node to null and deleting all the data in that node.
This is my schema
{
"folder" : {
"item1" : {
"dataset1" : {
"data1" : 123,
"data2" : 456,
"data3" : 789
}
}
}
}
These are my rules
{
"rules": {
"folder": {
// users cannot delete items in the node folder
".write": "newData.exists()",
// item is a variable
"$item": {
"dataset1": {
// dataset1 must have certain nodes and can't be deleted (set to null)
".validate": "data.hasChildren(['data1', 'data2', 'data3']) && newData.exists()",
"data1": {".validate": "newData.isNumber()"},
"data2": {".validate": "newData.isNumber()"},
"data3": {".validate": "newData.isNumber()"},
// using the variable $other means any node that isn't data1, data2, data3 is denied
"$other": {".validate": false}
}
}
}
}
}
Using the built in simulator I'm getting these results:
This works when location is set to "/folder/item1" and "/folder/item1/dataset1"
If I had deeper nodes in data1 they would all be deleted because the write was allowed.
Thanks for taking a read. Open to any answers, preferably I don't need to change the schema.
It's not really clear to me what you're asking. But there are a few problems with your rules, so I'll just point those out in hopes that they answer your question.
you grant write access on /folder, which you cannot take away at a lower level.
As long as any data is left under /folder any write is allowed. A thing to keep in mind is that newData is the data at the location as it will exist after the write operation; it is not just the new data that is written.
I have the impression that you're trying to prevent the deletion with .validate rules. Keep in mind that validation is not performed when deleting data, so you cannot use .validate rules to prevent deletion.
I suspect that #2 is causing your current problem.

How to write to Firebase table only if its greater than current value in table

I need to update the table level value in Firebase score table only if the table value is less than the value currently being posted to update. So the KeyOfLevel value never decreases.
Since the client side involved too much calls to fetch and check. I need to do this in rules tab of Firebase. And I don't exactly know how to write those rules.
Eg:
score:
{
"1234567890":
{
CoinsKey: 1000,
KeyOfLevel: 5
}
}
where 123456789 is the user ID.
You might want to write security rules like this.
{
"rules": {
"score": {
"$userId": {
"KeyOfLevel": {
".validate": "!data.exists() || data.val() < newData.val()"
}
}
}
}
}
In place of .validate you can also use .write

Issue with security rules getting data via auth.id

I'm having an issue setting my Security rules properly, specifically reading the post data.
The data hierarchy goes:
posts : {
0 : {
title: "Post One",
userId: 6
}
},
users : {
6 : {
name: "My Name"
}
}
And my rules are:
{
"rules": {
"posts" : {
"$post": {
".read":"data.child('userId').val() == auth.id",
".write":"newData.child('userId').val() == auth.id"
}
},
"users":{
"$user": {
".read":"auth.id == $user",
".write":"auth.id == $user"
}
}
}
}
I know that the "auth.id" is 6, because it's pulling the rules correctly for my user info. If I change the rules to pull the number statically, it works:
"$post": {
".read":"data.child('userId').val() == 6",
".write":"newData.child('userId').val() == auth.id"
}
but using auth.id does not. Is there something I'm missing?
One thing to keep in mind is that security rules are type-safe. In particular, In the rules, "6" != 6 (since one is a string and one is a number). So perhaps your auth.id is "6" (as a string), but your userId is 6 as a number?
If that's the case, one potential fix would be changing your rule expression to something like:
data.child('userId').val() + '' == auth.id
which will force userId to be a string. Alternatively, you could change your data to make sure userId is always stored as a string.
You haven't included the code you're using to look up this data--probably where the error is--or the error you are receiving; those would help quite a bit.
Your rules should work fine, assuming you are attempting to read a single post at a time, and assuming your authentication is set up correctly.
A quick guess would be that you're trying to read the entire "posts" path, and using security rules to filter your posts. But security rules are essentially atomic. If you try to read "posts", and one of the posts has a rule that prevents read, the entire operation is going to fail.
Instead, you need to segment the posts into paths where all the data can be read by the authenticated user, then you can apply security rules accordingly.
One thing that will help immensely is to test your security rules by going into your Forge and using the "simulator". You can log in as any user, then try a read/write, and see exactly which security rules is failing and why.

Resources