Consultar datos de forma segura

En esta página se desarrollan los conceptos de Estructurar reglas de seguridad y Escribir condiciones para las reglas de seguridad para explicar cómo interactúan las reglas de seguridad de Firestore con las consultas. Se analiza en detalle cómo afectan las reglas de seguridad a las consultas que puedes escribir y se describe cómo asegurarte de que tus consultas usen las mismas restricciones que tus reglas de seguridad. En esta página también se describe cómo escribir reglas de seguridad para permitir o denegar consultas en función de las propiedades de las consultas, 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, sino que las consultas son todo o nada. Para ahorrarte tiempo y recursos, Firestore evalúa una consulta en función de su conjunto de resultados potencial en lugar de los valores de campo reales de todos tus documentos. Si una consulta puede devolver documentos para los que el cliente no tiene permiso de lectura, se produce un error en toda la solicitud.

Consultas y reglas de seguridad

Como se muestra en los ejemplos de abajo, debes escribir tus consultas de forma que se ajusten a las restricciones de tus reglas de seguridad.

Proteger y consultar documentos en función de auth.uid

En el siguiente ejemplo se muestra cómo escribir una consulta para obtener documentos protegidos por una regla de seguridad. Supongamos que tienes una base de datos que contiene una colección de storydocumentos:

/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 controlar el acceso. En estos ejemplos se da por hecho que la aplicación usa Firebase Authentication para asignar al campo author el UID del usuario que creó el documento. La autenticación de Firebase también rellena la variable request.auth en las reglas de seguridad.

La siguiente regla de seguridad usa 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 tu aplicación incluye una página que muestra al usuario una lista de storydocumentos que ha creado. Podrías pensar que puedes usar la siguiente consulta para rellenar 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()

La consulta falla aunque el usuario actual sea el autor de todos los documentos de story. El motivo de este comportamiento es que, cuando Firestore aplica tus reglas de seguridad, evalúa la consulta en función de su conjunto de resultados potencial, no de las propiedades reales de los documentos de tu base de datos. Si una consulta puede incluir potencialmente documentos que infrinjan tus reglas de seguridad, la consulta fallará.

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

Válida: 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()

Proteger y consultar documentos en función de un campo

Para demostrar aún más la interacción entre las consultas y las reglas, las reglas de seguridad que se muestran a continuación amplían el acceso de lectura de la colección stories para permitir que cualquier usuario lea documentos story en los que el campo published esté definido como 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 es true para cualquier resultado. Por lo tanto, esta consulta cumple las reglas de seguridad y tiene permiso para leer datos.

OR consultas

Cuando se evalúa una consulta lógica OR (or, in o array-contains-any) en un conjunto de reglas, Firestore evalúa cada valor de comparación por separado. Cada valor de comparación debe cumplir las restricciones de la regla de seguridad. Por ejemplo, en 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 posibles

// 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álida: la consulta garantiza que x > 5 para todos los documentos posibles

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

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

Evaluar las restricciones de las consultas

Tus reglas de seguridad también pueden aceptar o rechazar consultas en función de sus restricciones. La variable request.query contiene las propiedades limit, offset y orderBy de una consulta. Por ejemplo, tus reglas de seguridad pueden denegar cualquier consulta que no limite el número máximo de documentos recuperados a un intervalo determinado:

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

El siguiente conjunto de reglas muestra cómo escribir reglas de seguridad que evalúen las restricciones aplicadas a las consultas. En este ejemplo se amplía el conjunto de reglas stories anterior 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 a documentos que haya creado el usuario.
  • La regla list aplica las mismas restricciones que get, pero a las consultas. También comprueba el límite de consultas y, a continuación, rechaza cualquier consulta sin límite o con un límite 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 limitan a una sola colección y solo devuelven resultados de esa colección. Con las consultas de grupo de colecciones, puedes obtener resultados de un grupo de colecciones formado por todas las colecciones que tengan el mismo ID. En esta sección se describe cómo proteger las consultas de grupos de colecciones mediante reglas de seguridad.

Proteger y consultar documentos en función de grupos de colecciones

En tus reglas de seguridad, debes permitir explícitamente las consultas de grupos de colecciones escribiendo una regla para el grupo de colecciones:

  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 nuevo comportamiento recursivo de comodín {name=**} de 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, imagina un foro organizado en forum documentos que contienen posts subcolecciones:

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

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

En esta aplicación, los propietarios pueden editar las publicaciones y los usuarios autenticados pueden leerlas:

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é ocurre si quieres mostrarle al usuario actual sus publicaciones en todos los foros? Puedes usar una consulta de grupo de colecciones para obtener resultados de todas las colecciones posts:

var user = firebase.auth().currentUser;

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

En tus reglas de seguridad, debes permitir esta consulta escribiendo una regla de lectura o de lista para el grupo de colecciones 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, 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 colecciones siguientes:posts

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

Proteger las 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 las restricciones definidas por tus reglas de seguridad. Por ejemplo, podemos añadir un published campo a cada publicación del foro, como hicimos en el ejemplo de stories anterior:

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

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

Después, podemos escribir reglas para el grupo de colecciones posts en función del estado published y la publicación author:

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 hacer las siguientes consultas:

  • Cualquier usuario puede recuperar las publicaciones de un foro:

    db.collection("forums/technology/posts").where('published', '==', true).get()
    
  • Cualquier usuario 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 sin publicar en todos los foros:

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

Buscar documentos de forma segura y hacer consultas en ellos en función del grupo de colecciones y de la ruta del documento

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

Imagina una aplicación que registra las transacciones de cada usuario en varias bolsas de valores 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",
}

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

  • Escribe consultas de grupo de colecciones que se limiten a los 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)
    
  • Aplica esta restricción a todas las consultas de la colecció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 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