Working in firebase and setting up complex (to me) rules for the first time.
I have a collection of Children which have a property "parentId". I only want certain roles and users to read this table. Specifically, I want any coach or referee role to be able to read as well as the parent of any child.
I believe that the problem lies in "data.child('parentId').val() === auth.uid" at the collection level .read. I feel like I perfectly followed the example here, but it still isn't working: https://www.firebase.com/docs/security/api/rule/data.html
Can anyone point out what I am doing wrong in my security rules?
I am aware that security rules do not filter and I do not believe that this is an example of that particular problem because my query is specifically limiting the results to what I am attempting to allow through security rules (though I may be mistaken).
Here is current security rules. The comments should explain what I'm going for:
"children": {
// give ability of any coach or referee to read any child. Give refs ability to write to any child. Allow parent to query for their own children.
".read": "(data.child('parentId').val() === auth.uid) ||(root.child('users').child(auth.uid).child('role').val() === 'coach') || (root.child('users').child(auth.uid).child('role').val() === 'referee')",
".write": "root.child('users').child(auth.uid).child('role').val() === 'referee'",
".indexOn": ["parentId", "isApproved"],
"$child": {
// give a parent read and write access to only their specific children.
".read": "root.child('children').child($child).child('parentId').val() === auth.uid",
".write": "root.child('children').child($child).child('parentId').val() === auth.uid"
}
}
In my code I am doing a query of all Children by their parentId. Here is my javascript that fails with "Error: permission_denied: Client doesn't have permission to access the desired data.":
factory.getChildrenByParent = function(parentId){
return new Promise(function(resolve, reject){
childrenRef.orderByChild('parentId').equalTo(parentId)
.once('value', function(childrenResponse){
var children = childrenResponse.val();
processChildren(children);
resolve(children);
},
function(err){
if(err){
reject(err);
}
})
})
}
The comments should explain what I'm going for. The problem is that a user can not query the Children by the parents id (which is equal to auth.uid). They can query one off with this:
factory.getChildById = function(id){
return new Promise(function(resolve, reject){
childrenRef.orderByKey().equalTo(id)
.once('value', function(childResult){
var childObjects = childResult.val();
processChildren(childObjects);
var child = Utilities.getFirst(childObjects);
resolve(child);
},
function(err){
if(err){
reject(err);
}
})
})
}
I've tried using the simulator but I can't figure out the syntax for .orderByChild('parentId').equalTo(parentId) and Chrome dev tools network doesn't show any activity when I fire this...
You might want to read the section of the Firebase documentation on "Rules are not filters" again.
Rules are applied in an atomic manner. That means that a read or write operation is failed immediately if there isn't a rule at that location or at a parent location that grants access. Even if every child path is accessible, reading at the parent location will fail completely. Consider this structure:
{
"rules": {
"records": {
"rec1": {
".read": true
},
"rec2": {
".read": false
}
}
}
}
Without understanding that rules are evaluated atomically, it might seem like fetching the /records/ path would return rec1 but not rec2. The actual result, however, is an error:
ref.child("records").once("value", function(snap) {
// success method is not called
}, function(err) {
// error callback triggered with PERMISSION_DENIED
});
Since the read operation at /records/ is atomic, and there's no read rule that grants access to all of the data under /records/, this will throw a
PERMISSION_DENIEDerror. If we evaluate this rule in the security simulator in our App Dashboard, we can see that the read operation was denied:
Attempt to read /records with auth=Success(null)
/
/records
No .read rule allowed the operation.
Read was denied.
The operation was denied because no read rule allowed access to the /records/ path, but note that the rule for rec1 was never evaluated because it wasn't in the path we requested. To fetch rec1, we would need to access it directly:
ref.child("records/rec1").once("value", function(snap) {
// SUCCESS!
}, function(err) {
// error callback is not called
});
In other words: you can only perform a query on a location when you have read access to that location.
Some questions about the same:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With