Firebase Realtime database rule to avoid duplicate value - firebase

I have the following structure in Firebase Realtime database :
{
"e_pass": [
{
"address": "M12, AV Colony",
"epass_id": "RPR/0003",
"epass_vehicle_id": "na",
"from_city": "Raipur",
"from_date": "31-03-2020",
"from_district": "Raipur",
"from_location": "AV Colony Mowa",
"isApproved": "1",
"mobile_number": "99999999",
"time_stamp": 1585577315093,
"user_name": "John Doe",
"vehicle_number": "CG04HX1234"
}
]
}
I am trying to apply the following rule to prevent the duplication of key mobile_number:
{
"rules": {
"e_pass": {
".read": true,
".write": {
"$mobile_number": {
".write" : "!data.parent().hasChild($mobile_number)"
}
}
}
}
}
but it keeps failing. What should be the rule?

With the way you have your data structure now, it's not possible. In general, it's not possible to use security rules to ensure uniqueness of child values among sibling nodes.
Your alternative is to write the value that should be unique as a node name (not a child value) somewhere else in the database that can be queried by security rules to check for existence.
For example, if you had a node called "mobile_numbers", you could populate children under it every time a new e_pass child is added:
"mobile_numbers": {
"99999999": true
}
Then you check for its existence in a rule like this:
root.child('mobile_numbers').hasChild($mobile_number)

Related

How does .indexOn works with users and unique keys?

I am confused with index rules in firebase. I have this database and I wanted to extract distance_traveled in battery_and_cables. The thing is, 1 is something like a userID set by the user itself so that will vary.
I used this code in pyrebase to extract the distance_traveled
db.child("mileage_maintenance").order_by_child("distance_traveled").get()
but I get
Index not defined, add ".indexOn": "distance_traveled", for path "/mileage_maintenance", to the rules
This is my database:
Any my rules:
{
"rules": {
".read": true,
".write": true,
"mileage_maintenance": {
"battery_and_cables":{
".indexOn": ["distance_traveled"]
}
},
}
}
Is there any way you can go through the 1 and the push key to acquire the distance_traveled?
Firebase Realtime Database queries work on a flat list of the nodes under the location where you run the query. So in your case, the query considers the direct children of the mileage_maintenance node (which is just the node 1 in the screenshot).
You can have wildcards in your rules, like in your case:
{
"rules": {
".read": true,
".write": true,
"mileage_maintenance": {
"$id": {
"battery_and_cables":{
".indexOn": ["distance_traveled"]
}
}
}
}
}
As you can see, I added a $id level here for the 1 node. That now means that you have an index defined on each /mileage_maintenance/$id/battery_and_cables node for the distance_traveled property of each child node under that.
So with this index, you can query a specific /mileage_maintenance/$id/battery_and_cables path for the distance_traveled values. But you can't run a query on just /mileage_maintenance for all distance_traveled values under it.
If you need such a query, you will need to modify/augment your data model to allow it. For example, by introducing a flat list of distance_traveled nodes that you can then query:
"distance_traveled": {
"1000000": "1/battery_and_cables/-M01CT...1hMj"
}
Or
"distance_traveled": {
"1~battery_and_cables~-M01CT...1hMj": 1000000
}
Where the ~ in that last model is just a separate for the segments in that key, since / isn't allowed.
Also see:
Firebase Query Double Nested
Firebase query if child of child contains a value

Allow delete and write with Firebase Rules

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()"
}
}

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

Can one use .validate directives to synchronize schema in Firebase?

In the Firebase security rules file, could one effectively use the '.validate' directives to synchronize schema, that is, make sure when one part of the Firebase is modified, another part gets updated as well? Or is this a bad/invalid idea?
Basically what I'm trying to do is create a Todo list app. I want to be able to create tasks in my todo list with specified tags. For each tag in the app, there should be a respective tag child field in the "tags" field in my Firebase backend. The tag field should contain an ordered list of references to tasks in the "tasks" field. So, if a task is created with "#foo" tag, then it's name (id) should be referenced in "tasks/foo". Also, whenever a task is removed, it's reference in each tag should be removed, and when a tag in has no children it should be removed.
To clarify here's an example of the schema:
{
"tasks": {
"-sdjfaowneono": {
"headline": "Get some milk",
"completed": false,
"tags": {
"0": "all",
"1": "shopping"
}
},
"-asdfhsadfsafwirn": {
"headline": "Pick up the kids",
"completed": false,
"tags": {
"0": "all"
}
}
},
"tags": {
"all": {
"0": "-sdjfaowneono",
"1": "-asdfhsadfsafwirn"
},
"shopping": {
"0": "-sdjfaowneono"
}
}
}
Again, if task "-sdjfaowneono" was removed, so would the "shopping" tag be removed.
In conclusion, I need to synchronize the "tags" schema with the "tasks" schema in a specific way. Should I do this synchronization in the Security Rules? Or should I do this at the app level? And if I do it at the app level, there is a risk that the app bugs out and doesn't add/remove a tag which it should, so how do I overcome that?
To use this with security rules, you will need to change one or the other to use the tag id as the key, rather than an array value, since there is no way to specify a wild card or search multiple children to see if one contains the correct value.
So if you changed your "tags" path to look like this:
"tags": {
"all": {
"-sdjfaowneono": true,
"-asdfhsadfsafwirn": true
},
"shopping": {
"-sdjfaowneono": true
}
}
Then you could validate like this:
"tasks": {
"$task": {
"tags": {
"$tag": {
".validate": "root.child('tags/'+newData.val()+'/'+$task).exists()"
}
}
}
}
This uncovers a basic design practice that I've discovered while building with Firebase: I tend to prefer keys to values for storing any sort of foreign key reference.
Also, keep in mind that this only enforces that they match up on write. It does help with deletions since .validate will only run if the value exists.
You could enforce the same behavior on deletions by appending this to the write rule:
".write": " ... && (newData.exists() || !root.child('tags/'+newData.val()+'/'+$task).exists())"

Resources