django-tastypieでDigest認証をかける

djang-tastypieでDigest認証をかける方法を試してみました。
前に試したApiKey認証と同じくApiKeyのレコードが必要ですので、下記のエントリの通り準備します。

django-tastypieでApiKey認証をかける
http://d.hatena.ne.jp/yuheiomori0718/20120921


さらにpython-digestに依存してますのでインストールします。

pip install python-digest

Resourceクラスにはauthentication = DigestAuthentication()を記述します

from tastypie.authentication import DigestAuthentication
from tastypie.resources import ModelResource
from tastypie.authorization import Authorization
from app.models import Book


class BookResource(ModelResource):
    class Meta:
        queryset = Book.objects.all()
        #authentication = BasicAuthentication()
        #authentication = ApiKeyAuthentication()
        authentication = DigestAuthentication()
        authorization = Authorization()

requestsを使ったアクセスの例はこちら。
requestsはDigest認証にも対応しているので簡単です。
HTTPDigestAuthを使うだけです。

# coding=utf-8
import requests
import json
from requests.auth import HTTPDigestAuth

username = u"user2"
api_key = u"430b1c2faed2328fd7a4133edefea90a4f06324b"

# データを全部削除
r = requests.delete('http://localhost:8000/api/book/',
                    auth=HTTPDigestAuth(username, api_key))
assert(r.status_code == 204)

# データが全て消えていることを確認
r = requests.get('http://localhost:8000/api/book/',
                 auth=HTTPDigestAuth(username, api_key))
assert(json.loads(r.text)["meta"]["total_count"] == 0)

## 登録
payload = {'title': u'Python Cook Book', 'pub_date': "2012/9/25"}
r = requests.post('http://localhost:8000/api/book/',
                  data=json.dumps(payload),
                  headers={"Content-Type": "application/json"},
                  auth=HTTPDigestAuth(username, api_key))
assert(r.status_code == 201)

# 取得
r = requests.get('http://localhost:8000/api/book/',
                 auth=HTTPDigestAuth(username, api_key))
res = json.loads(r.text)
assert(len(res["objects"]) == 1)
assert(res["objects"][0]["title"] == u"Python Cook Book")
assert(int(res["objects"][0]["id"]) == 1)

# idを指定して取得
r = requests.get('http://localhost:8000/api/book/1',
                 auth=HTTPDigestAuth(username, api_key))
res = json.loads(r.text)
assert(int(res["id"]) == 1)
assert(res["title"] == u"Python Cook Book")

# 更新
payload = {'id': 1,
           'title': u'Python Cook Book 2nd Edition',
           'pub_date': "2013/1/25"}
r = requests.post('http://localhost:8000/api/book/',
                  data=json.dumps(payload),
                  headers={"Content-Type": "application/json"},
                  auth=HTTPDigestAuth(username, api_key))
assert(r.status_code == 201)


# 更新内容を確認
r = requests.get('http://localhost:8000/api/book/1',
                 auth=HTTPDigestAuth(username, api_key))
res = json.loads(r.text)
assert(int(res["id"]) == 1)
assert(res["title"] == u"Python Cook Book 2nd Edition")


# idを指定して削除
r = requests.delete('http://localhost:8000/api/book/1',
                    auth=HTTPDigestAuth(username, api_key))
assert(r.status_code == 204)


# データが消えていることを確認
r = requests.get('http://localhost:8000/api/book/1/',
                 auth=HTTPDigestAuth(username, api_key))
assert(r.status_code == 404)


django側のログを見ると、アクセスすると一度401を返し、2度めのアクセスで正常なレスポンスを返していることが確認できます。
ちゃんとDigest認証しているみたいですね

[22/Sep/2012 23:23:03] "DELETE /api/book/ HTTP/1.1" 401 0
[22/Sep/2012 23:23:03] "DELETE /api/book/ HTTP/1.1" 204 0
[22/Sep/2012 23:23:03] "GET /api/book/ HTTP/1.1" 401 0
[22/Sep/2012 23:23:03] "GET /api/book/ HTTP/1.1" 200 101
[22/Sep/2012 23:23:03] "POST /api/book/ HTTP/1.1" 401 0
[22/Sep/2012 23:23:03] "POST /api/book/ HTTP/1.1" 201 0
[22/Sep/2012 23:23:03] "GET /api/book/ HTTP/1.1" 401 0
[22/Sep/2012 23:23:03] "GET /api/book/ HTTP/1.1" 200 213
[22/Sep/2012 23:23:03] "GET /api/book/1 HTTP/1.1" 301 0
[22/Sep/2012 23:23:03] "GET /api/book/1/ HTTP/1.1" 401 0
[22/Sep/2012 23:23:03] "GET /api/book/1/ HTTP/1.1" 200 112
[22/Sep/2012 23:23:03] "POST /api/book/ HTTP/1.1" 401 0
[22/Sep/2012 23:23:03] "POST /api/book/ HTTP/1.1" 201 0
[22/Sep/2012 23:23:03] "GET /api/book/1 HTTP/1.1" 301 0
[22/Sep/2012 23:23:03] "GET /api/book/1/ HTTP/1.1" 401 0
[22/Sep/2012 23:23:03] "GET /api/book/1/ HTTP/1.1" 200 117
[22/Sep/2012 23:23:03] "DELETE /api/book/1 HTTP/1.1" 301 0
[22/Sep/2012 23:23:03] "DELETE /api/book/1/ HTTP/1.1" 401 0
[22/Sep/2012 23:23:03] "DELETE /api/book/1/ HTTP/1.1" 204 0
[22/Sep/2012 23:23:03] "GET /api/book/1/ HTTP/1.1" 401 0
[22/Sep/2012 23:23:03] "GET /api/book/1/ HTTP/1.1" 404 0

またちょっとrequestsのバージョンが古いと、2度めのアクセスで404を返していても、reqeustsのレスポンスが1度目のアクセスのステータスコード(401)を保持しているというバグがあります。
上記のアクセス例だと最後のアクセスがassertエラーになる場合、requestsが古いかもしれません。

# データが消えていることを確認
r = requests.get('http://localhost:8000/api/book/1/', auth=HTTPDigestAuth(username, api_key))
assert(r.status_code == 404) # これが401になる


手元ではバージョン0.12.1ではエラーになり、0.14.0ではバグが解消されていることだけは確認できてます。