(機械翻訳)認証の追加¶
Pyramid は authentication と authorization のための機能を提供します。このセクションでは、ログインAPIとログアウト機能をWikiに追加するための認証APIのみに焦点を当てます。
次の手順で認証を実装します。
- 認証ポリシー`と ` request.user``の計算されたプロパティー( `` security.py``)を追加してください。
- `` /login``と `` /logout``( `` routes.py``)のルートを追加します。
- ログインビューとログアウトビューを追加します( `` views /auth.py``)。
- ログインテンプレートを追加します( `` login.jinja2``)。
- ユーザーの認証済み状態( `` layout.jinja2``)に基づいて、すべてのページに"ログイン"と"ログアウト"リンクを追加します。
- 既存のビューがユーザの状態を確認するようにします( `` views /default.py``)。
- デフォルトの "403 Forbidden"ページ( `` views /auth.py``)ではなく、アクセス権が必要なビューへのアクセスがユーザから拒否された場合、 `` /login``に??リダイレクトします。
リクエストの認証¶
The core of Pyramid authentication is an authentication policy
which is used to identify authentication information from a request
,
as well as handling the low-level login and logout operations required to
track users across requests (via cookies, headers, or whatever else you can
imagine).
認証ポリシーを追加する¶
以下の内容の `` tutorial /security.py``ファイルを新規作成してください:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from .models import User
class MyAuthenticationPolicy(AuthTktAuthenticationPolicy):
def authenticated_userid(self, request):
user = request.user
if user is not None:
return user.id
def get_user(request):
user_id = request.unauthenticated_userid
if user_id is not None:
user = request.dbsession.query(User).get(user_id)
return user
def includeme(config):
settings = config.get_settings()
authn_policy = MyAuthenticationPolicy(
settings['auth.secret'],
hashalg='sha512',
)
config.set_authentication_policy(authn_policy)
config.set_authorization_policy(ACLAuthorizationPolicy())
config.add_request_method(get_user, 'user', reify=True)
|
ここでは、
- Pyramidの
pyramid.authentication.AuthTktAuthenticationPolicy
からサブクラス化された` MyAuthenticationPolicy``という名前の新しい認証ポリシーは、署名されたクッキーを使って :term:`userid を追跡します(7-11行目)。 userid - `` unauthenticated_userid``をポリシーから `` User``オブジェクトに変換する `` get_user``関数です(13-17行目)。
- `` get_user``は、ログインしたユーザのために認証された `` User``オブジェクトとしてアプリケーション全体で使用される `` request.user``としてリクエストに登録されます(27行目)。
このファイルのロジックは少し面白いので、ここで何が起こっているのか詳しく説明します:
最初に、デフォルトの認証ポリシーはすべて、 `` unauthenticated_userid``という名前のメソッドを提供します。このメソッドは、リクエスト内の情報の低レベル解析(クッキー、ヘッダーなど)を担当します。 `` userid``が見つかった場合、このメソッドから返されます。最も低いレベルでは、それはクッキー内のユーザIDの値を知っているので、実際には私たちのシステム内のユーザであるかどうかはわからないので、これは `` unauthenticated_userid``という名前です(覚えておいて、信頼できません)。
第二に、アプリケーションは `` authenticated_userid``と `` request.user``だけを気にする必要があります。これはユーザーがログインしていることを検証するアプリケーション固有のプロセスを経ています。
`` authenticated_userid``を提供するためには、検証のステップが必要です。これはどこでも発生する可能性があるので、キャッシュされた `` request.user``の計算されたプロパティの中で実行することにしました。これは、 `` request.user``をシステムの真理の源にする便利な機能です。私たちのデータベースの `` None``か `` User``オブジェクトです。これは、 `` get_user``関数が `` unauthenticated_userid``を使ってデータベースをチェックする理由です。
アプリを設定する¶
新しい `` tutorial /security.py``モジュールを追加したので、それをインクルードする必要があります。ファイル `` tutorial /__ init __。py``を開き、以下の行を編集してください:
1 2 3 4 5 6 7 8 9 10 11 12 13 | from pyramid.config import Configurator
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
config = Configurator(settings=settings)
config.include('pyramid_jinja2')
config.include('.models')
config.include('.routes')
config.include('.security')
config.scan()
return config.make_wsgi_app()
|
私たちの認証方針は新しい設定 `` auth.secret``を期待しています。ファイル `` development.ini``を開き、以下の強調表示された行を追加してください:
19 20 21 | retry.attempts = 3
auth.secret = seekrit
|
最後に、ベストプラクティスでは、生産のために別の秘密を使用するように指示するので、 `` production.ini``を開き、別の秘密を追加してください:
17 18 19 | retry.attempts = 3
auth.secret = real-seekrit
|
権限チェックを追加する¶
Pyramid は宣言的承認を完全にサポートしています。これについては次の章で説明します。しかし、足を濡らすことを望んでいる多くの人々は、自家認証のいくつかの基本的な形で認証に興味を持っています。以下に、wikiの単純なセキュリティ目標を達成する方法を示します。これで、ユーザーのログイン状態を追跡できるようになります。
私たちの目標を思い出してください:
- ログインしたユーザーが `` editor``と `` basic``のみ新しいページを作成できるようにしてください。
- `` editor``ユーザとページ作成者(おそらく `` basic``ユーザ)のみがページを編集できるようにしてください。
`` tutorial/views/default.py``ファイルを開き、以下のインポートを修正してください:
5 6 7 8 9 10 11 12 13 | from pyramid.httpexceptions import (
HTTPForbidden,
HTTPFound,
HTTPNotFound,
)
from pyramid.view import view_config
from ..models import Page
|
強調表示されている2つの行を変更します。
同じファイル内で、 `` edit_page``ビュー関数を編集してください:
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | @view_config(route_name='edit_page', renderer='../templates/edit.jinja2')
def edit_page(request):
pagename = request.matchdict['pagename']
page = request.dbsession.query(Page).filter_by(name=pagename).one()
user = request.user
if user is None or (user.role != 'editor' and page.creator != user):
raise HTTPForbidden
if 'form.submitted' in request.params:
page.data = request.params['body']
next_url = request.route_url('view_page', pagename=page.name)
return HTTPFound(location=next_url)
return dict(
pagename=page.name,
pagedata=page.data,
save_url=request.route_url('edit_page', pagename=page.name),
)
|
強調表示されている行だけを変更する必要があります。
ユーザーがログインしていないか、ユーザーがページの作成者*ではなく、エディタではない場合は、 `` HTTPForbidden ''を生成します。
同じファイルで、 `` add_page``ビュー関数を編集してください:
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | @view_config(route_name='add_page', renderer='../templates/edit.jinja2')
def add_page(request):
user = request.user
if user is None or user.role not in ('editor', 'basic'):
raise HTTPForbidden
pagename = request.matchdict['pagename']
if request.dbsession.query(Page).filter_by(name=pagename).count() > 0:
next_url = request.route_url('edit_page', pagename=pagename)
return HTTPFound(location=next_url)
if 'form.submitted' in request.params:
body = request.params['body']
page = Page(name=pagename, data=body)
page.creator = request.user
request.dbsession.add(page)
next_url = request.route_url('view_page', pagename=pagename)
|
強調表示されている行だけを変更する必要があります。
ユーザーがログインしていないか、 `` basic``または `` editor``の役割にない場合、 `` HTTPForbidden`` を生成し、ユーザーに「403 Forbidden」応答を返します。しかし、これを後でフックしてログインページにリダイレクトします。また、 `` request.user`` ができたので、作成者を editor
のユーザとしてハードコーディングする必要がなくなり、最終的にそのハックを取り除くことができます。
これらの単純なチェックは私たちの見解を保護するはずです。
ログイン、ログアウト¶
ログインユーザーを検出できるようになったので、実際にログインしてログアウトできるように、 `` /login``と `` /logout``ビューを追加する必要があります。
`` /login``と `` /logout``のルートを追加する¶
`` tutorial /routes.py``に戻り、次の2つのルートをハイライト表示します:
3 4 5 6 | config.add_route('view_wiki', '/')
config.add_route('login', '/login')
config.add_route('logout', '/logout')
config.add_route('view_page', '/{pagename}')
|
注釈
上記の行は、以下の `` view_page``ルート定義の前に追加する必要があります:
6 | config.add_route('view_page', '/{pagename}')
|
これは、 `` view_page``のルート定義がcatch-all replacement marker /{pagename}
((参照 ルートパターンの構文 )を使用していて、その前に登録されているどのルートでも捕らえられます。したがって、 `` login``と `` logout`` ビューは一致する(またはキャッチされます)機会を持つためには、 `` /{pagename} `` の上になければなりません。
ログイン、ログアウト、禁止されたビューを追加する¶
新しいファイル `` tutorial /views /auth.py``を作成し、次のコードを追加します:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | from pyramid.httpexceptions import HTTPFound
from pyramid.security import (
remember,
forget,
)
from pyramid.view import (
forbidden_view_config,
view_config,
)
from ..models import User
@view_config(route_name='login', renderer='../templates/login.jinja2')
def login(request):
next_url = request.params.get('next', request.referrer)
if not next_url:
next_url = request.route_url('view_wiki')
message = ''
login = ''
if 'form.submitted' in request.params:
login = request.params['login']
password = request.params['password']
user = request.dbsession.query(User).filter_by(name=login).first()
if user is not None and user.check_password(password):
headers = remember(request, user.id)
return HTTPFound(location=next_url, headers=headers)
message = 'Failed login'
return dict(
message=message,
url=request.route_url('login'),
next_url=next_url,
login=login,
)
@view_config(route_name='logout')
def logout(request):
headers = forget(request)
next_url = request.route_url('view_wiki')
return HTTPFound(location=next_url, headers=headers)
@forbidden_view_config()
def forbidden_view(request):
next_url = request.route_url('login', _query={'next': request.url})
return HTTPFound(location=next_url)
|
このコードは、アプリケーションに3つの新しいビューを追加します。
`` login`` ビューはログインフォームをレンダリングし、ログインフォームからの投稿を処理し、データベースの `` users`` テーブルに対して資格をチェックします。
チェックは、まずデータベース内の `` User`` レコードを見つけてから、 `` user.check_password`` メソッドを使ってハッシュされたパスワードを比較して行います。
クレデンシャルが有効な場合は、私たちの認証ポリシーを使用して
pyramid.security.remember()
を使用してユーザーのIDをレスポンスに格納します。最後に、ユーザーは、アクセスしようとしていたページ( `` next``)またはフォールバックとしてフロントページにリダイレクトされます。このパラメータは、以下に説明するように、ログインワークフローを終了するために、禁止されたビューで使用されます。
`` logout``ビューは
pyramid.security.forget`を使ってクレデンシャルをクリアし、フロントページにリダイレクトすることで `()
/logout`` へのリクエストを処理します。forbidden_view
はpyramid.httpexceptions.HTTPForbidden
デコレータを使って登録されています。これは special exception view で、pyramid.httpexceptions.HTTPForbidden
例外が発生したときに呼び出されます。このビューは、ユーザを
/login
にリダイレクトすることによって禁じられたエラーを処理します。便宜上、next=
クエリ文字列を現在のURL(アクセスを禁止しているURL)に設定します。このようにして、ユーザーが正常にログインすると、アクセスしようとしていたページに戻ります。
`` login.jinja2``テンプレートを追加してください¶
以下の内容の `` tutorial /templates /login.jinja2`` を作成してください:
{% extends 'layout.jinja2' %}
{% block title %}Login - {% endblock title %}
{% block content %}
<p>
<strong>
Login
</strong><br>
{{ message }}
</p>
<form action="{{ url }}" method="post">
<input type="hidden" name="next" value="{{ next_url }}">
<div class="form-group">
<label for="login">Username</label>
<input type="text" name="login" value="{{ login }}">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" name="password">
</div>
<div class="form-group">
<button type="submit" name="form.submitted" value="Log In" class="btn btn-default">Log In</button>
</div>
</form>
{% endblock content %}
上記のテンプレートは、 `` tutorial /views /auth.py`` で追加したログインビューで参照されています。
ログインとログアウトのリンクを追加¶
`` tutorial /templates /layout.jinja2``を開き、強調表示された行に示すように次のコードを追加してください。
35 36 37 38 39 40 41 42 43 44 45 46 | <div class="content">
{% if request.user is none %}
<p class="pull-right">
<a href="{{ request.route_url('login') }}">Login</a>
</p>
{% else %}
<p class="pull-right">
{{request.user.name}} <a href="{{request.route_url('logout')}}">Logout</a>
</p>
{% endif %}
{% block content %}{% endblock %}
</div>
|
`` request.user``は、ユーザが認証されていなければ `` None``、ユーザが認証されている場合は `` tutorial.models.User``オブジェクトになります。このチェックでは、ユーザーがログインしているときのみログアウトリンクが表示され、逆にユーザーがログアウトするとログインリンクが表示されます。
ブラウザでのアプリケーションの表示¶
ブラウザでアプリケーションを調べることができます( アプリケーションを起動する(Start the application) を参照してください)。ブラウザを起動し、次の各URLにアクセスして、結果が期待どおりであることを確認します。
- http://localhost:6543/ invokes the
view_wiki
view. This always redirects to theview_page
view of theFrontPage
page object. It is executable by any user. - http://localhost:6543/FrontPageは `` FrontPage``ページオブジェクトの `` view_page``ビューを呼び出します。ユーザーが認証されていないときは右上に"Login "リンクがあり、認証されていない場合は"Logout "リンクです。
- http://localhost:6543/FrontPage/edit_pageは、 `` FrontPage``ページオブジェクトの `` edit_page``ビューを呼び出します。 `` editor``ユーザだけが実行可能です。別のユーザー(または匿名ユーザー)がそれを呼び出すと、ログインフォームが表示されます。ユーザー名 `` editor``とパスワード `` editor``を入力すると、編集ページのフォームが表示されます。
- http://localhost:6543/add_page/SomePageNameはページの `` add_page``ビューを呼び出します。ページがすでに存在する場合は、ページオブジェクトの `` edit_page``ビューにユーザをリダイレクトします。これは、 `` editor``または `` basic``ユーザのいずれかによって実行可能です。別のユーザー(または匿名ユーザー)がそれを呼び出すと、ログインフォームが表示されます。ユーザ名 `` editor``とパスワード `` editor``、またはユーザ名 `` basic``とパスワード `` basic``のいずれかで証明書を入力すると、編集ページのフォームが表示されます。
- http://localhost:6543/SomePageName/edit_pageは、既存のページの `` edit_page``ビューを呼び出し、ページが存在しない場合はエラーを生成します。これは、前の手順でそのユーザがページを作成した場合、 `` basic``ユーザによって編集可能です。代わりに、 `` editor``ユーザがページを作成した場合、 `` basic``ユーザのログインページが表示されます。
- ログインした後(編集やページを追加し、 `` editor``の資格情報を使ってログインフォームを送信した結果)、右上にLogoutというリンクが表示されます。クリックするとログアウトされ、フロントページにリダイレクトされ、右上にLoginリンクが表示されます。