2009年7月9日木曜日

Google App Engine上のアプリでOpen IDを利用する

英語版主体で運営しているLibroSpotに、どこの国の方か分かりませんが「OpenIDに対応してくれ!」という要望があったので、ひと肌脱ぐことにしました。

LibroSpotはGoogle App Enigne上でDjango用のヘルパー(google-app-engine-django)を利用して動いています。

OpenIDの認証を自前で実装するとなるとコトですが、
google-app-engine-django向けのOpenIDライブラリがあるので、これ幸いと活用させていただくことにしました。

google-app-engine-django-openid
http://code.google.com/p/google-app-engine-django-openid/


結論から言うと、正直OpenIDの実装方法なんて全然知りませんでしたが、小1時間で対応完了!

ただし、若干の不具合(!?)らしきものがあったのでご報告を。


1. ログイン後のリダイレクト先があやしい

サイトの作りにもよりますが、ログインしたらそれまで開いていたページに戻って来て欲しいもの。

google-app-engine-django-openid(gaedo)も当然そのような処理をくわえています、が。。。正しく動いていないような!?

openidgae/templates/openidgae-login.html

<form action="{% url openidgae.views.OpenIDStartSubmit %}?continue={{continueUrl}}" method="post">
<input type="text" name="openid_identifier" id="openid_identifier" />
<input type="submit" value="Verify" /<
</form>

openidgae.views
def get_continue_url(request, default_success_url):
continueUrl = request.GET.get('continue', default_success_url)

formのactionにcontinueというパラメーターを付けていますが、これがrequest.GETの中に入ってきません。
postでサブミットしているからでしょうか?
また、continueUrlという変数にも何も入っていないので、そのままでは元のページに戻ることはありません。


それをふまえて書き換えたのがこちら。

openidgae/templates/openidgae-login.html
<form action="{% url openidgae.views.OpenIDStartSubmit %}" method="post">
<input type="text" name="openid_identifier" class="standard" style="width:140px;" id="openid_identifier" />
<input type="submit" class="standard" value="{% trans 'Verify' %}" />
<input type="hidden" name="continue" value="{{ request.get_full_path }}" />
</form>

openidgae.views
def get_continue_url(request, default_success_url):
if request.method == 'POST':
continueUrl = request.POST.get('continue', default_success_url)
else:
continueUrl = request.GET.get('continue', default_success_url)

continueパラメーターは無難でhiddenで渡すように変更し、遷移元のパスを明示的に指定しました。

ちなみに、テンプレートでrequestオブジェクトを使うには

setting.py
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request',
)



2. GAE付属のGoogle Accounts Python APIとの併用

認証まわりはgaedo1本でいくとしてもGoogle Accounts Python APIと併用するにしても、問題が出てきます。

gaedoはDjangoの認証まわりの各種メソッドに対応していないので、例えば、viewで request.user.is_authenticated を実行しても常にFalseが返ってきます。

また、すでにGoogle Accounts Python APIを導入済みの場合は、ユーザー情報の管理がUserモデルとPersonモデルに分割され、しかも情報が共通ではありません。

LibroSpotの場合はすでにGoogle Accounts Python APIを利用して認証を行っていたので、こちらをベースになんとかできないものかと模索したところ、これでいいのかな?という形に一応まとまりました。

いじるのは appengine_django.auth.middleware ひとつだけです。

オリジナル
class LazyUser(object):
def __get__(self, request, obj_type=None):
if not hasattr(request, '_cached_user'):
user = users.get_current_user()
if user:
request._cached_user = User.get_djangouser_for_user(user)
else:
request._cached_user = AnonymousUser()
return request._cached_user


変更後
class LazyUser(object):
def __get__(self, request, obj_type=None, person=None):
if not hasattr(request, '_cached_user'):
user = users.get_current_user()
if user:
request._cached_user = User.get_djangouser_for_user(user)
else:
from django.http import HttpResponse
import openidgae
person = openidgae.get_current_person(request, HttpResponse())
if person:
request.__class__.openid_person = person
request._cached_user = self.get_djangouser_for_openiduser(person)
else:
request._cached_user = AnonymousUser()
return request._cached_user

def get_djangouser_for_openiduser(self, person):
if hasattr(person, 'user_ref') and person.user_ref:
django_user = User.get(person.user_ref)
else:
email = person.get_email()
name = person.person_name()
u = email if email else name
django_user = User(user=users.User(u), email=email, username=name)
django_user.save()
person.user_ref = django_user.key()
person.save()
return django_user


このMiddlewareはgoogle-app-engine-djangoの認証まわりのコアとなるクラスで、認証およびデータの格納を一元的に管理しているようです。

Personモデル(openidgae.models.Person)はdb.Expandoを継承しているので、後から要素を追加することが可能です。

OpenIDでログインしている場合は、新しくUserモデル(appengine_django.auth.models.User)のオブジェクトを作成し、そのKeyをPersonモデルに格納して両者をひもづけることにしました。

さらにUserオブジェクトをrequestオブジェクトにセットすることで、前述の request.user.is_authenticated などが使えるようになります。


と、まあ、上記のように自分なりにいろいろいじった部分がありますが、おおむねではもの凄く簡単にOpenID対応が可能になります。
少なくともOpenIDの認証方法には一切タッチすることなく!

0 件のコメント: