Search code examples
google-cloud-firestorefirebase-security

Firestore rules to read only one collection and rest of the database restricted


So I have a project where I have some predefined rules already set which includes allow database read if user is authenticated and then allow some seperate stuff which i don't know much but when i integrated stripe in my firebase then stripe asked me to add those in my firestore rules.

Now i want to allow everyone to read from one specific collection and its one subcollection but i am not being able to do that

Before my rules were this

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read: if
          request.auth != null;
    }
  }
  match /databases/{database}/documents {
    match /users/{uid} {
      allow read: if request.auth.uid == uid;

      match /checkout_sessions/{id} {
        allow read, write: if request.auth.uid == uid;
      }
      match /subscriptions/{id} {
        allow read: if request.auth.uid == uid;
      }
    }

    match /products/{id} {
      allow read: if true;

      match /prices/{id} {
        allow read: if true;
      }

      match /tax_rates/{id} {
        allow read: if true;
      }
    }
  }
}

After they are these

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if
          request.auth != null;
    }
     match /organization/{document=**} {
      allow read: if true;     
    }
  }
  match /databases/{database}/documents {
    match /users/{uid} {
      allow read: if request.auth.uid == uid;

      match /checkout_sessions/{id} {
        allow read, write: if request.auth.uid == uid;
      }
      match /subscriptions/{id} {
        allow read: if request.auth.uid == uid;
      }
    }

    match /products/{id} {
      allow read: if true;

      match /prices/{id} {
        allow read: if true;
      }

      match /tax_rates/{id} {
        allow read: if true;
      }
    }
  }
}

But none of them are allowing users to read the organization collection publically

This is my database with some collections and i only want organization + organization's subcollection campaign to be publicly available.

enter image description here


Solution

  • Your rules :

    rules_version = '2'; 
    service cloud.firestore { 
    match /databases/{database}/documents { 
    match /{document=**} { 
    allow read, write: if request.auth != null;
     } 
    match /organization/{document=**} { 
    allow read: if true; 
    } } 
    match /databases/{database}/documents { 
    match /users/{uid} { 
    allow read: if request.auth.uid == uid; 
    match /checkout_sessions/{id} {
    allow read, write: if request.auth.uid == uid; } 
    match /subscriptions/{id} { 
    allow read: if request.auth.uid == uid; 
    } 
    } 
    match /products/{id} { 
    allow read: if true; 
    match /prices/{id} { 
    allow read: if true; 
    } 
    match /tax_rates/{id} { 
    allow read: if true; 
    } } } }
    

    Rules modified that works :

    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
         match /organization/{document=**} {
          allow read: if true;     
        }
       
        match /users/{uid} {
          allow read: if request.auth.uid == "priya";
            }
          match /checkout_sessions/{id} {
            allow read : if request.auth.uid == "priya";
            allow write: if request.auth.uid == "priya";
          }
          match /subscriptions/{id} {
            allow read : if true;
          }
          match /products/{id} {
          allow read: if true;
        }
          match /prices/{id} {
            allow read: if true;
          }
          match /tax_rates/{id} {
            allow read: if true;
          }
        }
      }
    

    Explanation :

    1. I have removed the :

      match /{document=**} { 
      allow read, write: if request.auth != null; } 
      

      match /{document=**} matches all documents in the entire database. The wildcard there actually "gobbles up" the entire path of the document, for the purpose of further matches. You also nested match /organization/{document=**} underneath it, which doesn't actually have any meaning (as you can't nest more documents under an outermost document). My rules work because I am matching the organization collection at the top level, not nested under anything else.

      Source :

      https://stackoverflow.com/a/55719394/15803365 https://firebase.google.com/docs/rules/basics#default_rules_locked_mode

    2. I have changed :

          match /organization/{document=**} { 
              allow read: if true;
      
      
      
      To 
      
          match /organization/{org}/campaign/{document=**} {
                allow read: if true;     
              }
      
      As you specified, you only want organization + organization's
      subcollection campaign to be publicly available.  I have designed a
      Firestore security rule that allows only documents and
      subcollections inside your campaign sub collection to have public
      read access. If you try to give documents under organization
      collection public read access, it will be denied. As the public read
      access only applies to documents/subcollections under campaign sub
      collection which is under organization collection, If you want any
      collections/documents inside organization to have public read
      access, you can change this to 
      
      match /organization/{document=**}{ 
          allow read: if true;  }
      

      Source :

      https://firebase.google.com/docs/firestore/security/rules-structure#recursive_wildcards

    3. match /databases/{database}/documents {

      This was a duplicate/ repeat of the first
      /databases/{database}/documents and it pretty much means matching to
      the default database we have and it's where all our Firestore rules
      should be inside.  Creating another /databases/{database}/documents
      is not correct and does not make sense.
      

      Source :

      https://firebase.google.com/docs/firestore/security/rules-structure#overlapping_match_statements

    You should test your rules, using the Firestore Simulator. The above rules were checked and modified following this documentation and this video

    How to test the rules?

    Open your Firebase Console. Go to Firestore Database, Click on Rules tab as highlighted in this screenshot. Click on Rule Playground and here you can simulate and test your rules.

    If you are checking for public read access, change the simulation type to ‘get’, specify the exact location path for which you want to check the rule in Location field for eg. subscriptions/{id} for your match /subscription/{id} rule. Set Authentication to off. Run the simulator by clicking on Run button and you will get a green/red message specifying your rule was tested successfully/denied respectively.

    enter image description here

    If you are checking for authenticated read access, change the simulation type to ‘get’, specify the exact location path for which you want to check the rule in Location field .For eg. users/{uid} for your match /users/{uid} rule. Set Authentication to on. Specify the Firebase uid, email, name, phone number with some random values. Run the simulator by clicking on Run button and you will get a green/red message specifying your rule was tested successfully/denied respectively. Here you have to keep in mind that request.auth.uid should be equal to uid. As we are testing it on a simulator, we hard code the values in uid, as request.auth.uid is already set when we give value to Firebase uid. When in production, you can set uid in your application.

    enter image description here

    If you are checking for authenticated write access, change the simulation type to ’create’, specify the exact location path for which you want to check the rule in Location field .For eg. checkout_sessions/{id} for your match /checkout_sessions/{id} rule. Set Authentication to on. Specify the Firebase uid, email, name, phone number with some random values. Run the simulator by clicking on Run button and you will get a green/red message specifying your rule was tested successfully/denied respectively. Here you have to keep in mind that request.auth.uid should be equal to uid. As we are testing it on a simulator, we hard code the values in uid, as request.auth.uid is already set when we give value to Firebase uid. When in production, you can set uid in your application.

    enter image description here