firebase-tools icon indicating copy to clipboard operation
firebase-tools copied to clipboard

RTDB emulator doesn't work properly with databaseAuthVariableOverride

Open rhodgkins opened this issue 4 years ago • 6 comments

[REQUIRED] Environment info

firebase-tools: 8.7.0 Platform: macOS node: v10.22.0 firebase-admin: 9.1.0 firebase-database-emulator: v4.5.0

[REQUIRED] Test case

Add some rules like the following:

{
   "rules": {
      "$userId": {
         "publicAccess": {
            ".validate": "newData.isBoolean()"
         },
         "data": {
            ".read": "$userId === auth.uid || (auth.token.auth_time >= ((now / 1000) - 60 * 60) && auth.token.customClaimAccess === $userId) || data.parent().child('publicAccess').val() === true"
         }
      }
   }
}

[REQUIRED] Steps to reproduce


const admin = require('firebase-admin')

const adminApp = admin.initializeApp({
   databaseURL: 'https://blah.firebaseio.com',
})

const noAuthApp = admin.initializeApp({
   databaseURL: 'https://blah.firebaseio.com',
   databaseAuthVariableOverride: null,
}, 'no-auth')
const userAuthApp = admin.initializeApp({
   databaseURL: 'https://blah.firebaseio.com',
   databaseAuthVariableOverride: {
      uid: 'USER_ID_1',
   },
}, 'user-auth')
const customClaimsAuthApp = admin.initializeApp({
   databaseURL: 'https://blah.firebaseio.com',
   databaseAuthVariableOverride: {
      uid: 'USER_ABC',
      token: {
         customClaimAccess: 'USER_ID_2',
         auth_time: (Date.now() / 1000)
      },
   },
}, 'custom-claims-auth')

const assert = require('assert').strict

async function test() {
   await adminApp.database().ref().set({
      USER_ID_1: {
         data: {
            blah: "hello1",
         },
      },
      USER_ID_2: {
         data: {
            blah: "hello2",
         },
      },
      USER_ID_3: {
         publicAccess: true,
         data: {
            blah: "hello3",
         },
      }
   })

   await assert.rejects(noAuthApp.database().ref('USER_ID_1/data').once('value'), /permission_denied/i, 'Should deny access with noAuthApp as not accessing public data')
   await assert.doesNotReject(userAuthApp.database().ref('USER_ID_1/data').once('value'), 'Should allow access with userAuthApp as accessing own data')
   await assert.doesNotReject(customClaimsAuthApp.database().ref('USER_ID_2/data').once('value'), 'Should allow access with customClaimsAuthApp as accessing data specified in claims')
   await assert.doesNotReject(noAuthApp.database().ref('USER_ID_3/data').once('value'), 'Should allow access with noAuthApp as accessing public data')
}

test().catch(error => {
   console.error(error)
}).finally(() => Promise.all(admin.apps.map(a => a.delete())))
  1. Run emulator firebase --project blah emulators:start --only database (can't use emulators:exec as need to keep it running to view the rule evaluations).
  2. Run the above script against the emulator FIREBASE_DATABASE_EMULATOR_HOST=localhost:9000 node script.js.
  3. View the evaluated DB rules: http://localhost:9000/.inspect/coverage?ns=blah

[REQUIRED] Expected behavior

I'd expect the tests to pass and the auth_time rule to be evaluated 3 times - returning true once and false twice.

[REQUIRED] Actual behavior

Rules are not always evaluated - seems to bail out...

image image image

To simplify the case when running:

await assert.rejects(noAuthApp.database().ref('USER_ID_1/data').once('value'), /permission_denied/i, 'Should deny access with noAuthApp as not accessing public data')

I'd expect this rule to be evaluated (as there's no auth.uid): (auth.token.auth_time >= ((now / 1000) - 60 * 60) && auth.token.customClaimAccess === $userId) || data.parent().child('publicAccess').val() === true

image

The whole rule seems not to be evaluated, despite the first rule being evaluated...!

image

image

rhodgkins avatar Aug 18 '20 11:08 rhodgkins

Just to add to this, I think the missing evaluations might be to do with null values because if you have a rule such as data.child('blah').val() < 100 || data.child('other').exists() and blah is unset (null) the hover info will show .val() returning null each time but the result count of data.child('blah').val() < 100 won't match this count.

rhodgkins avatar Aug 19 '20 07:08 rhodgkins

I'm also tackling with similar.

If you're still on this (2 years later!!!!):

  • are you sure the calls lead to the emulators? .firebaseio.com looks suspicious
  • By using demo-blah (anything starting with demo-) one makes sure the calls will not aim at the real Firebase project in the cloud.

I've created a request for proper documentation on how to use databaseAuthVariableOverride with firebase-admin and emulation.

akauppi avatar Jun 21 '22 11:06 akauppi

@akauppi I don't understand your points sorry. The .firebaseio.com hostname is swapped out by the internals of the SDK when FIREBASE_DATABASE_EMULATOR_HOST envar is specified. I imagine its something to do with null checks in the emulator etc. etc. that doesn't quite match production.

rhodgkins avatar Jul 04 '22 14:07 rhodgkins

@rhodgkins - have you tried running this with the latest version of firebase-admin and firebase-database-emulator?

maneesht avatar Aug 01 '22 17:08 maneesht

@maneesht yep, if you run the reproduction steps above the same thing happens.

Ran firebase setup:emulators:database to get the latest DB emulator (it downloaded v4.8.0).

rhodgkins avatar Aug 02 '22 08:08 rhodgkins

@rhodgkins thank you for your patience. We might have identified what the problem is, however we do not have the bandwidth to support this right now. I will keep this open until we can look further into this.

maneesht avatar Aug 24 '22 20:08 maneesht