firebase / firebase-tools

The Firebase Command Line Tools
MIT License
4.01k stars 925 forks source link

Rules using resource.data run twice in Firestore emulator #6252

Open acmertz opened 1 year ago

acmertz commented 1 year ago

Rules that use resource.data appear to be evaluated twice in the Firestore emulator. The first time always fails with an error, but the second one succeeds and allows/denies access as expected. This feels similar to #4325, but doesn't involve the use of onSnapshot (although I observed the same issue when using onSnapshot instead of getDoc).

Everything appears correct from the client side - the code doesn't appear to run twice, nor are any errors returned from the request. My rules using resource.data appear to work correctly when deployed to Firebase and don't show any errors in the Firebase console.

[REQUIRED] Environment info

firebase-tools: 12.4.6

Platform: Windows 11

[REQUIRED] Test case

Rules:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /projects/{projectId} {
      allow read: if (request.auth != null) && (request.auth.uid != null) && (resource.data.owner == request.auth.uid);
    }
  }
}

Firestore data at /projects/doc_id_here (replacing doc_id_here with the ID of the doc and uuid_here with the UUID of the currently authenticated user):

{ owner: "uuid_here" }
Screenshot from Firestore emulator:Firestore emulator screenshot

[REQUIRED] Steps to reproduce

Set up a project with the rules and data listed above, then make a request to it from the JS client SDK. For example:

import {doc, getDoc, getFirestore, connectFirestoreEmulator, Firestore} from 'firebase/firestore';

const firebaseApp = { /* Firebase app config here */ }
const firestore = getFirestore(firebaseApp);
connectFirestoreEmulator(firestore, location.hostname, 8080);

export function getProject(projectId) {
    getDoc(doc(firestore, `projects/${projectId}`))
}

[REQUIRED] Expected behavior

The Firestore emulator shows one entry with the result of the request on the Requests tab.

Screenshot 2023-08-13 140609

[REQUIRED] Actual behavior

The Firestore emulator shows two entries for each request; the first one failing with an unknown error and the second one successfully evaluating the rules.

Screenshot 2023-08-13 140651

Click on the request to view details. There's an error icon at the top of the table and a green checkmark on the line containing the related rule, and the resource section under "Detailed information" in the right column reads (Error: unknown) (undefined):

Screenshot 2023-08-13 140915

DavidWeiss2 commented 10 months ago

Same here.

Did you find any workaround by any chance? @acmertz ?

kossnocorp commented 8 months ago

I'm having this, too, and I suspect it causes the data never to resolve or throw an error; it just hangs forever. Did you experience that, maybe, or is it another emulator bug?

joshqn commented 8 months ago

I'm seeing a similar issue on Mac. When I look into the logs on my IDE running the Firebase client it looks like only a single write is being performed but then I see this logged in the emulator Firestore requests.

Removing request.auth.uid == request.resource.data.userId from the firestore rules seems to fix the emulator logs.

image
thekntl commented 8 months ago

Same here.

For my app, I use role-based security and I check user's capabilities via Firestore collection. If this case also appears in production, I wonder if my document reads will peak?

Sample Role Document

{
    userId_12345: {
        ...
        capabilities: [
                "products.list",
                "products.get"
                ...
        ],
        ...
    }
}

Security Rule Function I wrote a function in security rules to get document and check capability for related action.

function checkCapabilities(request, capabilities) {

    // Gets user's role from collection
    let role = debug(
        get(/databases/$(database)/documents/Claims/$(request.auth.uid)).data
    );

    return // boolean if capabilities match with required action capability or not.

}

Sample Query This query fires double security rule check. debug() function logs checkCapabilities, it logs twice too.

....

const querySnapshot = await getDocs(
    query(
        collection(
            getFirestore(),
            "Products"
        ),
        where(
            "companyId", "==", selectedCompanyId
        )
    )
    .withConverter(productsConverter)
);

...

Emulator's Firestore Requests

Screenshot 2024-01-08 at 7 31 37 AM
acmertz commented 8 months ago

Did you find any workaround by any chance?

@DavidWeiss2 I haven't found a proper workaround, unfortunately - in the meantime I've resorted to using a separate set of rules in my dev environment based on custom claims to cut down on the noise during development. This is less than ideal because it means I also have to test against my production ruleset before deploying. My application is fairly small so it isn't too difficult for me to run a smoke test of all critical features before deploying, but as it continues to grow, a proper fix or guidance from Firebase would definitely be appreciated.

transfer1992 commented 6 months ago

This is also happening to me. For every successful rule evaluation there is a preceding one that always errors out.

Here a single request has been made: image

Piogar commented 6 months ago

Any idea if this issue exists only in emulator?

DavidWeiss2 commented 6 months ago

Yes, it is emulator only.

On Sun, Feb 18, 2024, 13:43 Piogar @.***> wrote:

Any idea if this issue exists only in emulator?

— Reply to this email directly, view it on GitHub https://github.com/firebase/firebase-tools/issues/6252#issuecomment-1951217354, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADBVIS57VR3IXW7QJSAIZ73YUHSPNAVCNFSM6AAAAAA3O2T6V2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTSNJRGIYTOMZVGQ . You are receiving this because you were mentioned.Message ID: @.***>

ASE55471 commented 6 months ago

After upgrade firebase to version 13.4.0 still have this issue, Anyone got any idea to fix this now?

dubem-design commented 4 months ago

Experiencing same issue

CodeSmith32 commented 3 months ago

Same here.

Did Firestore introduce some undocumented change that runs it once without a read, and if it fails, it runs it again with a read? It seems like some weird trick to save on reads.. (i.e., if it fails without an error when given an empty resource object, then it wouldn't have to waste a read.)

BuyMyBeard commented 1 month ago

Same problem here. Is this simply an annoyance for the request log in the emulator, or does it have any impact on unit testing with @firebase/rules-unit-testing?

BuyMyBeard commented 1 month ago

I have my answer. It absolutely does break rules unit testing. This is highly problematic as it doesn't allow for easy integration into a CI pipeline.

Anybody has any leads on a cause? This is highly problematic for my team. Maybe we can try to fix the issue and bring up a PR.

BuyMyBeard commented 1 month ago

After browsing for a bit through the code, I found where the firestore emulator resides in the source code: https://github.com/firebase/firebase-tools/blob/a49e11a5be402f711401794445f8d34c965aaa8e/src/emulator/downloadableEmulators.ts#L100

I am under the impression the emulator is closed source, since we only have access to the compiled .jar file.

I submitted a bug report through the support troubleshooter, I'll give info when I get an answer back.