Codemountain, Paulo Suzart's Blog

Arquivo da categoria ‘python

Basic Authentication no GAE com Tornado

with 2 comments

Olá! Pra quem já usou o Tornado, vai perceber que o framework permite autenticação via cookie criptografado. O exemplo original da documentação usa uma autenticação por formulário, o que é ótimo para visitantes que desejem utilizar as funcionalidades da nossa aplicação via página web, mas o que fazer para proteger uma API rest construída no GAE com o Tornado?

Uma possibilidade simples é o uso da autenticação no nível do transporte, o HTTP Basic Authentication, que não é lá estas coisas, mas tem seu lugar. Outro ponto importante é que na minha API, eu gostaria de usar um esquema de Decorator em Python (explicar o funcionamento dos decorators está fora do escopo deste post, mas o link anterior oferece bastante info) para facilitar a minha vida. Isto é, eu gostaria de usar algo semelhante a interceptors ou filters em Java. O uso deveria ficar mais ou menos assim:

MyRequestHandler(tornado.web.RequestHandler):
	@authenticated(authenticator)
	def post(self, username):
		#alguma protegida aqui

def authenticator(user,pass):
	#Nossa logica de auth aqui, seja acessar o DataStore
        #ou qualquer coisa para verificar a autenticidade da informação

@authenticated é uma função de decoração que receberá nosso autenticador como parâmetro, que por sua vez será utilizado dentro do código que irá iterceptar a invocação da função post em MyRequestHandler.

Agora a parte mais interessante, o nosso Decorator propriamente dito. Este cara precisa fazer o seguinte: verificar se o usuário tem um cookie criptografado, se não tiver, verificar os cabeçalhos http referentes a Basic Authentication, que por sua vez fornecerão usuário e senha para passarmos para a função authenticator. Naturalmente, se existe um cookie criptografado e válido, pulamos a validação do cabeçalho HTTP e permitimos a invocação do da função post do MyRequestHandler. Abaixo o código inteiro do Decorator e depois os comentários:

def authenticated(auth):
	"""Decorate a function to request user authentication before processing.
		Just uses Basic HTTP Authentication.
	"""
	def decore(f):

		def _request_auth(handler):
			"""Responds 401 to the client, that may prompts the user for auth data."""
			handler.set_header('WWW-Authenticate', 'Basic realm=tmr')
			handler.set_status(401)
			handler.finish()
			return False

		@functools.wraps(f)
		def new_f(*args):
			"""The actual wrapper of the function. Check if auth data is present, if so, invokes
				the auth (authenticated argument) for credential check, if ok, the wrapper function is invoked
				with an aditional argument, the username."""
			handler = args[0]

			auth_header = handler.request.headers.get('Authorization')

			if (not handler.get_secure_cookie("user") and (auth_header is None or not auth_header.startswith('Basic '))):
				return _request_auth(handler)
			elif handler.get_secure_cookie("user"):
				logging.info("####Login by cookie")
				user = handler.get_secure_cookie("user")
			else:
				logging.info("###First login by http")
				auth_decoded = base64.decodestring(auth_header[6:])
				user, password = auth_decoded.split(':', 2)
				if not auth(user, password):
					_request_auth(handler)
				handler.set_secure_cookie("user", user)
			f(username=user, *args)
			return

		return new_f
	return decore

O código poderia ter sido escrito de alguma outra fora, sei lá, organizar e encadear os ifs de outra maneira, enfim. Acho que aqui fica suficiente pra representar o que falei logo acima, que é a lógica de execução da coisa.

Veja que authenticated na verdade é uma função definida como outra qualquer. Ela recebe uma função que recebe dois argumentos (usuário e senha), que por sua vez retornará True ou False, indicando sucesso na autenticação. authenticated de fato retorna outra função, a função decore. decore é uma função qure retorna uma função decorada (a new_f) com @functools.wraps, que nada mais é permitir que new_f fique com a mesma “cara” que a nossa função post do MyRequestHandler. Estranho? Veja detalhes aqui.

Pois bem, nossa decore recebe f como parâmetro, que na verdade é a função post do Request Handler e retorna new_f, que por sua vez recebe os mesmos parâmetros que post na forma de *args. *args é o jeito python de uma função receber um número de argumentos variável, dado que quando decoramos uma função, não sabemos exatamente quantos parâmetros aquela função receberá. Mais detalhes sobre *args e **kwargs aqui.

Quem mantém a lógica de verificação da presença das credenciais (seja por cookie, seja por http) é new_f, que delega para _request_auth os casos onde a credencial não está presente e para auth os casos onde existem as credenciais http. _request_auth só envia um status code 401 para o client, para que este envie as informações do usuário e senha.

A execução de post fica no final de new_f onde ela invoca f (username=user, *args). Aqui podemos ver uma pseudo injeção de dependência, dado que username poderia ser um objeto do tipo user retornado da base ou qualquer outra coisa. Por que injeção? Por que o Tornado não conhece este parâmetro, quer ficaria nulo caso a função post não tivesse sido anotada com @authenticated.

O código aqui está em funcionamento em uma aplicação que estou fazendo. Mas não postei aqui a aplicação completa. Ao usar este decorator, meu log mostra (considerando usuário jordan e uma senha qualquer):

INFO     2010-11-06 13:30:27,448 utils.py:37] ###First login by http
INFO     2010-11-06 13:30:27,448 api.py:34] ########jordan######
INFO     2010-11-06 13:30:27,461 dev_appserver.py:3283] "GET /api/track/twitter/mentions HTTP/1.1" 200 -
INFO     2010-11-06 13:30:31,903 utils.py:34] ####Login by cookie
INFO     2010-11-06 13:30:31,904 api.py:34] ########jordan######

Meu autenticador passado para @authenticated, por enquanto é um lambda que retorna True para qualquer usuário e senha.

@authenticated(lambda x,y: True)
def post(self, username): #...

Acho que é só. A intenção aqui é de orientação e mostrar alguns dos desafios que podemos enfrentar usando o Google App Engine (GAE) e o Tornado.
Não mostro aqui os detalhes gerais de se usar o Tornado no GAE, mas é possível encontrar muito material bom na internet. Espero que tenham gostado e não esqueçam, follow me on twitter: @paulosuzart

Post UPDATE: por algum motivo sobrenatural eu escrevi *Basich* e não Basic. Por isto a URL contem um erro que ficará para a posteridade. Mereço um #Fail!

Written by paulosuzart

novembro 6, 2010 at 2:12 pm

Publicado em python

Etiquetado com ,

Python, Java NIO => JTornado

leave a comment »

Desde o últmimo post parei com Scala. 0.o

Devido a um problema com as versões da linguagem (incompatibilidade binária e mudanças na estratégia de Collections para aumentar a compatibilidade com o Java), muito se especulou sobre a estabilidade da linguagem e muitas discussões acirradas (vejam os tweets entre @jboner e @codemonkeyism. Por este motivo tirei férias de Scala e mergulhei profundamente em Python.

Fui surpreendido com uma linguagem extremamente madura, poderosa e completa. Foi tão apaixonante quanto Scala. Como já desejava construir algo no GAE usando python, não perdi tempo e criei o Twittograph (@twittograph), o que deve ser em breve a nova maneira de pedir autógrafos aos seus ídolos.

Então estudei muito (não só Python): Eventlet, Twisted, Tornado, Linux Epoll, Python Select, Python Fabric, fiz Gists, Avro, Boost e Java NIO, clojure. Devo com certeza ter esquecido algo. Não dá pra comentar de cada um destes ou o post ficaria bastante extenso. Fica a dica: Python, Tornado e Clojure. Usem.

Passei a usar o Python Fabric no dia-a-dia, e usei o Tornado para construir o Twittograph. Como vocês podem perceber eu estava navegando pra todos os lados ao mesmo tempo, e as chances de ficar parado eram grandes, até que @abraaoisvi me alertou que eu deveria focar. Foi então que @rafaelfelini – até então recém apaixonado por python – sugeriu “construir” uma versão do Tornado em Java.

Assim nasce o JTornado. Construído em conjunto com o Rafael. Tá, o nome não é lá essa criatividade. Pensamos em Cyclone, Transtornado, Huracan, etc. Bom, deixa JTornado mesmo.

Se você quer praticidade, precisa conhecer o Tornado, feito pelo Facebook (link acima). Mas se você quer esta mesma praticidade em Java e sabe que Java para Web != Servlet. Conheça o JTornado. O JTornado pode ser visto como um framework web com um servidor de alta performance embutido, ou um servidor de alta performance com um framework web embutido. Este é um direcionamento que ainda vamos discutir.

O próprio @felipejcruz, criador do easy_tornado já se mostrou a favor do foco num framework para aplicações assíncronas de rede, que eventualmente oferece um framework web. Vamos ver!

Logo logo devo postar os percalços enfrentados para usar Java NIO e ao mesmo tempo usar low-level concurrency, leia-se threads. Como foi construir um server baseado em processos (Tornado) utilizando a JVM que nos limita neste aspecto. E muito mais. Devo escrever sobre algumas boas práticas – que acreditamos que sejam boas – para o uso de NIO aliado à alta performance com threads.

Veja algumas discussões no Stackoverflow.com durante este tempo:

Como não pode faltar código num post, segue uma micro app usando o JTornado, mas escrita em Clojure:

;Starts an JTornado HTTP Server and a sample RequestHandler.
; Bit verbose due to compilation directives. Recommendation is to generate
; a set of macros to hide this.
(ns org.ctornadoweb
; Compiled and implements a static main method to start the server
  (:import (org.jtornadoweb HttpServer)
           (org.jtornadoweb.Web$Application)
           (org.jtornadoweb.Web$RequestHandler))
  (:gen-class :prefix "server-"))

(gen-class
 :name org.ctornadoweb.MyHandler
 :extends org.jtornadoweb.Web$RequestHandler
 :prefix "do-")

(defn do-get [this]
  "Handles the HTTP GET method"
  (.write "hello clojure"))
(defn do-post [this]
  "Handles the HTTP POST method"
  (.write (.getArgument "name" "default" false)))

(defn server-main []
  "main method"
 (.listen
  (org.jtornadoweb.HttpServer.
   (.add (org.jtornadoweb.Web$Application.) "/" org.ctornadoweb.MyHandler)
     false nil false) 8089))

;use (compile 'org.ctornadoweb)

Basta fazer um curl http://localhost:8089 e tudo funcionará :)

Aguarde o próximopost para informações completas de Java NIO  e JTornado. ;)

Update: Desde que a versão Scala 2.8.0 final foi liberada, retomei os estudos e meu interesse pela linguagem continua. A propósito, o único motivo para o JTornado ser construído em Java é ganhar tempo, pois os colaboradores se sentem mais a vontade com Java. Provavelmente algumas funcionalidades podem ser escritas em Scala ou até mesmo Clojure.

Written by paulosuzart

julho 23, 2010 at 2:51 pm

Publicado em Java, nio, python

Etiquetado com , ,

Seguir

Obtenha todo post novo entregue na sua caixa de entrada.