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": "..."
}
},
Related
{
"rules": {
"users": {
"$uid":{
//Private whatever under "uid" but Public is exposed
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid",
"public": { ".read": "auth != null" }
}
}
}
}
I've created these rules to have users public/private profile
"users/{uid}/public" profile should be accessible by any users those are authenticated, but cannot access the data under "users/uid"
Here is some fake data that is stored in my firebase database.
{
"users" : {
"YFIIAgwa2kaannrXjwvSZmoywma2" : {
"Name:" : "Example 1",
//This public child should be accessible by
//"Example 2" but cannot know the name of
// this user
"public" : {
"email" : "example1#gmail.com"
}
},
"YgSfSzPzxLbyDL17r6P9id2cdvH2" : {
"Name:" : "Example 2",
//This public child should be accessible by
//"Example 1" but cannot know the name of
// this user
"public" : {
"email" : "example2#gmail.com"
}
}
}
}
I want to know if this is the robust way to prevent any users from accessing user's critical information! Is there anyway I can improve this by using validate? I am open to any suggestions you guys have. I want to create the best and simple security rules for my app.
You can definitely secure access to the private and public data with your current data structure.
But one use-case you'll likely want at some point is to show a list of the public info for all users. With your current data structure that is not possible, because Firebase's security model cannot be used to filter data. For a great answer covering this, see Restricting child/field access with security rules.
Most developers split the public and private data in completely separate subtrees:
{
"users" : {
"YFIIAgwa2kaannrXjwvSZmoywma2" : {
"Name:" : "Example 1",
},
"YgSfSzPzxLbyDL17r6P9id2cdvH2" : {
"Name:" : "Example 2",
}
},
"public_profiles": {
"YFIIAgwa2kaannrXjwvSZmoywma2" : {
"email" : "example1#gmail.com"
},
"YgSfSzPzxLbyDL17r6P9id2cdvH2" : {
"email" : "example2#gmail.com"
}
}
}
You can then secure access with:
{
"rules": {
"users": {
"$uid":{
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid",
}
},
"public_profiles": {
".read": "auth != null",
"$uid":{
".write": "auth != null && auth.uid == $uid",
}
}
}
}
Now any authenticated user can listen to /public_profiles, which means you can easily show a list of these profiles.
Hmm wouldn't it be easier to (re)structure the db so that you have a public and a private field per user? Something like:
{
"users" : {
"YFIIAgwa2kaannrXjwvSZmoywma2" : {
"private": {
"Name:" : "Example 1"
},
"public" : {
"email" : "example1#gmail.com"
}
},
"YgSfSzPzxLbyDL17r6P9id2cdvH2" : {
"private": {
"Name:" : "Example 2"
},
"public" : {
"email" : "example2#gmail.com"
}
}
}
}
/UPD: This way it should be easy(er) to have the different permissions because they won't inherit them from the parent?
{
"rules": {
"users": {
"$uid":{
//Private whatever under "uid" but Public is exposed
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid",
"public": { ".read": "auth != null" }
}
}
}
}
I've created these rules to have users public/private profile
"users/{uid}/public" profile should be accessible by any users those are authenticated, but cannot access the data under "users/uid"
Here is some fake data that is stored in my firebase database.
{
"users" : {
"YFIIAgwa2kaannrXjwvSZmoywma2" : {
"Name:" : "Example 1",
//This public child should be accessible by
//"Example 2" but cannot know the name of
// this user
"public" : {
"email" : "example1#gmail.com"
}
},
"YgSfSzPzxLbyDL17r6P9id2cdvH2" : {
"Name:" : "Example 2",
//This public child should be accessible by
//"Example 1" but cannot know the name of
// this user
"public" : {
"email" : "example2#gmail.com"
}
}
}
}
I want to know if this is the robust way to prevent any users from accessing user's critical information! Is there anyway I can improve this by using validate? I am open to any suggestions you guys have. I want to create the best and simple security rules for my app.
You can definitely secure access to the private and public data with your current data structure.
But one use-case you'll likely want at some point is to show a list of the public info for all users. With your current data structure that is not possible, because Firebase's security model cannot be used to filter data. For a great answer covering this, see Restricting child/field access with security rules.
Most developers split the public and private data in completely separate subtrees:
{
"users" : {
"YFIIAgwa2kaannrXjwvSZmoywma2" : {
"Name:" : "Example 1",
},
"YgSfSzPzxLbyDL17r6P9id2cdvH2" : {
"Name:" : "Example 2",
}
},
"public_profiles": {
"YFIIAgwa2kaannrXjwvSZmoywma2" : {
"email" : "example1#gmail.com"
},
"YgSfSzPzxLbyDL17r6P9id2cdvH2" : {
"email" : "example2#gmail.com"
}
}
}
You can then secure access with:
{
"rules": {
"users": {
"$uid":{
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid",
}
},
"public_profiles": {
".read": "auth != null",
"$uid":{
".write": "auth != null && auth.uid == $uid",
}
}
}
}
Now any authenticated user can listen to /public_profiles, which means you can easily show a list of these profiles.
Hmm wouldn't it be easier to (re)structure the db so that you have a public and a private field per user? Something like:
{
"users" : {
"YFIIAgwa2kaannrXjwvSZmoywma2" : {
"private": {
"Name:" : "Example 1"
},
"public" : {
"email" : "example1#gmail.com"
}
},
"YgSfSzPzxLbyDL17r6P9id2cdvH2" : {
"private": {
"Name:" : "Example 2"
},
"public" : {
"email" : "example2#gmail.com"
}
}
}
}
/UPD: This way it should be easy(er) to have the different permissions because they won't inherit them from the parent?
I'm trying to create a twitter clone to learn to use Firebase and I would love to get a suggestion about how to create the database structure. My biggest concern is related to followers and how to create a timeline when you are following for example 500 users. You would need to perform 500 queries and sort somehow for datetime.
{
"followers" : {
"cesar" : {
"followers" : {
"cesar2" : true
},
"following" : {
"cesar2" : true
}
},
"cesar2" : {
"followers" : {
"cesar" : true
},
"following" : {
"cesar" : true
}
}
},
"tweet" : {
"cesar" : [ null, {
"content" : "tweet 1"
} ]
},
"users" : {
"cesar" : {
"name" : "César",
"notifications" : true,
"username" : "cesar"
},
"cesar2" : {
"name" : "César2",
"notifications" : false,
"username" : "cesar2"
}
}
}
See Firefeed, Firebase's open-source Twitter clone. It includes a walkthrough of the data structure it uses, which boils down to using a fan-out approach when new messages are posted. Here's a copy of the rules used, which describes the underlying data structure:
{
"rules": {
// All data is readable by anyone.
".read": true,
"people": {
// A list of users with their names on the site.
"$userid": {
// Only the user can write their own entry into this list.
".write": "$userid ==auth.uid"
}
},
"users": {
"$userid": {
// The user is allowed to write everything in their bucket.
".write": "$userid ==auth.uid",
"following": {
// The following list should only contain actual ids from the "people" list.
"$followingid": {
".validate": "root.child('people').hasChild($followingid)"
}
},
"followers": {
// Anyone can add themself to to this user's followers list.
"$followerid": {
".write": "$followerid ==auth.uid"
}
},
"feed": {
"$sparkid": {
// User A can write in user B's feed, but only if A is following B, and only for sparks for which they are the author.
".write": "root.child('users/' + $userid + '/following').hasChild(auth.uid) && root.child('sparks/' + $sparkid + '/author').val() ==auth.uid"
}
}
}
},
"sparks": {
// A global list of sparks (the "firehose").
"$sparkid": {
// Modifying an existing spark is not allowed.
".write": "!data.exists()",
// Every spark should have an author and a body.
".validate": "newData.hasChildren(['author', 'content'])",
// A user can attribute a spark only to themselves.
"author": {
".validate": "newData.val() ==auth.uid"
},
"content": {
".validate": "newData.isString()"
}
}
},
"recent-users": {
// Users can add themselves to the list of users with recent activity.
"$userid": {
".write": "$userid ==auth.uid"
}
},
"recent-sparks": {
// Authors of sparks can add their sparks to this list.
"$sparkid": {
".write": "root.child('sparks/' + $sparkid + '/author').val() ==auth.uid"
}
},
"search": {
"firstName": {
"$searchKey": {
".write": "auth != null && (root.child('people/' +auth.uid + '/firstName').val() + '|' + root.child('people/' +auth.uid + '/lastName').val() + '|' +auth.uid) == $searchKey && newData.val() ==auth.uid"
}
},
"lastName": {
"$searchKey": {
".write": "auth != null && (root.child('people/' +auth.uid + '/lastName').val() + '|' + root.child('people/' +auth.uid + '/firstName').val() + '|' +auth.uid) == $searchKey && newData.val() ==auth.uid"
}
}
}
}
}
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
}
}
}
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()",
...
}