A partir de la sesión 102 del Pista móvil Couchbase LIVE Nueva Yorkcontinuamos iterando sobre la aplicación de ejemplo de Couchbase Mobile que se describía en el código de la publicación Sesión "Couchbase Mobile 101: Cómo crear tu primera aplicación móvil".

En la sesión Couchbase Mobile 102, exploramos Pasarela de sincronización en profundidad sobre sus características y sobre "Cómo añadir una sincronización segura a sus aplicaciones móviles" mediante la aplicación de ejemplo Grocery Sync, que puede encontrarse en el repositorio de Github de iOS y Android. En este blog, vamos a repasar lo que se discutió en Sync Gateway, que es el componente que une Couchbase Lite y Couchbase Server; usted puede referencia de las diapositivas sobre el tema y fragmentos de código que se describen a continuación.

Principales problemas de seguridad de los datos móviles

Un área que Sync Gateway ayuda a resolver es la replicación de datos y esto se refiere a cómo los datos se sincronizan de ida y vuelta desde la nube a su aplicación móvil en el dispositivo. La autenticación de los usuarios antes de que pueda producirse la replicación es otra área clave que Sync Gateway aborda en la que se produce la partición de datos. Una vez que la replicación está en su lugar, los datos tendrían que ser particionado en consecuencia para sus usuarios para determinar dónde se distribuirán los datos específicos. Y luego está el control de acceso a los datos en el que, dado un usuario que ha sido autenticado, Sync Gateway puede ayudar a configurar los permisos de lectura y escritura en consecuencia. Analicemos cada uno de estos aspectos con más detalle.

[1] Autenticación de usuarios

Con el modelo de autenticación pluggable que soporta Couchbase Mobile, hay una variedad de maneras de implementar la autenticación donde Sync Gateway permite configuraciones personalizadas al marco de seguridad de la arquitectura de la aplicación. Sync Gateway soporta tres proveedores públicos con modelos de autenticación 'Basic Auth', 'Facebook', y 'Persona.

Autenticación de Facebook: Configuración de la pasarela de sincronización
El siguiente fragmento de código ilustra cómo configurar Facebook para Sync Gateway.

En el archivo de configuración, definimos que queremos usar Facebook como proveedor de autenticación. Más allá de este punto, Sync Gateway hará el trabajo de comunicarse con Facebook para autenticar el token para los clientes.
Autenticación de proveedores personalizada: Ruta de comunicación   
Para implementar un proveedor personalizado donde existe una base de usuarios en una configuración de servidor LDAP, la aplicación móvil apunta primero a la aplicación o al servidor de autenticación. El servidor de autenticación se encarga de autenticar a los usuarios y, una vez que autentica correctamente a un usuario, el servidor de autenticación realiza una llamada a la API de administración de Sync Gateway en el segundo paso para obtener una sesión válida para ese usuario. Ese token de sesión se reenvía al servidor de autenticación y se devuelve al cliente móvil. El tercer y último paso, una vez que la aplicación móvil obtiene el token, es comunicarse con Sync Gateway directamente utilizando el token de sesión para obtener todos los datos necesarios para el usuario en el dispositivo.
[2] Acceso de lectura y escritura de datos

Muchas de las cuestiones de seguridad se refieren a cómo gestionar el acceso de lectura y escritura. Sync Gateway permite definir políticas de seguridad de grano fino para las políticas de lectura a nivel de documento y, a continuación, definir las políticas de escritura hasta el nivel de campo. El marco general de aplicación de políticas se basa en una función de Javascript Sync donde es flexible definir reglas de seguridad complejas para extender la aplicación móvil que ejecuta Couchbase Lite.

Función Sync: Configuración de la pasarela de sincronización

La función de sincronización es fundamental en Sync Gateway para gestionar el acceso de lectura y escritura, y es donde se definen la mayoría de las reglas de acceso a los datos. Así que es realmente el núcleo de su implementación de seguridad donde la función de sincronización es una función JavaScript que se ejecuta cada vez que cualquier documento JSON se escribe en Sync Gateway.

La función se define en el archivo de configuración de Sync Gateway, donde las firmas del método toman la revisión actual del documento, doc, y la revisión anterior, oldDoc. El cuerpo del método es donde se definen las reglas de seguridad basadas en esas dos entradas. Aquí es donde una simple Función de Sincronización básica puede ser definida primero y luego lentamente reglas de seguridad más avanzadas pueden construirse para cubrir todos los casos. Con este enfoque, podemos modificar la función de sincronización a medida que se definen nuevos tipos de documentos o si hay cambios en el esquema JSON.

Función de sincronización: Permiso de escritura

Con la seguridad de escritura, podemos determinar si un documento concreto procedente de un usuario conocido puede escribirse o no en el almacén backend. Para la puerta de enlace de sincronización, hay cuatro métodos clave en la función de sincronización que se utilizan para los permisos de escritura:
  1. requerirUsuario(): Toma como entrada una lista de identificadores de usuario y valida si los usuarios actualmente activos forman parte de esa lista.
  2. requireRole(): Toma como entrada una lista de roles, donde podemos ver si al usuario requerido se le ha otorgado ese rol en particular. Si no es así, el documento será rechazado.
  3. requireAccess(): Toma como entrada una lista de canales donde se toma una lista de usuarios actuales y su lista de canales para ver si se les ha concedido un canal en particular. Si el usuario no forma parte de la lista, será rechazado.
  4. throw(): El lanzamiento le permite hacer cualquier inspección que desee en el documento entrante. Usted puede tener la validación allí y decir, por ejemplo, si un documento es de tipo 'artículo' y si el tipo no coincide, entonces usted puede rechazar el documento. Del mismo modo, se puede realizar una validación basada en que un valor se encuentre dentro de un rango determinado. Por lo tanto, la validación de tipo de nivel de campo muy granular que se puede implementar en la función de sincronización, se puede utilizar con un método throw() en los documentos que no cumplen esos criterios. Con throw() también puede proporcionar detalles de error sobre por qué se lanza y la razón por la que los documentos son rechazados.

Función de sincronización: Permiso de lectura

Con la seguridad del lado de la lectura, podemos utilizar "Canales" para la partición de datos mediante el uso de la primitiva 'channel()' para los documentos, mientras que los usuarios se asignan a diferentes canales utilizando la primitiva 'access()'. El concepto aquí es que cada documento se asocia con un conjunto de uno o más canales y que cada rol de usuario tiene un conjunto de canales que puede leer.

Un canal determinado empezará a existir en cuanto se utilice el comando canal para asignar un usuario a un canal. En ese momento, el usuario buscará documentos etiquetados con el mismo nombre de canal. ¡Existen dos canales especiales con los parámetros estrella [*] y exclamación [!]

  1. Estrella[*]: El canal * estrella es el canal en el que todos los documentos se añaden automáticamente a ese canal. Por ejemplo, si a un usuario invitado se le concede el canal * estrella, tendrá acceso abierto a todos los documentos. Un caso de uso claro para este atributo de canal sería similar a los privilegios de administrador donde cualquier usuario que tenga la * estrella en su canal podrá ver todo en el sistema.
  2. Exclamación[!]: El segundo canal especial es el atributo ! exclamation que se utiliza para describir el canal público que está enfocado desde la perspectiva del documento donde si añades un documento al canal público, entonces todos los usuarios podrán ver ese documento en particular. Un caso de uso claro para este atributo de canal sería similar a tener un documento para anuncios públicos o emitir un mensaje a todos los usuarios.

Función Sync: Por ejemplo

En el siguiente ejemplo, exploraremos utilizando las funciones descritas anteriormente en cómo asegurar el Grocery Sync Application desde la sesión Couchbase Mobile 101 donde existe como un aplicación completamente insegura y sin privacidad. Iterando con Sync Gateway, los usuarios acabarán viendo sólo sus propios elementos en lugar de ver la misma lista con todos los elementos disponibles que se añadan.

En el archivo de configuración inicial de Sync Gateway no se activa nada y es la configuración por defecto que viene con la aplicación Grocery Sync. También habilitamos el usuario invitado para que el usuario tenga acceso a todos los canales lo que significa que veremos todos los documentos y nuevos artículos que se añadan. Lo primero para agregar seguridad es tener usuarios para autenticar que es lo que vamos a implementar a continuación.

La idea es identificar a los usuarios individuales que forman parte de la aplicación y utilizar eso como una medida de seguridad dentro de la aplicación. La actualización del archivo de configuración es crear los usuarios Alice y Bob. Eliminamos la cuenta de invitado y usamos autenticación básica predefiniendo dos usuarios donde inicialmente los usuarios tienen acceso a todos los canales. Ahora con los usuarios creados, queremos crear una función de sincronización personalizada para permitir la definición de la lógica de seguridad de lectura y escritura para los usuarios en la aplicación.

Ahora agregamos una Función de Sincronización de marcador de posición y aquí es donde nos enfocaremos y continuaremos iterando sobre la lógica para proveer control de acceso para los usuarios sobre los datos en el sistema. A continuación vamos a asegurar aún más los canales, proporcionando nombres de canales específicos. 

Ahora daremos a los usuarios acceso a su propio canal de items en la configuración de sync-gateway eliminando la estrella ["*"] para el canal admin y añadiendo un nuevo canal privado que sea específico para el usuario como en este caso, "items-alice". Anteriormente con el canal ["*"], permitía a los usuarios ver todos los documentos del sistema y no queremos eso como medida de seguridad. Hacemos lo mismo para 'Bob' también configurando los 'admin_channels' para que sean específicos de "items-bob". Al hacer esto, tendremos artículos sólo accesibles para sus propietarios por lo que necesitamos vincular el campo propietario en un documento de artículo de comestibles al canal de artículos personales del propietario en consecuencia. A continuación, modificaremos la función de sincronización para que esto ocurra.

Cuando se escribe un documento en Sync Gateway, lo asignamos a un canal cuyo nombre tiene la forma "items"-prefijo con el valor de la propiedad owner desde dentro del documento owner que se obtiene por "doc.owner". La configuración anterior es cuando la propiedad del documento se establece en "bob" y los "items" se asignarían entonces al canal "items-bob". El usuario, "bob" tiene acceso al canal "items-bob" y básicamente ese es el canal al que se le ha dado acceso a Bob en su registro de usuario. Ahora, cualquier ítem que tenga dueño 'bob' como propiedad será visto a través de ese canal por el propio Bob pero no será visto por Alice porque no estará en el canal 'ítems-alice'. Lo que ocurrió en la configuración es que asignamos un documento a un canal apropiado y también programáticamente dimos acceso al propietario a través de su canal propietario "items-".

Así que ahora los usuarios sólo pueden ver/leer sus propios artículos y efectivamente sus propias listas de la compra. Pero todavía pueden escribir en la lista de los demás, porque Bob podría subir un artículo y establecer la propiedad "Alice", donde, en efecto, Bob podría infractar fácilmente la lista de Alice. El siguiente paso en la adición de seguridad es asegurarse de que cuando un documento entra, tendríamos la coincidencia propietario-propiedad con el usuario autenticado actual.

La configuración actualizada contiene ahora la función 'requireUser' al principio de la función de sincronización con un parámetro de entrada que es la propiedad owner que se obtiene a través de 'doc.owner' desde dentro del documento. Básicamente, el usuario autenticado que está intentando subir el documento debe tener un ID de usuario que coincida con la propiedad del propietario en el documento del artículo de alimentación que está subiendo.  Y si no coinciden entonces se lanzará un error en él para el nuevo artículo de comestibles, ya que no aceptará. Ahora los usuarios tienen su propia lista de la compra y es privado donde nadie puede escribir a ellos.
Sería genial que los usuarios pudieran añadir amigos para añadir artículos a una lista concreta. Lo que vamos a actualizar a continuación en la configuración es la posibilidad de añadir un nuevo tipo de 'documento' con un 'tipo de documento de amigo' y este documento se utilizará para conceder a los amigos el acceso a la lista de la compra de los usuarios dados.

Dado que ahora hay una clave de "tipo" en los dos documentos que se pueden diferenciar por "artículos" o "amigos" tipos de documentos, ahora podemos añadir un interruptor en la función de sincronización que nos permite procesar los diferentes documentos de diferentes maneras dentro de la función de sincronización
Primero comprobamos el tipo de documento para ver si es del tipo "amigos" y luego si la propiedad del propietario del documento coincide con el ID del usuario autenticado actual a través del método "requireUser()" y si no es así, lo rechazamos, ya que sólo el propietario puede invitar a amigos a actualizar su lista de la compra. Si la propiedad owner coincide con el usuario autenticado actual, entonces daremos acceso a todos los amigos a través del canal 'items-doc.owner'.
Así, por ejemplo, cuando bob pone su documento de amigos para invitar a Alice, básicamente le damos a Alice acceso al canal items-bob.
A continuación, vamos a asignar el documento que es un tipo 'amigos' a un canal llamado private-doc.owner' que en este caso sera 'private-bob' y noten que esta vez, este es un canal creado dinamicamente. No se crea en el registro de usuario de los canales de administración y se ve que estos canales se crean sobre la marcha cuando el usuario envía por primera vez una invitación. Esto básicamente significa que nadie más verá la lista de amigos que han sido invitados por el usuario, ya que es privada sólo para el propio usuario. Y luego vamos a darle al 'doc.owner' o al usuario(s) autenticado(s), acceso a ese nuevo canal privado que ha sido creado para ellos.
La cláusula "else" es donde si no se trata de un documento de un amigo, entonces este será un documento de elemento de sincronización de comestibles y de nuevo validamos que la propiedad owner coincide con el ID de usuario autenticado actual. Como antes, añadimos los ítems al canal 'ítems-' para la propiedad del propietario del ítem y le damos a ese usuario acceso dinámico a ese canal.
Mientras actualizamos la Función de Sincronización con la lógica de cambio anterior, podemos añadir una prueba adicional para rechazar cualquier tipo de documento desconocido, de modo que si no es un tipo de documento 'friends' o 'item', entonces rechazaremos el documento llamando al método 'throw()'.

Puedes ver que hemos añadido otra cláusula 'else' para capturar todos los otros tipos de documentos y simplemente lanzaremos un mensaje de "tipo de documento inválido" que será devuelto al cliente. Esta es una manera de evitar que la gente ponga cosas que no queremos en nuestra base de datos.
Ahora que hemos invitado a nuestros amigos, queremos darles la posibilidad de escribir nuevos elementos en la lista de propietarios. Lo que tenemos que hacer es cambiar el método anterior 'requireUser() a 'requireAccess()' para que no sólo el propietario pueda escribir ítems en el canal de ítems, sino que cualquiera al que se le haya dado acceso a ese canal en particular pueda también añadir/escribir nuevos ítems. En este caso, los amigos sólo pueden acceder al canal de artículos de un usuario a través de una invitación de un documento de un amigo.

En lugar del método 'requireUser()', lo sustituiremos por el método El método 'requireAccess("items-"+doc.owner)' donde ahora básicamente si quieres escribir un nuevo ítem en una lista, tienes que tener acceso al canal de ítems para el usuario en particular para el que quieres añadir el ítem. Algunas características adicionales para la aplicación es que si un amigo escribe un nuevo elemento a nuestra lista, queremos asegurarnos de que no tienen la capacidad de marcar el elemento. Lo que queremos es que sólo puedan añadir nuevos artículos que no estén marcados y que sólo el propietario de la lista de la compra pueda marcarlos. Haremos una comprobación adicional cuando un amigo escriba un documento, que el documento no tenga su propiedad establecida a true y así comprobar el valor booleano de la propiedad doc.checked.

Primero comprobaremos si 'oldDoc' es nulo, lo que significaría que se trata de un elemento recién creado. Así que validaremos la propiedad 'check' en el documento para ver si es verdadera y si es verdadera entonces lanzaremos un error donde los 'nuevos items no pueden ser chequeados' cuando son creados. Sólo queremos que los propietarios comprueben o desprotejan los ítems cuando han sido creados y validaremos esto asegurándonos de que hay un (doc y oldDoc) lo que significa que hay una actualización del ítem y entonces veremos si la propiedad 'check' ha sido cambiada entre la versión anterior del ítem y la nueva versión. Podemos hacer una simple comprobación para asegurarnos de que 'oldDoc' no es NULL y luego requireUser(owner) pero también queremos que los amigos puedan corregir errores tipográficos en el texto del elemento, incluso si no pueden marcar un elemento, los amigos pueden querer cambiar el título o la etiqueta del elemento. A continuación, haremos una validación a nivel de campo para ver si el documento anterior existe o no. Compararemos 'doc.Checked' con 'oldDoc.checked' para ver si ha cambiado. Entonces, si ha cambiado, haremos un 'requireUser(owner)' en el propietario del documento, ya que son los únicos que pueden cambiar la casilla de verificación.

Observando la "cláusula else hay código adicional para ver si el valor 'check' ha cambiado entre la versión anterior del elemento y la nueva que se está enviando. Si ha cambiado, entonces requerimos que el usuario autenticado actual sea el propietario del documento.
Para que sólo el propietario pueda cambiar la casilla. Pero otros amigos del propietario pueden cambiar cosas como el título del elemento.
Tenemos un último problema que queremos ver y solucionar. Queremos asegurarnos de que nadie pueda cambiar el "propietario" de un elemento una vez que ha sido creado. Eso significa que los amigos pueden hacer modificaciones en las etiquetas pero no cambiar la casilla de verificación de un elemento o cambiar el propietario real de un elemento. Veamos cómo podemos solucionar este problema en la configuración final.

Así que haremos una comprobación adicional y si se trata de una actualización en la que 'oldDoc != nil' y el propietario de la nueva versión del documento no coincide con el propietario de la versión anterior (doc.owner != oldDoc.owner) entonces lanzaremos un error. Ahora, puedes ver que estamos haciendo una prueba adicional para que si el propietario ha cambiado entre la versión anterior y la versión actual, entonces lanzaremos un error básicamente de vuelta al cliente ya que no pueden cambiar el propietario del documento.

[3] Transporte de datos por cable

Ahora, aparte de la autenticación y la lectura/escritura de datos para el acceso de los usuarios a la seguridad, el siguiente tema es asegurarse de que sus datos están protegidos en el cable a un punto final remoto. Sync Gateway admite SSL y TLS en el transporte. Es fácil de configurar en el archivo de configuración de Sync Gateway para habilitar SSL en Sync Gateway para su aplicación móvil para tener seguridad adicional de datos.

[4] Almacenamiento de datos

Para almacenar datos en el cliente, en realidad estás encriptando el sistema de archivos del dispositivo. Hay buena información sobre lo que necesitas hacer para encriptar la base de datos local de Couchbase Lite en el portal de desarrolladores móviles. Estamos hablando de un entorno seguro en la nube y de configurarlo para el cifrado del sistema de archivos en el cliente.

Resumen

Sync Gateway es realmente la pieza que une Couchbase Lite, el framework que vive en el dispositivo integrado. Tienes tu Couchbase Server corriendo en la nube y Sync Gateway los une. Sync Gateway tiene un puñado de funciones clave que proporciona para convertir una aplicación que se ejecuta en el dispositivo local, que es una aplicación independiente desconectada en una experiencia de sincronización multiusuario con todas las funciones. Lo que se muestra arriba es cómo podemos serializar cosas en la base de datos iterando sobre el archivo de configuración de la Función Sync.

A continuación nos adentraremos en la clase del componente listener HTTP de Couchbase Lite es la sesión 103 de Couchbase Mobile sobre cómo habilitar la función Peer-to-Peer donde podrás crear experiencias sociales in-app únicas mediante "Building a Peer-to-Peer App with Couchbase Mobile".

Autor

Publicado por William Hoang, Defensor del Desarrollador Móvil, Couchbase

William fue Developer Advocate en el equipo de Mobile Engineering/Developer Experience de Couchbase. Su amor por el café y el código le ha trascendido al mundo de los móviles, al tiempo que aprecia las experiencias presenciales fuera de línea. Anteriormente, William trabajó en el equipo de Relaciones con Desarrolladores en Twitter, BlackBerry y Microsoft, además de haber sido ingeniero de Software Embedded GPS en Research In Motion. William se licenció en Ingeniería Eléctrica de Software por la Universidad McGill.

Dejar una respuesta