21: 承認によるリソースの保護(21: Protecting Resources With Authorization)

操作行うための必要な権限(パーミッション))を説明するリソースにセキュリティーステートメントを割り当てます。

背景(Background)

アプリケーションにはWebブラウザ経由でコンテンツを追加/編集/削除できるURLがあります。アプリケーションにセキュリティを追加する時です。追加/編集ビューを保護するためにログイン(usernameに editor passwordに editor )を要求するようにしましょう。他のビューはパスワードなしで引き続き使用できます。

目的(Objectives)

  • 認証、許可、アクセス許可、およびアクセス制御リスト(ACL)についてのPyramidの概念を紹介します。
  • 作成した root factory はアプリケーションのトップにクラスのインスタンスを返します。
  • セキュリティステートメントをルートリソースに割り当てます。
  • ビューにパーミッションを追加します。
  • 適切な権限のないURLへのアクセス処理をするために Forbidden view を提供します。。

手順(Steps)

  1. 認証の手順を出発点として使用します:

    $ cd ..; cp -r authentication authorization; cd authorization
    $ $VENV/bin/pip install -e .
    
  2. authorization/tutorial/__init__.py を変更して、configurator にルートファクトリを指定します:

     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
    from pyramid.authentication import AuthTktAuthenticationPolicy
    from pyramid.authorization import ACLAuthorizationPolicy
    from pyramid.config import Configurator
    
    from .security import groupfinder
    
    
    def main(global_config, **settings):
        config = Configurator(settings=settings,
                              root_factory='.resources.Root')
        config.include('pyramid_chameleon')
    
        # Security policies
        authn_policy = AuthTktAuthenticationPolicy(
            settings['tutorial.secret'], callback=groupfinder,
            hashalg='sha512')
        authz_policy = ACLAuthorizationPolicy()
        config.set_authentication_policy(authn_policy)
        config.set_authorization_policy(authz_policy)
    
        config.add_route('home', '/')
        config.add_route('hello', '/howdy')
        config.add_route('login', '/login')
        config.add_route('logout', '/logout')
        config.scan('.views')
        return config.make_wsgi_app()
    
  3. authorization/tutorial/resources.py に実装する必要があることを意味します:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from pyramid.security import Allow, Everyone
    
    
    class Root(object):
        __acl__ = [(Allow, Everyone, 'view'),
                   (Allow, 'group:editors', 'edit')]
    
        def __init__(self, request):
            pass
    
  4. authorization/tutorial/views.py を変更して hello ビューの編集権限を要求するようにする、禁止ビューを実装します::

     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
    65
    66
    67
    68
    69
    70
    from pyramid.httpexceptions import HTTPFound
    from pyramid.security import (
        remember,
        forget,
        )
    
    from pyramid.view import (
        view_config,
        view_defaults,
        forbidden_view_config
        )
    
    from .security import (
        USERS,
        check_password
    )
    
    
    @view_defaults(renderer='home.pt')
    class TutorialViews:
        def __init__(self, request):
            self.request = request
            self.logged_in = request.authenticated_userid
    
        @view_config(route_name='home')
        def home(self):
            return {'name': 'Home View'}
    
        @view_config(route_name='hello', permission='edit')
        def hello(self):
            return {'name': 'Hello View'}
    
        @view_config(route_name='login', renderer='login.pt')
        @forbidden_view_config(renderer='login.pt')
        def login(self):
            request = self.request
            login_url = request.route_url('login')
            referrer = request.url
            if referrer == login_url:
                referrer = '/'  # never use login form itself as came_from
            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']
                hashed_pw = USERS.get(login)
                if hashed_pw and check_password(password, hashed_pw):
                    headers = remember(request, login)
                    return HTTPFound(location=came_from,
                                     headers=headers)
                message = 'Failed login'
    
            return dict(
                name='Login',
                message=message,
                url=request.application_url + '/login',
                came_from=came_from,
                login=login,
                password=password,
            )
    
        @view_config(route_name='logout')
        def logout(self):
            request = self.request
            headers = forget(request)
            url = request.route_url('home')
            return HTTPFound(location=url,
                             headers=headers)
    
  5. Pyramidアプリケーションを以下のように実行します:

    $ $VENV/bin/pserve development.ini --reload
    
  6. http://localhost:6543/ をブラウザで開きます。

  7. まだログインしているなら 「Log Out」リンクをクリックします。

  8. http://localhost:6543/howdy をブラウザでアクセスするとログインするように求められます。

分析(Analysis)

今回のシンプルなチュートリアルのステップは次のように成り立ってます:

  • ビューにはパーミッション (edit) が必要です
  • ビュー (the Root) のコンテキストにはアクセスコントロールリスト(ACL)があります。
  • ACLは``edit`` 権限が``Root`` で group:editors プリンシパルに対して利用可能であることを示します。
  • 登録された groupfinder の回答は、特定のユーザ (editor) が特定のグループ (group:editors) を持っているかどうかを示します。

要約すると、hello には edit 権限が必要です。 Rootgroup:editors の edit 権限を持っています。

もちろんこれは``Root`` にのみ適用されます。 サイトの他の部分(a.k.aコンテキスト)は、異なるACLを有する可能性があります。

ログインしていない場合は /howdy にアクセスしてログイン画面を表示する必要があります。 Pyramidは使用するログインページをどのように知っていますか? Pyramidではビューに @forbidden_view_config をデコレートすることによってログインビューを使用するように指示できます。

エクストラクレジット(Extra credit)

  1. renderer@forbidden_view_config デコレータを入れる必要がありますか?
  2. おそらく充分な権限(禁止されている)が不足していてもっとリッチになることを望むでしょう。どのように変更できますか?
  3. おそらくデータベースにセキュリティステートメントを格納してブラウザ経由で編集したいと考えています。これはどうすれば実現しますか?
  4. 異なる種類のオブジェクトに異なるセキュリティステートメントが必要な場合はどうなりますか?同じ種類のオブジェクトでもURL階層の異なる部分にあるのでしょうか?