Escribir condiciones para las reglas de seguridad

Esta guía se basa en la guía Estructurar reglas de seguridad para mostrar cómo añadir condiciones a las reglas de seguridad de Firestore. Si no conoces los conceptos básicos de las reglas de seguridad de Firestore, consulta la guía de inicio.

El componente principal de las reglas de seguridad de Firestore es la condición. Una condición es una expresión booleana que determina si se debe permitir o denegar una operación concreta. Usa reglas de seguridad para escribir condiciones que comprueben la autenticación de los usuarios, validen los datos entrantes o incluso accedan a otras partes de tu base de datos.

Autenticación

Uno de los patrones de reglas de seguridad más habituales es controlar el acceso en función del estado de autenticación del usuario. Por ejemplo, es posible que tu aplicación solo quiera permitir que los usuarios que hayan iniciado sesión escriban datos:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to access documents in the "cities" collection
    // only if they are authenticated.
    match /cities/{city} {
      allow read, write: if request.auth != null;
    }
  }
}

Otro patrón habitual es asegurarse de que los usuarios solo puedan leer y escribir sus propios datos:

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches name of the user
    // document. The wildcard expression {userId} makes the userId variable
    // available in rules.
    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null;
    }
  }
}

Si tu aplicación usa Firebase Authentication o Google Cloud Identity Platform, la variable request.auth contiene la información de autenticación del cliente que solicita datos. Para obtener más información sobre request.auth, consulta la documentación de referencia.

Validación de datos

Muchas aplicaciones almacenan información de control de acceso como campos en documentos de la base de datos. Las reglas de seguridad de Firestore pueden permitir o denegar el acceso de forma dinámica en función de los datos del documento:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

La variable resource hace referencia al documento solicitado y resource.data es un mapa de todos los campos y valores almacenados en el documento. Para obtener más información sobre la variable resource, consulta la documentación de referencia.

Al escribir datos, puede que quieras comparar los datos entrantes con los datos que ya tienes. En este caso, si tu conjunto de reglas permite la escritura pendiente, la variable request.resource contiene el estado futuro del documento. En el caso de las update operaciones que solo modifican un subconjunto de los campos del documento, la variable request.resource contendrá el estado pendiente del documento después de la operación. Puedes consultar los valores de los campos en request.resource para evitar actualizaciones de datos no deseadas o incoherentes:

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure all cities have a positive population and
    // the name is not changed
    match /cities/{city} {
      allow update: if request.resource.data.population > 0
                    && request.resource.data.name == resource.data.name;
    }
  }
}

Acceder a otros documentos

Con las funciones get() y exists(), tus reglas de seguridad pueden evaluar las solicitudes entrantes en comparación con otros documentos de la base de datos. Las funciones get() y exists() esperan rutas de documentos totalmente especificadas. Cuando se usan variables para crear rutas de get() y exists(), es necesario escapar las variables de forma explícita con la sintaxis $(variable).

En el ejemplo siguiente, la variable database se captura mediante la instrucción match match /databases/{database}/documents y se usa para formar la ruta:

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      // Make sure a 'users' document exists for the requesting user before
      // allowing any writes to the 'cities' collection
      allow create: if request.auth != null && exists(/databases/$(database)/documents/users/$(request.auth.uid));

      // Allow the user to delete cities if their user document has the
      // 'admin' field set to 'true'
      allow delete: if request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true;
    }
  }
}

En el caso de las escrituras, puedes usar la función getAfter() para acceder al estado de un documento después de que se complete una transacción o un lote de escrituras, pero antes de que se confirme la transacción o el lote. Al igual que get(), la función getAfter() usa una ruta de documento totalmente especificada. Puede usar getAfter() para definir conjuntos de escrituras que deben realizarse juntas como una transacción o un lote.

Acceder a los límites de llamadas

Hay un límite en las llamadas de acceso a documentos por evaluación de conjunto de reglas:

  • 10 para solicitudes de un único documento y de consultas.
  • 20 para lecturas de varios documentos, transacciones y escrituras por lotes. El límite anterior de 10 también se aplica a cada operación.

    Por ejemplo, imagina que creas una solicitud de escritura por lotes con 3 operaciones de escritura y que tus reglas de seguridad usan 2 llamadas de acceso al documento para validar cada escritura. En este caso, cada escritura usa 2 de sus 10 llamadas de acceso, mientras que la solicitud de escritura por lotes usa 6 de sus 20 llamadas de acceso.

Si se supera cualquiera de los límites, se obtiene un error de permiso denegado. Algunas llamadas de acceso al documento pueden permanecer en caché, pero las llamadas en caché no se tienen en cuenta para los límites.

Para obtener una explicación detallada de cómo afectan estos límites a las transacciones y a las escrituras por lotes, consulta la guía para proteger las operaciones atómicas.

Acceder a llamadas y precios

Al usar estas funciones, se ejecuta una operación de lectura en tu base de datos, lo que significa que se te cobrará por leer documentos aunque tus reglas rechacen la solicitud. Consulta la página Precios de Firestore para obtener información más específica sobre la facturación.

Funciones personalizadas

A medida que tus reglas de seguridad se vuelvan más complejas, puede que quieras envolver conjuntos de condiciones en funciones que puedas reutilizar en todo tu conjunto de reglas. Las reglas de seguridad admiten funciones personalizadas. La sintaxis de las funciones personalizadas es similar a la de JavaScript, pero las funciones de las reglas de seguridad se escriben en un lenguaje específico del dominio que tiene algunas limitaciones importantes:

  • Las funciones solo pueden contener una instrucción return. No pueden contener ninguna lógica adicional. Por ejemplo, no pueden ejecutar bucles ni llamar a servicios externos.
  • Las funciones pueden acceder automáticamente a las funciones y variables del ámbito en el que se definen. Por ejemplo, una función definida en el ámbito de service cloud.firestore tiene acceso a la variable resource y a funciones integradas como get() y exists().
  • Las funciones pueden llamar a otras funciones, pero no pueden ser recursivas. La profundidad total de la pila de llamadas está limitada a 10.
  • En la versión v2 de las reglas, las funciones pueden definir variables mediante la palabra clave let. Las funciones pueden tener hasta 10 enlaces let, pero deben terminar con una instrucción return.

Una función se define con la palabra clave function y utiliza cero o más argumentos. Por ejemplo, puede combinar los dos tipos de condiciones que se han usado en los ejemplos anteriores en una sola función:

service cloud.firestore {
  match /databases/{database}/documents {
    // True if the user is signed in or the requested data is 'public'
    function signedInOrPublic() {
      return request.auth.uid != null || resource.data.visibility == 'public';
    }

    match /cities/{city} {
      allow read, write: if signedInOrPublic();
    }

    match /users/{user} {
      allow read, write: if signedInOrPublic();
    }
  }
}

Si usas funciones en tus reglas de seguridad, serán más fáciles de mantener a medida que aumente su complejidad.

Las reglas no son filtros

Una vez que hayas protegido tus datos y empieces a escribir consultas, ten en cuenta que las reglas de seguridad no son filtros. No puedes escribir una consulta para todos los documentos de una colección y esperar que Firestore devuelva solo los documentos a los que el cliente actual tiene permiso para acceder.

Por ejemplo, considera la siguiente regla de seguridad:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

Denegada: esta regla rechaza la siguiente consulta porque el conjunto de resultados puede incluir documentos en los que visibility no sea public:

Web
db.collection("cities").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
    });
});

Permitida: esta regla permite la siguiente consulta porque la cláusula where("visibility", "==", "public") garantiza que el conjunto de resultados cumple la condición de la regla:

Web
db.collection("cities").where("visibility", "==", "public").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
        });
    });

Las reglas de seguridad de Firestore evalúan cada consulta en función de su resultado potencial y rechazan la solicitud si puede devolver un documento que el cliente no tiene permiso para leer. Las consultas deben seguir las restricciones definidas por tus reglas de seguridad. Para obtener más información sobre las reglas y las consultas de seguridad, consulta el artículo sobre consultar datos de forma segura.

Pasos siguientes