TLDR: How can I set up the DB access rules so that I can read all the items I have authorization to from a given endpoint?
I have a similar set of data in my Firebase database:
"employees" : [ {
"first_name" : "Constance",
"last_name" : "Smith",
"createdBy: "vdBoGzI2i9f12er6ZcPjG9AiTip2"
}, {
"first_name" : "Agatha",
"last_name" : "Carlton",
"createdBy: "Tpg1mFR99meDV2QGT44pU6y7s1T2"
},
...
}
I also have a list of application users:
"users" : {
"Tpg1mFR99meDV2QGT44pU6y7s1T2" : {
"name" : "Alex Lund",
"isAdmin": true
},
"vdBoGzI2i9f12er6ZcPjG9AiTip2" : {
"name" : "David Peterson",
"isAdmin": false
},
...
},
Basic users will have access just to the employees they created; the admin will be able to read everything.
{
"rules": {
".write": "auth != null",
"employees": {
"$employee": {
".read": "root.child('users').child(auth.uid).child('isAdmin').val() === true || data.child('createdBy').val() === auth.uid"
}
},
}
With this rules, an admin will be able to read ref.child('/employees/0'), but won't have access to ref.child('employees').
How can I get all the employees I have read access to? Is running a query the only solution?
With the rules as you have them now, a query on /users won't work. Since you don't have read permission on /employees any listener on that location will immediately be rejected.
You probably want the rules to be like this:
{
"rules": {
".write": "auth != null",
"employees": {
".read": "root.child('users').child(auth.uid).child('isAdmin').val() === true"
"$employee": {
".read": "data.child('createdBy').val() === auth.uid"
}
},
}
With these rules administrators can read (and thus query) /users, while regular users can only access children that they created.
This is a common pitfall when it comes to Firebase Database security rules and is typically referred to as "rules are not filters". See the relevant section in the documentation, this original Q&A on it and any of the questions in this list.
Related
My database:
I want to set firebase rules where user can update existing tags value but not create new tags
If you only want the user to be able to update existing properties, and not create any new properties, that'd be:
{
"rules": {
"$uid": {
".write": "auth.uid === $uid"
"$property": {
".validate": "data.exists()"
}
}
}
}
So this rule validates that a property can only be written if there's already data for that property in the database.
If you only want the user to be able to write a name and address, and not any other properties, that'd be:
{
"rules": {
"$uid": {
".write": "auth.uid === $uid"
"name": {
".validate": true
},
"address": {
".validate": true
},
"$other": {
".validate": false
}
}
}
}
In the above rules we allow writing the named name and address properties, and reject any other properties with a wildcard capture rule.
Initial approach/problem
The user will be able to write to his own user node. Then the user has to be able to write as many buildings he/she wants and as many depts as he/she wants (there are also rooms but I will leave that aside for now for clarity's sake). The user should be able to read (and write) his own user node, buildings and departments but not the other users' node, buildings and departments.
Basically:
User > user's Building > building's Department (TOTAL read and write permissions)
User > Another User's Stuff (NO Permissions at all)
Here is the database strucure:
{
"buildings" : {
"-L9Bc9aazn3mNiW1elJk" : {
"address" : "",
"comments" : "",
"hasDepts" : {
"-L9FwBmYEnkZQzdFJ4lU" : true
},
"name" : "J house",
"ownerID" : "6hwNde08Wuaa9bfReR28niSbOsF3"
}
},
"depts" : {
"-L9FwBmYEnkZQzdFJ4lU" : {
"comments" : "",
"inBuilding" : "-L9Bc9aazn3mNiW1elJk",
"name" : "Dep 1"
},
},
"users" : {
"6hwNde08Wuaa9bfReR28niSbOsF3" : {
"isAdmin" : {
"-L9Bc9aazn3mNiW1elJk" : true,
}
}
}
Initial approach
Each users node has a child node isAdmin that contains all push keys of the buildings this user has created. Using the same logic, the buildings' node contains a hasDepts node with all the push keys from the depts that the user has created in that building.
If you can help I would really appreciate. Anybody out there?
I am using vue.js to write to firebase like this:
addBuilding: function () {
let userId = firebase.auth().currentUser.uid;
let buildingKey = buildingsRef.push().key;
this.newBuilding.ownerID = userId;
buildingsRef.child(buildingKey).set(this.newBuilding);
usersRef.child(userId).child('isAdmin').child(buildingKey).set(true);
}
Closest solution so far / by #André Kool (partially using the initial approach)
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
},
},
"buildings": {
"$pushKey" : {
".read": "root.child('buildings').child($pushKey).child('ownerID').val() === auth.uid",
".write": "!data.exists() || root.child('buildings').child($pushKey).child('ownerID').val() === auth.uid"
}
},
"depts": {
"$pushKey": {
".read": "root.child('buildings').child(root.child('depts').child($pushKey).child('inBuilding').val()).child('ownerID').val() === auth.uid",
".write": "root.child('buildings').child(root.child('depts').child($pushKey).child('inBuilding').val()).child('ownerID').val() === auth.uid"
}
},
}
With #André Kool's attempt the simulator allows it to read/write to the buildings/$pushKey node. However while it shows the node on the frontend as soon as it it is created, when we refresh the browser or add a new building the node disappears from the frontend (remains on the database). Firebase it's also not allowing to write to the depts node. Any clues and possible solutions?
UPDATE: another approach.
1) making sure both the buildings and depts nodes both have the ownerId child node, like this:
{
"buildings" : {
"-L9HIbKu5fIe8rfoePgi" : {
"address" : "",
"comments" : "",
"hasDepts" : {
"-L9HIdScisDItysCnMlm" : true
},
"name" : "building 1",
"ownerID" : "6hwNde08Wuaa9bfReR28niSbOsF3"
}
},
"depts" : {
"-L9HIdScisDItysCnMlm" : {
"comments" : "",
"inBuilding" : "-L9HIbKu5fIe8rfoePgi",
"name" : "dep 1",
"ownerID" : "6hwNde08Wuaa9bfReR28niSbOsF3"
}
},
"users" : {
"6hwNde08Wuaa9bfReR28niSbOsF3" : {
"isAdmin" : {
"-L9HIbKu5fIe8rfoePgi" : true
},
"name" : "João Alves Marrucho",
"userEmail" : "joaomarrucho#hotmail.com"
}
}
}
2) Use the ownerId to authorise read and writing on all buildings and depts:
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
},
},
"buildings": {
"$id": {
".read": "data.child('ownerID').val() == auth.uid" ,
".write": "data.child('ownerID').val() == auth.uid"
}
},
"depts": {
"$id": {
".read": "data.child('ownerID').val() == auth.uid" ,
".write": "data.child('ownerID').val() == auth.uid"
}
},
}
3) New problem with this approach!
With the rules above, using the simulator, it seems to me like firebase is not allowing the user to read/write to the buildings node but it's allowing to read/write to buildings/$pushKey node. Does firebase needs the user to be able read/write to both parent (buildings) and child node (buildings/$pushKey)?. And if so how can you prevent the user from deleting (.set) the whole buildings node?
I am asking this because if we add ".read": true, ".write": true before the $wildcards it writes the intended database structure while rendering the next rules on the cascade completely useless... So that's no good, but at least it kind of hints where part of the solution may reside.
"users": {
".read": true, <<<<<<
".write": true, <<<<<<
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
},
}, // but is should also be able to write to his own buildings
"buildings": {
".read": true, <<<<<<
".write": true, <<<<<<
"$id": {
".read": "data.child('ownerID').val() == auth.uid" ,
".write": "data.child('ownerID').val() == auth.uid"
}
},
"depts": {
".read": true, <<<<<<
".write": true, <<<<<<
"$id": {
".read": "data.child('ownerID').val() == auth.uid" ,
".write": "data.child('ownerID').val() == auth.uid"
}
},
}
(Other thoughts)
I don't see why the user shouldn't be able to read/write to the buildings parent node (provided the rules prevent him from deleting the whole buildings node and only grant him full access to the buildings/$pushKeys he creates). Is that complicated? I may be getting this wrong, but doesn't firebase need to scan the buildings node before it knows which one belongs to the user?
If firebase rules can't resolve this issue like this, that means that in theory, every firebase app in which the user needs to read and write its own content to the realtime database, which is often the case, needs to store the information under a users.uid node to make it available to him/her. That seems to go against the "keep your database as flat as possible" firebase general instruction, and it also doesn't play well with the fact that any function with database references that have children in firebase require that data.snapshots nightmarish iteration.
Moreover if you can't design a shallow structure, and the user id will constantly change, how would you go about writing the database references in the Apps firebase configuration: const buildingsRef = db.ref('users/'+ userId!!!!+'/buildings'); : >How to constrain read/write rules to the users that create the nodes while keeping this structure?
There must be an good way to do this without bending over backwards.
?
Here are the rules for each part of your datastructure:
For the users node I removed the more global read and write rules because if they are false they are redundant because that is the default state. And if they are true they will override these rules because rules cascade. To allow users to only read/write their own data:
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
Next is the buildings node. Here you can read if you are the owner (ownerID === your uid) and write if there is no data or you are the owner:
"buildings" : {
"$pushKey" : {
".read": "root.child('buildings').child($pushKey).child('ownerID').val() === auth.uid",
".write": "!data.exists() || root.child('buildings').child($pushKey).child('ownerID').val() === auth.uid"
}
}
And last is the departments node where it gets a little tricky because a user can should only read/write departments in buildings he owns. So we have to check if the user has a building with the pushkey of the department. Here I check if the building with the value of inBuilding in the department has ownerID that is your uid (mind spinning complicated):
"depts" : {
"$pushKey" : {
".read": "root.child('buildings').child(root.child('depts').child($pushKey).child('inBuilding').val()).child('ownerID').val() === auth.uid",
".write": "root.child('buildings').child(root.child('depts').child($pushKey).child('inBuilding').val()).child('ownerID').val() === auth.uid"
}
}
Another option is changing your datastructure to something like this where you store buildings and departments under the user id:
{
"buildings" : {
"$uid" : {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid",
"$buildingid" : {
}
}
},
"depts" : {
"$uid" : {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid",
"$deptid" : {
}
}
}
}
I want to check if there exists a user that have email = someemail#gmai.com in the Firebase database.
I use this function and is download 50MB of data in my PC after 10 minute I get a response that exists. There is any way that I can get that less than 5 second I don't want to wait 10 minute.
in PHP is like the
SELECT FROM ALL USERS WERE EMAIL = 'george#gmail.com'
This is the database:
"users" : {
user: {
"email" : "george#gmail.com",
"custom_id" : 534253,
"description" : "some small description"
},
1754: {
"email" : "natassa#gmail.com", <---- i want to find if this email from 5000 users exist in database and if exist i want to return true
"custom_id" : 110571,
"description" : "some small description"
},
1755: {
"email" : "george#gmail.com",
"custom_id" : dsgfsdfds,
"description" : "some small description"
},
}
This function give me response after 10 min:
check_if_email_exist(email: string) {
this.users = this.db.list('/user', { query: {
orderByChild: 'email',
equalTo: x,
limitToLast: 10,
}} ) as FirebaseListObservable<Product[]>;
console.log(this.users);
return this.users;
}
OMG I find the problem thanx to https://stackoverflow.com/users/209103/frank-van-puffelen is say something about index and when I look in my chrome console I find warning that says something about index and when I look in the database rules I understand what is missing
I add this inside database rules
{
"rules": {
".read": "auth != null",
".write": "auth != null",
"needs": {
".read": "true",
".write": "auth != null",
".indexOn": "name"
},
"brands": {
".read": "true",
".write": "auth != null",
".indexOn": "name"
},
"products": {
".read": "true",
".write": "auth != null",
".indexOn": "custom_id" <========== This line you want
}
}
}
I'm having some issues wrapping my head around the database rules and the documentation isn't helping. I am trying to set things up so that only the user can delete their own items, however at the moment I'm getting permission_denied errors. I am assuming that it is because I don't have a read/write rule on the 'items' level. However I feel that if I just added a 'auth != null' rule it would give to much permission. Any thoughts?
the database setup:
users:
{
user_1 {
items:
{
item_1: true,
item_2: true,
}
},
user_2 {...},
etc {...},
},
items:
{
item_1
{
user: "user_1"
....
},
item_2
{
user: "user_1"
....
},
}
The database rules look like
{
"rules": {
"users": {
"$uid":{
".read": "auth != null && auth.uid==$uid",
".write": "auth != null && auth.uid==$uid"
}
},
"items": {
"$itemID": {
".read": "root.child('Users/'+auth.uid+'/'+$itemID).exists()",
".write": "root.child('Users/'+auth.uid+'/'+$itemID).exists()"
}
}
}
}
At the moment any user can delete any item.
To ensure that only the owner can delete the item, you need to not just verify that:
"items": {
"$itemID": {
".read": "auth.uid == data.child('user').val()",
".write": "auth.uid == data.child('user').val()"
}
}
There is no need to check if they exist in the /users node as far as I can tell, although you can easily add that back if needed.
But if a user can only read/write their own items, I'd model the data differently:
"items": {
"$uid": {
".read": "auth.uid == $uid",
".write": "auth.uid == $uid"
"$itemID": {
}
}
}
This is much simpler to model and will give you much better scalability, since the database only ever has to consider the data for one user.
i am trying to allow my self to be able to read and write everything in the database.
i am getting a read denied error when trying to run this in the firebase simulator.
{
"rules": {
".read": "root.child('Admin').child('isAdmin').child('+$AUID').val() === true",
"Admin": {
"isAdmin": {
"$AUID": {
".read": "$AUID === auth.uid",
".write": "$AUID === auth.uid",
".validate": "newData.hasChildren(['active'])",
"active": {
"active": "true"
},
},
},
},
}
If you want to give a user access to your Firebase whose level of access is determined by the value in another node, something like this may work. (Not tested)
users
uid_0
is_admin:true //your uid
uid_1
is_admin:false
and rules
rules
".read": "root.child('users').child(auth.uid).child('is_admin').val() == true"
".write": "root.child('users').child(auth.uid).child('is_admin').val() == true"