(機械翻訳)承認を追加する¶
オブジェクトを使っていくつかの明示的な authorization チェックを実行しました。これは多くのアプリケーションでうまくいきますが、app:Pyramid は、これをクリーンアップし、ビュー関数自体から制約を切り離すためのいくつかの機能を提供します。 authorization
次の手順でアクセス制御を実装します。
- authentication policy を更新して userid を以下のリストに分解してください principals ( security.py`)です。
- ユーザ、リソース、権限( `` security.py``)をマッピングするための:authentication policy を定義してください。
- Wikiページ( `` routes.py``)の context として使用されるnew resource 定義を追加します。
- Add an ACL to each resource (
routes.py
). - ビューのインラインチェックを permission 宣言(` views/default.py`)に置き換えてください。
ユーザープリンシパルを追加する¶
A principal は、ユーザの能力、役割、または一般化が容易な他の識別子に関してユーザを記述する raw userid の上に抽象化のレベルです。関連する正確なユーザーに焦点を当てることなく、プリンシパルに対してアクセス許可が書き込まれます。
Pyramid pyramid.security.Everyone
と pyramid.Authenticated
`` 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 28 29 30 31 32 33 34 35 36 37 38 39 40 | from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from pyramid.security import (
Authenticated,
Everyone,
)
from .models import User
class MyAuthenticationPolicy(AuthTktAuthenticationPolicy):
def authenticated_userid(self, request):
user = request.user
if user is not None:
return user.id
def effective_principals(self, request):
principals = [Everyone]
user = request.user
if user is not None:
principals.append(Authenticated)
principals.append(str(user.id))
principals.append('role:' + user.role)
return principals
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)
|
強調表示された行だけを追加する必要があります。
役割は `` User`` オブジェクトから来ていることに注意してください。その正確なユーザが自分が作成したページを編集できるようにするために、 `` user.id`` をプリンシパルとして追加します。
承認ポリシーを追加する¶
前の章で authorization policy を追加しました Pyramid は authentication policy を追加するときに必要です。しかし、それはどこにも使われていなかったので、ここで言及します。
ファイル `` tutorial/security.py``には、次の行があります。
38 39 40 | config.set_authentication_policy(authn_policy)
config.set_authorization_policy(ACLAuthorizationPolicy())
config.add_request_method(get_user, 'user', reify=True)
|
私たちは pyramid.authorization.ACLAuthorizationPolicy
を使っています。これは、ほとんどのアプリケーションで十分です。 context を使用して、__acl__
を介して現在のリクエストに対して principal と permission の間のマッピングを定義します。
リソースとACLを追加する¶
リソースは app:Pyramid の隠された宝石です。あなたはそれを作った!
WebアプリケーションのすべてのURLは resource (Uniform Resource Locatorの「R」)を表します。多くの場合、リソースはデータ・モデル内のものですが、多くのモデルで抽象化することもできます。
私たちのwikiには2つのリソースがあります:
- `` NewPage``です。存在しない潜在的な `` Page``を表します。 `` basic``または `` editor``の役割を持つログインしているユーザは、ページを作成できます。
- `` PageResource``です。表示または編集される「ページ」を表します。 `` Page``の編集者は、 `` PageResource``を編集することができます。誰でもそれを見ることができます。
注釈
wikiデータモデルは、単純に `` PageResource``が `` models.Page``のSQLAlchemyクラスと重複しているほど簡単です。これらを1つのクラスにまとめることは完全に有効です。しかし、このチュートリアルでは、app:Pyramid がアプリケーション定義のオブジェクトと何を区別しているのかを明確に区別するために明示的に区切られています。
これらのリソースを定義するには多くの方法があり、階層構造のコレクションにグループ化することもできます。しかし、ここでは簡単にしています!
tutorial/routes.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 47 48 49 50 51 52 53 54 55 56 | from pyramid.httpexceptions import (
HTTPNotFound,
HTTPFound,
)
from pyramid.security import (
Allow,
Everyone,
)
from .models import Page
def includeme(config):
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('view_wiki', '/')
config.add_route('login', '/login')
config.add_route('logout', '/logout')
config.add_route('view_page', '/{pagename}', factory=page_factory)
config.add_route('add_page', '/add_page/{pagename}',
factory=new_page_factory)
config.add_route('edit_page', '/{pagename}/edit_page',
factory=page_factory)
def new_page_factory(request):
pagename = request.matchdict['pagename']
if request.dbsession.query(Page).filter_by(name=pagename).count() > 0:
next_url = request.route_url('edit_page', pagename=pagename)
raise HTTPFound(location=next_url)
return NewPage(pagename)
class NewPage(object):
def __init__(self, pagename):
self.pagename = pagename
def __acl__(self):
return [
(Allow, 'role:editor', 'create'),
(Allow, 'role:basic', 'create'),
]
def page_factory(request):
pagename = request.matchdict['pagename']
page = request.dbsession.query(Page).filter_by(name=pagename).first()
if page is None:
raise HTTPNotFound
return PageResource(page)
class PageResource(object):
def __init__(self, page):
self.page = page
def __acl__(self):
return [
(Allow, Everyone, 'view'),
(Allow, 'role:editor', 'edit'),
(Allow, str(self.page.creator_id), 'edit'),
]
|
強調表示された行を編集または追加する必要があります。
NewPage
クラスには、 principal から permission までのマッピングのリストを返す __acl__ があります。これは who が何をできるかを定義します resource 。私たちの場合、 role:editor または role:basic のいずれかのプリンシパルを持つユーザのみが create パーミッションを持つことを許可します:
30 31 32 33 34 35 36 37 38 | class NewPage(object):
def __init__(self, pagename):
self.pagename = pagename
def __acl__(self):
return [
(Allow, 'role:editor', 'create'),
(Allow, 'role:basic', 'create'),
]
|
`` NewPage`` は add_page
ルートの context`としてロードされ、ルート上に ``factory` が宣言されます:
18 19 | config.add_route('add_page', '/add_page/{pagename}',
factory=new_page_factory)
|
`` PageResource``クラスは `` Page``のための ACL を定義します。実際の `` Page``オブジェクトを使って*誰がページに何を*できるかを決定します。
47 48 49 50 51 52 53 54 55 56 | class PageResource(object):
def __init__(self, page):
self.page = page
def __acl__(self):
return [
(Allow, Everyone, 'view'),
(Allow, 'role:editor', 'edit'),
(Allow, str(self.page.creator_id), 'edit'),
]
|
PageResource
は view_page
と ` edit_page`` ルートの context としてロードされ、ルート上に factory
が宣言されます:
17 18 19 20 21 | config.add_route('view_page', '/{pagename}', factory=page_factory)
config.add_route('add_page', '/add_page/{pagename}',
factory=new_page_factory)
config.add_route('edit_page', '/{pagename}/edit_page',
factory=page_factory)
|
表示権限を追加する¶
現時点では、実際の Page
モデルを page_factory
に含めて、 PageResource
を読み込むようにアプリケーションを修正しました。 `` PageResource`` はすべての view_page
と edit_page
ビューの context です。同様に NewPage
は add_page
ビューのコンテキストになります。
`` tutorial/views/default.py``ファイルを開きます。
まず、不要になったインポートをいくつか削除できます。
5 6 7 | from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_config
|
view_page
ビューを編集して view
パーミッションを宣言し、ビュー内の明示的なチェックを削除してください:
18 19 20 21 22 23 | @view_config(route_name='view_page', renderer='../templates/view.jinja2',
permission='view')
def view_page(request):
page = request.context.page
def add_link(match):
|
ページを読み込む作業はすでに工場で行われているので、 pageResource
から page.
オブジェクトを取り出し、 request.context
として読み込みます。私たちの工場では、 Page
が存在しない場合には HTTPNotFound
例外が発生し、ビューロジックをやはり単純化するので、 `` Page``を保証します。
edit_page
ビューを編集して edit
パーミッションを宣言してください:
38 39 40 41 42 | @view_config(route_name='edit_page', renderer='../templates/edit.jinja2',
permission='edit')
def edit_page(request):
page = request.context.page
if 'form.submitted' in request.params:
|
add_page
ビューを編集して create
パーミッションを宣言してください:
52 53 54 55 56 | @view_config(route_name='add_page', renderer='../templates/edit.jinja2',
permission='create')
def add_page(request):
pagename = request.context.pagename
if 'form.submitted' in request.params:
|
ここで pagename
は request.matchdict
ではなくコンテキストから取り除かれていることに注意してください。工場では、実際のルートパターンを隠すために多くの作業を行っています。
各 resource で定義されたACLは authorization policy によって使用され principal が何らかの permission を許可されているかどうかを判断します。。このチェックが失敗した場合(たとえば、ユーザがログインしていない場合)、 HTTPForbidden
例外が自動的に呼び出されます。したがって、私たちはビューからそれらの例外やチェックを削除することができます。むしろ、リソースに対する操作の観点からそれらを定義しました。
最後の tutorial/views/default.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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | from pyramid.compat import escape
import re
from docutils.core import publish_parts
from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_config
from ..models import Page
# regular expression used to find WikiWords
wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
@view_config(route_name='view_wiki')
def view_wiki(request):
next_url = request.route_url('view_page', pagename='FrontPage')
return HTTPFound(location=next_url)
@view_config(route_name='view_page', renderer='../templates/view.jinja2',
permission='view')
def view_page(request):
page = request.context.page
def add_link(match):
word = match.group(1)
exists = request.dbsession.query(Page).filter_by(name=word).all()
if exists:
view_url = request.route_url('view_page', pagename=word)
return '<a href="%s">%s</a>' % (view_url, escape(word))
else:
add_url = request.route_url('add_page', pagename=word)
return '<a href="%s">%s</a>' % (add_url, escape(word))
content = publish_parts(page.data, writer_name='html')['html_body']
content = wikiwords.sub(add_link, content)
edit_url = request.route_url('edit_page', pagename=page.name)
return dict(page=page, content=content, edit_url=edit_url)
@view_config(route_name='edit_page', renderer='../templates/edit.jinja2',
permission='edit')
def edit_page(request):
page = request.context.page
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),
)
@view_config(route_name='add_page', renderer='../templates/edit.jinja2',
permission='create')
def add_page(request):
pagename = request.context.pagename
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)
return HTTPFound(location=next_url)
save_url = request.route_url('add_page', pagename=pagename)
return dict(pagename=pagename, pagedata='', save_url=save_url)
|
ブラウザでのアプリケーションの表示¶
ブラウザでアプリケーションを調べることができます( アプリケーションを起動する(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 リンクが表示されます。