Da sessão 102 no Couchbase LIVE New York pista móvelEm seguida, continuamos a iterar no aplicativo de amostra do Couchbase Mobile que foi descrito no código do Sessão "Couchbase Mobile 101: Como criar seu primeiro aplicativo móvel".
Na sessão Couchbase Mobile 102, exploramos Gateway de sincronização em detalhes sobre seus recursos e sobre "Como adicionar sincronização segura a seus aplicativos móveis", protegendo o aplicativo de amostra Grocery Sync, que pode ser encontrado no repositório do Github para iOS e Android. Neste blog, abordaremos o que foi discutido sobre o Sync Gateway, que é o componente que une o Couchbase Lite e o Couchbase Server; você pode referência aos slides sobre o tópico e os trechos de código descritos a seguir.
Principais preocupações com a segurança de dados móveis
Uma área que o Sync Gateway ajuda a resolver é a replicação de dados, que diz respeito à forma como os dados são sincronizados entre a nuvem e o aplicativo móvel no dispositivo. A autenticação dos seus usuários antes que a replicação possa ocorrer é outra área importante que o Sync Gateway aborda e sobre a qual ocorre o particionamento de dados. Quando a replicação estiver em vigor, os dados precisarão ser particionados de acordo com os usuários para determinar onde os dados específicos serão distribuídos. Além disso, há o controle de acesso aos dados, no qual, se um usuário tiver sido autenticado, o Sync Gateway poderá ajudar a configurar as permissões de leitura e gravação adequadamente. Vamos explorar cada um desses aspectos em mais detalhes.
[1] Autenticação do usuário
Com o modelo de autenticação conectável suportado pelo Couchbase Mobile, há uma variedade de maneiras de implementar a autenticação em que o Sync Gateway permite configurações personalizadas para a estrutura de segurança da arquitetura do aplicativo. O Sync Gateway oferece suporte a três provedores públicos com modelos de autenticação "Basic Auth", "Facebook" e "Persona.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ "facebook" : { "registrar" : falso }, "bancos de dados": { "grocery-sync": { "servidor":"http://cbserver:8091", "balde":"mercearia-sincronização", "usuários": {"CONVIDADO": {"desativado": true}}, "sincronização":`função(doc) {canal(doc.canais);}` } } } |
Muitas das questões de segurança estão na forma de como gerenciamos o acesso de leitura e gravação. O Sync Gateway permite definir políticas de segurança de granulação fina para políticas de leitura no nível do documento e, em seguida, definir as políticas de gravação até o nível do campo. A estrutura geral de aplicação de políticas baseia-se em uma função Javascript Sync, na qual é flexível definir regras de segurança complexas para estender o aplicativo móvel que executa o Couchbase Lite.
Função de sincronização: Configuração do gateway de sincronização
A função Sync é o núcleo do Sync Gateway para gerenciar o acesso de leitura e gravação e é onde a maioria das regras de acesso aos dados é definida. Portanto, é realmente o núcleo de sua implementação de segurança, em que a função Sync é uma função JavaScript que é executada sempre que qualquer documento JSON é gravado no Sync Gateway.
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "bancos de dados": { "grocery-sync": { "servidor":"http://walrus:", "balde":"mercearia-sincronização", "usuários": {"CONVIDADO": {"desativado": verdadeiro}}, "sincronização":`função(doc,oldDoc) { canal(doc.canais); }` } } } |
A função é definida no arquivo de configuração do Sync Gateway, no qual as assinaturas do método recebem a revisão atual do documento, doc, e a revisão anterior, oldDoc. O corpo do método é onde as regras de segurança são definidas com base nessas duas entradas. É aqui que uma função de sincronização básica simples pode ser definida primeiro e, em seguida, regras de segurança lentamente mais avançadas podem ser criadas para cobrir todos os casos. Com essa abordagem, podemos modificar a função de sincronização à medida que novos tipos de documentos forem definidos ou se houver alterações no esquema JSON.
Função de sincronização: Permissão de gravação
- requireUser(): Recebe como entrada uma lista de IDs de usuário e valida se os usuários ativos no momento fazem parte dessa lista
- requireRole(): Recebe como entrada uma lista de funções, onde podemos ver se o requireUser recebeu essa função específica. Se não tiver, o documento será rejeitado.
- requireAccess(): Recebe como entrada uma lista de canais, na qual é obtida uma lista de usuários atuais e sua lista de canais para verificar se lhes foi concedido um determinado canal. Se o usuário não fizer parte da lista, ele será rejeitado.
- lançar(): O lançamento permite que você faça qualquer inspeção que desejar no documento de entrada. Você pode ter uma validação e dizer, por exemplo, se um documento é do tipo "item" e se o tipo não corresponder, você pode rejeitar o documento. Da mesma forma, você pode ter uma validação baseada em um valor que esteja dentro de um determinado intervalo. Portanto, a validação de tipo em nível de campo muito granular que pode ser implementada na Sync Function pode ser usada com um método throw() nos documentos que não atendem a esses critérios. Com throw(), você também pode fornecer detalhes do erro sobre o motivo pelo qual ele é lançado e o motivo pelo qual os documentos são rejeitados.
Função de sincronização: Permissão de leitura

Um canal específico passará a existir assim que o comando channel for usado para atribuir um usuário a um canal. Nesse momento, o usuário procurará documentos marcados com o mesmo nome de canal. Existem dois canais especiais com os parâmetros estrela [*] e exclamação [!].
- Estrela[*]: O canal * Star é o canal em que todos os documentos são adicionados automaticamente a esse canal. Um exemplo é quando um usuário convidado recebe o canal *estrela, ele terá acesso ilimitado a todos os documentos. Um caso de uso claro para esse atributo de canal seria semelhante aos privilégios de administrador, em que qualquer usuário que tenha a * estrela em seu canal poderá ver tudo no sistema.
- Exclamação[!]: O segundo canal especial é o atributo ! exclamation, que é usado para descrever o canal público que é focado na perspectiva do documento, em que, se você adicionar um documento ao canal público, todos os usuários poderão ver esse documento específico. Um caso de uso claro para esse atributo de canal seria semelhante a ter um documento para anúncios públicos ou transmitir uma mensagem para todos os usuários.
Função de sincronização: Por exemplo
No exemplo a seguir, exploraremos usando os recursos descritos acima em como proteger o Grocery Sync Application da sessão Couchbase Mobile 101, onde ele existe como um aplicativo totalmente inseguro e sem privacidade. Ao interagir com o Sync Gateway, os usuários acabarão vendo apenas seus próprios itens, em vez de ver a mesma lista com todos os itens disponíveis que são adicionados.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{ "log" : ["*"], "bancos de dados": { "grocery-sync": { "servidor":"morsa:", "balde":"mercearia-sincronização", "usuários": { "CONVIDADO": { "desativado": falso, "administrador_canais" : ["*"] } } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
{ "registro" : ["*"], "bancos de dados": { "mercearia-sincronização": { "servidor": "morsa:", "bucket" (balde):"grocery-sync", "usuários": { "alice": { "desativado" : falso, "senha": "senha", "administrador_canais":["*"] }, "bob": { "desativado" : falso, "senha": "senha", "administrador_canais":["*"] } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{ "registro" : ["*"], "bancos de dados": { "mercearia-sincronização": { "servidor": "morsa:", "bucket" (balde):"grocery-sync", "usuários": { "alice": { "desativado" : falso, "senha": "senha", "administrador_canais":["*"] }, "bob": { "desativado" : falso, "senha": "senha", "administrador_canais":["*"] } }, "sincronização" : ' função(doc, oldDoc) { /Adicionar função de sincronização de espaço reservado, adicionar lógica de leitura/gravação personalizada aqui }' } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
{ "registro" : ["*"], "bancos de dados": { "mercearia-sincronização": { "servidor": "morsa:", "bucket" (balde):"grocery-sync", "usuários": { "alice": { "desativado" : falso, "senha": "senha", "administrador_canais":["itens-alice"] }, "bob": { "desativado" : falso, "senha": "senha", "administrador_canais":["itens-bob"] } }, "sincronização" : ' função(doc, oldDoc) { /Adicionar função de sincronização de espaço reservado, adicionar lógica de leitura/gravação personalizada aqui } ' } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
{ "registro" : ["*"], "bancos de dados": { "mercearia-sincronização": { "servidor": "morsa:", "bucket" (balde):"grocery-sync", "usuários": { "alice": { "desativado" : falso, "senha": "senha", "administrador_canais":["itens-alice"] }, "bob": { "desativado" : falso, "senha": "senha", "administrador_canais":["itens-bob"] } }, "sincronização" : ' função(doc, oldDoc) { canal("itens-"+doc.proprietário); } /Adicionar documento do item ao canal de itens do proprietário ' } } } |
Quando um documento está sendo gravado no Sync Gateway, nós o atribuímos a um canal em que o nome do canal tem o formato de "itens" - prefixado com o valor da propriedade do proprietário de dentro do documento do proprietário que é obtido por "doc.owner". A configuração anterior é quando a propriedade do documento é definida como "bob" e os "itens" são atribuídos ao canal "items-bob". O usuário "bob" tem acesso ao canal "items-bob" e, basicamente, esse é o canal ao qual Bob recebeu acesso em seu registro de usuário. Agora, qualquer item que tenha o proprietário "bob" como propriedade será visto por esse canal pelo próprio Bob, mas não será visto por Alice porque não estará no canal "items-alice". O que ocorreu na configuração foi que atribuímos um documento a um canal apropriado e também concedemos acesso ao proprietário de forma programática por meio do canal "items-" do proprietário.
Portanto, agora os usuários só podem ver/ler seus próprios itens e, efetivamente, suas próprias listas de compras. Mas eles ainda podem escrever na lista um do outro, pois Bob pode carregar um item e definir a propriedade do proprietário como "Alice", o que, de fato, permite que Bob infrinja facilmente a lista de compras de Alice. A próxima etapa para adicionar segurança é garantir que, quando um documento for recebido, a propriedade do proprietário corresponda ao usuário autenticado atual.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
{ "registro" : ["*"], "bancos de dados": { "mercearia-sincronização": { "servidor": "morsa:", "bucket" (balde):"grocery-sync", "usuários": { "alice": { "desativado" : falso, "senha": "senha", "administrador_canais":["itens-alice"] }, "bob": { "desativado" : falso, "senha": "senha", "administrador_canais":["itens-bob"] } }, "sincronização" : ' função(doc, oldDoc) { requireUser(doc.proprietário); /O proprietário do documento do item deve ser o usuário autenticado canal("itens-"+doc.proprietário); } ' } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
{ "registro" : ["*"], "bancos de dados": { "mercearia-sincronização": { "servidor": "morsa:", "bucket" (balde):"grocery-sync", "usuários": { "alice": { "desativado" : falso, "senha": "senha", "administrador_canais":["itens-alice"] }, "bob": { "desativado" : falso, "senha": "senha", "administrador_canais":["itens-bob"] } }, "sincronização" : ' função(doc, oldDoc) { se (doc.tipo == "amigos") { //processar documento de novos amigos requireUser(doc.proprietário); // O proprietário dos amigos acesso(doc.amigos, "itens-"+doc.proprietário); canal("privado-"+doc.proprietário); acesso(doc.proprietário, "privado-"+doc.proprietário) } mais { requireUser(doc.proprietário) canal("itens-"+doc.proprietário); } } ' } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
{ "registro" : ["*"], "bancos de dados": { "mercearia-sincronização": { "servidor": "morsa:", "bucket" (balde):"grocery-sync", "usuários": { "alice": { "desativado" : falso, "senha": "senha", "administrador_canais":["itens-alice"] }, "bob": { "desativado" : falso, "senha": "senha", "administrador_canais":["itens-bob"] } }, "sincronização" : ' função(doc, oldDoc) { se (doc.tipo == "amigos") { //processar documento de novos amigos requireUser(doc.proprietário); // O proprietário dos amigos acesso(doc.amigos, "itens-"+doc.proprietário); canal("privado-"+doc.proprietário); acesso(doc.proprietário, "privado-"+doc.proprietário); } mais se (doc.tipo == "item") { requireUser(doc.proprietário); canal("itens-"+doc.proprietário); } mais{ lançar({proibido: "Inválido documento tipo"}); } } }' } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
{ "registro" : ["*"], "bancos de dados": { "mercearia-sincronização": { "servidor": "morsa:", "bucket" (balde):"grocery-sync", "usuários": { "alice": { "desativado" : falso, "senha": "senha", "administrador_canais":["itens-alice"] }, "bob": { "desativado" : falso, "senha": "senha", "administrador_canais":["itens-bob"] } }, "sincronização" : ' função(doc, oldDoc) { se (doc.tipo == "amigos") { //processar documento de novos amigos requireUser(doc.proprietário); // O proprietário dos amigos acesso(doc.amigos, "itens-"+doc.proprietário); canal("privado-"+doc.proprietário); acesso(doc.proprietário, "privado-"+doc.proprietário); } mais se (doc.tipo == "item") { requireAccess("itens-"+doc.proprietário) canal("itens-"+doc.proprietário); } mais{ lançar({proibido: "Inválido documento tipo"}); } } ' } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
{ "registro" : ["*"], "bancos de dados": { "mercearia-sincronização": { "servidor": "morsa:", "bucket" (balde):"grocery-sync", "usuários": { "alice": { "desativado" : falso, "senha": "senha", "administrador_canais":["itens-alice"] }, "bob": { "desativado" : falso, "senha": "senha", "administrador_canais":["itens-bob"] } }, "sincronização" : ' função(doc, oldDoc) { se (doc.tipo == "amigos") { //processar documento de novos amigos requireUser(doc.proprietário); // O proprietário dos amigos acesso(doc.amigos, "itens-"+doc.proprietário); canal("privado-"+doc.proprietário); acesso(doc.proprietário, "privado-"+doc.proprietário); } mais se (doc.tipo == "item") { requireAccess("itens-"+doc.proprietário) se (oldDoc == nulo) { se (doc.verificar == verdadeiro) { lançar( {proibido: "novo itens não pode ser verificado"}); } } canal("itens-"+doc.proprietário); } mais { lançar( {proibido: "Inválido documento tipo"}); } } ' } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
{ "registro" : ["*"], "bancos de dados": { "mercearia-sincronização": { "servidor": "morsa:", "bucket" (balde):"grocery-sync", "usuários": { "alice": { "desativado" : falso, "senha": "senha", "administrador_canais":["itens-alice"] }, "bob": { "desativado" : falso, "senha": "senha", "administrador_canais":["itens-bob"] } }, "sincronização" : ' função(doc, oldDoc) { se (doc.tipo == "amigos") { //processar documento de novos amigos requireUser(doc.proprietário); // O proprietário dos amigos acesso(doc.amigos, "itens-"+doc.proprietário); canal("privado-"+doc.proprietário); acesso(doc.proprietário, "privado-"+doc.proprietário); } mais se (doc.tipo == "item") { requireAccess("itens-"+doc.proprietário) se (oldDoc == nulo) { se (doc.verificar == verdadeiro) { lançar( {proibido: "novo itens não pode ser verificado"}); } mais { se (doc.verificar != oldDoc.verificar) { requireUser(doc.proprietário); } } } canal("itens-"+doc.proprietário); } mais { lançar( {proibido: "Inválido documento tipo"}); } } ' } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
{ "registro" : ["*"], "bancos de dados": { "mercearia-sincronização": { "servidor": "morsa:", "bucket" (balde):"grocery-sync", "usuários": { "alice": { "desativado" : falso, "senha": "senha", "administrador_canais":["itens-alice"] }, "bob": { "desativado" : falso, "senha": "senha", "administrador_canais":["itens-bob"] } }, "sincronização" : ' função(doc, oldDoc) { se (doc.tipo == "amigos") { //processar documento de novos amigos requireUser(doc.proprietário); // O proprietário dos amigos acesso(doc.amigos, "itens-"+doc.proprietário); canal("privado-"+doc.proprietário); acesso(doc.proprietário, "privado-"+doc.proprietário); } mais se (doc.tipo == "item") { requireAccess("itens-"+doc.proprietário) se (oldDoc == nulo) { se (doc.verificar == verdadeiro) { lançar( {proibido: "novo itens não pode ser verificado"}); } mais { se (doc.proprietário != oldDoc.proprietário) { lançar({proibido: "Desiste Roubo Itens"}); } se (doc.verificar != oldDoc.verificar) { requireUser(doc.proprietário); } } } canal("itens-"+doc.proprietário); } mais { lançar( {proibido: "Inválido documento tipo"}); } } ' } } |
[3] Transporte de dados no fio
Agora, além das questões de autenticação e leitura/gravação de dados para acesso do usuário com relação à segurança, o próximo tópico é garantir que os dados estejam protegidos ao serem transmitidos para um ponto de extremidade remoto. O Sync Gateway é compatível com SSL e TLS no transporte. É fácil configurar no arquivo de configuração do Sync Gateway para ativar o SSL no Sync Gateway para que seu aplicativo móvel tenha mais segurança de dados.
[4] Armazenamento de dados
Para armazenamento de dados no cliente, você está realmente fazendo a criptografia do sistema de arquivos no dispositivo. Há boas informações sobre o que você precisa fazer para criptografar o banco de dados local do Couchbase Lite no portal do desenvolvedor móvel. Estamos falando de um ambiente de nuvem seguro e de sua configuração para a criptografia do sistema de arquivos no cliente.
Resumo
O Sync Gateway é realmente a peça que une o Couchbase Lite, a estrutura que está no dispositivo incorporado. Você tem o Couchbase Server em execução na nuvem, e o Sync Gateway une tudo isso. O Sync Gateway tem um punhado de funções-chave que ele fornece para transformar um aplicativo em execução no dispositivo local, que é um aplicativo autônomo desconectado, em uma experiência de sincronização com vários usuários e com todos os recursos. O que é mostrado acima é como podemos serializar as coisas no banco de dados por meio da iteração no arquivo de configuração Sync Function.
Em seguida, veremos a classe de componente de ouvinte HTTP do Couchbase Lite e a sessão do Couchbase Mobile 103 sobre como ativar o recurso Peer-to-Peer, em que você pode criar experiências sociais exclusivas no aplicativo com "Building a Peer-to-Peer App with Couchbase Mobile".