From 4c95ccd5e9b9657165f6ba061ee795fc4a5fcd30 Mon Sep 17 00:00:00 2001 From: Theron Luhn Date: Sun, 12 May 2019 12:13:26 -0700 Subject: Narrative docs WIP --- docs/glossary.rst | 5 +- docs/narr/advanced-features.rst | 9 +- docs/narr/security.rst | 181 ++++++++++++++++++++++++---------------- src/pyramid/interfaces.py | 7 +- 4 files changed, 122 insertions(+), 80 deletions(-) diff --git a/docs/glossary.rst b/docs/glossary.rst index 8a1d27734..5d374e0ec 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -307,8 +307,9 @@ Glossary define their principals differently. identity - An identity is an opaque identifier of the user associated with the - current request. + An identity is an object identify the user associated with the + current request. The identity can be any object, but should implement a + ``__str__`` method for logging and debugging purposes. security policy A security policy in :app:`Pyramid` terms is a bit of code which has an diff --git a/docs/narr/advanced-features.rst b/docs/narr/advanced-features.rst index b169aad95..431b4f030 100644 --- a/docs/narr/advanced-features.rst +++ b/docs/narr/advanced-features.rst @@ -104,13 +104,14 @@ For example, if you want to reuse an existing application that already has a bun Authenticate Users Your Way --------------------------- -:app:`Pyramid` ships with prebuilt, well-tested authentication and authorization schemes out of the box. Using a scheme is a matter of configuration. So if you need to change approaches later, you need only update your configuration. - -In addition, the system that handles authentication and authorization is flexible and pluggable. If you want to use another security add-on, or define your own, you can. And again, you need only update your application configuration to make the change. +:app:`Pyramid` has a powerful security system that can be tailored to your +needs. Build your own security policy tailored to your needs, or use one of +the many helpers provided to easily implement common authentication and +authorization patterns. .. seealso:: - See also :ref:`enabling_authorization_policy`. + See also :ref:`writing_security_policy`. Build Trees of Resources ------------------------ diff --git a/docs/narr/security.rst b/docs/narr/security.rst index 6b6f9a1f1..438a13380 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -6,17 +6,12 @@ Security ======== -:app:`Pyramid` provides an optional, declarative, security system. Security in -:app:`Pyramid` is separated into authentication and authorization. The two -systems communicate via :term:`principal` identifiers. Authentication is merely -the mechanism by which credentials provided in the :term:`request` are resolved -to one or more :term:`principal` identifiers. These identifiers represent the -users and groups that are in effect during the request. Authorization then -determines access based on the :term:`principal` identifiers, the requested -:term:`permission`, and a :term:`context`. +:app:`Pyramid` provides an optional, declarative security system. The system +determines the identity of the current user (authentication) and whether or not +the user has access to certain resources (authorization). The :app:`Pyramid` authorization system can prevent a :term:`view` from being -invoked based on an :term:`authorization policy`. Before a view is invoked, the +invoked based on the :term:`security policy`. Before a view is invoked, the authorization system can use the credentials in the :term:`request` along with the :term:`context` resource to determine if access will be allowed. Here's how it works at a high level: @@ -37,89 +32,124 @@ how it works at a high level: - A :term:`view callable` is located by :term:`view lookup` using the context as well as other attributes of the request. -- If an :term:`authentication policy` is in effect, it is passed the request. - It will return some number of :term:`principal` identifiers. To do this, the - policy would need to determine the authenticated :term:`userid` present in - the request. +- If a :term:`security policy` is in effect, it is passed the request and + returns the :term:`identity` of the current user. -- If an :term:`authorization policy` is in effect and the :term:`view +- If a :term:`security policy` is in effect and the :term:`view configuration` associated with the view callable that was found has a - :term:`permission` associated with it, the authorization policy is passed the - :term:`context`, some number of :term:`principal` identifiers returned by the - authentication policy, and the :term:`permission` associated with the view; - it will allow or deny access. + :term:`permission` associated with it, the policy is passed the + :term:`context`, the current :term:`identity`, and the :term:`permission` + associated with the view; it will allow or deny access. -- If the authorization policy allows access, the view callable is invoked. +- If the security policy allows access, the view callable is invoked. -- If the authorization policy denies access, the view callable is not invoked. +- If the security policy denies access, the view callable is not invoked. Instead the :term:`forbidden view` is invoked. -Authorization is enabled by modifying your application to include an -:term:`authentication policy` and :term:`authorization policy`. :app:`Pyramid` -comes with a variety of implementations of these policies. To provide maximal -flexibility, :app:`Pyramid` also allows you to create custom authentication -policies and authorization policies. +The security system is enabled by modifying your application to include a +:term:`security policy`. :app:`Pyramid` comes with a variety of helpers to +assist in the creation of this policy. .. index:: - single: authorization policy + single: security policy -.. _enabling_authorization_policy: +.. _writing_security_policy: -Enabling an Authorization Policy --------------------------------- +Writing a Security Policy +------------------------- -:app:`Pyramid` does not enable any authorization policy by default. All views -are accessible by completely anonymous users. In order to begin protecting -views from execution based on security settings, you need to enable an -authorization policy. +:app:`Pyramid` does not enable any security policy by default. All views are +accessible by completely anonymous users. In order to begin protecting views +from execution based on security settings, you need to write a security policy. -Enabling an Authorization Policy Imperatively -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Security policies are simple classes implementing a +:class:`pyramid.interfaces.ISecurityPolicy`, defined as follows: -Use the :meth:`~pyramid.config.Configurator.set_authorization_policy` method of -the :class:`~pyramid.config.Configurator` to enable an authorization policy. +.. autointerface:: pyramid.interfaces.ISecurityPolicy + :members: -You must also enable an :term:`authentication policy` in order to enable the -authorization policy. This is because authorization, in general, depends upon -authentication. Use the -:meth:`~pyramid.config.Configurator.set_authentication_policy` method during -application setup to specify the authentication policy. +A simple security policy might look like the following: -For example: +.. code-block:: python + :linenos: + + from pyramid.security import Allowed + + class SessionSecurityPolicy: + def identify(self, request): + """ Return the user ID stored in the session. """ + return request.session.get('userid') + + def permits(self, request, context, identity, permission): + """ Indiscriminately allow access to everything. """ + return Allowed('User is signed in.') + + def remember(request, userid, **kw): + request.session.get('userid') + return [] + + def forget(request): + del request.session['userid'] + return [] + +Use the :meth:`~pyramid.config.Configurator.set_security_policy` method of +the :class:`~pyramid.config.Configurator` to enforce the security policy on +your application. + +Writing a Security Policy Using Helpers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To assist in writing common security policy, Pyramid provides several helpers. +The following authentication helpers assist with implementing ``identity``, +``remember``, and ``forget``. + +* :class:`pyramid.authentication.SessionAuthenticationHelper` + +* :class:`pyramid.authentication.AuthTktCookieHelper` + +The following authorization helper assists with implementing ``permits``. + +* :class:`pyramid.authorization.ACLHelper` + +For example, our above security policy can leverage these helpers like so: .. code-block:: python :linenos: - from pyramid.config import Configurator - from pyramid.authentication import AuthTktAuthenticationPolicy - from pyramid.authorization import ACLAuthorizationPolicy - authn_policy = AuthTktAuthenticationPolicy('seekrit', hashalg='sha512') - authz_policy = ACLAuthorizationPolicy() - config = Configurator() - config.set_authentication_policy(authn_policy) - config.set_authorization_policy(authz_policy) + from pyramid.security import Allowed + from pyramid.authentication import SessionAuthenticationHelper -.. note:: The ``authentication_policy`` and ``authorization_policy`` arguments - may also be passed to their respective methods mentioned above as - :term:`dotted Python name` values, each representing the dotted name path to - a suitable implementation global defined at Python module scope. + class SessionSecurityPolicy: + def __init__(self): + self.helper = SessionAuthenticationHelper() -The above configuration enables a policy which compares the value of an "auth -ticket" cookie passed in the request's environment which contains a reference -to a single :term:`userid`, and matches that userid's :term:`principals -` against the principals present in any :term:`ACL` found in the -resource tree when attempting to call some :term:`view`. + def identify(self, request): + """ Return the user ID stored in the session. """ + return self.helper.identify(request) -While it is possible to mix and match different authentication and -authorization policies, it is an error to configure a Pyramid application with -an authentication policy but without the authorization policy or vice versa. If -you do this, you'll receive an error at application startup time. + def permits(self, request, context, identity, permission): + """ Indiscriminately allow access to everything. """ + return Allowed('User is signed in.') -.. seealso:: + def remember(request, userid, **kw): + return self.helper.remember(request, userid, **kw) + + def forget(request): + return self.helper.forget(request) - See also the :mod:`pyramid.authorization` and :mod:`pyramid.authentication` - modules for alternative implementations of authorization and authentication - policies. +Helpers are intended to be used with application-specific code, so perhaps your +authentication also queries to database to ensure the identity is valid. + +.. code-block:: python + :linenos: + + def identify(self, request): + """ Return the user ID stored in the session. """ + user_id = self.helper.identify(request) + if validate_user_id(user_id): + return user_id + else: + return None .. index:: single: permissions @@ -165,11 +195,16 @@ performed via the ``@view_config`` decorator: pass As a result of any of these various view configuration statements, if an -authorization policy is in place when the view callable is found during normal -application operations, the requesting user will need to possess the ``add`` -permission against the :term:`context` resource in order to be able to invoke -the ``blog_entry_add_view`` view. If they do not, the :term:`Forbidden view` -will be invoked. +security policy is in place when the view callable is found during normal +application operations, the security policy will be queried to see if the +requesting user is allowed the ``add`` permission within the current +:term:`context`. If the policy allows access, ``blog_entry_add_view`` will be +invoked. If not, the :term:`Forbidden view` will be invoked. + +Allowing and Denying Access With a Security Policy +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The security policy's ``permits`` function is queried .. index:: pair: permission; default diff --git a/src/pyramid/interfaces.py b/src/pyramid/interfaces.py index 9dabb9cfc..bc52e324e 100644 --- a/src/pyramid/interfaces.py +++ b/src/pyramid/interfaces.py @@ -484,13 +484,18 @@ class IViewMapperFactory(Interface): class ISecurityPolicy(Interface): def identify(request): - """ Return an object identifying a trusted and verified user. """ + """ Return an object identifying a trusted and verified user. This + object may be anything, but should implement a ``__str__`` method for + logging and debugging purposes. + + """ def permits(request, context, identity, permission): """ Return an instance of :class:`pyramid.security.Allowed` if a user of the given identity is allowed the ``permission`` in the current ``context``, else return an instance of :class:`pyramid.security.Denied`. + """ def remember(request, userid, **kw): -- cgit v1.2.3