Reputation: 2979
I'm new to Firebase and trying to understand the security rules. For this I'm implementing typical functionality of Project - Team Members - Tasks. Each project will have a team leader, multiple members and multiple tasks.
Here is the structure and rules that I'm trying to implement (a.k.a. Requirements):
/Members - each member has { displayName, email, emailVerified }
any logged in user should be able to read data from Members (to get the
display names of all users)
any logged in user should be able to update his/her record
/Projects - each project has { Lead, Members{}, Name, Tasks{} }
any logged in user should be able to read the list of projects
any logged in user should be able to read the list of members (if possible
only for the projects where they are part of)
any logged in user should be able to read the list of tasks (if possible only
for the projects where they are part of)
only the team leader should be able to update project details i.e.
- add / remove members
- add / remove tasks
- change project title
/Tasks - { project, status, title }
team leader / team members should be able to read the tasks
team leader can add/edit/delete tasks
team members can update only status (of a task that is associated with their project)
team leader / team members should be able to filter project tasks based on
task status (completed / not completed)
I've setup following Firebase rules:
{
"rules": {
"Members": {
".read": "auth != null",
"$mid" : {
".write": "auth != null && auth.uid == $mid"
}
}, // Members
"Projects": {
".read": "auth != null",
// only team lead can edit project details
".write": "auth != null && auth.uid == data.child('Lead').val()",
// Lead and Name are mandatory fields
".validate": "newData.hasChildren(['Lead', 'Name'])",
"Name": {
".validate": "newData.isString() && newData.val().length > 0"
},
"Lead": {
".validate": "root.child('Members/' + newData.val()).exists()"
},
"Members": {
"$mid": {
".validate": "root.child('Members/' + $mid).exists()"
}
},
"Tasks": {
"$tid": {
".validate": "root.child('Tasks/' + $tid).exists()"
}
}
}, // Projects
"Tasks": {
// allow read / write only if current user is team lead or a member of the project
".read": "(auth != null) && (data.child('project').val() == 'Project1')",
".write": "auth != null && ( root.child('Projects/' + newData.child('project').val() + '/Lead').val() == auth.uid || root.child('Projects/' + newData.child('project').val() + '/Members/' + auth.uid).exists() )",
".validate": "newData.hasChildren(['project', 'status', 'title'])",
"status": {
".validate": "newData.isString() && newData.val().length > 0"
},
// if team member is saving the item, allow changes only to status
"title": {
".validate": "(root.child('Projects/' + newData.parent().child('project').val() + '/Lead').val() == auth.uid) ? newData.isString() && newData.val().length > 0 : data.exists() && data.val() == newData.val()"
}
} // Tasks
} // rules
}
Currently I'm evaluating the .read
functionality. I've not tested .write
functionality yet.
I'm able to get Members
list (members' displayName
) for a given project.
But while getting project's task details (from /Tasks
) I'm getting permission denied error.
Note that I would like to use .read
rule same as .write
rule for Tasks
. But as I was getting error, I changed it to the current rule (such that, any authenticated user can read the tasks for Project1
- Project1
is the key of a project). Even then I'm getting permission denied. If I keep only "auth != null"
then I'm able to read the tasks but that's not what I want.
Can someone help me understand what changes should I make to the Firebase rules so as to implement the requirements given above?
Upvotes: 2
Views: 1124
Reputation: 2979
After trying out different combinations here is what I've found out.
I was trying to access /Tasks
with orderByChild('project').equalTo(projectKey)
so as to get details of tasks associated with a project. But when I do this, the .read
rule gets executed at /Tasks
level and there is no child named 'project'
at that level. 'project'
is available at /Tasks/<taskId>/project
. So I need to change the Task
rule as:
"Tasks": {
"$tid": {
// allow read / write only if current user is team lead or a member of the project
".read": "auth != null && ( root.child('Projects/' + data.child('project').val() + '/Lead').val() == auth.uid || root.child('Projects/' + data.child('project').val() + '/Members/' + auth.uid).exists() )",
".write": "auth != null && ( root.child('Projects/' + newData.child('project').val() + '/Lead').val() == auth.uid || root.child('Projects/' + newData.child('project').val() + '/Members/' + auth.uid).exists() )",
".validate": "newData.hasChildren(['project', 'status', 'title'])",
"status": {
".validate": "newData.isString() && newData.val().length > 0"
},
// if team member is saving the item, allow changes only to status
"title": {
".validate": "(root.child('Projects/' + newData.parent().child('project').val() + '/Lead').val() == auth.uid) ? newData.isString() && newData.val().length > 0 : data.exists() && data.val() == newData.val()"
}
}
} // Tasks
Even with this rule accessing /Tasks
with orderByChild('project').equalTo(projectKey)
gives permission denied. This is because now there is no .read
rule defined at /Tasks
level. So I need to change the program logic to iterate on /Projects/<projectId>/Tasks
and for each taskId
access /Tasks/<taskId>
. When I do this, the .read
rule gets evaluated properly and user can access tasks details only for the projects that they are part of. And then I need to process these task details at the client side to separate out completed and non-completed tasks.
I'm yet to verify .write
and .validate
rules. But meanwhile I'll wait for someone to confirm my understanding or correct it.
Upvotes: 2