firebasegoogle-cloud-platformgoogle-cloud-firestorefirebase-securityfirebase-console

Firestore security rules: Function [get] called with malformed path


Cloud Firestore Rule

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /admins/{document=**} {
      allow read, write: if true;
    }

    match /{userId}/{document=**} {
      allow read, write: if request.auth.uid == userId || get(/databases/$(database)/documents/$(userId)/users/$(request.auth.token.email)).exists;
    }
  }
}

Details provided in Rules Playground of Cloud Firestore

Simulation type: get

Location: /databases/(default)/documents/cTgy9W6Zt2dShSgqig2ToeYLEzt2/products

Authenticated: True

Authentication Payload

{
  "uid": "dQkped8ggvUv6iHfI9kVD7Vrnlf2",
  "token": {
    "sub": "dQkped8ggvUv6iHfI9kVD7Vrnlf2",
    "aud": "undefined",
    "email": "abc@example.com",
    "email_verified": false,
    "firebase": {
      "sign_in_provider": "password"
    }
  }
}

Error

Function [get] called with malformed path: /databases/(default)/documents/cTgy9W6Zt2dShSgqig2ToeYLEzt2/users/abc@example.com

Please see this image enter image description here


Solution

  • You need to pass a Document path to the get() method.

    As explained in the doc: "The get() and exists() functions both expect fully specified document paths."

    With

    /databases/$(database)/documents/$(userId)/users/$(request.auth.token.email)
    

    you are actually passing a Collection path (3 elements).

    You need to change your data model or add a document in the abc@example.com subcollection to point to.


    In addition I think you're mixing up the get() and exists() functions.

    The get() method returns a rules.firestore.Resource which does not have an exists property.

    So doing get(**path**).exists cannot work. You should either use exists() to check if a specific document exists or use get() to check the value of a field of the specific document through the data property.