1
votes

We tried to list all docs from an 'events' collection. (/event/{eventId}) in each event's doc, a property 'users' (array) exists with all user's id in event. We have another collection 'role' with the role of each user for each event. (/role/{eventId}/userRole/{userId})

We have permission to get one event with

db.collection('event').doc(eventId).get()

But when we tried (from android and web) to make query like

db.collection('event').where("users", "array-contains", user.uid).get()

we got FirebaseError: Missing or insufficient permissions.

Event's rules :

    match /event/{eventId} {
      allow read:
        if isAuth() && 
        inEvent(eventId, request.auth.uid);
      allow create: 
        if isAuth() &&
        checkEventName() &&
        isOwner(database) &&
        request.resource.data.users == [];
      allow update:
        if isAuth() &&
        resource.data.ownerId == request.auth.uid &&
        checkEventName() &&
        isOwner(database) &&
        request.resource.data.mediaCount == resource.data.mediaCount &&
        request.resource.data.users == resource.data.users;
      allow delete:
        if false;
    }

Role's rules :

    match /role/{roleId} {
      allow read: 
        if isAuth();
      allow write:
        if false;

      match /userRole/{userRoleId} {
        allow read:
          if isAuth() &&
          userRoleId == request.auth.uid;
        allow create:
          if isAuth() &&
          userRoleId == request.auth.uid && 
          exists(/databases/$(database)/documents/event/$(roleId)) &&
          request.resource.data.actual is number &&
          request.resource.data.actual >= 0 &&
          request.resource.data.actual <= 10 &&
          request.resource.data.previous is number &&
          request.resource.data.actual == request.resource.data.previous;
        allow update: 
          if isAuth() &&
          userRoleId == request.auth.uid &&
          request.resource.data.actual == 0 &&
          resource.data.actual != 0;
        allow delete: 
          if false;
      }
    }

The role id match the event id.

In event's read rule we have inEvent(eventId, request.auth.uid). When we write

function inEvent(eventID, userID) {
      return true;
}

the query works but if we try something like

return exists(/databases/$(database)/documents/role/$(eventID)/userRole/$(userID));

or

return get(/databases/$(database)/documents/role/$(eventID)/userRole/$(userID)).data.actual >= 10;

we always get permission error

We have checked all data and rules but we don't understand why we can get individual event but not list. What we doing wrong ? Can we use get() and exists() for list permissions ?

1

1 Answers

1
votes

Firebase's security rules are enforced when you first attach a listener. At that point the server must be able to validate that the read operation will forever only return documents that you're authorized to receive. In your current rules that would require the server to read each events document, and then check the user's role for each document. This is an unscalable operation, so the server doesn't allow it and rejects the read.

This typically means that the information that you want to secure on should be present in (or a subcollection under) the document you're trying to allow access to, or in a fixed location for the current user. The best I can think of for your case is to have the ACL under the event that is being read, although even then I'm not entirely sure if you can model it so that the server can statically evaluate.

Also see: