(機械翻訳)承認を追加する

オブジェクトを使っていくつかの明示的な 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.Everyonepyramid.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 を追加しました Pyramidauthentication 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__ を介して現在のリクエストに対して principalpermission の間のマッピングを定義します。

リソースとACLを追加する

リソースは app:Pyramid の隠された宝石です。あなたはそれを作った!

WebアプリケーションのすべてのURLは resource (Uniform Resource Locatorの「R」)を表します。多くの場合、リソースはデータ・モデル内のものですが、多くのモデルで抽象化することもできます。

私たちのwikiには2つのリソースがあります:

  1. `` NewPage``です。存在しない潜在的な `` Page``を表します。 `` basic``または `` editor``の役割を持つログインしているユーザは、ページを作成できます。
  2. `` 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'),
        ]

PageResourceview_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_pageedit_page ビューの context です。同様に NewPageadd_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:

ここで pagenamerequest.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 the view_page view of the FrontPage page object. It is executable by any user.
  • http://localhost:6543/FrontPageFrontPage ページオブジェクトの 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 リンクが表示されます。