domingo, 12 de agosto de 2007

Mais sobre OpenID

OpenID Outro dia publiquei um artigo sobre OpenID, que é um sistema de descentralização de autenticação e autorização.

Como ainda estava começando a entender seu funcionamento, cometi alguns erros, nada muito esdrúxulo ou que atrapalhe seu entendimento posterior.

Bem, agora, depois de duas semanas estudando sobre o funcionamento de um consumidor, vou explicar aqui um pouco do que descobri.

Numa transação de autenticação OpenID, há pelo menos três nós envolvidos: cliente, consumidor (RP, relying party) e provedor (OP, provider).

O cliente é o usuário que quer se autenticar, geralmente com seu navegador.

O consumidor é o sistema onde o cliente quer se autenticar.

O provedor é o serviço OpenID onde o cliente se cadastrou.

A autenticação então funciona mais ou menos assim:
  1. o cliente acessa o consumidor e informa sua URL de identificação;
  2. o consumidor «canonicaliza» a URL, e procura saber qual a identificação verdadeira (IDP) e quem é o provedor;
  3. o consumidor toma quaisquer medidas necessárias para verificar a identificação e redireciona o cliente para o provedor;
  4. cliente e provedor se entendem e, a partir daí, a conexão volta para o consumidor;
  5. a partir dos dados voltados, o consumidor sabe se o cliente está autorizado ou não.


Alternativamente o provedor pode enviar dados extra sobre o cliente, em formato SRE (ou sreg) ou AX.

Há dois modos de transação entre consumidor e provedor: dumb mode e smart mode.

Dumb mode


O dumb mode é usado quando o consumidor não suporta cálculos de criptografia.

Primeiro o cliente informa sua URL e o consumidor a usa para descobrir qual sua identificação e quem é o provedor. Na versão 1.0 esses dados se encontram nas seguintes tags:
<link rel="openid.delegate" href="identidade" />
<link rel="openid.server" href="provedor" />


Na versão 2.0:
<link rel="openid2.local_id" href="identidade" />
<link rel="openid2.provider" href="provedor" />


É interessante verificar todos.

Obtidos esses dados, o próximo passo é o ajuste de verificação (checkid_setup): o consumidor redireciona o cliente para o provedor, passando os seguintes dados em GET ou POST:
  • openid.mode: o modo, checkid_setup;
  • openid.identity: a identidade do cliente;
  • openid.return_to: a URL para onde o provedor deve redirecionar o cliente depois da autenticação;
  • openid.trust_root: o nome do servidor do consumidor;
  • openid.ns: a versão do OpenID, http://specs.openid.net/auth/2.0;
  • openid.claimed_id: a URL original fornecida pelo cliente (geralmente igual à identidade).


Depois disso o cliente se resolverá com o provedor. Depois o provedor redirecionará o cliente de volta ao consumidor, para a URL informada por return_to, passando os seguintes parâmetros em POST:
  • openid.mode: pode ser id_res se tudo correu bem, ou cancel;
  • openid.identity: a identidade do cliente;
  • openid.return_to: a mesma URL que o provedor recebeu no passo anterior;
  • openid.signed: lista de parâmetros cobertos pela assinatura;
  • openid.sig: string chave da assinatura;
  • openid.invalid_handle: se o provedor rejeitou algum parâmetro, ele será informado aqui.


No entanto é preciso verificar se esses dados vieram do provedor mesmo ou se foram forjados. Para tanto, valos ao próximo passo, a verificação da autenticação (check_authentication).

O consumidor acessa diretamente o provedor via HTTP reenviando os dados recebidos, mas mudando o parâmetro openid.mode para check_authentication.

O provedor deve responder o consumidor com uma página texto, com os parâmetros terminados por mudança de linha (LF), cada linha no formato chave:valor.

As chaves recebidas são:
  • is_valid: true caso o provedor tenha mesmo enviado os dados ou false;
  • invalid_handle: caso algum campo não tenha sido enviada pelo provedor, ele será informado aqui.


No entanto não é possível trocar dados de persona, que são as informações sobre o usuário.

Se for necessário obter informações de persona, é preciso usar o smart mode.

Smart mode


Esse modo é usado quando se deseja trocar dados de persona, como nome completo, endereço de correio, língua ou país.

Após o cliente ter enviado ao consumidor sua URL e o consumidor tenha identificado o provedor e a identidade, o consumidor precisa então escolher um par gerador-módulo para criptografia Diffie-Hellman. Geralmente é usado 2 como gerador e um primo seguro de 1024 bits como módulo.

O consumidor deve então escolher uma chave privada, um número grande maior que um e menor que o módulo menos um, e calcular a chave pública:
geradorchave_privada  chave_publica mod módulo


Todos esses números grandes devem ser convertidos em formato btwoc (big-endian signed two's complement), representados como base64.

Então é feita a associação: o consumidor acessa diretamente o provedor via HTTP, passando os seguintes dados via GET ou POST:
  • openid.mode: associate, indicando que se trata de uma associação;
  • openid.assoc_type: HMAC-SHA1 para chaves de 160 bits ou HMAC-SHA256 para chaves de 256 bits (acoselho 256b);
  • openid.session_type: DH-SHA1 ou DH-SHA256, conforme o parâmetro anterior;
  • openid.dh_modulus: o módulo escolhido no formato correto (base64 !! btwoc);
  • openid.dh_gen: o gerador no formato correto(se for 2, será Ag==);
  • openid.dh_consumer_public: a chave pública calculada.


A resposta será similar à resposta ao check_authorization, ou seja, parâmetros encerrados por mudança de linha e dois pontos (:) separando chave e valor.

Os parâmetros são:
  • assoc_type: o tipo de associação (o mesmo enviado pelo consumidor);
  • assoc_handle: uma chave que deve ser informada a cada conexão posterior;
  • expires_in: indica quando o assoc_handle expira;
  • session_type: o mesmo tipo enviado pelo consumidor;
  • dh_server_public: a chave pública do servidor;
  • enc_mac_key: o segredo criptogrado.


Então o consumidor deve armazenar esses dados e, assim como no dumb mode, efetuar a verificação de autenticação (checkid_setup), da mesma forma vista anteriormente, mas com umas chaves a mais.

O primeiro é a chave openid.assoc_handle, informando a mesma chave recebida anteriormente.

Caso queira obter dados de SRE (Simple Registration Extension, ou sreg), os parâmetros extra passados são:
  • openid.ns.reg: a versão: http://openid.net/extensions/sreg/1.1;
  • openid.sreg.required: os campos requeridos separados por vírgulas;
  • openid.sreg.optional: campos opcionais separados por vírgulas;
  • openid.sreg.policy_url: uma URL que informe o que será feito com os dados.


Caso queira obter dados de AX (Attribute Exchange), siga a URL. =P

Quando o cliente for redirecionado de volta para o consumidor, o consumidor receberá via POST os mesmos dados recebidos no dumb mode, mas com alguns parâmetros extra: openid.sreg.*, onde * é cada um dos campos requeridos e opcionais solicitados.

Resumo


Ou seja, em dumb mode:
  • checkid_setup
  • check_authentication


Em smart mode:
  • associate
  • checkid_setup


Alternativamente pode ser usado checkid_immadiate em vez de checkid_setup, geralmente quando trabalando com AJAX. Veja as especificações.

Espero ter ajudado.

[]'s
Cacilhas

quarta-feira, 8 de agosto de 2007

Pylons, ToscaWidgets e Unicode

Alguns dias atrás o Torcato anunciou que estava iniciando um pequeno projeto pra estudar Pylons, o Ferlons. Logo pedi autorização pra entrar no projeto e conhecer um pouco mais do ToscaWidgets.

Widgets são velhos conhecidos dos desenvolvedores Plone. A idéia é não escrever o formulário HTML, mas construir uma estrutura de dados que represente um campo em nosso esquema. Por exemplo, imagine uma classe usuário, com o nome do usuário e a data da admissão. Em vez de escrever um formulário para esses dados, o uso de widgets nos permite representar essas estruturas programaticamente, de forma que a geração do formulário seja automática e rica, incluindo um javascript para nos mostrar um calendário onde podemos escolher a data, e um pouco de CSS.

O TurboGears já inclui widgets há um bom tempo. Alberto Valderde extraiu os widgets do core do TurboGears e criou o projeto ToscaWidgets, que permite que esse widgets sejam usados por outros frameworks. Em nosso caso, incluímos o ToscaWidgets no Ferlons (Pylons).

Porém, incluí-lo não foi tarefa trivial, porque tivemos dois problemas chatos com Unicode. O método padrão para mostrar o widget renderizado é o display, que retorna um stream. Ao executar um print para esse stream, o resultado é uma string, mas ele tem caracteres Unicode lá dentro. O Mako passa um unicode() no resultado disso daí, e o resultado é um temido UnicodeDecodeError. Após alguns (4 dias :D) de testes, descobri como corrigir: em vez de chamar form.display no meu template, agora chamo form.render().decode('utf-8').

O outro bug era no validador, novamente relacionado com Unicode. Estamos usando o validador UnicodeString do FormEncode, porém ele não funcionava corretamente em nosso formulário. Após uma breve discussão na lista do TurboGears, descobri que o UnicodeString do FormEncode tem um problema com o ToscaWidgets, mas o próprio ToscaWidgets possui um validador Unicode, que funciona corretamente. Na verdade, o método o ToscaWidgets espera que o método from_python retorne uma string Unicode, ao passo que o método from_python do UnicodeString do FormEncode retorna uma string UTF-8. Um exemplo do que estou falando:

>>> from toscawidgets.widgets.forms import validators as tosca_validators


>>> tosca_unicode = tosca_validators.UnicodeString()
>>> tosca_unicode.from_python('é')
'\xc3\xa9'
>>> tosca_unicode.from_python(u'é')
u'\xe9'
>>> import formencode.validators as formencode_validators
>>> formencode_unicode = formencode_validators.UnicodeString()
>>> formencode_unicode.from_python('é')
'\xc3\xa9'
>>> formencode_unicode.from_python(u'é')
'\xc3\xa9'

Agora, livres (eu acho!) dos bugs de Unicode, ficamos livres pra realmente implementar o código. Parece fácil escrevendo aqui, mas tomou uma semana minha pra descobrir isso.