hgweb にユーザー認証させる - Mercurial Advent Calendar 2012

2012 年 12 月 18 日 はてなブックマークへ追加 はてなブックマーク - hgweb にユーザー認証させる - Mercurial Advent Calendar 2012 Bookmark this on Delicious

Mercurial Advent Calendar 2012 の 18 日目は、 hgweb とユーザー認証の話をします。

とその前に、 LDAP の bind で認証するコードが https://gist.github.com/4104596 にあります。こんな記事を見るよりコードを読んだほうが早いですよ。

認証レイヤー

HTTP で Mercurial リポジトリを公開する場合、ユーザー認証を挟む場所が 2 つあります。

  1. Web サーバー — Apache の mod_auth_basic や nginx の auth_basic を使う。
  2. WSGI — application(environ, start_response) 関数で処理する。

簡単なのは 1. です。 ですから、もちろん 2. を使います。

テストしやすくする

コードを書く前に、まず hgweb_wsgi.py をテストしやすくしましょう。

すでに Flask はインストールされていることと思いますので、 Flask と一緒にインストールされた Werkzeug を使ってみます。

#!/usr/bin/env python
from mercurial import hgweb
application = hgweb.hgwebdir('hgweb.config')

if __name__ == '__main__':
    from werkzeug import serving
    serving.run_simple('localhost', 8000, application,
                       use_debugger=True, use_reloader=True)

これだけです。 hgweb_wsgi.py を直接実行できて、例外でデバッガが起動するようになりました。

401 Unauthorized をフックする

クライアントへ認証を要求するには、 401 Unauthorized レスポンスに WWW-Authenticate をのせる必要があります。 hgweb は認証が必要な場面で 401 を返しますが、 WWW-Authenticate ヘッダがついていません。 hgweb が認証の手段を知らないためです。

A very basic description of authentication opportunities in WSGI を参考にレスポンスヘッダをのせます。

hgapp = hgweb.hgwebdir('hgweb.config')

def application(environ, start_response):
    def wrapped_start_response(status, headers, exc_info=None):
        if status.startswith('401'):
            realm = 'Mercurial Repository'
            headers.append(('WWW-Authenticate', 'Basic realm="%s"' % realm))
        return start_response(status, headers, exc_info)

    return hgapp(environ, wrapped_start_response)

応答をみる

Basic 認証要求に対して、クライアントは HTTP_AUTHORIZATION ヘッダで応答します。 ここでやるべきは、認証データを検証して REMOTE_USER を渡すことです。 hgweb が REMOTE_USER をもとに操作を「認可」します。

from werkzeug import http

def check_auth(username, password):
    return {'hoge': 'fuga'}.get(username) == password

def application(environ, start_response):
    auth = http.parse_authorization_header(environ.pop('HTTP_AUTHORIZATION', None))
    if auth and check_auth(auth.username, auth.password):
        environ['REMOTE_USER'] = auth.username
    ...

あとは、 check_auth() を真面目に実装するだけですね。

最後に

認証が必要かどうかを hgweb が判断してくれるので楽です。 Apache の mod_auth_basic を使うと、そうは行きませんね。

コメント

blog comments powered by Disqus