Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to allow user to query collection but only access certain results in collection?

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...

like image 486
RyanDagg Avatar asked Dec 07 '25 05:12

RyanDagg


1 Answers

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_DENIED error. 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:

  • Can't read data from Firebase after authentication
  • Workaround for Firebase's "Rules Are Not Filters" constraint
  • How to allow read/write objects only to authenticated user with Firebase?
like image 95
Frank van Puffelen Avatar answered Dec 12 '25 15:12

Frank van Puffelen



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!