Search code examples
firebasegoogle-cloud-firestorefirebase-storagefirebase-security

Firebase cross-service Security Rules not working in application


I'm trying to use the new Firebase cross-service Security Rules (https://firebase.blog/posts/2022/09/announcing-cross-service-security-rules) but I having some problems with Storage Rules accessing to Firestore data.

The problem seems to be with userIsCreator() function

match /certification/{certificationId}/{fileId} {
  function userIsCreator() {
    let certification = firestore.get(/databases/(default)/documents/certifications/$(certificationId));
    return firestore.get(certification.data.creatorRef).id == request.auth.uid;
  }

  allow read, write: if userIsCreator()
}

The content of the Firestore Document is:

{
  "data": {
    othersValues,
    "creatorRef": "/databases/%28default%29/documents/users/CuutSAtFkDX2F9T8hlT4pjMUByS2"
  }
  "id": "3EhQakDrsKxlacUjdibs"
  "__name__": 
    "/databases/%28default%29/documents/certifications/3EhQakDrsKxlacUjdibs"
}

The creatorRef variable is a reference to a Firestore Document to user. Inside Users collection, the doc id is the UID of an user, so I'm obtaining the creatorRef of an item and then checking if the id of that user collection referenced is the same UID that user logged in.

enter image description here

The same function is working for Firestore Rules to avoid updating certification document if not the creator, without any problem.

It seems to be a problem calling to firestore.get to creatorRef after obtaining it but it not make sense!

Tested:

  1. If I use Firestore Storage Rules validator, it is not failing and it says I have access to that resource from the UID typed in the tester (for other UID is failing as expected). But in my app, even logged in with creator user is getting permission error.

  2. If changing the function to only one call directly to the Users collection id (return firestore.get(/databases/(default)/documents/users/CuutSAtFkDX2F9T8hlT4pjMUByS2).id == request.auth.uid;), it is working in the tester and my app. But it isn't a solution because I need to get first the Users collection ref for the creator!

For original function in the tester It's getting the variables as expected and returning true if simulate the creator UID! But for any reason, in the real app access it is getting unauthorized if making both calls!


Solution

  • Firebaser here!

    It looks like you've found a bug in our implementation of cross-service rules. With that said, your example will create two reads against Firestore but it's possible to simplify this to avoid the second read.

    Removing the second read

    From your post:

    return firestore.get(certification.data.creatorRef).id == request.auth.uid;
    

    This line is a bit redundant; the id field is already contained in the certification.data.creatorRef path. Assuming you are indeed using Firestore document references, the format of creatorRef will be /projects/<your-project-id>/databases/(default)/documents/users/<some-user-id>. You can therefore update your function to the following:

    function userIsCreator() {
      let certification = firestore.get(/databases/(default)/documents/certifications/$(certification));
      let creatorRef = certification.data.creatorRef;
    
      // Make sure to replace <your-project-id> with your project's actual ID
      return creatorRef ==
          /projects/<your-project-id>/databases/(default)/documents/users/$(request.auth.uid);
    }
    

    I've tested this out in the emulator and in production and it works as expected. The benefit of doing it this way is you only have to read from Firestore once, plus it works around the bug you've discovered.