認証管理

たいていのデータベースウェブアプリは何らかの認証システムを持っています。例えばGmailアカウントでemailを読んだり、Amazonブックストアでカスタマイズサービスを受けたりするために "login" するでしょう。それと同様にデータベースウェブアプリを書いている間にも、認証システムを作ることを求められる可能性が大いにあります。

TurboGears?はユーザ・パーミッションベースの認証サポートを提供しています。

認証の管理はコントローラとテンプレートの両方から利用可能です:

  • controllers の中ではアクセス制限を指定する
  • templates ユーザの認証によって見た目を変化させる

このチュートリアルでは、新しくquickstartから生成したプロジェクトで認証サポートをどのように可能にし、使うのかを示します。ORマッパーはSQLObjectを、またデータベースはSQLiteを使いますが、TurboGearsがサポートする他のORマッパーやデータベースを使う場合でも基本的には同じです。

さぁ、はじめよう

プロジェクトを作る

最初に "identity_tutorial" という新規のプロジェクトを作りましょう:

$ tg-admin quickstart --identity identity_tutorial

デフォルトのパッケージ名は "identity_tutorial" とします。TurboGearsは認証のためのデータオブジェクトとコントローラを含んだプロジェクトのスケルトンを生成するでしょう。

ここでデータベースを初期化します:

$ tg-admin sql create

こうすると "devdata.sqlite" という空のデータベースができあがります。これは認証管理に必要な空のテーブルを含んでいます。

ログインページのテスト

ここですべてうまくセットアップされているかどうかをチェックしてみましょう。普段と同じようにプロジェクトを起動します:

$ python start-identity_tutorial.py

ログインページである http://localhost:8080/login を見てみてください。そうするとユーザ名とパスワードの各フィールドがあるちょっとしたログインページが見えるはずです。

何かをフィールドに入力してログインを試してみることができますが、まだユーザを追加していないのでログインに失敗するでしょう。次のステップに進んでユーザとグループを追加しましょう。

ユーザとグループの追加

データベースのサポートとしてSQLObjectを選んだのであれば、ユーザを追加するためにTurboGearsのシェルやCatWalkを利用することができます。他のORマッパー(すなわちSQLAlchemy)を利用している場合はTurboGearsのシェルを使ってください。

CatWalkの利用

TurboGears?のツールボックスを起動します:

$ tg-admin toolbox

CatWalkに入りましょう(ブラウザが自動的に立ち上がるはずです; 自動的に立ち上がらない場合は http://localhost:7654/catwalk/ に行ってください)。

ページの左側に認証関連のクラスがリストアップされているでしょう。

ユーザの作成

ページの左側から "User" を選ぶとユーザのリストが表示されます。さらにページ中央の "Add User+" ボタンをクリックすると、編集インターフェイスが表示されます。そのフィールドにユーザの情報を入力します。例えば:

email_address: jdoe@example.com
display_name: Jane Doe
user_name: jdoe
password: xxx

"Save" をクリックします。これでユーザが作成されました。

adminグループの作成

今度はページの左側から "Group" を選びます。ページ中央の "Add Group+" ボタンをクリックし、 display_namegroup_name を入力します。例えば:

display_name: Administrators
group_name: admin

"Save" をクリックします。これでユーザとグループの両方ができました。次はこれらを連結しましょう。

新規ユーザをadminグループに追加する

現在新規グループが表示された状態で "Browse" タブにいることに注意してください。

"users" の前にある三角形をクリックすると、 "Manage Relations" リンクが現れますのでこれをクリックしましょう。

CatWalkは2つのリストを表示するでしょう。左側のリストにはこのグループに割り当てられたユーザが表示され、右側にはすべてのユーザが見えます。ここで右側にある新規ユーザを選んで "Add Selected" をクリックすると左側のリストに移動します。そして変更を確認するために "Save" をクリックします。

シェルを使う

ユーザとグループを作成するのにCatWalkよりもシェルを使いたい場合、あるいはSQLAlchemyを使っている場合は、プロジェクトのトップレベルディレクトリから次のようにします:

$ tg-admin shell

TurboGears?のシェル環境では( "from model import *" をするように)常にモデルがインポートされるということを思い出してください。

まずは最初のユーザを作成します:

>>> u = User(user_name='jdoe', email_address='jdoe@example.com', display_name='Jane Doe', password='xxx')

実行すると、シェルがidentityモデルを読み込んだメッセージや、プレーンテキストのパスワードを使っていることに関する警告メッセージが見えますが、今はこのままで結構です。プロダクト環境ではパスワードの暗号化スキーマを指定したいでしょう(これについては後述します)。

同じようにグループを生成します:

>>> g = Group(group_name='admin', display_name='Administrators')

グループにユーザを追加します:

>>> g.addUser(u)

このユーザが本当にグループの一員となったのかを確認することができます:

>>> g.users
[<User 1 user_name=u'jdoe' email_address=u'jdoe@example.com' display_name=u'Jane Doe' password=u'xxx' created='datetime.datetime...)'>]

>>> u.groups
[<Group 1 group_name=u'admin' display_name=u'Administrators' created='datetime.datetime...)'>]

ここでシェルを抜けましょう(WindowsではCtrl-Z、他のシステムではCtrl-Dとします)。データベースの変更をコミットするかどうかを尋ねられますので、 "yes" を選びます。

コントローラでidentityを使う

コントローラのメソッドを保護したいときには、そのメソッドに @identity.require(...) デコレータを追加します。このデコレータは、チェックする条件を指定するための一つの引数をとります。

identityモジュールはたくさんのメソッドを提供します。例えばindexページをadminグループに所属するメンバーだけがアクセスできるようにするためには identity.in_group("admin") を使います:

@turbogears.expose(template=".templates.welcome")
@identity.require(identity.in_group("admin"))
def index(self):
            ...

さぁこれを試してみましょう。 http://localhost:8080/ にいくと、indexページは保護されているのでログインページにリダイレクトされるでしょう。先ほど作成したアカウントの名前とパスワードでログインします。すると "Welcome, Jane Doe." というメッセージと "Logout" のリンクがあるindexページが見えるでしょう。このメッセージやリンクはmaster.kidテンプレートで生成されています。

identityのメソッド

一般によく使われるidentityのメソッドとしては以下のようなものがあります:

シングルパーミッションチェック

そのユーザがログインしているかをチェックする:

@identity.require(identity.not_anonymous())

アクセスグループをチェックする:

@identity.require(identity.in_group("admin"))

複数グループを指定してチェックする:

@identity.require(identity.in_all_groups("admin", "editor"))

@identity.require(identity.in_any_group("admin", "editor"))

アクセスパーミッションをチェックする:

@identity.require(identity.has_permission("edit"))

@identity.require(identity.has_all_permissions("edit", "delete", "update"))

@identity.require(identity.has_any_permission("edit", "delete", "update"))

ホストをチェックする:

@identity.require(identity.from_host("127.0.0.1"))

@identity.require(identity.from_any_host(("127.0.0.1", "10.0.0.1")))

メソッドの連結

identity.Anyidentity.All を使うと、メソッドを連結できます:

@identity.require(identity.Any(identity.in_group("admin"), identity.has_permission("edit")))

このデコレータは "edit" パーミッションを持っているすべてのユーザと同様に "admin" グループのメンバーにもアクセス権限を与えます:

@identity.require(identity.All(identity.from_host("127.0.0.1"), identity.has_permission("edit")))

@identity.require(identity.All(identity.from_any_host(("127.0.0.1", "10.0.0.1")), identity.in_group("editor")))

テンプレートでidentityチェックを使う

ユーザのidentityによってページの見た目をカスタマイズするために、Kidテンプレートの中でidentityチェックをすることも可能です。例えば、管理者機能を使うためのページへのリンクは管理者ユーザのみに見えるようにしたいでしょう。(普通はこれらのリンクを扱っているコントローラの中でもidentityのチェックをすることが必要でしょう。なぜならそれらを隠すだけではURIを直接入力してアクセスしてきたユーザは防ぐことができないからです。)

テンプレートの中では、 tg.identityturbogears.identity.current のエイリアスです。

アクセスグループをチェックする:

<a py:if="'admin' in tg.identity.groups" href="/admin">This is a link for admins</a>

アクセスパーミッションをチェックする:

<div py:if="'write' in tg.identity.permissions">This is a write permissions area</div>

ユーザ特有の情報を表示する:

<div py:if="tg.identity.anonymous">Welcome, guest!</div>
<div py:if="not tg.identity.anonymous">Welcome, ${tg.identity.user.display_name}!</div>

付加的なidentityのレシピ

時には個々のメソッド(ページ)へのアクセスを制限するだけでは不十分なときがあります。例えば、コントローラ全体を保護したり、データに依存した形でアクセスパーミッションを保護したりしたいかもしれません。

ディレクトリの保護

コントローラ全体(サブディレクトリ)のアクセスを制限したいときには、 identity.SecureResource? から Controller を得て、クラスレベルの属性を追加します:

class MySecureController(turbogears.Controller, identity.SecureResource):
    require = identity.in_group("admin")

    # etc. ...

MySecureController? インスタンスのメソッドにはどのようなデコレータも適用できます。ですのでそれぞれのメソッドにさらに追加的な制限を加えることも可能です。また MySecureController?SecureObjects? を持つこともできます。このとき MySecureController? のexposedメソッドや SecureObjects? へのアクセスは MySecureController? の権限を満たしている必要があります。

明示的なパーミッションチェック

例えば今あなたは、ブログツールや写真共有など、ユーザが自分自身のコンテンツを追加できるようなウェブサイトを作っているとしましょう。ユーザは自分自身のコンテンツは編集できますが、他人の追加したコンテンツは編集できないようにします。デコレータを使ったチェックではこういったことはできません。なぜならメソッドだけが読み込む実際のデータにアクセスする必要があるからです(そのデータを2回読み込むことはしたくないでしょう)。そこで、デコレータを使う代わりに、メソッドでidentityチェックを行うことにしましょう。

再び identity.SecureResource? からコントローラを得ます。そしてメソッドレベルでidentityチェックを行います。ユーザが要求されたパーミッションを持たないときは、 IdentityException? を投げます:

class MyController(controllers.Controller, identity.SecureResource):

     @turbogears.expose(html="mytemplate")
     def myFunction(self):
         if not ("admin" in identity.current.groups or
            "super" in identity.current.groups):
            raise identity.GroupMembershipRequiredException(("admin", "super"))

この例では SecureResource? がパーミッションをチェックするコードとともにすべてのexposedメソッドをラップし、 IdentityExceptions? を仕掛けます。よって IdentityException? が起こったときにもすべてはうまく処理されるでしょう。

もちろんそれぞれの関数にこれをコピー&ペーストするよりも、関数の中に自分なりの認証ロジックを埋め込むこともできます。

自分のコードでidentityのメソッドを使うことも可能です:

if identity.in_group("admin") and identity.has_permission("edit"):
    pass
else:
    pass

自分のデコレータ関数を書く

みなさん全員におすすめできることではないですが、とても柔軟性のある結果が得られます。

ちょっと turbogears/identity/conditions.py にあるデコレータを見てみてください。きっとあなたが自分のデコレータ関数を書くために何をしなければならないか、そのきっかけが見つかるでしょう。

また簡単にするために、turbogears.decoratorをベースにすることも考えて見てください。turbogears.decoratorは骨格となるものを与えてくれるでしょうし、実際に他のデフォルトのデコレータでも使われています。

identityモデルをカスタマイズする

ユーザクラスにいくつかの属性(ユーザの画像や電話番号など)を追加することによって、あるいは完全に置換してしまってもよいのですが、ユーザ、グループ、パーミッションなどに対して自分のクラスをカスタマイズすることもできます。

しかしidentityは、自動生成されたクラスやそれらの属性について、名前によって関連づけれられています。ですのでそれを削除したりリネームしたりすることはすべきでありません。

外部ソースによる認証

パスワード以外のものはidentityモデルを使い、パスワードは外部ソースにあるもので認証したいという場合があるでしょう。

例えば既存のWindowsやSambaのドメインコントローラで認証すれば、ユーザはWindowsにログインするのと同じパスワードをTurboGearsプロジェクトでも使うことができます。(同じ事はLDAPなどについても言えるでしょう。)

これをどのように実現するのかということの例は、 sosmbprovider.py を参照してください。これはSQLObjectプロバイダのサブクラスですが、Windowsドメインからユーザとパスワードを確認します。これは純粋なSMBプロバイダではありません(ユーザとパスワードだけをドメインコントローラでチェックする)ので、やはりidentityテーブルにユーザやグループなどを追加する必要があります。

LDAPディレクトリでユーザとパスワードをチェックする方法についての例は、 soldapprovider.py を参照してください。

TurboGears? 1.0では soprovider.py において SqlObjectIdentityProvider? オブジェクトに validate_password() メソッドが加えられました。これによってサブクラスが単純になり、自分のプロバイダを生成することが簡単になりました。