1

I am working on an app for education that gives teachers access to student assignment documents based on a common subject. I have set a custom claim client-side for the teacher and set documents with a common field, "subject". My rule looks like this:

    match /assignments/{entry} {

        allow read: if isSignedIn() && resource.data.subject == request.auth.token.subject;
        allow write: if request.auth.token.moderator == true;


    //TODO: assignment rules go here
    function isSignedIn() {
    return request.auth.uid != null;
    }
        
    }
  }

Despite everything I cannot get the resource to pull any data. Even resource.data != null displays false.

My custom claims come through fine and request.auth.token.subject == "Biology" displays true for my example account.

Here is the query:

_guestBookSubscription = FirebaseFirestore.instance
            .collection('assignments')
            .orderBy('start_date', descending: true)
            .limit(30)
            .snapshots()
            .listen((snapshot) {
          _guestBookMessages = [];
          for (final document in snapshot.docs) {
            _guestBookMessages.add(RTIAssignment(
              // endDate: (document.data()['end_date'] as DateTime),
              standard: document.data()['standard'] as String,
              startDate: DateTime.now(), // (document.data()['start_date']),
              student: Student(name: document.data()['student_name']),
              subject: document.data()['subject'] as String,
              teacher: document.data()['name'] as String,
            ));
          }
          notifyListeners();
        });

It's from the nifty codelabs on writing queries that Google puts out.

I'm not sure how to copy in the target document for the query. It's in Firebase and one of the fields is "subject".

jhbiggs
  • 78
  • 7
  • 1
    Can you share the query that you are making and also the document that is supposed to match it? – Dharmaraj Jan 22 '22 at 13:37
  • 1
    @JustinBiggs Note that [rules are not filters](https://firebase.google.com/docs/firestore/security/rules-conditions#rules_are_not_filters) – Renaud Tarnec Jan 22 '22 at 13:39
  • What about this from the [Firestore help on writing conditions](https://cloud.google.com/firestore/docs/security/rules-conditions): `service cloud.firestore { match /databases/{database}/documents { // Allow the user to read data if the document has the 'visibility' // field set to 'public' match /cities/{city} { allow read: if resource.data.visibility == 'public'; } } }` – jhbiggs Jan 22 '22 at 14:56
  • @JustinBiggs Those rules determine what documents you code can access, but they don't filter on their own. You must always combine the rules with a condition in your query that ensures you only request the documents that you have permission to read. In addition to the link Renaud gave, see the Firestore documentation on [securely querying data](https://firebase.google.com/docs/firestore/security/rules-query). – Frank van Puffelen Jan 22 '22 at 15:05
  • Got it. I'm still stuck with the nulls though when I add the resource.data into my rule. Why won't it make the fields available as they are in the tutorials (e.g. resource.data.visiblity, and in my case resource.data.subject)? – jhbiggs Jan 22 '22 at 15:20

1 Answers1

1

Firestore rules are not for sorting data you can download and cannot download. You probably query data with no where() method/function included. Firestore thinks you want to query all documents so he reject whole query.

Include in your query:

JavaScript

const q = query(citiesRef, where("subject", "==", "Biology"));
...

or

db.collection("cities").whereField("subject", isEqualTo: "Biology")

Probobly second .whereField() method will work with flutter but i don't know.

This is how rules should looks like:

match /databases/{database}/documents {
    match /{document=**} { // here you restrict access to whole database
      allow read, write: if false;
    }
    match /assignments/{docID} { // here except data to whole database was restricted you allow to read write documents in this collection path.
        allow read: if isTeacher() || isModerator()
        allow write: if isModerator()
    }
    function isTeacher() {
        return request.auth.token.subject == resource.data.subject
    }
    function isModerator() {
        return request.auth.token.moderator == true;
    }
  }

Teachers with token.subject == "Biology" can only request documents with field subject == "Biology" so they need to use whereField() in query. If teacher subject is "Math" he need to request only documents whereField("subject", isEqualTo: "Math") Moderators no need to use "whereField()" they can read whole data.

Mises
  • 4,251
  • 2
  • 19
  • 32
  • Yes, however there was a comment from @thatjennperson [on her video on writing firestore rules](https://www.youtube.com/watch?v=SSiLsIkPQWs) in which she explains that you never want to rely on client filters for security purposes. She explained that the custom claims can be used to match only those documents in firebase to which the user has been granted access. – jhbiggs Jan 22 '22 at 15:00
  • @jhbiggs I exacly know how it works and i didn't wrote any thing about deleting rules! You request all documents but you cannot get all document becouse some of them are subject == "Math" not "Biology" so whole request fails!!! – Mises Jan 22 '22 at 15:27
  • @jhbiggs By the way your moderator wont be able to read any data. Funy thing he can save and update data but no read. XD – Mises Jan 22 '22 at 15:33
  • Ok, that makes sense. It's all or nothing. What about the tip from [Cloud Firestore Security Rules condition writing](https://cloud.google.com/firestore/docs/security/rules-conditions): `allow read: if resource.data.visibility == 'public';`. Why couldn't I write a similar rule to `allow read: if resource.data.subject == 'Biology'`? – jhbiggs Jan 22 '22 at 15:47
  • @jhbiggs You can go a head but in this way any one can read document if field in document is == "Biology". The problem is in rules you wrote in question you compare with user token no just that field contains some value. – Mises Jan 22 '22 at 15:53
  • Yes, that's what I'm wondering. My resource.data is null and I can't figure out why. – jhbiggs Jan 22 '22 at 15:55
  • @jhbiggs Now pleas give me an arrow up near my answer and under votes arrows you can mark my answer as correct. – Mises Jan 22 '22 at 16:21