REST-Like API com Embrapa-Auth 1/2

234 views
Skip to first unread message

Camilo Carromeu

unread,
Jun 4, 2014, 11:05:25 PM6/4/14
to titan-f...@googlegroups.com
Olá pessoal,

Este é o primeiro e-mail de uma explicação mais longa sobre o desenvolvimento de aplicativos móveis utilizando o Titan. Nesta mensagem pretendo explicar a REST-Like API disponibilizada no framework. As funcionalidades do Titan relacionadas ao desenvolvimento de aplicativos móveis foram desenvolvidas pela equipe do PLEASE Lab da Embrapa Gado de Corte:


A REST-Like API do Titan, como não poderia deixar de ser, segue os princípios arquiteturais básicos de REST, implementando uma camada de comunicação simples baseada no protocolo HTTP puro. Assim, esta é uma camada de comunicação cliente/servidor sem estado, e dispõe das operações HTTP básicas (POST, GET, PUT e DELETE) permitindo a obtenção de conteúdo a partir de serviços (URIs) em texto puro (JSON). Há uma vasta documentação sobre os princípios inerentes a interfaces REST disponíveis na Web.

A implementação desta camada no Titan utiliza, inicialmente, o padrão Embrapa-Auth para autenticação. Conforme já citado, por seguir diretrizes arquiteturais REST, trata-se de uma camada sem estado (stateless protocol). Por conta disso, cada requisição precisa ser individualmente autenticada na instância. Uma explicação detalhada deste padrão de comunicação está disponível no seguinte endereço:


Para ajudar na compreensão do uso da REST-Like API, disponibilizei no repositório uma instância-exemplo do Titan:


Nesta instância há uma seção CRUD básica (Menu > Testes > CRUD), com um formulário com diversos campos. Esta seção permite simular o comportamento de diversos tipos de dados do Titan na REST-Like API.

Para testar a comunicação com a instância, foi disponibilizado também um teste unitário em Java:


Reparem que é uma instância simples do framework. Nela foi habilitado o suporte à registro e autenticação por meio do Google+, dispensando a tela de autenticação padrão. A explicação detalhada do uso deste recurso está em outra mensagem desta lista:


Assim, conforme explicado neste outro post, para utilizar a instância-exemplo você precisará registrar a aplicação no Google Developers Console.

Para habilitar o uso da REST-Like API nesta instância foi adicionado o seguinte trecho no "configure/titan.xml":

<api xml-path="configure/api.xml" />

E foi, em seguida, criado o arquivo "configure/api.xml" com o seguinte conteúdo:

<?xml version="1.0" encoding="UTF-8"?>
<api-mapping>
<application
name="mobile"
auth="APP|CLIENT-AS-USER"
token="mnOTyIPfrpkMh8w1qDFOl9VvHii4XoZedFUgTxrQqmB7zuPS6CXH1hKsz0ilGgdf"
request-timeout="36000"
protocol="Embrapa"
gcm-api-key=""
send-alerts="false"
/>
</api-mapping>

Reparem que este arquivo possibilita diversas tags '<application />', de forma que pode-se habilitar diferentes aplicações que se comunicarão com a instância. O atributo 'protocol' refere-se ao padrão de autenticação utilizado. Conforme já dito, até o momento o único padrão implementado no Titan é o Embrapa-Auth, portanto, é importante que você tenha lido e compreendido este padrão (no link acima).

Conforme definido no Embrapa-Auth, é possível efetuar a autenticação da requisição de três maneiras (que podem ser combinadas): aplicação, cliente e usuário. O atributo 'auth' define estas combinações. Assim, o Titan aceita os seguintes valores para este atributo:
  • APP: autenticação da aplicação por meio de um nome e um token fixo que não varia (que têm o mesmo valor do atributo 'token' definido na tag '<application />');
  • CLIENT: autenticação do cliente por meio de um identificador e uma chave-privada;
  • CLIENT-AS-USER: autenticação do usuário utilizando credenciais de um cliente, ou seja, um identificador e uma chave-privada de dispositivo (usável quando o dispositivo é pessoal);
  • USER: autenticação do usuário por meio de login e senha;
  • USER-BY-ID: autenticação do usuário por meio de seu identificador e senha; e
  • USER-BY-MAIL: autenticação do usuário por meio de seu e-mail e senha (somente quando a coluna '_email' é 'unique' na tabela '_user' da instância).
Desta forma, combina-se estas constantes para relatar à instância como virão os cabeçalhos que autenticarão as requisições. No caso da nossa instância-exemplo, o atributo 'auth' possui o valor "APP|CLIENT-AS-USER". Isto significa que cada requisição será autenticada primeiro pela aplicação (usando o nome e o token) e, em seguida, o usuário será reconhecido pelas credenciais do cliente. Quando se utiliza a autenticação "CLIENT-AS-USER" assume-se que cada usuário da instância têm seus próprios dispositivos registrados, de forma que aquele (o usuário) pode ser identificado por estes (os dispositivos). Quando do uso deste método, é necessário criar no Titan uma tabela que permite ao usuário registrar seus dispositivos:

CREATE TABLE _mobile (
  _id SERIAL, 
  _pk CHAR(16) NOT NULL, 
  _user INTEGER NOT NULL, 
  _create TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, 
  _update TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, 
  _name VARCHAR(128) NOT NULL, 
  _access TIMESTAMP WITH TIME ZONE, 
  _counter INTEGER DEFAULT 0 NOT NULL, 
  _gcm VARCHAR(4096), 
  CONSTRAINT _mobile_pkey PRIMARY KEY(_id), 
  CONSTRAINT _mobile_user_fk FOREIGN KEY (_user)
    REFERENCES _user(_id)
    ON DELETE RESTRICT
    ON UPDATE CASCADE
    NOT DEFERRABLE
) WITHOUT OIDS;

Além disso, para permitir que o usuário registre seus dispositivos, o componente "global.home" do CORE do Titan foi alterado. No "config.inc.xml" das seções que instanciam este componente deve-se adicionar as seguintes linhas que ativam a gestão de dispositivos para o usuário:

<?xml version="1.0" encoding="UTF-8"?>
<action-mapping>
<directive name="_NEED_UPDATE_AFTER_DAYS_" value="0" />
<action
name="profile"
label="Dados Pessoais"
default="true">
<menu function="js" js="showMobileDevices ()" image="mobile.png" label="Mobile Devices | pt_BR: Dispositivos Móveis" />
<menu action="personal" image="edit.png" />
</action>
<action
name="personal"
label="Editar Dados Pessoais">
<menu function="js" js="showMobileDevices ()" image="mobile.png" label="Mobile Devices | pt_BR: Dispositivos Móveis" />
</action>
</action-mapping>

Isto fará com que um novo botão seja disponibilizado na interface da seção, provendo acesso à inclusão e remoção de dispositivos pessoais:


Para cada dispositivo, é possível obter o identificador e chave-privada do cliente registrado por meio de um QR Code:


Em relação aos serviços da camada que podem ser acessados, a REST-Like API dispões de URIs pré-definidas (pertencentes ao CORE) e provê ao desenvolvedor a capacidade de criar suas próprias URIs em componentes e seções específicas.

As URIs pré-definidas são acessíveis pela adição do sufixo "api" à URL da instância, seguido do nome do serviço. Por exemplo, "http://seu-host.com/instância/api/disambiguation".

A seguir, são listados os serviços do CORE:


Este serviço verifica as credenciais de acesso da requisição, retornando o HTTP status code "200" em caso de sucesso. Caso esteja autenticando o usuário (por meio dos métodos CLIENT-AS-USER, USER, USER-BY-ID e USER-BY-MAIL), este serviço também retorna os dados do usuário em formato JSON:

{
    "id": 9,
    "login": "camilo",
    "name": "Camilo Carromeu",
    "mail": "cam...@carromeu.com",
    "type": "manager",
    "language": "pt_BR",
    "timezone": "America/Campo_Grande"
}


Em se tratando de dispositivos móveis com sistema operacional Android, é possível afirmar que o usuário do aplicativo têm, necessariamente, uma conta do Google. Sem ela, a menos que a distribuição do aplicativo seja feita pelo seu binário (APK), o usuário não poderia tê-lo obtido, afinal, a conta do Google é necessária para o acesso ao Google Play.

Esta certeza gera a situação, bastante interessante, de podermos contar no aplicativo com o acesso à conta local de usuário (Google Account) para fazer o registro do usuário no sistema. A maior vantagem desta abordagem é oferecer uma forma de registro ao usuário extremamente simplificada e rápida, sem a exigência do preenchimento de formulários enfadonhos. Para se registrar o usuário precisa apenas escolher uma das contas do Google previamente sincronizadas no dispositivo.


Desta forma, este serviço permite registrar um usuário na instância do Titan utilizando a API do Google+. Para isto, este método recebe o e-mail do usuário e um token de acesso a uma Google Account. Este token vem criptografado (utilizando Blowfish), sendo a chave privada desta criptografia o token da autenticação da aplicação no Embrapa-Auth.

Para que este serviço funcione é necessário que o suporte ao logon por meio do Google+ esteja ativo na instância, por conta disso este recurso foi habilitado na instância-exemplo (veja a descrição detalhada deste recurso no link postado acima).



Futuramente, esta URI permitirá também o registro com o uso da API da Apple (Apple ID) para sistemas iOS.

POST ou PUT http://seu-host.com/instância/api/disambiguation

Outro ponto fundamental no desenvolvimento de aplicativos móveis é garantir a integridade dos dados locais em relação aos do servidor remoto. É fácil garantir esta integridade quando não existe criação de tuplas no dispositivo móvel. Nestes casos, o aplicativo assume uma arquitetura "provedor-consumidor", onde o servidor será o provedor de dados e o aplicativo irá apenas "consumí-los". Desta forma, o controle da sincronia de dados deverá ser responsável por consultar o servidor e obter as novas tuplas e aquelas que já existiam, mas foram alteradas. A abordagem utilizada neste caso têm os seguintes passos:
  1. O servidor é consultado passando-se o timestamp da última atualização (caso seja a primeira vez, é passado zero);
  2. É obtido um JSON com as tuplas e o timestamp do servidor (que vêm na requisição HTTP);
  3. Este JSON é percorrido, inserindo-se as tuplas que não existem e atualizando as existentes; e
  4. Caso tenha tudo corrido com sucesso, o timestamp de controle é sobrescrito pelo novo.
Repare que quase todos os softwares implementados terão entidades que serão tratadas em uma arquitetura provedor-consumidor, é o caso, por exemplo, de entidades de metadados (p.e., uma tabela de categorias ou a tabela de cidades do Titan). Assim, a abordagem descrita, que possui uma implementação mais simples do que a comunicação de duas vias, pode ser sempre utilizada.

Quando imaginamos uma entidade que será sincronizada no aplicativo e que pode ser criada e modificada neste, a coisa complica um pouco. Neste caso, se a entidade tiver uma chave natural, podemos utilizá-la para controlar a sincronia. Podemos imaginar, por exemplo, uma entidade 'pessoa' que tenha uma coluna de preenchimento obrigatório e valores únicos denominada 'cpf'. Neste caso, nosso fluxo de controle ficaria:
  1. A tarefa de sincronia varre as tuplas locais buscando todas aquelas que foram criadas ou modificadas após o timestamp da última sincronia de dados (caso seja a primeira vez, são enviadas todas);
  2. Conforme veremos, estas tuplas são enviadas pelos métodos POST ou PUT. O servidor verifica, por meio da chave primária natural, se a tupla já existe localmente. Caso não exista, ele a cria e, caso exista, verifica se deve ou não atualizá-la (considerando a mais recente);
  3. O servidor é então consultado passando-se o timestamp da última sincronia (caso seja a primeira vez, é passado zero);
  4. É obtido um JSON com as tuplas e o timestamp do servidor (que vêm na requisição HTTP);
  5. Este JSON é percorrido, inserindo-se as tuplas que não existem e atualizando as existentes (desde que mais recente que a existente localmente); e
  6. Caso tenha tudo corrido com sucesso, o timestamp de controle é sobrescrito pelo novo.
Com isso o servidor e a aplicação ficam sincronizados. Repare, no entanto, que podem existir problemas caso o timestamp do servidor seja muito diferente que o do aplicativo. Por conta disso, a REST-Like API do Titan exige que o relógio do dispositivo esteja corretamente configurado (emitindo um erro caso isto não ocorra).

No fluxo acima, comparamos as tuplas utilizando a chave natural 'cpf'. Nem sempre, no entanto, podemos contar com a presença de uma chave natural. Por exemplo, imagine um aplicativo de registro de "anotações", onde diversos usuários podem criar anotações ao mesmo tempo e, quando sincronizadas, todos eles recebem todas as anotações cadastradas. Se considerarmos que nenhum campo desta entidade é obrigatório nem, tampouco, existe um campo que garanta unicidade entre suas tuplas, teremos um sério problema.

Mais especificamente, vamos considerar que chave primária da entidade "anotação" no servidor pode é um inteiro sequencial. O problema é que no dispositivo móvel é possível criar anotações, que posteriormente são enviadas ao servidor e sincronizadas em outros dispositivos. Perceba que, no momento da criação desta anotação no dispositivo, seria preciso consultar o servidor para obter uma chave única. Mas isto impede que o sistema funcione quando não houver acesso à internet. É muito difícil garantir que a chave sequencial seja respeitada no dispositivo, uma vez que haverá outros dispositivos clientes criando tuplas na mesma entidade.

Para resolver este problema (finalmente chegando ao ponto), esta arquitetura propõe uma abordagem denominada "desambiguação". Por meio desta técnica o servidor centralizado disponibiliza um serviço (URI) que é acessado pelo aplicativo no momento em que este é instalado no dispositivo (exigindo a conexão à internet apenas neste momento). Este serviço concede uma chave única ao aplicativo, que é então armazenada localmente. Toda vez que o aplicativo está lidando com uma entidade que não possua uma chave primária natural, ele é utilizado como prefixo para formar a chave:


No servidor a geração do prefixo de desambiguação é controlado por meio de um "serial" do banco de dados PostgreSQL. Assim, para que este serviço funcione corretamente, você precisa criar este serial no banco de dados da instância:

CREATE SEQUENCE titan._disambiguation INCREMENT 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 CACHE 1;

Desta forma, cada consulta ao serviço incrementa o serial garantindo que cada dispositivo receberá um prefixo único. No dispositivo este prefixo é armazenado localmente (usando a Shared Preferences), conforme veremos em um novo e-mail.


Este serviço permite registrar um dispositivo móvel para que receba mensagens na Action Bar. Para esta funcionalidade o Google Cloud Message foi integrado ao sistema de alertas do Titan.

Sobre o sistema de alertas do Titan, veja o post específico no link:


Sobre o Google Cloud Message, veja a documentação em:


Esta funcionalidade funciona apenas caso esteja sendo utilizada a autenticação por meio dos métodos CLIENT ou CLIENT-AS-USER, uma vez que a chave GCM será armazenada na tabela '_mobile' para o dispositivo (previamente registrado) que está efetuando a requisição.


Estes métodos permitem que o usuário do dispositivo móvel interaja com o sistema de alertas do Titan (link acima). Perceba que é necessário que o usuário esteja autenticado (por meio dos métodos CLIENT-AS-USER, USER, USER-BY-ID e USER-BY-MAIL).

Respectivamente, o primeiro serviço permite obter todos os alertas do usuário:

[
{"id":12,"message":"Quisque eu ante nec libero consequat facilisis rutrum dapibus tortor.","icon":"SECURITY","read":"false"},
{"id":11,"message":"Aliquam faucibus tortor et est sollicitudin ullamcorper.","icon":"CONFIRM","read":"false"}
]

O segundo método deleta um alerta do usuário, passando-se o ID do alerta. O terceiro marca um alerta como "lido".

Para não tornar muito grande este e-mail, vou encerrar a explicação por aqui. No próximo explicarei como é possível que você crie em suas seções seus próprios serviços (URIs).

Abraços,

Camilo

--
http://www.carromeu.com/

e-Mails:
cam...@carromeu.com
camilo....@embrapa.br

Skype: ccarromeu
gTalk: cam...@carromeu.com

http://twitter.com/ccarromeu/

Camilo Carromeu

unread,
Jul 6, 2016, 1:43:00 PM7/6/16
to Titan Framework
Olá pessoal,

Seguem algumas atualizações das instruções desta postagem.

Agora, a tag 'application' do 'configute/api.xml' permite que sejam declarados exatamente quais os endpoints estarão habilitados para cada aplicação. Este recurso é útil quando o barramento da instância é acessado por diferentes clientes (p.e., um aplicativo para smartphone, outro para tablet e uma single page em AngularJS). Um exemplo seria:

<?xml version="1.0" encoding="UTF-8"?>
<api-mapping>
<application
name="phone"
auth="APP|CLIENT-AS-USER"
token=""
request-timeout="36000"
protocol="Embrapa"
gcm-api-key=""
send-alerts="true"
/>

<application
name="tablet"
auth="APP"
token=""
request-timeout="36000"
protocol="Embrapa">
<endpoint method="GET" uri="\/util\/weather" />
<endpoint method="GET" uri="\/trainee\/profile\/[0-9]+\/[0-9a-zA-Z]+" />
<endpoint method="POST" uri="\/trainee\/presence" />
</application>

<application
name="site"
auth="APP|USER-BROWSER"
token=""
request-timeout="36000"
protocol="Embrapa">
<endpoint method="POST" uri="\/social" /> <endpoint method="GET" uri="\/auth" /> <endpoint method="GET" uri="\/biome\/list\/[0-9]+" />
</application>
</api-mapping>

Reparem que é inserido no atributo 'uri' da tag 'endpoint' a expressão regular que "casa" com a URI aceita. Dado, por exemplo, que o endpoint tem a sintaxe:

GET /trainee/profile/[id]/[auth]

Onde '[id]' será sempre um inteiro e '[auth]' uma sequência composta por números e letras (maiúscula e minúsculas), teríamos o seguinte critério (mostrado no exemplo acima):

<endpoint method="GET" uri="\/trainee\/profile\/[0-9]+\/[0-9a-zA-Z]+" />

Outra entrada que tornaria as requisições válidas, seria:

<endpoint method="GET" uri="\/trainee\/profile[^\z]*" />

Desta segunda forma, o Titan irá aceitar qualquer chamada GET para a seção "trainee", uma vez que, na prática, olha-se apenas para o prefixo estático da URI.

Importante! Caso não seja declarada nenhuma tag 'endpoint' na tag 'application' o Titan entende que a aplicação tem acesso a todos os endpoints nativos e a todos aqueles habilitados nas seções. No exemplo acima, a aplicação 'phone' terá acesso a todos os endpoints.

Abraços,

Camilo
Reply all
Reply to author
Forward
0 new messages