获取我们在 Firebase 峰会上发布的所有信息,了解 Firebase 可如何帮助您加快应用开发速度并满怀信心地运行应用。了解详情

Acceso seguro a datos para usuarios y grupos

Muchas aplicaciones colaborativas permiten a los usuarios leer y escribir diferentes datos en función de un conjunto de permisos. En una aplicación de edición de documentos, por ejemplo, los usuarios pueden querer permitir que algunos usuarios lean y escriban sus documentos mientras bloquean el acceso no deseado.

Solución: control de acceso basado en roles

Puede aprovechar el modelo de datos de Cloud Firestore, así como las reglas de seguridad personalizadas para implementar el control de acceso basado en roles en su aplicación.

Suponga que está creando una aplicación de escritura colaborativa en la que los usuarios pueden crear "historias" y "comentarios" con los siguientes requisitos de seguridad:

  • Cada historia tiene un propietario y se puede compartir con "escritores", "comentaristas" y "lectores".
  • Los lectores solo pueden ver historias y comentarios. No pueden editar nada.
  • Los comentaristas tienen todo el acceso de los lectores y también pueden agregar comentarios a una historia.
  • Los escritores tienen todo el acceso de los comentaristas y también pueden editar el contenido de la historia.
  • Los propietarios pueden editar cualquier parte de una historia y controlar el acceso de otros usuarios.

Estructura de datos

Suponga que su aplicación tiene una colección de stories donde cada documento representa una historia. Cada historia también tiene una subcolección de comments donde cada documento es un comentario sobre esa historia.

Para realizar un seguimiento de los roles de acceso, agregue un campo de roles que sea un mapa de ID de usuario para roles:

/historias/{id de la historia}

{
  title: "A Great Story",
  content: "Once upon a time ...",
  roles: {
    alice: "owner",
    bob: "reader",
    david: "writer",
    jane: "commenter"
    // ...
  }
}

Los comentarios contienen solo dos campos, la ID de usuario del autor y algo de contenido:

/historias/{id de la historia}/comentarios/{id del comentario}

{
  user: "alice",
  content: "I think this is a great story!"
}

Normas

Ahora que tiene las funciones de los usuarios registradas en la base de datos, debe escribir reglas de seguridad para validarlas. Estas reglas asumen que la aplicación usa Firebase Auth para que la variable request.auth.uid sea la ID del usuario.

Paso 1 : comience con un archivo de reglas básicas, que incluye reglas vacías para historias y comentarios:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
         // TODO: Story rules go here...

         match /comments/{comment} {
            // TODO: Comment rules go here...
         }
     }
   }
}

Paso 2 : agregue una regla de write simple que les brinde a los propietarios un control total sobre las historias. Las funciones definidas ayudan a determinar los roles de un usuario y si los nuevos documentos son válidos:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          // Read from the "roles" map in the resource (rsc).
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          // Determine if the user is one of an array of roles
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          // Valid if story does not exist and the new story has the correct owner.
          return resource == null && isOneOfRoles(request.resource, ['owner']);
        }

        // Owners can read, write, and delete stories
        allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner']);

         match /comments/{comment} {
            // ...
         }
     }
   }
}

Paso 3 : escriba reglas que permitan a un usuario de cualquier rol leer historias y comentarios. El uso de las funciones definidas en el paso anterior mantiene las reglas concisas y legibles:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          return resource == null
            && request.resource.data.roles[request.auth.uid] == 'owner';
        }

        allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner']);

        // Any role can read stories.
        allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);

        match /comments/{comment} {
          // Any role can read comments.
          allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                      ['owner', 'writer', 'commenter', 'reader']);
        }
     }
   }
}

Paso 4 : permita que los escritores de historias, los comentaristas y los propietarios publiquen comentarios. Tenga en cuenta que esta regla también valida que el owner del comentario coincida con el usuario solicitante, lo que evita que los usuarios escriban sobre los comentarios de los demás:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          return resource == null
            && request.resource.data.roles[request.auth.uid] == 'owner';
        }

        allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner'])
        allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);

        match /comments/{comment} {
          allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                      ['owner', 'writer', 'commenter', 'reader']);

          // Owners, writers, and commenters can create comments. The
          // user id in the comment document must match the requesting
          // user's id.
          //
          // Note: we have to use get() here to retrieve the story
          // document so that we can check the user's role.
          allow create: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                        ['owner', 'writer', 'commenter'])
                        && request.resource.data.user == request.auth.uid;
        }
     }
   }
}

Paso 5 : Brinde a los escritores la capacidad de editar el contenido de la historia, pero no editar los roles de la historia ni cambiar ninguna otra propiedad del documento. Esto requiere dividir la regla de write de historias en reglas separadas para create , update y delete , ya que los escritores solo pueden actualizar historias:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          return request.resource.data.roles[request.auth.uid] == 'owner';
        }

        function onlyContentChanged() {
          // Ensure that title and roles are unchanged and that no new
          // fields are added to the document.
          return request.resource.data.title == resource.data.title
            && request.resource.data.roles == resource.data.roles
            && request.resource.data.keys() == resource.data.keys();
        }

        // Split writing into creation, deletion, and updating. Only an
        // owner can create or delete a story but a writer can update
        // story content.
        allow create: if isValidNewStory();
        allow delete: if isOneOfRoles(resource, ['owner']);
        allow update: if isOneOfRoles(resource, ['owner'])
                      || (isOneOfRoles(resource, ['writer']) && onlyContentChanged());
        allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);

        match /comments/{comment} {
          allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                      ['owner', 'writer', 'commenter', 'reader']);
          allow create: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                        ['owner', 'writer', 'commenter'])
                        && request.resource.data.user == request.auth.uid;
        }
     }
   }
}

Limitaciones

La solución que se muestra arriba demuestra cómo proteger los datos del usuario mediante reglas de seguridad, pero debe tener en cuenta las siguientes limitaciones:

  • Granularidad : en el ejemplo anterior, varios roles (escritor y propietario) tienen acceso de escritura al mismo documento pero con diferentes limitaciones. Esto puede volverse difícil de administrar con documentos más complejos y puede ser mejor dividir documentos individuales en varios documentos, cada uno de los cuales pertenece a un solo rol.
  • Grupos grandes : si necesita compartir con grupos muy grandes o complejos, considere un sistema en el que los roles se almacenen en su propia colección en lugar de como un campo en el documento de destino.