Consultar datos de forma segura

Esta página se basa en los conceptos de Estructuración de reglas de seguridad y Escritura de condiciones para reglas de seguridad para explicar cómo las reglas de seguridad de Cloud Firestore interactúan con las consultas. Examina más de cerca cómo las reglas de seguridad afectan las consultas que puede escribir y describe cómo garantizar que sus consultas utilicen las mismas restricciones que sus reglas de seguridad. Esta página también describe cómo escribir reglas de seguridad para permitir o denegar consultas basadas en propiedades de consulta como limit y orderBy .

Las reglas no son filtros

Al escribir consultas para recuperar documentos, tenga en cuenta que las reglas de seguridad no son filtros: las consultas son todo o nada. Para ahorrarle tiempo y recursos, Cloud Firestore evalúa una consulta con su conjunto de resultados potenciales en lugar de con los valores de campo reales de todos sus documentos. Si una consulta podría devolver documentos que el cliente no tiene permiso para leer, toda la solicitud falla.

Consultas y reglas de seguridad.

Como lo demuestran los ejemplos siguientes, debe escribir sus consultas para que se ajusten a las limitaciones de sus reglas de seguridad.

Proteger y consultar documentos basados ​​en auth.uid

El siguiente ejemplo demuestra cómo escribir una consulta para recuperar documentos protegidos por una regla de seguridad. Considere una base de datos que contiene una colección de documentos story :

/historias/{id de historia}

{
  title: "A Great Story",
  content: "Once upon a time...",
  author: "some_auth_id",
  published: false
}

Además de los campos title y content , cada documento almacena el author y los campos published para utilizarlos en el control de acceso. Estos ejemplos suponen que la aplicación usa Firebase Authentication para establecer el campo author en el UID del usuario que creó el documento. Firebase Authentication también completa la variable request.auth en las reglas de seguridad.

La siguiente regla de seguridad utiliza las variables request.auth y resource.data para restringir el acceso de lectura y escritura de cada story a su autor:

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Only the authenticated user who authored the document can read or write
      allow read, write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

Supongamos que su aplicación incluye una página que muestra al usuario una lista de documentos story de su autoría. Es de esperar que pueda utilizar la siguiente consulta para completar esta página. Sin embargo, esta consulta fallará porque no incluye las mismas restricciones que sus reglas de seguridad:

No válido : las restricciones de consulta no coinciden con las restricciones de las reglas de seguridad

// This query will fail
db.collection("stories").get()

La consulta falla incluso si el usuario actual es en realidad el autor de cada documento story . El motivo de este comportamiento es que cuando Cloud Firestore aplica sus reglas de seguridad, evalúa la consulta con su conjunto de resultados potenciales , no con las propiedades reales de los documentos en su base de datos. Si una consulta podría incluir documentos que violen sus reglas de seguridad, la consulta fallará.

Por el contrario, la siguiente consulta tiene éxito porque incluye la misma restricción en el campo author que las reglas de seguridad:

Válido : las restricciones de consulta coinciden con las restricciones de las reglas de seguridad.

var user = firebase.auth().currentUser;

db.collection("stories").where("author", "==", user.uid).get()

Proteger y consultar documentos basados ​​en un campo.

Para demostrar aún más la interacción entre consultas y reglas, las reglas de seguridad siguientes amplían el acceso de lectura a la colección stories para permitir que cualquier usuario lea documentos story donde el campo published esté establecido en true .

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Anyone can read a published story; only story authors can read unpublished stories
      allow read: if resource.data.published == true || (request.auth != null && request.auth.uid == resource.data.author);
      // Only story authors can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

La consulta de páginas publicadas debe incluir las mismas restricciones que las reglas de seguridad:

db.collection("stories").where("published", "==", true).get()

La restricción de consulta .where("published", "==", true) garantiza que resource.data.published sea true para cualquier resultado. Por lo tanto, esta consulta satisface las reglas de seguridad y puede leer datos.

OR consultas

Al evaluar una consulta OR lógica ( or , in , o array-contains-any ) con un conjunto de reglas, Cloud Firestore evalúa cada valor de comparación por separado. Cada valor de comparación debe cumplir las restricciones de las reglas de seguridad. Por ejemplo, para la siguiente regla:

match /mydocuments/{doc} {
  allow read: if resource.data.x > 5;
}

No válido : la consulta no garantiza que x > 5 para todos los documentos potenciales

// These queries will fail
query(db.collection("mydocuments"),
      or(where("x", "==", 1),
         where("x", "==", 6)
      )
    )

query(db.collection("mydocuments"),
      where("x", "in", [1, 3, 6, 42, 99])
    )

Válido : la consulta garantiza que x > 5 para todos los documentos potenciales

query(db.collection("mydocuments"),
      or(where("x", "==", 6),
         where("x", "==", 42)
      )
    )

query(db.collection("mydocuments"),
      where("x", "in", [6, 42, 99, 105, 200])
    )

Evaluación de restricciones en consultas

Sus reglas de seguridad también pueden aceptar o rechazar consultas según sus restricciones. La variable request.query contiene las limit , offset y orderBy de una consulta. Por ejemplo, sus reglas de seguridad pueden rechazar cualquier consulta que no limite la cantidad máxima de documentos recuperados a un rango determinado:

allow list: if request.query.limit <= 10;

El siguiente conjunto de reglas demuestra cómo escribir reglas de seguridad que evalúen las restricciones impuestas a las consultas. Este ejemplo amplía el conjunto de reglas stories anteriores con los siguientes cambios:

  • El conjunto de reglas separa la regla de lectura en reglas para get y list .
  • La regla get restringe la recuperación de documentos individuales a documentos públicos o documentos escritos por el usuario.
  • La regla list aplica las mismas restricciones que get pero para consultas. También verifica el límite de consultas, luego rechaza cualquier consulta sin límite o con un límite mayor a 10.
  • El conjunto de reglas define una función authorOrPublished() para evitar la duplicación de código.
service cloud.firestore {

  match /databases/{database}/documents {

    match /stories/{storyid} {

      // Returns `true` if the requested story is 'published'
      // or the user authored the story
      function authorOrPublished() {
        return resource.data.published == true || request.auth.uid == resource.data.author;
      }

      // Deny any query not limited to 10 or fewer documents
      // Anyone can query published stories
      // Authors can query their unpublished stories
      allow list: if request.query.limit <= 10 &&
                     authorOrPublished();

      // Anyone can retrieve a published story
      // Only a story's author can retrieve an unpublished story
      allow get: if authorOrPublished();

      // Only a story's author can write to a story
      allow write: if request.auth.uid == resource.data.author;
    }

  }
}

Consultas de grupos de recopilación y reglas de seguridad.

De forma predeterminada, las consultas tienen como ámbito una única colección y recuperan resultados únicamente de esa colección. Con las consultas de grupos de colecciones , puede recuperar resultados de un grupo de colecciones que consta de todas las colecciones con el mismo ID. Esta sección describe cómo proteger las consultas de su grupo de recopilación mediante reglas de seguridad.

Proteger y consultar documentos basados ​​en grupos de colección.

En sus reglas de seguridad, debe permitir explícitamente consultas del grupo de recopilación escribiendo una regla para el grupo de recopilación:

  1. Asegúrate de rules_version = '2'; es la primera línea de su conjunto de reglas. Las consultas de grupos de colecciones requieren el nuevo comportamiento del comodín recursivo {name=**} de las reglas de seguridad versión 2.
  2. Escriba una regla para su grupo de colección usando match /{path=**}/ [COLLECTION_ID] /{doc} .

Por ejemplo, considere un foro organizado en documentos forum que contienen subcolecciones posts :

/foros/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
}

En esta aplicación, hacemos que las publicaciones sean editables por sus propietarios y legibles por usuarios autenticados:

service cloud.firestore {
  match /databases/{database}/documents {
    match /forums/{forumid}/posts/{post} {
      // Only authenticated users can read
      allow read: if request.auth != null;
      // Only the post author can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

Cualquier usuario autenticado puede recuperar las publicaciones de cualquier foro:

db.collection("forums/technology/posts").get()

Pero, ¿qué sucede si desea mostrarle al usuario actual sus publicaciones en todos los foros? Puede utilizar una consulta de grupo de colecciones para recuperar resultados de todas las colecciones posts :

var user = firebase.auth().currentUser;

db.collectionGroup("posts").where("author", "==", user.uid).get()

En sus reglas de seguridad, debe permitir esta consulta escribiendo una regla de lectura o lista para el grupo de colección posts :

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {
    // Authenticated users can query the posts collection group
    // Applies to collection queries, collection group queries, and
    // single document retrievals
    match /{path=**}/posts/{post} {
      allow read: if request.auth != null;
    }
    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth != null && request.auth.uid == resource.data.author;

    }
  }
}

Sin embargo, tenga en cuenta que estas reglas se aplicarán a todas las colecciones con posts de ID, independientemente de la jerarquía. Por ejemplo, estas reglas se aplican a todas las siguientes colecciones posts :

  • /posts/{postid}
  • /forums/{forumid}/posts/{postid}
  • /forums/{forumid}/subforum/{subforumid}/posts/{postid}

Consultas de grupos de recopilación segura basadas en un campo

Al igual que las consultas de una sola colección, las consultas de grupos de colecciones también deben cumplir las restricciones establecidas por sus reglas de seguridad. Por ejemplo, podemos agregar un campo published a cada publicación del foro como lo hicimos en el ejemplo stories anterior:

/foros/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
  published: false
}

Luego podemos escribir reglas para el grupo de colección posts según el estado de published y el author de la publicación:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    // Returns `true` if the requested post is 'published'
    // or the user authored the post
    function authorOrPublished() {
      return resource.data.published == true || request.auth.uid == resource.data.author;
    }

    match /{path=**}/posts/{post} {

      // Anyone can query published posts
      // Authors can query their unpublished posts
      allow list: if authorOrPublished();

      // Anyone can retrieve a published post
      // Authors can retrieve an unpublished post
      allow get: if authorOrPublished();
    }

    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth.uid == resource.data.author;
    }
  }
}

Con estas reglas, los clientes Web, Apple y Android pueden realizar las siguientes consultas:

  • Cualquiera puede recuperar publicaciones publicadas en un foro:

    db.collection("forums/technology/posts").where('published', '==', true).get()
    
  • Cualquiera puede recuperar las publicaciones de un autor en todos los foros:

    db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
    
  • Los autores pueden recuperar todas sus publicaciones publicadas y no publicadas en todos los foros:

    var user = firebase.auth().currentUser;
    
    db.collectionGroup("posts").where("author", "==", user.uid).get()
    

Proteger y consultar documentos según el grupo de recopilación y la ruta del documento

En algunos casos, es posible que desee restringir las consultas del grupo de recopilación según la ruta del documento. Para crear estas restricciones, puede utilizar las mismas técnicas para proteger y consultar documentos basados ​​en un campo.

Considere una aplicación que realiza un seguimiento de las transacciones de cada usuario entre varias bolsas de valores y criptomonedas:

/usuarios/{userid}/exchange/{exchangeid}/transactions/{transaction}

{
  amount: 100,
  exchange: 'some_exchange_name',
  timestamp: April 1, 2019 at 12:00:00 PM UTC-7,
  user: "some_auth_id",
}

Observe el campo user . Aunque sabemos qué usuario posee un documento transaction a partir de la ruta del documento, duplicamos esta información en cada documento transaction porque nos permite hacer dos cosas:

  • Escriba consultas de grupos de colección que estén restringidas a documentos que incluyan un /users/{userid} específico en la ruta del documento. Por ejemplo:

    var user = firebase.auth().currentUser;
    // Return current user's last five transactions across all exchanges
    db.collectionGroup("transactions").where("user", "==", user).orderBy('timestamp').limit(5)
    
  • Aplique esta restricción para todas las consultas en el grupo de recopilación transactions para que un usuario no pueda recuperar los documentos transaction de otro usuario.

Aplicamos esta restricción en nuestras reglas de seguridad e incluimos validación de datos para el campo user :

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    match /{path=**}/transactions/{transaction} {
      // Authenticated users can retrieve only their own transactions
      allow read: if resource.data.user == request.auth.uid;
    }

    match /users/{userid}/exchange/{exchangeid}/transactions/{transaction} {
      // Authenticated users can write to their own transactions subcollections
      // Writes must populate the user field with the correct auth id
      allow write: if userid == request.auth.uid && request.data.user == request.auth.uid
    }
  }
}

Próximos pasos