Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Catch all other" Firebase database rule

Perhaps I'm tackling this problem too much from an SQL kind of perspective, but I'm having troubles understanding how to properly restrict which children should be allowed to populate a node.

Say that I want to keep a record of products with arbitrary names. Each product must contain a price, but nothing else is allowed.

My naive approach was to add a .validate rule to the products requiring newData to contain a price child, explicitly granting write access to the price node and then removing all access an $other node (somewhat like a default clause in a switch statement):

{
    "rules": {
        "$product": {
            ".read": true,
            ".write": true,
            ".validate": "newData.hasChildren(['price'])",
            "price": {
                ".write": true,
                ".validate": "newData.isNumber()"
            },
            "$other": {
                ".read.": false,
                ".write": false,
            }
        }
    }
}

This does not work. Adding a new product with {"price": 1234, "foo": "bar"} will still be accepted. If I however add a ".validate": false rule to $other, nothing is accepted instead (e.g. {"price": 1234} is not allowed). (I did that wrong, somehow.)

Is there some way to implement something similar to what I'm trying to do here? If not, what is the proper way of restricting a data structure in Firebase? Should I do it at all? What stops the user from filling my database with trash if I don't?

like image 470
Daniel Perván Avatar asked Mar 11 '23 08:03

Daniel Perván


1 Answers

You're falling into a few common Firebase security pits here. The most common one is that permission cascades down: once you've granted read or write permission on a certain level in the tree, you cannot take that permission away at a lower level.

That means that these rules are ineffectual (since you've granted read/write one level higher already):

"$other": {
    ".read.": false,
    ".write": false,
}

To solve the problem you must realize that .validate rules are different: data is only considered valid when all validation rules are met. So you can reject the $other data with a validation rules:

{
    "rules": {
        "$product": {
            ".read": true,
            ".write": true,
            ".validate": "newData.hasChildren(['price'])",
            "price": {
                ".validate": "newData.isNumber()"
            },
            "$other": {
                ".validate": false
            }
        }
    }
}
like image 85
Frank van Puffelen Avatar answered Mar 23 '23 12:03

Frank van Puffelen