Estructurar reglas de seguridad

Las reglas de seguridad de Firestore te permiten controlar el acceso a los documentos y las colecciones de tu base de datos. La sintaxis flexible de las reglas te permite crear reglas que coincidan con cualquier elemento, desde todas las escrituras en toda la base de datos hasta operaciones en un documento específico.

En esta guía se describe la sintaxis y la estructura básicas de las reglas de seguridad. Combina esta sintaxis con condiciones de reglas de seguridad para crear conjuntos de reglas completos.

Declaración de servicio y base de datos

Las reglas de seguridad de Firestore siempre empiezan con la siguiente declaración:

service cloud.firestore {
  match /databases/{database}/documents {
    // ...
  }
}

La declaración service cloud.firestore limita el alcance de las reglas a Firestore, lo que evita conflictos entre las reglas de seguridad de Firestore y las reglas de otros productos, como Cloud Storage.

La declaración match /databases/{database}/documents especifica que las reglas deben coincidir con cualquier base de datos de Firestore del proyecto. Actualmente, cada proyecto solo tiene una base de datos llamada (default).

Reglas básicas de lectura y escritura

Las reglas básicas constan de una instrucción match que especifica una ruta de documento y una expresión allow que detalla cuándo se permite leer los datos especificados:

service cloud.firestore {
  match /databases/{database}/documents {

    // Match any document in the 'cities' collection
    match /cities/{city} {
      allow read: if <condition>;
      allow write: if <condition>;
    }
  }
}

Todas las declaraciones de concordancia deben apuntar a documentos, no a colecciones. Una instrucción de coincidencia puede apuntar a un documento específico, como en match /cities/SF, o usar comodines para apuntar a cualquier documento de la ruta especificada, como en match /cities/{city}.

En el ejemplo anterior, la instrucción de coincidencia usa la sintaxis de comodín {city}. Esto significa que la regla se aplica a cualquier documento de la colección cities, como /cities/SF o /cities/NYC. Cuando se evalúan las expresiones allow de la instrucción match, la variable city se resuelve en el nombre del documento de la ciudad, como SF o NYC.

Operaciones granulares

En algunas situaciones, es útil desglosar read y write en operaciones más granulares. Por ejemplo, tu aplicación puede querer aplicar condiciones diferentes a la creación de documentos que a la eliminación de documentos. También puedes permitir la lectura de documentos individuales, pero denegar las consultas grandes.

Una regla read se puede dividir en get y list, mientras que una regla write se puede dividir en create, update y delete:

service cloud.firestore {
  match /databases/{database}/documents {
    // A read rule can be divided into get and list rules
    match /cities/{city} {
      // Applies to single document read requests
      allow get: if <condition>;

      // Applies to queries and collection read requests
      allow list: if <condition>;
    }

    // A write rule can be divided into create, update, and delete rules
    match /cities/{city} {
      // Applies to writes to nonexistent documents
      allow create: if <condition>;

      // Applies to writes to existing documents
      allow update: if <condition>;

      // Applies to delete operations
      allow delete: if <condition>;
    }
  }
}

Datos jerárquicos

Los datos de Firestore se organizan en colecciones de documentos y cada documento puede ampliar la jerarquía mediante subcolecciones. Es importante entender cómo interactúan las reglas de seguridad con los datos jerárquicos.

Imagina una situación en la que cada documento de la colección cities contiene una subcolección landmarks. Las reglas de seguridad solo se aplican a la ruta coincidente, por lo que los controles de acceso definidos en la colección cities no se aplican a la subcolección landmarks. En su lugar, escribe reglas explícitas para controlar el acceso a las subcolecciones:

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      allow read, write: if <condition>;

        // Explicitly define rules for the 'landmarks' subcollection
        match /landmarks/{landmark} {
          allow read, write: if <condition>;
        }
    }
  }
}

Cuando se anidan instrucciones match, la ruta de la instrucción match interna siempre es relativa a la ruta de la instrucción match externa. Por lo tanto, los siguientes conjuntos de reglas son equivalentes:

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      match /landmarks/{landmark} {
        allow read, write: if <condition>;
      }
    }
  }
}
service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city}/landmarks/{landmark} {
      allow read, write: if <condition>;
    }
  }
}

Comodines recursivos

Si quieres que las reglas se apliquen a una jerarquía de profundidad arbitraria, usa la sintaxis de comodín recursivo, {name=**}. Por ejemplo:

service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the cities collection as well as any document
    // in a subcollection.
    match /cities/{document=**} {
      allow read, write: if <condition>;
    }
  }
}

Cuando se usa la sintaxis de comodín recursivo, la variable de comodín contendrá todo el segmento de ruta coincidente, aunque el documento se encuentre en una subcolección anidada. Por ejemplo, las reglas indicadas arriba coincidirían con un documento ubicado en /cities/SF/landmarks/coit_tower y el valor de la variable document sería SF/landmarks/coit_tower.

Sin embargo, ten en cuenta que el comportamiento de los comodines recursivos depende de la versión de las reglas.

Version 1

Las reglas de seguridad usan la versión 1 de forma predeterminada. En la versión 1, las comodines recursivos coinciden con uno o varios elementos de ruta. No coinciden con una ruta vacía, por lo que match /cities/{city}/{document=**} coincide con los documentos de las subcolecciones, pero no con los de la colección cities, mientras que match /cities/{document=**} coincide con los documentos de la colección cities y de las subcolecciones.

Los comodines recursivos deben colocarse al final de una instrucción de coincidencia.

Versión 2

En la versión 2 de las reglas de seguridad, los comodines recursivos coinciden con cero o más elementos de ruta. match/cities/{city}/{document=**} coincide con los documentos de cualquier subcolección, así como con los documentos de la colección cities.

Para habilitar la versión 2, debes añadir rules_version = '2'; en la parte superior de tus reglas de seguridad:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the cities collection as well as any document
    // in a subcollection.
    match /cities/{city}/{document=**} {
      allow read, write: if <condition>;
    }
  }
}

Puedes tener como máximo un comodín recursivo por instrucción de coincidencia, pero en la versión 2 puedes colocar este comodín en cualquier lugar de la instrucción de coincidencia. Por ejemplo:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the songs collection group
    match /{path=**}/songs/{song} {
      allow read, write: if <condition>;
    }
  }
}

Si usas consultas de grupos de colecciones, debes usar la versión 2. Consulta cómo proteger las consultas de grupos de colecciones.

Declaraciones de coincidencia superpuestas

Es posible que un documento coincida con más de una instrucción match. En el caso de que varias expresiones allow coincidan con una solicitud, se permitirá el acceso si alguna de las condiciones es true:

service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the 'cities' collection.
    match /cities/{city} {
      allow read, write: if false;
    }

    // Matches any document in the 'cities' collection or subcollections.
    match /cities/{document=**} {
      allow read, write: if true;
    }
  }
}

En el ejemplo anterior, se permitirán todas las lecturas y escrituras en la colección cities porque la segunda regla siempre es true, aunque la primera regla siempre sea false.

Límites de las reglas de seguridad

Cuando trabajes con reglas de seguridad, ten en cuenta los siguientes límites:

Límite Detalles
Número máximo de llamadas exists(), get() y getAfter() por solicitud
  • 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.

Número máximo de niveles de anidamiento de declaraciones match 10
Longitud máxima de ruta (en segmentos de ruta) permitida en un conjunto de declaraciones match anidadas 100
Número máximo de variables de captura de ruta permitido en un conjunto de declaraciones match anidadas 20
Profundidad máxima de la llamada de funciones 20
Número máximo de argumentos de función 7
Número máximo de vinculaciones de variable let por función 10
Número máximo de llamadas de funciones cíclicas o periódicas 0 (no permitidas)
Número máximo de expresiones evaluadas por solicitud 1000
Tamaño máximo de un conjunto de reglas Hay dos límites de tamaño para los conjuntos de reglas:
  • El límite de tamaño del origen de texto del conjunto de reglas publicado desde la consola de Firebase o desde la CLI usando firebase deploy es de 256 kB.
  • El límite de tamaño del conjunto de reglas compilado que se genera cuando Firebase procesa el origen y lo activa en el backend es de 250 kB.

Pasos siguientes