2008年2月12日

python-openidを直接使ってopenid認証の流れを確認


python-openidを直接使ってOpenID認証の流れを確認してみた。
URLとか直接書いちゃってて適当なプログラムだけど、とりあえず認証したURLを取得して表示するまで。
greetingとか、sampleappはpaste_deployのテンプレートに入ってるやつが残ってるだけなので、意味はないです。



signin が認証フォーム
submitSignin でopenidのconsumerモジュールを使って、認証サーバーにリダイレクトすると同時に、認証セッションを作成。
認証後verifyに戻ってくるので、認証セッションを終了させて、結果を取得する。



privateを保護するようなサンプルを考えていたんだけど、まだやってない。
passurlをセッションに置いてとか、middleware書くのもだるいので、とりあえずこんなとこ。



OpenID2.0では、Attribute ExchangeとかAssosciationとかあるようなので、もうちょっと調べないとな。


wsgiapp.py


import sampleapp
from paste.deploy.config import ConfigMiddleware
import selector
import webob
import tempita
import os
import formencode
from formencode import validators
from openid.consumer import consumer

path = tempita.bunch()
path.root = os.path.dirname(__file__)
path.templates = os.path.join(path.root, 'templates')

class PassurlSchema(formencode.Schema):
passurl = validators.String(not_empty=True)

def signin(environ, start_response):
request = webob.Request(environ)
response = webob.Response(request=request)
tmpl = tempita.HTMLTemplate.from_filename(os.path.join(path.templates, 'signin.html'))
response.body = tmpl.substitute()
return response(environ, start_response)

from openid.store.filestore import FileOpenIDStore
filestore = FileOpenIDStore(os.path.join(path.root, 'store'))
realm = 'http://localhost:8080/'
return_to = 'http://localhost:8080/verify'

def submitSiginin(environ, start_response):
request = webob.Request(environ)
response = webob.Response(request=request)
try:
schema = PassurlSchema()
params = schema.to_python(request.params)
except formencode.Invalid, e:
tmpl = tempita.HTMLTemplate.from_filename(os.path.join(path.templates, 'signin.html'))
response.body = tmpl.substitute()
return response(environ, start_response)
openidsession = dict()

c = consumer.Consumer(openidsession, filestore)
req = c.begin(request.params['passurl'])
print req
url = req.redirectURL(realm, return_to=return_to)
response.location = url
response.status = 301
session = environ['beaker.session']
session['openid.consumer'] = c
session.save()
return response(environ, start_response)

def verify(environ, start_response):
request = webob.Request(environ)
response = webob.Response(request=request)

session = environ['beaker.session']
c = session['openid.consumer']
res = c.complete(request.GET, return_to)
if res.status == 'success':
response.body = res.identity_url
else:
response.body = "%s" % res
return response(environ, start_response)


def private(environ, start_response):
request = webob.Request(environ)
response = webob.Response(request=request)
response.body = "This page is private."
return response(environ, start_response)

def make_app(
global_conf,
# Optional and required configuration parameters
# can go here, or just **kw; greeting is required:
greeting,
**kw):
# This is a WSGI application:
app = selector.Selector()
app.add('/', GET=sampleapp.application)
app.add('/signin', GET=signin, POST=submitSiginin)
app.add('/private', GET=private)
app.add('/verify', GET=verify)
# Here we merge all the keys into one configuration
# dictionary; you don't have to do this, but this
# can be convenient later to add ad hoc configuration:
conf = global_conf.copy()
conf.update(kw)
conf['greeting'] = greeting

from beaker.middleware import SessionMiddleware
app = SessionMiddleware(app, key='mysession', secret='randomsecret')
# ConfigMiddleware means that paste.deploy.CONFIG will,
# during this request (threadsafe) represent the
# configuration dictionary we set up:
app = ConfigMiddleware(app, conf)
return app



templates/signin.html

<html>
<body>
<form action="" method="post">
<label for="passurl">Passurl</label>
<input id="passurl" type="text" name="passurl"/>
<button type="submit">Signin</button>
</form>
</body>
</html>

そういえば、AuthKitの次期バージョン(0.4.1かな)はpython-openidの2.0系つまりopenid2.0を使うみたいだ。