firebase / firebase-tools

The Firebase Command Line Tools
MIT License
4.02k stars 937 forks source link

EvaluationException when using crosss-service security rule with Emulator #5251

Closed johnnyoshika closed 1 year ago

johnnyoshika commented 1 year ago

[REQUIRED] Environment info

firebase-tools: 11.16.1

Platform: Windows

[REQUIRED] Test case

Use cross-service security rule (i.e. firestore.get()) in storage.rules:

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /courses/{courseId}/files/{fileName} {
      allow get: if firestore.get(/databases/(default)/documents/courses/$(courseId)).data.users[request.auth.uid].exists;
    }
  }
}

Add the following document to Emulator's Firestore at courses/course1:

{
  users: {
    user1: { exists: true }
  }
}

[REQUIRED] Steps to reproduce

Download file from storage using the JavaScript client SDK:

    const storage = getStorage();
    const fileRef = ref(storage, 'courses/4VnY71FQeT6XDAGNDjSSX/files/foo.txt');
    const url = await getDownloadURL(fileRef);
    const response = await fetch(url);

[REQUIRED] Expected behavior

Based on this announcement: https://firebase.blog/posts/2022/09/announcing-cross-service-security-rules, I expected cross-service security rules to be supported automatically for firebase-tools@11.10.0 and above.

[REQUIRED] Actual behavior

firestore.get() inside the storage rules results in this exception, so the security rule never allows the request:

  com.google.firebase.rules.runtime.common.EvaluationException: Error: C:\Users\...\storage.rules line [5], column [31]. Service call error. Function: [firestore.get], Argument: [path_value {
  segments {
    simple: "databases"
  }
  segments {
    simple: "(default)"
  }
  segments {
    simple: "documents"
  }
  segments {
    simple: "courses"
  }
  segments {
    simple: "4VnY71FQeT6XDAGNDjSSX"
  }
}
].
sam-gc commented 1 year ago

I'm unable to reproduce this issue. Using your rules and version 11.16.1, everything works as expected on my end. Just to double check, you're also using the Firebase Auth emulator as well right?

johnnyoshika commented 1 year ago

@sam-gc I'll try to reproduce it in a fresh environment. I'll share the repo if I can.

johnnyoshika commented 1 year ago

Just to double check, you're also using the Firebase Auth emulator as well right?

@sam-gc Yes

onra2 commented 1 year ago

I got the same problem.

having a create if: isManagerInRoles(idVenue) and 2 functions as:

function getEmailFromUserRequest(){
  return firestore.get(/databases/(default)/documents/users/$(request.auth.uid)).data.email;
}

function isManagerInRoles(venueId){
  return firestore.get(/databases/(default)/documents/roles/$(getEmailFromUserRequest())).data.venues[venueId] == 0;
}

When debugging it finds the email of my user. When hardcoding the email, it works too, but the function call fails.

johnnyoshika commented 1 year ago

@sam-gc I created a fresh new firebase local dev environment using the emulator and I wasn't able to repro this issue. Everything worked as expected. Now I have the fun task of figuring out why it works in the isolated environment but not in my other project.

Do you know what can cause this exception? com.google.firebase.rules.runtime.common.EvaluationException? Any hint may lead me in the right direction.

johnnyoshika commented 1 year ago

@onra2 are you also getting this exception?

com.google.firebase.rules.runtime.common.EvaluationException
onra2 commented 1 year ago

yes i get the exact same error as you

onra2 commented 1 year ago

if i may add my steps to reproduce:

have a collection "roles" in that collection add a document with id = "test@gmail.com" in that document add a field "venues" (type map) and inside that map an entry: 123456: 0

have a collection "users" when logging in, i create a document for the user, (id of document is the uid of the user) that user has a field "email" equal to his email.

basically i want to check if a user has the "0" role from the venue he is in to upload an image.

in my view i have:

async uploadImage(image: string, path: string){
    const storageRef = ref(this.storage, path);
    uploadString(storageRef, image, 'data_url');
  }

await this.uploadImage(this.imageURLData, "123456/venue_cover.png");

in the rules i have:

match /{idVenue}/{imgName}{
    allow read: if true;
    allow create: if isManagerInRoles(idVenue) && imgName == "venue_cover.png";
  }

  function getEmailFromUserRequest(){
    return firestore.get(/databases/(default)/documents/users/$(request.auth.uid)).data.email;
  }

  function isManagerInRoles(venueId){
    return firestore.get(/databases/(default)/documents/roles/$(getEmailFromUserRequest())).data.venues[venueId] == 0;
  }

when debugging, getEmailFromUserRequest() gets the correct email from my user. And isManagerInRoles gets the correct value if the email is hardcoded in the rule. But combining both functions like shown above doesn't work and raises an com.google.firebase.rules.runtime.common.EvaluationException exception.

I hope this helps.

kalenmike commented 1 year ago

I am getting the same error anytime I call firestore.get, the column error is on get:

firestore.get(/databases/(default)/documents/users/GkbacaBOL8eKrcLGXxgRjEiJOYpk);

com.google.firebase.rules.runtime.common.EvaluationException: Error: /home/ace/Projects/a/websites/b/c/storage.rules line [10], column [27]. Service call error. Function: [firestore.get], Argument: [path_value {
  segments {
    simple: "databases"
  }
  segments {
    simple: "(default)"
  }
  segments {
    simple: "documents"
  }
  segments {
    simple: "users"
  }
  segments {
    simple: "GkbacaBOL8eKrcLGXxgRjEiJOYpk"
  }
}
]

I am using firebase-tools 11.17.0 and using the emulators. Active emulators are Authentication, Functions, Firestore, Database, Hosting, Storage.

johnnyoshika commented 1 year ago

I was able to reproduce the bug. The exception occurs if the Firestore document contains a field with a value of null. If there are no fields with a value of null, everything works as expected.

@sam-gc I created this repo for you to reproduce: https://github.com/examind-ai/firebase-tools-cross-service-rules

Follow the instructions in README and you'll experience the EvaluationException. It's due to having this deletedAt: null value in the Firestore document: https://github.com/examind-ai/firebase-tools-cross-service-rules/blob/2af68b07df15b796d78174314f813d4102e75420/hosting/src/App.tsx#L28

Remove that one line of code and run the application again and the exception will not occur.

Here's a video demo using the sample repository linked above:

https://user-images.githubusercontent.com/504505/206770238-b513abca-2d0f-40f4-b98b-4775a263a166.mp4

Don't ask me how long it took me to figure this out 😖

sam-gc commented 1 year ago

@johnnyoshika thank you so much for digging deep to try to find out the missing piece. I will take a look as soon as I can, but hopefully with this repro it will be a straightforward fix!

sam-gc commented 1 year ago

The issue should be fixed by the linked PR. Keep an eye out for the changelog note in a future release!

Psycarlo commented 1 year ago

@sam-gc Was this fixed?

Psycarlo commented 1 year ago

@johnnyoshika Hey John I also get

com.google.firebase.rules.runtime.common.EvaluationException

But only on Github Actions With emulators:exec command I am importing test data. Locally on macOs and Windows the test pass but not in CI. Do you have any clue?

johnnyoshika commented 1 year ago

@Psycarlo No idea, sorry

johnnyoshika commented 1 year ago

@sam-gc Was this fixed?

Yes

natedx commented 3 months ago

But only on Github Actions With emulators:exec command

@Psycarlo, I am getting the same as you. While unit testing our cross-service rules, firestore.get() succeeds locally, but fails in our Github Actions CI. I've double-checked with logging that the firestore document exists before the rules evaluation. Have you been able to solve this ?