0

I'm developing a app that allows teachers to record their students' lessons. Using Firebase Rules I want to allow teachers access to certain schools. I have the following Rules:

{
  "rules": {
    "Schools" : {
      "$schoolId" : {
          ".read" : "data.child('Teachers').hasChild(auth.uid)",
         ".write" : "data.child('Teachers').hasChild(auth.uid) ||
          root.child('User').child(auth.uid).child('Invitation').exists()"
      }
    },
    "Users" : {
      "$uid" : {
        ".read" : "auth.uid === $uid",
            ".write": "auth.uid === $uid"
      }
    }
  }
}

In the simulator everything works fine, but when firing the below all permissions are denied to all schools.

DataService.ds.REF_SCHOOLS.observeSingleEvent(of: .value) { (snapshot) in
    MySchools.mySchoolKeyList.removeAll()
    MySchools.mySchoolList.removeAll()
    if let snapshot = snapshot.children.allObjects as? [DataSnapshot] {
        for snap in snapshot {
            if let recordDict = snap.value as? Dictionary<String, AnyObject> {
                if let name = (recordDict["SchoolName"] as? String) {
                    MySchools.mySchoolList.append(name)
                }
                MySchools.mySchoolKeyList.append(snap.key)
            }
        }
            if MySchools.mySchoolList.isEmpty {
                MySchools.mySchoolList.append("You do not belong to any schools.")
            }
    }
}

Below is snapshot of the Firebase database for reference:

enter image description here

Does anyone know what's wrong with my rules? Also how to handle the permission denied result would be appreciated.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Davo
  • 11
  • 3

1 Answers1

0

Your code is trying to read from /Schools, but your rules don't give anyone permission to read from /Schools. So Firebase correctly rejects the read.

I have a feeling that you expected the read operation to automatically filter the result to only return the school nodes (/Schools/$schoolId) that the user has access to. But that is not how Firebase security rules work: rules cannot be used to filter data.

This is a common source of confusion for developers getting started with Firebase's server side security rules, so I recommend you check out of the some previous questions mentioning "rules are not filters".


Nowadays there is a way to get filtered data, without giving the user access to all Schools. For this you will need to:

  1. Use a query to only request the schools that you're supposed to have access to.
  2. Validate that this query is allowed on /Schools in your security rules.

For an example of this see query based rules in the documentation.

The code should be something like this:

let query = DataService.ds.REF_SCHOOLS.queryOrdered(byChild: "Teachers/"+uid).queryValue(equalTo: true);
query.observeSingleEvent(of: .value) { (snapshot) in
  ...

But the problem with this is that you would need to define an index for each teacher. While this is technically possible, it would lead to an unreasonable number of indexes.


In hindsight: your current data structure allows you to efficiently find the teachers for a specific school. It does not however allow you to efficiently find the schools for a specific teacher.

To efficiently allow finding the schools for a specific teacher, add an additional data structure to your database that maps schools to each teacher. E.g.

teacherSchools
  teacherUid1
    schoolId1: true
    schoolId2: true
  teacherUid2
    schoolId2: true
    schoolId3: true

With this structure you can easily read all school Ids for a teacher, and then load each school. This loading is a lot faster than most developers expect, since Firebase pipelines the requests over a single connection.

For a longer/another explanation of this approach, see Firebase query if child of child contains a value and Firebase Query Double Nested.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807