firebasegoogle-cloud-firestorefirebase-security

Firestore rules related with User Custom Claims


I'm having issues on some conditions on my firestore rules. On my use case I have 2 types of users with different custom claims:

My plan is to give the following accesses:

For the first permission i made the following conditions:

match /Customers/{customerId}/{document=**} {
      allow read, write: if isCustomerAccessible(customerId);
    }
function isCustomerAccessible(customerId) {
      return 
        request.auth.token.customerIds.hasAny([customerId]) ||
        request.auth.token.customerId == customerId ||
        request.auth.token.isSuper;
    }

Getting the specific permitted document from the client-side its possible, but doing a query that is going to retrieve allowed documents It returns "permission-denied". The Objective to allow the collection independent of the query that is running.

For the second condition i made the following:

match /Sites/{site}/{document=**} {
      allow read: if isSiteAccessibleView(site);
      allow write: if isSiteAccessibleEdit(site);
    }

function isSiteAccessibleView(site) {
      return request.auth != null && (
        request.auth.token.isSuper ||
        hasSiteViewPermission(site) ||
        hasSitePermissionThroughCustomerIds(site)
      );
    }
    
    function isSiteAccessibleEdit(site) {
      return request.auth != null && (
        request.auth.token.isSuper ||
        hasSiteEditPermission(site) ||
        hasSitePermissionThroughCustomerIds(site)
      );
    }

    function hasSiteViewPermission(site) {
      let siteDoc = get(/databases/$(database)/documents/Sites/$(site));
      let permissionValue = siteDoc.data.permissionsToView;
      return permissionValue.hasAny([request.auth.token.customerId]);
    }
    
    function hasSiteEditPermission(site) {
      let siteDoc = get(/databases/$(database)/documents/Sites/$(site));
      let permissionValue = siteDoc.data.permissionsToEdit;
      let sites = request.auth.token.sites;
      return permissionValue.hasAny([request.auth.token.customerId]) && sites.hasAny([site]);
    }

    function hasSitePermissionThroughCustomerIds(site) {
      let siteDoc = get(/databases/$(database)/documents/Sites/$(site));
      let permissionValue = siteDoc.data.permissionsToEdit;
      return request.auth.token.customerIds.hasAny(permissionValue);
    }

Both conditions should give access to all documents of the nested collections however getting the collection Customer/{customerId}/People with a user that has the correct customerId isn't allowed.

To reproduce use the following rules:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /Customers/{customerId}/{document=**} {
      allow read, write: if isCustomerAccessible(customerId);
    }
        function isCustomerAccessible(customerId) {
      return 
        request.auth.token.customerIds.hasAny([customerId]) ||
        request.auth.token.customerId == customerId ||
        request.auth.token.isSuper;
    }
  }
}

use a client side and signIn with a user. This user must have the variable customerId inside. When doing a query to the collection Customers (that is going to only return allowed documents) it returns permissions denied

const customersCollection = collection(db, "Customers")
    const customersQuery = query(
        customersCollection,
        where("tenantID", "==", tenantID)
    )
    const docs = await getDocs(customersQuery)

Solution

  • Your rules don't match your query. Your rules only allow access to documents in the "Customers" collection which have an ID that must be in their custom claims. That means a client query absolutely must call out that ID in a get of a single document. A collection query won't work.

    Your query is actually requesting all of the documents in "Customers" filtered by a document field "tenantID". It's not limiting the query based on the document ID that is required by your rules, so the rules simply reject the query because it's potentially asking for more than what's allowed.

    If your rules accurately describe the access to be given, your client code will need to request only single documents whose IDs are in the user's custom claims. You can't simply ask for everything and expect the rules to filter the results for you. Security rules are not filters (you should definitely read and understand what that doc is say).

    An alternative might be to put the customerId in a document field, and use that field as a filter in the client client query. Your rules should also then check the contents of that field instead of the document ID.