Search code examples
fluttergoogle-cloud-firestorefirebase-security

Firebase - firestore rule to get data from nested collection if that particular user id present


I have the data in the following structure:

stage/data/groups(coll)/

       group_name

       group_id

       participants(coll)/userid

My requirement is to write the firebase rule with the following condition.

  1. If user A calls the groups collection, then it should returns ALL the groups if the A user ID available in the participant list.
  2. If the particular user is not available in the participant list, then that group should not be accessible to that user.

I have tried the following rule, but it doesn't help.

rules_version = '2';    
service cloud.firestore {
  match /databases/{database}/documents {
      
    function isAuthorised(document){
      return exists(/document/participants/$(request.auth.uid))
    }
      
    match /{document=**} {
      allow read, write: if isAuthorised(document);
    }
  }
}

I would just iterate the list and return only a valid list if it is an ASP.NET API code. As I am new to Firebase, I am learning the complex rules to protect the data.

Edited: Updated the rule as below, it works if I pass the hardcoded group id instead of passing "$(groupid)" to the exists validation method.

rules_version = '2';

service cloud.firestore {
  match /databases/{database}/documents {
      
      function isAuthenticated(){
        return request.auth.uid != null;
      }
      
    match /{document=**}/groups/{groupid} {
      allow read, write: if 
      isAuthenticated() && 
      exists(/databases/$(database)/documents/stage/data/groups/$(groupid)/participants/$(request.auth.uid));
    }
    
    match /{document=**}/users/{userid} {
      allow read, write: if true;
    }
  }
}

The flutter code I am trying to access the groups.

  final groupList = await ref.collection(Constants.Environment)
      .doc("data")
      .collection("groups").get();
  var docs = groupList.docs.toList();

Solution

  • From the example in the documentation, it seems that exists requires the full path of the document you want to check on. You're passing a relative path, which won't work.

    function isAuthorised(document){
      return exists(/databases/$(database)/documents/participants/$(request.auth.uid))
    }
    

    Update: the query you added will never work with your rules as rules are not filters (read the link please). Rather rules ensure that the data that your code accesses matches your requirements. In this case, this requirement of your rules is not matches by the code:

    exists(/databases/$(database)/documents/stage/data/groups/$(groupid)/participants/$(request.auth.uid))
    

    When you access a single document, the rules engine may be able to check this condition. But it can never do that for a list-call, as it'd have to check each individual group to see if its subcollection has a matching document. This wouldn't scale, so is not possible.

    The typical approach is to add an array of participants in each groups document. If that won't work for your use-case, consider storing a reverse index in the data, so a list of the groups for each UID:

    userGroups (collection)
      $uid (docs)
        groupIds (collection)
          $groupId