認証、認可ともに、Configuratorでの設定によって有効になる。
config.set_authetication_policy(AuthTktAuthenticationPolicy(secret="fieaoji3w-bb#"))
config.set_authorization_policy(ACLAuthorizationPolicy())
AuthTktは、クッキーに認証トークンを持たせる方法の認証ポリシーである。
Webアプリケーションサーバーを分散している場合でも、セッションレプリケーションなどを必要とせずに認証情報を扱える。
ACLAuthorizationPolicyはすでに説明したとおり、標準ではこれしか提供されていない。
例として、ありがちなWikiアプリケーションに以下のようなセキュリティモデルを設定してみよう。
- WikiPageは 誰でも 読むこと(view)ができる
- WikiPageは 認証済のユーザー だけ作成(create)できる
- WikiPageは 作成者 によってロック(lock) することができる
- WikiPageは 作成者 によってロック解除(unlock) することができる
- ロックされたWikiPageは 作成者 のみが編集(edit)できる
- ロックされていないWikiPageは 認証済のユーザー が編集(edit)できる
これらを __acl__ に定義したモデル、WikiPage:
class WikiPage(Persistent):
def __init__(self, pagename, data, owner):
self.__name__ = pagename
self.pagename = pagename
self.data = data
self.owner = onwer
self.locked = False
@property
def __acl__(self):
if self.locked:
return [(Allow, Everyone, "view"),
(Allow, self.owner, "unlock"),
(Allow, self.owner, "edit")]
else:
return [(Allow, Everyone, "view"),
(Allow, self.owner, "lock"),
(Allow, Authenticated, "edit")]
def lock(self):
self.locked = True
def unlock(self):
self.locked = False
class Wiki(PersistentMapping):
@property
def __acl__(self):
return [(Allow, Authenticated, "create")
]
上記のようにコンテキストによって、権限が与えられる。
では、これらの権限を要求するビューの定義をしていこう。
@view_config(context=WikiPage, permission="view", renderer="wikipage.pt")
def wikipage_view(context, request):
return dict(wikipage=context)
@view_config(context=WikiPage, name="lock", permission="lock", request_method="POST")
def wikipage(context, request):
context.lock()
return HTTPFound(location=request.resource_url(context))
@view_config(context=WikiPage, name="unlock", permission="unlock", request_method="POST")
def wikipage(context, request):
context.unlock()
return HTTPFound(location=request.resource_url(context))
@view_config(context=WikiPage, name="edit", permission="edit", renderer="wikipage_edit.pt")
def wikipage_edit(context, request):
form = Form(context)
if request.method == "POST":
if form.validate(request.POST):
context.data = request.POST['data']
return HTTPFound(location=request.resource_url(context)
return dict(wikipage=context, form=form)
@view_config(context=Wiki, name="create", permission="create", renderer="wikipage_create.pt")
def wikipage_create(context, request):
form = Form()
pagename = request.matchdict['subpath']
if request.method == "POST":
if form.validate(request.POST):
page = WikiPage(data=request.POST['data'], pagename=pagename, owner=authenticated_userid(request))
page.__parent__ = context
context[page.__name__] = page
return HTTPFound(location=request.resource_url(page)
return dict(wiki=context, form=form)
このように、ビュー内では権限などを気にすることなく本来の機能的な処理だけを記述できる。
ログイン中のユーザーは authenticated_userid で取得できる。
さて、ここまで話を避けていた感のあるログイン処理である。
認証というとログイン処理のことであろうと思われるかもしれないが、前述のとおりログイン処理はpyramidで提供していないため、アプリケーションで用意しなければならない。
まずは、ユーザーデータである。
import hashlib
class User(Persistent):
def __init__(self, username, password):
self.username = username
self.password = password
def set_password(self, password):
self._password = hashlib.sha1(password.encode('utf-8')).hexdigest()
password = property(fset=set_password)
def verify_password(self, password):
return self._password == hashlib.sha1(password.encode('utf-8')).hexdigest()
パスワードをハッシュ化して保存する処理があるため分かりにくいが、やれることは usernameに対するpasswordが正しいかどうかだけである。
このUserモデルを使ったlogin APIを作る:
def get_user(request, username):
conn = get_connection(request):
root = conn.root()
users = root['users']
return users.get(username)
def login(request, username, password):
user = get_user(request)
if user is None:
return None
if not user.verify_password(password):
return None
return user
ログインフォームではこのAPIを利用する:
def login_form(request):
if request.POST:
user = login(request, request.POST.get('username'), request.POST.get('password'))
if user is not None:
headers = remember(request, user.username)
return HTTPFound(request.resource_url(request.root), headers=headers)
return dict()
rememberは、クッキーやAuthorizationヘッダなど、レスポンスに必要な情報を返してくる。
(認証の種類によっては、ヘッダ情報を使わずにセッションなどに追加する場合もあるが、その場合は空のリストが返ってくる。)
ログインに成功した場合はm、rememberの結果をヘッダに追加したレスポンスを返すようにする。
ログアウトは単純にforgetを呼び、戻り値をレスポンスヘッダに追加する:
def logout(request):
headers = forget(request)
return HTTPFound(request.resource_url(request.root), headers=headers)