What is the scope of Firebase security rules variables? - firebase

Is that ok in Firebase to use the same variable name multiple times, e.g. $itemId here:
{
"rules": {
"items": {
"$itemId": { // first time
".write": "$itemId == 1"
}
},
"users": {
"$userId": {
"items": {
"$itemId": { // second time
".write": "$itemId == 2"
}
}
}
}
}
}
Do variables have scope?

In general, security rules cascade. The variables only apply to the block {...} under which they are declared. They are applicable to all of the children of that block.
{
"rules": {
"$level1": {
"$level2": {
"bar": {
".validate": "..." // $level1, $level2 are both usable here
}
}
}
"pathb": {
".validate": "..." // here $level1 is undefined
}
}
}

Related

Firebase database security rules and dollar signs

I'm creating a Google docs type of app where multiple users can edit a document in real-time.
These are my security rules:
{
"rules": {
"docs": {
"$doc_id": {
".read": "auth.token.doc_id === $doc_id",
".write": "auth.token.doc_id === $doc_id"
}
}
}
}
This is my Auth token payload (example)
{
"provider": "anonymous",
"uid": "e71afdf1-2b31-4c75-8f10-a9b0f916915e",
"token": {
"doc_id": 11
}
}
So when I try to read this path /docs/11/data I get a Simulated read denied error.
On the other hand when I put this rule instead
".read": "auth.token.doc_id === 11", (i.e. hard code $doc_id as 11)
The read passes.
But isn't $doc_id equal to 11 when I read /docs/11/data or am I misunderstanding something?
P.S. In case it matters, here is the data inside my database
{
"docs": {
"2": {
"data": {
"title": "test"
}
},
"11": {
"data": {
"title": "test"
}
}
}
}

I can't get the user uid signed in in the rules of the firebase console

Here is my data.
"users" : {
"user1": {
"1234": {
"role": "admin"
},
"1235": {
"role": "normal"
}
},
"user2": {
"1236": {
"role": "admin"
},
"1237": {
"role": "normal"
}
}
}
And here is rules for that.
"rules" {
"users": {
".read": "root.child('users').child('user1').child(auth.uid).child('role') === 'admin'"
}
}
But the rule doesn't work. I seem the auth.uid isn't gotten correctly.
Try this :-
{
"rules": {
"users": {
"user1": {
"$user_id": {
".read": "$user_id === auth.uid && root.child('users/user1/' + $user_id + '/role/').val() === 'admin' "
}
}
}
}
}

Firebase Rules -- Multiple Default Values

I have a parent as 'Client' in my firebase. This is then followed by a child of 'invoices' and then followed by children on the invoice
'clients' {
'uid' {
'invoices' {
'number': '1003 //etc...
'payments' {
}
'history' {
}
'discussion' {
}
}
}
}
I am trying to only allow clients to write to payments, history, and discussion. But when I do the following I get an error Cannot have multiple default rules ('$payments' and '$history'). in the Firebase Dashboard Rules Section.
"clients": {
".write" : "root.child('roles').child(auth.uid).child('level').val() == 4",
".indexOn" : "number",
"$estimates": {
"$discussion": {
".write": "root.child('roles').child(auth.uid).child('level').val() == 2"
}
},
"$invoices": {
"$payments": {
".write": "root.child('roles').child(auth.uid).child('level').val() == 2"
},
"$history": {
".write": "root.child('roles').child(auth.uid).child('level').val() == 2"
},
"$discussion": {
".write": "root.child('roles').child(auth.uid).child('level').val() == 2"
}
}
},
What is the correct way to write these rules then?
You seem to be missing some levels from your JSON tree in the rules.
"clients": {
"$clientid": {
".write" : "root.child('roles').child(auth.uid).child('level').val() == 4",
"invoices": {
".indexOn" : "number",
"$invoiceid": {
"payments": {
".write": "root.child('roles').child(auth.uid).child('level').val() == 2"
},
"history": {
".write": "root.child('roles').child(auth.uid).child('level').val() == 2"
},
"discussion": {
".write": "root.child('roles').child(auth.uid).child('level').val() == 2"
}
}
},
You should only use $ variables for a rule that needs to be applied to all non-otherwise matched children under a node.
So if you keep all payments as children of the payments node and want specific validation rules for them, you could:
"payments": {
".write": "root.child('roles').child(auth.uid).child('level').val() == 2",
"$paymentid": {
".validate": "..."
}
},

Firebase rules: using parent values to validate?

I have this set of rules:
{
"rules": {
"facebook_users": {
"$user": {
".read": "$user == auth.id",
".write": "$user == auth.id",
"userData": {
"maxProjects": {
".validate": false
},
"userProjects": {
".validate": ???
}
}
}
}
}
}
I'd like to let an user not being able to add a children to the userProjects array if userProjects.length > userData.maxProjects. The user is denied writing in the maxProjects already, due to the ".validate": false rule. How can I compare userProjects' length with maxProjects?
If it's not possible, what is a correct way to do this?
You can use the root special variable along with child() to do this comparison:
{
"rules": {
"facebook_users": {
"$user": {
".read": "$user == auth.id",
".write": "$user == auth.id",
"userData": {
"maxProjects": {
".validate": false
},
"userProjects": {
".validate": "data.val().length < root.child('facebook_users').child($user).child('userData/maxProjects').val()"
}
}
}
}
}
}
Have you read this?
parent()
Gets a RuleDataSnapshot for the parent location.
Return Value: RuleDataSnapshot - The RuleDataSnapshot for the parent location.

A better way of enforcing a list

Please consider the follow rule:
"list" : {
"$item" : {
".validate": "newData.hasChildren(['field1', 'field2'])",
"field1" : {
".validate": newData.isString()
},
"field2" : {
".validate": newData.isString()
},
"$other" : {
".validate": false
}
}
}
}
It should only allow you to build a list of objects of a certain type. However, there's nothing to prevent the following:
{
"list" : "i've been hacked"
}
Only thing I've been able to come up with is to add this to the "list" rules:
".validate": "!newData.isString() && !newData.isNumber() && !newData.isBoolean()"
.. which is a bit clunky. Is there a better to enforce this or is my whole approach just wrong-headed?
You could just add .write: false to the list itself, and only enable it on the $item. This way, you can't write to list without validating $item rule.
To elaborate on #cwehrung's answer:
"list": {
".write": false,
"$item": {
".validate": "newData.hasChildren(['field1', 'field2'])",
"field1": {
".write": "newData.isString()"
},
"field2": {
".write": "newData.isString()"
}
}
}
Another option:
"list": {
".validate": "newData.hasChildren()",
...
}

Resources