diff options
Diffstat (limited to 'docs/quick_tutorial')
| -rw-r--r-- | docs/quick_tutorial/authentication.rst | 30 | ||||
| -rw-r--r-- | docs/quick_tutorial/authentication/tutorial/__init__.py | 18 | ||||
| -rw-r--r-- | docs/quick_tutorial/authentication/tutorial/security.py | 24 | ||||
| -rw-r--r-- | docs/quick_tutorial/authorization.rst | 15 | ||||
| -rw-r--r-- | docs/quick_tutorial/authorization/tutorial/__init__.py | 18 | ||||
| -rw-r--r-- | docs/quick_tutorial/authorization/tutorial/security.py | 38 |
6 files changed, 95 insertions, 48 deletions
diff --git a/docs/quick_tutorial/authentication.rst b/docs/quick_tutorial/authentication.rst index cd038ea36..12eb738e2 100644 --- a/docs/quick_tutorial/authentication.rst +++ b/docs/quick_tutorial/authentication.rst @@ -55,16 +55,15 @@ Steps :language: ini :linenos: -#. Get authentication (and for now, authorization policies) and login route - into the :term:`configurator` in ``authentication/tutorial/__init__.py``: +#. Create an ``authentication/tutorial/security.py`` module that can find our + user information by providing a :term:`security policy`: - .. literalinclude:: authentication/tutorial/__init__.py + .. literalinclude:: authentication/tutorial/security.py :linenos: -#. Create an ``authentication/tutorial/security.py`` module that can find our - user information by providing an *authentication policy callback*: +#. Register the ``SecurityPolicy`` with the :term:`configurator` in ``authentication/tutorial/__init__.py``: - .. literalinclude:: authentication/tutorial/security.py + .. literalinclude:: authentication/tutorial/__init__.py :linenos: #. Update the views in ``authentication/tutorial/views.py``: @@ -111,14 +110,12 @@ are you) and authorization (what are you allowed to do) are not just pluggable, but decoupled. To learn one step at a time, we provide a system that identifies users and lets them log out. -In this example we chose to use the bundled :ref:`AuthTktAuthenticationPolicy -<authentication_module>` policy. We enabled it in our configuration and -provided a ticket-signing secret in our INI file. +In this example we chose to use the bundled :class:`pyramid.authentication.AuthTktCookieHelper` helper to store the user's logged-in state in a cookie. +We enabled it in our configuration and provided a ticket-signing secret in our INI file. Our view class grew a login view. When you reached it via a ``GET`` request, it returned a login form. When reached via ``POST``, it processed the submitted -username and password against the "groupfinder" callable that we registered in -the configuration. +username and password against the ``USERS`` data store. The function ``hash_password`` uses a one-way hashing algorithm with a salt on the user's password via ``bcrypt``, instead of storing the password in plain @@ -134,6 +131,9 @@ submitted password and the user's password stored in the database. If the hashed values are equivalent, then the user is authenticated, else authentication fails. +Assuming the password was validated, we invoke :func:`pyramid.security.remember` to generate a cookie that is set in the response. +Subsequent requests return that cookie and identify the user. + In our template, we fetched the ``logged_in`` value from the view class. We use this to calculate the logged-in user, if any. In the template we can then choose to show a login link to anonymous visitors or a logout link to logged-in @@ -143,13 +143,9 @@ users. Extra credit ============ -#. What is the difference between a user and a principal? - -#. Can I use a database behind my ``groupfinder`` to look up principals? +#. Can I use a database instead of ``USERS`` to authenticate users? #. Once I am logged in, does any user-centric information get jammed onto each request? Use ``import pdb; pdb.set_trace()`` to answer this. -.. seealso:: See also :ref:`security_chapter`, - :ref:`AuthTktAuthenticationPolicy <authentication_module>`, `bcrypt - <https://pypi.org/project/bcrypt/>`_ +.. seealso:: See also :ref:`security_chapter`, :class:`pyramid.authentication.AuthTktCookieHelper`, `bcrypt <https://pypi.org/project/bcrypt/>`_ diff --git a/docs/quick_tutorial/authentication/tutorial/__init__.py b/docs/quick_tutorial/authentication/tutorial/__init__.py index efc09e760..ec8a66a23 100644 --- a/docs/quick_tutorial/authentication/tutorial/__init__.py +++ b/docs/quick_tutorial/authentication/tutorial/__init__.py @@ -1,25 +1,21 @@ -from pyramid.authentication import AuthTktAuthenticationPolicy -from pyramid.authorization import ACLAuthorizationPolicy from pyramid.config import Configurator -from .security import groupfinder +from .security import SecurityPolicy def main(global_config, **settings): config = Configurator(settings=settings) 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.set_security_policy( + SecurityPolicy( + secret=settings['tutorial.secret'], + ), + ) 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()
\ No newline at end of file + return config.make_wsgi_app() diff --git a/docs/quick_tutorial/authentication/tutorial/security.py b/docs/quick_tutorial/authentication/tutorial/security.py index e585e2642..8324000ed 100644 --- a/docs/quick_tutorial/authentication/tutorial/security.py +++ b/docs/quick_tutorial/authentication/tutorial/security.py @@ -1,4 +1,5 @@ import bcrypt +from pyramid.authentication import AuthTktCookieHelper def hash_password(pw): @@ -12,9 +13,24 @@ def check_password(pw, hashed_pw): USERS = {'editor': hash_password('editor'), 'viewer': hash_password('viewer')} -GROUPS = {'editor': ['group:editors']} -def groupfinder(userid, request): - if userid in USERS: - return GROUPS.get(userid, [])
\ No newline at end of file +class SecurityPolicy: + def __init__(self, secret): + self.authtkt = AuthTktCookieHelper(secret=secret) + + def authenticated_identity(self, request): + identity = self.authtkt.identify(request) + if identity is not None and identity['userid'] in USERS: + return identity + + def authenticated_userid(self, request): + identity = self.authenticated_identity(request) + if identity is not None: + return identity['userid'] + + def remember(self, request, userid, **kw): + return self.authtkt.remember(request, userid, **kw) + + def forget(self, request, **kw): + return self.authtkt.forget(request, **kw) diff --git a/docs/quick_tutorial/authorization.rst b/docs/quick_tutorial/authorization.rst index e80f88c51..b1ef86a17 100644 --- a/docs/quick_tutorial/authorization.rst +++ b/docs/quick_tutorial/authorization.rst @@ -55,6 +55,11 @@ Steps .. literalinclude:: authorization/tutorial/resources.py :linenos: +#. Define a ``GROUPS`` data store and the ``permits`` method of our ``SecurityPolicy``: + + .. literalinclude:: authorization/tutorial/security.py + :linenos: + #. Change ``authorization/tutorial/views.py`` to require the ``edit`` permission on the ``hello`` view and implement the forbidden view: @@ -87,8 +92,10 @@ This simple tutorial step can be boiled down to the following: - This ACL says that the ``edit`` permission is available on ``Root`` to the ``group:editors`` *principal*. -- The registered ``groupfinder`` answers whether a particular user (``editor``) - has a particular group (``group:editors``). +- The ``SecurityPolicy.effective_principals`` method answers whether a particular user (``editor``) is a member of a particular group (``group:editors``). + +- The ``SecurityPolicy.permits`` method is invoked when Pyramid wants to know whether the user is allowed to do something. + To do this, it uses the :class:`pyramid.authorization.ACLHelper` to inspect the ACL on the ``context`` and determine if the request is allowed or denied the specific permission. In summary, ``hello`` wants ``edit`` permission, ``Root`` says ``group:editors`` has ``edit`` permission. @@ -105,6 +112,10 @@ Pyramid that the ``login`` view should be used by decorating the view with Extra credit ============ +#. What is the difference between a user and a principal? + +#. Can I use a database instead of the ``GROUPS`` data store to look up principals? + #. Do I have to put a ``renderer`` in my ``@forbidden_view_config`` decorator? #. Perhaps you would like the experience of not having enough permissions diff --git a/docs/quick_tutorial/authorization/tutorial/__init__.py b/docs/quick_tutorial/authorization/tutorial/__init__.py index 8f7ab8277..255bb35ac 100644 --- a/docs/quick_tutorial/authorization/tutorial/__init__.py +++ b/docs/quick_tutorial/authorization/tutorial/__init__.py @@ -1,8 +1,6 @@ -from pyramid.authentication import AuthTktAuthenticationPolicy -from pyramid.authorization import ACLAuthorizationPolicy from pyramid.config import Configurator -from .security import groupfinder +from .security import SecurityPolicy def main(global_config, **settings): @@ -10,17 +8,15 @@ def main(global_config, **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.set_security_policy( + SecurityPolicy( + secret=settings['tutorial.secret'], + ), + ) 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()
\ No newline at end of file + return config.make_wsgi_app() diff --git a/docs/quick_tutorial/authorization/tutorial/security.py b/docs/quick_tutorial/authorization/tutorial/security.py index e585e2642..5b3e04a5f 100644 --- a/docs/quick_tutorial/authorization/tutorial/security.py +++ b/docs/quick_tutorial/authorization/tutorial/security.py @@ -1,4 +1,7 @@ import bcrypt +from pyramid.authentication import AuthTktCookieHelper +from pyramid.authorization import ACLHelper +from pyramid.security import Authenticated, Everyone def hash_password(pw): @@ -15,6 +18,35 @@ USERS = {'editor': hash_password('editor'), GROUPS = {'editor': ['group:editors']} -def groupfinder(userid, request): - if userid in USERS: - return GROUPS.get(userid, [])
\ No newline at end of file +class SecurityPolicy: + def __init__(self, secret): + self.authtkt = AuthTktCookieHelper(secret=secret) + self.acl = ACLHelper() + + def authenticated_identity(self, request): + identity = self.authtkt.identify(request) + if identity is not None and identity['userid'] in USERS: + return identity + + def authenticated_userid(self, request): + identity = self.authenticated_identity(request) + if identity is not None: + return identity['userid'] + + def remember(self, request, userid, **kw): + return self.authtkt.remember(request, userid, **kw) + + def forget(self, request, **kw): + return self.authtkt.forget(request, **kw) + + def permits(self, request, context, permission): + principals = self.effective_principals(request) + return self.acl.permits(context, principals, permission) + + def effective_principals(self, request): + principals = [Everyone] + userid = self.authenticated_userid(request) + if userid is not None: + principals += [Authenticated, 'u:' + userid] + principals += GROUPS.get(userid, []) + return principals |
