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 story
documentos:
/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 story
documentos 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
ylist
. - 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 queget
, 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:
- 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. - 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 documentostransaction
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
- Para ver un ejemplo más detallado del control de acceso basado en roles, consulta Proteger el acceso a los datos de usuarios y grupos.
- Consulta la referencia de reglas de seguridad.