[[ともっくす alloc] init]

ともっくすの雑多な日記と技術的なメモ

Pyramidのチュートリアルをやってみる④ 〜 wikiアプリケーションの設計(認証の追加)

Pyramidのチュートリアルをやってみる③ 〜 wikiアプリケーションの設計(ビューの定義) - [[ともっくす alloc] init]の続き.


今回は,前回までに作ったwikiアプリケーションに認証を追加するところまで.

今のままだと,誰でも記事を編集できたりするからね.


で,チュートリアルも今回で終わりかと思う.

やるとしても,しばらく先かなと.

アクセス制御

ユーザとグループの追加

tutorialディレクトリ内(views.pyとかと同じ階層)にsecurity.pyを作成し,以下を記述する.

USERS = {'editor': 'editor', 'viewer': 'viewer'}
GROUPS = {'editor':['group:editors']}

def groupfinder(userid, request):
    if userid in USERS:
        return GROUPS.get(userid, [])

groupfinderは,指定されたuseridが登録されているかを確認し,その権限を返す.

例えば,useridがeditorのユーザは,group:editorという権限を持つ.


本来は,これらのデータはDB等で管理されるが,今回はチュートリアルなので,こんな感じの管理方法になっている.

ACLの追加

models.pyに以下のfrom-import文を追加.

from pyramid.security import (
    Allow,
    Everyone,
    )

そして,以下のクラスも追加.

class RootFactory(object):
    __acl__ = [(Allow, Everyone, 'view'), (Allow, 'group:editors', 'edit')]
    def __init__(self, request):
        pass

そしてそして,__init__.pyのConfiguratorコンストラクタにroot_factoryパラメータを追加する.

config = Configurator(settings=settings, root_factory='tutorial.models.RootFactory')


ACLはAccess Control Listの略.
それを__acl__で定義している.

簡単に言うと,全てのユーザ(Everyone)に'view'の権限を与え,'group:editor'であるユーザに'edit'の権限を与えるというもの.

詳しくは > Assigning ACLs to your Resource Objects

認証と認可のポリシーを追加

__init__.pyに以下のfrom-import文を追加.

from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from tutorial.security import groupfinder

そして,Configuratorコンストラクタの前後に以下を追加する.

authn_policy = AuthTktAuthenticationPolicy('sosecret', callback=groupfinder, hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
config = Configurator(settings=settings, root_factory='tutorial.models.RootFactory')
config.set_authentication_policy(authn_policy)
config.set_authorization_policy(authz_policy)

パーミッション宣言を追加

@view_configデコレータにパーミッションを追加する.

add_page()とedit_page()のパーミッションはedit,つまり,edit権限のあるユーザのみ

@view_config(route_name='add_page', renderer='templates/edit.pt', permission='edit')
@view_config(route_name='edit_page', renderer='templates/edit.pt', permission='edit')

view_wiki()とview_page()のパーミッションはview,つまり,view権限のあるユーザのみ(全ユーザ)

@view_config(route_name='view_wiki', permission='view')
@view_config(route_name='view_page', renderer='templates/view.pt', permission='view')

ログインとログアウト

routeの追加

__init__.pyに以下のrouteを追加する.

config.add_route('login', '/login')
config.add_route('logout', '/logout')

ビューの追加

views.pyのfrom-import文で,以下のように編集したり追加したり.

from pyramid.view import (
    view_config,
    forbidden_view_config,
    )

from pyramid.security import (
    remember,
    forget,
    )

from .security import USERS

で,以下を追加.

@view_config(route_name='login', renderer='templates/login.pt')
@forbidden_view_config(renderer='templates/login.pt')
def login(request):
    login_url = request.route_url('login')
    referrer = request.url
    if referrer == login_url:
        referrer = '/'
    came_from = request.params.get('came_from', referrer)
    message = ''
    login = ''
    password = ''
    if 'form.submitted' in request.params:
        login = request.params['login']
        password = request.params['password']
        if USERS.get(login) == password:
            headers = remember(request, login)
            return HTTPFound(location = came_from, headers = headers)
        message = 'Failed login'

    return dict(
        message = message,
        url = request.application_url + '/login',
        came_from = came_from,
        login = login,
        password = password,
        )

@view_config(route_name='logout')
def logout(request):
    headers = forget(request)
    return HTTPFound(location = request.route_url('view_wiki'), headers = headers)


ここによって,

@forbidden_view_config(renderer='templates/login.pt')

ユーザがログインせずにページの追加や編集をしようとすると,ログインページに飛ばされる.

テンプレートの追加

templatesディレクトリにlogin.ptを作成し,内容を以下のようにする.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal">
<head>
  <title>Login - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
  <meta name="keywords" content="python web application" />
  <meta name="description" content="pyramid web application" />
  <link rel="shortcut icon" href="${request.static_url('tutorial:static/favicon.ico')}" />
  <link rel="stylesheet" href="${request.static_url('tutorial:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" />
  <!--[if lte IE 6]>
  <link rel="stylesheet" href="${request.static_url('tutorial:static/ie6.css')}" type="text/css" media="screen" charset="utf-8" />
  <![endif]-->
</head>
<body>
  <div id="wrap">
    <div id="top-small">
      <div class="top-small align-center">
        <div>
          <img width="220" height="50" alt="pyramid" src="${request.static_url('tutorial:static/pyramid-small.png')}" />
        </div>
      </div>
    </div>
    <div id="middle">
      <div class="middle align-right">
        <div id="left" class="app-welcome align-left">
          <b>Login</b><br/>
          <span tal:replace="message"/>
        </div>
        <div id="right" class="app-welcome align-right"></div>
      </div>
    </div>
    <div id="bottom">
      <div class="bottom">
        <form action="${url}" method="post">
          <input type="hidden" name="came_from" value="${came_from}"/>
          <input type="text" name="login" value="${login}"/><br/>
          <input type="password" name="password" value="${password}"/><br/>
          <input type="submit" name="form.submitted" value="Log In"/>
        </form>
      </div>
    </div>
  </div>
  <div id="footer">
    <div class="footer">&copy; Copyright 2008-2011, Agendaless Consulting.</div>
  </div>
</body>
</html>

logged_inフラグ

views.pyのfrom-import文を以下のように変更.

from pyramid.security import (
    remember,
    forget,
    authenticated_userid,
    )

view_page(),edit_page(),add_page()のreturn文のdict(〜)に,以下を追加する.

return dict(〜〜〜, logged_in = authenticated_userid(request))

Logoutリンクの追加

edit.ptとview.ptを以下のように変更する.divタグの内側に追加.

<div id="right" class="app-welcome align-right">
  <span tal:condition="logged_in">
    <a href="${request.application_url}/logout">Logout</a>
  </span>
</div>

アプリケーションの起動

これで,認証機能を追加できたので,アプリケーションを起動してみる.

$ pserve development.ini --reload
Starting subprocess with file monitor
Starting server in PID 4044.
serving on http://0.0.0.0:6543

で,起動して,http://localhost:6543/FrontPageにアクセス.
(別にルートにアクセスでいいんだけど)

「Edit this page」をクリックしてみると,
URL自体はhttp://localhost:6543/FrontPage/edit_pageなのに,loginページに飛ばされる.
f:id:o_tomox:20130722170930p:plain

で,ユーザ名をeditor,パスワードをeditorと入力する.
f:id:o_tomox:20130722170943p:plain

loginボタンをクリックすると,しっかりと,FrontPageの編集ページにアクセスできる.
f:id:o_tomox:20130722171001p:plain

ここで,デバッグツールバーのHTTP Headersを見てみると,Cookieにauthトークンが追加されていることがわかる.
f:id:o_tomox:20130722171014p:plain

で,ログインしたので,FrontPageに戻ると,しっかりと右上にlogoutリンクができている.
f:id:o_tomox:20130722171026p:plain


簡単に,認証機能付きの簡易Wikipediaアプリケーションができた!



今回はこんな感じで終わり.

チュートリアルには,テストの追加とアプリケーションの配布という項目があるが,これらをやるかは未定.

次 > 未定
前 > Pyramidのチュートリアルをやってみる③ 〜 wikiアプリケーションの設計(ビューの定義) - [[ともっくす alloc] init]