Por: Juan Andrés Núñez
Juan Andrés Núñez - juanwmedia

Especialista en tecnologías Web. Me dedico a enseñar desarrollo Web moderno a cualquier persona (físicamente en clase y a través de Internet) desde una perspectiva holística: teniendo en cuenta las competencias técnicas necesarias, junto a las habilidades personales o soft skills. Tienes más información en mi Web.

Enlaces a la documentación


  • La clave aquí es que vamos a definir una serie de reglas de acceso a cada nodo (colección o documento) de nuestra base de datos en Cloud Firestore.
  • Todas las reglas consisten en bloques match, los cuales identifican documentos en tu base de datos y permiten expresiones que controlan el acceso a los mismos.
  • Vamos a examinar las reglas actuales y examinos cada parte.
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if
          request.time < timestamp.date(2021, 10, 2);
    }
  }
}
  • rules_version = '2' nos indica que estamos usando la nueva versión de las reglas. Si has usado Realtime Database sabrás que las anteriores eran un objeto JSON.
  • service cloud.firestore define el servicio de la plataforma Firebase que estamos utilizando.
  • match /databases/{database}/documents define una variable o placeholder llamado database que apunta a la base de datos del proyecto actual.
  • match /{document=**} crea una nueva regla usando un recursive wildcard que indica todos los documentos en todas las colecciones y subcolecciones.
  • allow read, write: if request.time < timestamp.date(2021, 10, 2) permite lectura y escritura a cualquier usuario que se conecte antes del 10 del 2 de 2021.
  • Como ves hay diferentes partes que se combinan para crear las reglas de seguridad. Vamos a aprender un poco más sobre ellas.

Selectores de patrón

  • Comienzan con la palabra clave match y siguen con un patrón que debe coincidir con la estructura de tu base de datos.
  • Todas las sentencias match debe apuntar a documentos, no colecciones.
  • Por ejemplo, si queremos alcanzar las subcolecciones messages de los documentos de la colección rooms, podríamos utilizar:
match /rooms/{room}/messages/{message} {
    // Selecciona todos los documentos de la colección messages
}
  • También se pueden anidar los bloques match para hacerlo más legible o para indicar diferentes reglas de acceso ya que las reglas solo afectan al segmento al que se tiene acceso, no a posibles sub-colecciones (a no ser que se indique lo contrario).
match /rooms/{room} {
    // Selecciona todos los documentos de la colección rooms
    match /messages/{message} {
        // Selecciona todos los documentos de la colección messages
    }
}
  • También podemos utilizar las variables y los recursive wildcards para seleccionar todos los documentos de posibles sub-colecciones anidadas.
match /users/{user} {
    // Selecciona todos los documentos de la colección users
}

match /users/{user=**} {
    // Selecciona todos los documentos de la colección users y los documentos de todas sus posibles sub-colecciones
}

// Selecciona todos los documentos de todas las sub-colecciones messages (collection group)
match /{path=**}/messages/{message} {
  allow read, write: if request.auth != null;
}

Permisos

  • Antes hemos visto como allow read, write permite al usuario ejercer los dos permisos esenciales: leer y escribir. Pero podemos ir más allá.
  • El permiso read se compone de get, para leer un único documento y list para leer los documentos de una colección/sub-colección o hacer consultas sobre ella. Si otorgamos permiso read estamos otorgando de forma implícita get y list.
  • El permiso write se compone de create para crear documentos en colecciones, update para para actualizarlos y delete para eliminarlos. Igual que con read, si otorgamos permiso write estamos concendiendo create, update y delete.

Reglas básicas

  • Teniendo en cuenta los selectores de patrón y los permisos, podemos comenzar a componer alguna relga muy básica.
// Nadie puede escribir en el documento con ID "juanwmedia" de la colección users
match /users/juanwmedia {
    allow read;
    allow write: if false;
}

// El secreto se mantiene secreto
match /users/juanwmedia/secrets/the_meaning_of_life {
    allow read, write: if false;
}
  • Sin embargo son reglas estáticas y poco útiles. En la mayoría de proyectos necesitamos algo más versátil. Como por ejemplo, condiciones.

Condiciones

  • El mayoría de reglas debemos comprobar alguna condición o el valor de alguna variable. Para ello podemos usar la estructura de control if dentro de nuestras reglas.
match /users/{userID}/{docs=**} {
    // Permite solo al usuario autentificado operar sobre su perfil
    allow read, write: if request.auth.uid == userID;
}
    
match /rooms/adminRoom/{messages=**} {
    // Acceder a la sala de administración en base a los custom claims
    allow read, write: if request.auth.token.admin == true;
}

match /{path=**} {
    // Contenido sólo para usuarios autentificados
    allow read, write: if request.auth != null;
}
  • Como ves estamos haciendo uso del objeto request que nos trae información sobre la petición que está siendo procesada por las reglas. La propiedad de request que más se suele utilizar es auth, donde se nos indica un montón de información sobre la autentificación del usuario en la que podemos operar.
{
  "auth": {
    "uid": "my-unique-user-id",
    "token": {
      "some-custom-claim": true,
      "email": "me@email.is",
      "email_verified": false,
      "phone_number": null,
      "name": "Name",
    }
  },
 ...
}
  • Otro objeto al que tenemos acceso es resource (no confundir con request.resource), este hace referencia al documento que estamos intentando acceder. En resource.data tenemos un objeto con todos las propiedades y los valores. Teniendo en cuenta esto podemos comenzar a crear reglas de validación en base al contenido.
match /rooms/{room} {
    // Permitir salas de conversación privadas
  allow read: if resource.data.public == true;
}
  • También podemos usar los dos resources (request.resource y resource.data) para comparar valores y validar en base a ello.
match /rooms/{room} {
    // Permite actualizar sólo la descripcín, no el nombre
  allow update: if request.resource.data.description is string &&
                request.resource.data.description.size() > 10 &&
                request.resource.data.description.size() < 160 &&
                request.resource.data.name == resource.data.name;
}

Acceder a otros documentos

  • Con los métodos get() y exists() podemos evaluar las peticiones en base a la presencial de otros documentos en la base de datos.
match /rooms/{room} {
  // Permitir eliminar salas de conversación si el usuario está autentificado y es administrador
  allow delete: if request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true
}

Presentación


Firebase Auth


Cloud Firestore 🔥


Cloud Storage 🗄


No te pierdas ninguna novedad

Escuela Vue en Twitter

Participa en la Comunidad Escuela Vue

Comunidad Escuela Vue