Ir a la consola

Consulta datos de forma segura

En esta página, se amplían los conceptos mencionados en Estructura las reglas de seguridad y Condiciones de escritura de las reglas de seguridad para explicar la forma en la que interactúan las consultas con las reglas de seguridad de Cloud Firestore. Examina con más detalle la manera en que las reglas de seguridad afectan a las consultas que puedes escribir y describe cómo garantizar que las consultas usen las mismas restricciones que tus reglas de seguridad. En esta página, también se explica cómo escribir reglas de seguridad para permitir o rechazar consultas según las propiedades de una consulta, como limit y orderBy.

Las reglas no son filtros

Cuando escribas consultas para recuperar documentos, ten en cuenta que las reglas de seguridad no son filtros: las consultas son todo o nada. Para ahorrar tiempo y recursos, Cloud Firestore evalúa una consulta en comparación con su conjunto de resultados potenciales en lugar de con los valores de campo reales para todos tus documentos. Si una consulta llegara a mostrar documentos que el cliente no tiene permiso para leer, fallará toda la solicitud.

Consultas y reglas de seguridad

Tal como demuestran los ejemplos a continuación, debes escribir tus consultas de modo que se adapten a las restricciones de tus reglas de seguridad.

Cómo asegurar 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. Considera una base de datos que contenga una colección de documentos story:

/stories/{storyid}

{
  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 los campos author y published para usar para control de acceso. En estos ejemplos, se supone que la app usa Firebase Authentication para configurar el campo author como el UID del usuario que creó el documento. Firebase Authentication también propaga la variable request.auth en las reglas de seguridad.

La siguiente regla de seguridad usa las variables request.auth y resource.data a fin de restringir el acceso de lectura y escritura para 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.uid == resource.data.author;
    }
  }
}

Supón que tu app incluye una página que muestra a los usuarios una lista de documentos story que ellos mismos crearon. Es de esperar que puedas usar la siguiente consulta para llenar esta página. Sin embargo, esta consulta fallará porque no incluye las mismas restricciones que tus reglas de seguridad:

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

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

No se logra realizar la consulta incluso si el usuario actual es realmente el autor de cada documento story. La razón de este comportamiento es que, cuando Cloud Firestore aplica tus reglas de seguridad, evalúa la consulta en comparación con su conjunto de resultados posibles, no con las propiedades reales de los documentos de la base de datos. Si una consulta llegara a incluir documentos que violen tus reglas de seguridad, la consulta fallará.

Por el contrario, la siguiente consulta se ejecuta de forma correcta porque incluye la misma restricción en el campo author que las reglas de seguridad:

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

var user = firebase.auth().currentUser;

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

Cómo asegurar y consultar documentos basados en un campo

Para demostrar aún más la interacción entre consultas y reglas, las siguientes reglas de seguridad amplían el acceso de lectura para la colección stories para que cualquier usuario pueda leer los documentos de story donde el campo published esté configurado 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.uid == resource.data.author;
      // Only story authors can write
      allow write: if request.auth.uid == resource.data.author;
    }
  }
}

La consulta para 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 cumple con las reglas de seguridad y tiene permitido leer los datos.

Cómo evaluar restricciones en consultas

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

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 con los siguientes cambios:

  • El conjunto de reglas separa la regla read en reglas para get y list.
  • La regla get restringe la recuperación de documentos individuales a documentos públicos o documentos que haya creado el usuario.
  • La regla list aplica las mismas restricciones que get, pero para consultas. También verifica el límite de consulta y, luego, rechaza cualquier consulta sin límite o con uno superior 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 colecciones y reglas de seguridad

De forma predeterminada, las consultas se enfocan a una sola colección y solo obtienen resultados de esa colección. Con las consultas de grupos de colecciones, puedes obtener resultados de un grupo de colecciones que contenga todas las recopilaciones con el mismo ID. En esta sección, se describe cómo proteger tus consultas de grupos de colecciones con las reglas de seguridad.

Protege y consulta documentos basados en grupos de colecciones

En tus reglas de seguridad, debes permitir explícitamente las consultas de grupos de colecciones. Para ello, escribe una regla específica para estos grupos:

  1. Asegúrate de que rules_version = '2'; sea la primera línea de tu conjunto de reglas. Las consultas de grupos de colecciones requieren el comportamiento nuevo del comodín recurrente {name=**} correspondiente a la versión 2 de las reglas de seguridad.
  2. Escribe una regla para tu grupo de colecciones con match /{path=**}/[COLLECTION_ID]/{doc}.

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

/forums/{forumid}/posts/{postid}

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

En esta aplicación, permitimos que los propietarios puedan editar sus publicaciones y que los usuarios autenticados puedan leerlas:

service cloud.firestore {
  match /databases/{database}/documents {
    match /forums/{forumid}/posts/{post} {
      // Only authenticated users can read
      allow read: if request.auth.uid != null;
      // Only the post author can write
      allow write: if 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 deseas mostrar al usuario actual sus publicaciones en todos los foros? Puedes usar una consulta de grupo de colecciones para obtener resultados de todas las colecciones de posts:

var user = firebase.auth().currentUser;

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

En tus reglas de seguridad, para autorizar esta consulta, debes escribir una regla de lectura o de lista para el grupo de colecciones de 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.uid != null;
    }
    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth.uid == resource.data.author;

    }
  }
}

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

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

Protege consultas de grupos de colecciones en función de un campo

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

/forums/{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 colecciones de posts según el estado 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, iOS y Android pueden realizar las siguientes consultas:

  • Cualquiera puede obtener las publicaciones de un foro:

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

    db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
    
  • Los autores pueden obtener todo lo que publicaron, o no, en todos los foros:

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

Protege y consulta documentos en función de un grupo de colecciones y la ruta del documento

En algunos casos, es posible que desees restringir las consultas de grupos de colecciones en función de la ruta del documento. Para crear estas restricciones, puedes usar las mismas técnicas para proteger y consultar documentos en función de un campo.

Imagina una aplicación que realiza un seguimiento de las transacciones de cada usuario entre varios intercambios de acciones y criptomonedas:

/users/{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",
}

Observa el campo user. Aunque sabemos qué usuario posee un documento transaction de la ruta del documento, duplicamos esta información en cada documento de transaction porque nos permite realizar estas dos acciones:

  • Escribir consultas de grupos de colecciones que estén restringidas a documentos que incluyan un /users/{userid} específico en su ruta de 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)
    
  • Implementar esta restricción para todas las consultas del grupo de colecciones de transactions para que un usuario no pueda obtener los documentos de transaction de otro usuario.

Aplicamos esta restricción en nuestras reglas de seguridad y, además, incluimos la 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
    }
  }
}

Pasos siguientes