Gene Bo
Gene Bo

Reputation: 12063

Firebase Security rules to restrict access on all paths but one?

Question:

For the different top-level firestore collections below, how to restrict access to all but one of the paths?

We are building a data schema in Firestore to support a chat app for teachers across multiple schools.

The top-level firestore collections include:

Below is the security rules setup we are trying now - where we check for:

  1. valid user auth
  2. expected value exists in userClaim variable request.auth.token.chatFlatList

However, the read listener for /messages is being blocked.

Error message:

FirebaseError: [code=permission-denied]: Missing or insufficient permissions


service cloud.firestore {
  match /databases/{database}/documents {

    match /{document=**} {
      allow read, write: if false;
    }

    match /schools/{schoolId}/chats/{discussionId}/messages {
      allow write: if false;
      allow read: if request.auth != null
                  && request.auth.token != null
                  && request.auth.token.chatFlatList.val().contains($discussionId);
    }
}

Details

We are using cloud functions for all data read/write, so for almost every case we can just block all client access.

The one exception is for the chat discussions, where we need to set a snapshot listener in the mobile client to know when there are new messages.

Sub-collection notes:

At a school, there are discussion sessions for school staff (teachers, admins, etc)

/schools/{schoolId}/chats/{discussionId}

Where each discussion-document contains:

  1. list of participant teacher ids
  2. subcollection for actual messages where each document is an indivual posted message:

/schools/{schoolId}/chats/{discussionId}/messages

User Claim code from Cloud Function

Looking at the cloud function logs, we have verified that the userClaim is being set.

return firebaseAdmin
    .auth()
    .setCustomUserClaims(
        uid, {
            chatFlatList: 'id1 id2 id3'
        }
    );

UPDATE #1

Tried the following variation where rules skip/omit the check on userClaim and auth.token.

However, still same permission error.

service cloud.firestore {
  match /databases/{database}/documents {

        match /{document=**} {
          allow read, write: if false;
        }

        match /schools/{schoolId}/chats/{discussionId}/messages {
          allow write: if false;
          allow read: if request.auth != null;
        }

    }
}

Upvotes: 5

Views: 3145

Answers (3)

Jamshaid Alam
Jamshaid Alam

Reputation: 527

This worked for me if I wanted to read and write all collection but not one collection named "backStage";

rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /{collection}/{document} {
          allow read: if true
          allow write: if (collection != "backStage");
        }
      }
    }

Upvotes: 2

Gene Bo
Gene Bo

Reputation: 12063

Here's a solution (seems to be working), which includes the check on chatFlatList user claim variable (from original question) for a substring :

        match /schools/{schoolId}/chats/{discussionId}/messages {
          allow write: if false;
          allow read: if request.auth != null
          && request.auth.token.chatFlatList.matches(discussionId);
        }

Figured this out thanks to:

  1. Firebase storage rules based on custom parameters

    • Here the post shows there is not any $ notation to access the path var. I recall seeing this in a security rules example code example - maybe it's specific to database tiers?
  2. https://firebase.google.com/docs/reference/security/storage/#string

  3. https://regex-golang.appspot.com/assets/html/index.html

    • Trying some example inputs here, to get an understanding for how to create the regex's.

Upvotes: 0

Revolution88
Revolution88

Reputation: 698

I think the issue here is that you are writing a rule on the collection called messages.

All match statements should point to documents, not collections. https://firebase.google.com/docs/firestore/security/rules-structure

You should try adding /{document=**} after your path to messages, something like:

    match /schools/{schoolId}/chats/{discussionId}/messages/{document=**} {
      allow write: if false;
      allow read: if request.auth != null;
    }

Upvotes: 4

Related Questions