主にプログラミングに関して。Python, .NET Framework(C#), JavaScript, その他いくらか。
記事にあるサンプルやコードは要検証。使用に際しては責任を負いかねます

スポンサーサイト

                
tags:
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Python: GoogleAppEngineのXSRF(CSRF)対策

                
 GoogleAppEngineにのっけているブログにXSRF対策を施していなかった。そんなものに対してクラックしかけて喜ぶ人なんていないだろうと思いつつ、GoogleAppEngineのwebapp2にはとくにxsrf対策が用意されていないようなので後学のためにモジュールを作っておくことにする。

# -*- coding: utf-8 -*-
import uuid
import os

if os.environ.get('SERVER_SOFTWARE','').startswith('Development'):
DEBUG = True
else:
DEBUG = False


def _check_token(handler):
"""check xsrf token equality between cookie and request parameter"""
cookie_token = handler.request.cookies.get("_xsrf")
form_token = handler.request.get("_xsrf")
if cookie_token != form_token:
return False
return True

def token_required(handler_method):
"""decorator checks xsrf token equality"""
def _token_required(handler, *args, **kwargs):
if _check_token(handler):
handler_method(handler, *args, **kwargs)
else:
handler.abort(403)
return _token_required

def set_token(handler, secure_attr=True):
"""set xsrf token cookie and return token value"""
token = str(uuid.uuid4())
if secure_attr and (not DEBUG):
handler.response.set_cookie("_xsrf", token, secure=True)
else:
handler.response.set_cookie("_xsrf", token)
return token


HOW TO USE
HTMLとクッキーにトークンを埋め込みたい場合は、関数set_tokenにリクエストハンドラselfを渡す。これでクッキーにトークンが仕込まれ、かつトークンが文字列で関数から返される。返されたトークン値をテンプレートエンジンに渡すなりなんなり。
あとはトークンの整合性を確認してから処理をしたいメソッドにデコレータtoken_requiredを付ける。これでリクエストのパラメータで渡されたトークン値とクッキーとして渡されたトークン値の整合が取れなければ403エラーを返す。整合が取れればメソッドをそのまま実行する。
class EntryHandler(webapp2.RequestHandler):
def get(self):
template_values = {"xsrf":xsrf.set_token(self)}
template = jinja_environment.get_template('add.html')
self.response.out.write(template.render(template_values))

@xsrf.token_required
def post(self):
entry = Entry()
entry.title = self.request.get('title')
entry.body = self.request.get('body').

フォームは以下のように。
<input name="_xsrf" type="hidden" value="{{ xsrf }}" />


 クッキーに埋め込まれたxsrfトークンは基本的に盗聴されていいものだと考えていないので、デフォルトではGoogleAppEngineサーバで使われるときはsecure属性を付加している。ただローカルの開発サーバでテストするときはそもそもSSL通信ができないので、このままでは開発サーバ上でのテストが実行できなくなる。開発サーバかGoogleAppEngineのパブリックなサーバかを判別してsecure属性の付加を判断している。
if secure_attr and (not DEBUG):
handler.response.set_cookie("_xsrf", token, secure=True)
else:
handler.response.set_cookie("_xsrf", token)


余談:メソッドのデコレータと引数の命名
 デコレータとして使う関数token_requiredはクラス内で使わるので、内部関数の第一引数にはインスタンスが渡されるようになっている。これをselfと書くかどうか。
def token_required(handler_method):
def _token_required(self or ???, *args, **kwargs):
...

 そもそもPythonのメソッドはその最初の引数にインスタンスが渡されることが決まっていて、それをselfという引数で受け取ってつらつら書いていくのが慣例だ。
http://docs.python.jp/2/tutorial/classes.html#tut-remarks
 あらゆるタイプのメソッドの第一引数をselfと書くわけでもないらしく、公式ドキュメントではクラスメソッドを書くときはselfでなくclsと書いている。
http://docs.python.jp/2/library/functions.html#classmethod
これを考えると、慣例的にはselfに入っているのはインスタンスへの参照のようだ。
 今回デコレータの内部関数はメソッドのように使われる。その内部関数が実行されるとき第一引数に入っているのはインスタンスへの参照だ。だから第一引数をselfと書いてコーディングするのが一般的かもしれない。しかし一方でメソッドでなく関数としてクラスの外で定義されているので、そこにselfという名前の引数があるのもモヤッとした。
 結局第一引数として渡されているインスタンスはRequestHandlerクラスのインスタンスなので、handlerという変数名にしておいた。一通り書き終えたあと、モヤッを解消するためにググってみたところ、脳内で暗黙のうちに今回書いたデコレータのモデルにしていたwebapp2のおまけモジュールのlogin_requiredではselfを使っていた。
https://webapp-improved.appspot.com/_modules/webapp2_extras/appengine/users.html
 stackoverflowではぼくと同様にモヤッたのかはわからないが、selfを使っていない例を見つけられた。
http://stackoverflow.com/questions/1123117/python-function-decorators-in-google-app-engine
どっちがいい命名プラクティスなんだろう。
            

コメントの投稿

非公開コメント

プロフィール

hMatoba

Author:hMatoba
Github

最新記事
リンク
作ったものなど
月別アーカイブ
カテゴリ
タグリスト

検索フォーム
Amazon
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。