summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci-tests.yml1
-rw-r--r--CHANGES.rst52
-rw-r--r--HACKING.txt2
-rw-r--r--RELEASING.txt24
-rw-r--r--TODO.txt3
-rw-r--r--contributing.md2
-rw-r--r--docs/api/authentication.rst2
-rw-r--r--docs/api/request.rst4
-rw-r--r--docs/glossary.rst37
-rw-r--r--docs/narr/install.rst6
-rw-r--r--docs/narr/project.rst4
-rw-r--r--docs/narr/sessions.rst64
-rw-r--r--docs/narr/upgrading.rst1
-rw-r--r--docs/quick_tutorial/requirements.rst2
-rw-r--r--docs/tutorials/wiki2/authentication.rst15
-rw-r--r--docs/tutorials/wiki2/authorization.rst4
-rw-r--r--docs/tutorials/wiki2/src/authentication/tests/conftest.py43
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/security.py2
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/templates/layout.jinja24
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/views/auth.py2
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/views/default.py6
-rw-r--r--docs/tutorials/wiki2/src/authorization/tests/conftest.py43
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/security.py2
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja24
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/views/auth.py2
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/views/default.py2
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tests/conftest.py43
-rw-r--r--docs/tutorials/wiki2/src/installation/tests/conftest.py43
-rw-r--r--docs/tutorials/wiki2/src/models/tests/conftest.py43
-rw-r--r--docs/tutorials/wiki2/src/tests/tests/conftest.py42
-rw-r--r--docs/tutorials/wiki2/src/tests/tests/test_views.py48
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/security.py2
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja24
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views/auth.py2
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views/default.py2
-rw-r--r--docs/tutorials/wiki2/src/views/tests/conftest.py43
-rw-r--r--docs/tutorials/wiki2/tests.rst7
-rw-r--r--docs/whatsnew-1.10.rst4
-rw-r--r--docs/whatsnew-1.5.rst2
-rw-r--r--docs/whatsnew-2.0.rst390
-rw-r--r--pyproject.toml1
-rw-r--r--setup.py6
-rw-r--r--src/pyramid/authorization.py2
-rw-r--r--src/pyramid/config/security.py8
-rw-r--r--src/pyramid/interfaces.py17
-rw-r--r--src/pyramid/security.py8
-rw-r--r--src/pyramid/session.py4
-rw-r--r--tox.ini2
48 files changed, 672 insertions, 384 deletions
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
index b56a638a3..625c6a60f 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/ci-tests.yml
@@ -15,7 +15,6 @@ jobs:
strategy:
matrix:
py:
- - "3.5"
- "3.6"
- "3.7"
- "3.8"
diff --git a/CHANGES.rst b/CHANGES.rst
index 32e260037..d2dbe071b 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,13 +1,24 @@
-unreleased
-==========
+2.0b0 (2020-12-15)
+==================
+
+- Overhaul tutorials and update cookiecutter to de-emphasize ``request.user``
+ in favor of ``request.identity`` for common use cases.
+ See https://github.com/Pylons/pyramid/pull/3629
+
+- Improve documentation and patterns with builtin fixtures shipped in the
+ cookiecutters.
+ See https://github.com/Pylons/pyramid/pull/3629
+
+2.0a0 (2020-11-29)
+==================
Features
--------
- Add support for Python 3.9.
- See https://github.com/Pylons/pyramid/issue/3622
+ See https://github.com/Pylons/pyramid/issues/3622
-- The ``aslist`` method now handles non-string object when flattening.
+- The ``aslist`` method now handles non-string objects when flattening.
See https://github.com/Pylons/pyramid/pull/3594
- It is now possible to pass multiple values to the ``header`` predicate
@@ -20,11 +31,11 @@ Features
- New security APIs have been added to support a massive overhaul of the
authentication and authorization system. Read
"Upgrading Authentication/Authorization" in the "What's New in Pyramid 2.0"
- document for information about using this new system.
+ chapter of the documentation for information about using this new system.
- ``pyramid.config.Configurator.set_security_policy``.
- ``pyramid.interfaces.ISecurityPolicy``
- - ``pyramid.request.Request.authenticated_identity``.
+ - ``pyramid.request.Request.identity``.
- ``pyramid.request.Request.is_authenticated``
- ``pyramid.authentication.SessionAuthenticationHelper``
- ``pyramid.authorization.ACLHelper``
@@ -37,8 +48,8 @@ Features
``pyramid.session.SignedCookieSessionFactory`` to use
``pyramid.session.JSONSerializer`` instead of
``pyramid.session.PickleSerializer``. Read
- "Changes to ISession in Pyramid 2.0" in the "Sessions" chapter of the
- documentation for more information about why this change was made.
+ "Upgrading Session Serialization" in the "What's New in Pyramid 2.0" chapter
+ of the documentation for more information about why this change was made.
See https://github.com/Pylons/pyramid/pull/3413
- It is now possible to control whether a route pattern contains a trailing
@@ -134,9 +145,9 @@ Deprecations
- Deprecated the authentication and authorization interfaces and
principal-based support. See "Upgrading Authentication/Authorization" in
- the "What's New in Pyramid 2.0" document for information on equivalent APIs
- and notes on upgrading. The following APIs are deprecated as a result of
- this change:
+ the "What's New in Pyramid 2.0" chapter of the documentation for information
+ on equivalent APIs and notes on upgrading. The following APIs are deprecated
+ as a result of this change:
- ``pyramid.config.Configurator.set_authentication_policy``
- ``pyramid.config.Configurator.set_authorization_policy``
@@ -174,18 +185,17 @@ Deprecations
See https://github.com/Pylons/pyramid/pull/3563
- Deprecated ``pyramid.session.PickleSerializer``.
- See https://github.com/pylons/pyramid/issues/2709
- and https://github.com/pylons/pyramid/pull/3353
+ See https://github.com/pylons/pyramid/issues/2709,
+ and https://github.com/pylons/pyramid/pull/3353,
and https://github.com/pylons/pyramid/pull/3413
Backward Incompatibilities
--------------------------
-- Drop support for Python 2.7.
- https://github.com/Pylons/pyramid/pull/3421
-
-- Drop support for Python 3.4.
- See https://github.com/Pylons/pyramid/pull/3547
+- Drop support for Python 2.7, 3.4, and 3.5.
+ See https://github.com/Pylons/pyramid/pull/3421,
+ and https://github.com/Pylons/pyramid/pull/3547,
+ and https://github.com/Pylons/pyramid/pull/3634
- Removed the ``pyramid.compat`` module. Integrators should use the ``six``
module or vendor shims they are using into their own codebases going forward.
@@ -237,9 +247,9 @@ Backward Incompatibilities
- Changed the default ``serializer`` on
``pyramid.session.SignedCookieSessionFactory`` to use
``pyramid.session.JSONSerializer`` instead of
- ``pyramid.session.PickleSerializer``. Read
- "Changes to ISession in Pyramid 2.0" in the "Sessions" chapter of the
- documentation for more information about why this change was made.
+ ``pyramid.session.PickleSerializer``. Read "Upgrading Session Serialization"
+ in the "What's New in Pyramid 2.0" chapter of the documentation for more
+ information about why this change was made.
See https://github.com/Pylons/pyramid/pull/3413
- ``pyramid.request.Request.invoke_exception_view`` will no longer be called
diff --git a/HACKING.txt b/HACKING.txt
index 7bf05080c..b9cdcc940 100644
--- a/HACKING.txt
+++ b/HACKING.txt
@@ -50,7 +50,7 @@ In order to add a feature to Pyramid:
- The feature must be documented in both the API and narrative documentation
(in `docs/`).
-- The feature must work fully on the following CPython versions: 3.5, 3.6, 3.7, 3.8, and 3.9 on both UNIX and Windows.
+- The feature must work fully on the following CPython versions: 3.6, 3.7, 3.8, and 3.9 on both UNIX and Windows.
- The feature must work on the latest version of PyPy3.
diff --git a/RELEASING.txt b/RELEASING.txt
index 24b3f54fa..9cf8a1090 100644
--- a/RELEASING.txt
+++ b/RELEASING.txt
@@ -119,13 +119,11 @@ Update previous version (final releases only)
- Configure RTD to point the "latest" alias to the new release version of the
docs.
-
Cookiecutters
-------------
- For each cookiecutter, clone the newly released branch to "latest" branch.
-
Marketing and communications
----------------------------
@@ -142,17 +140,17 @@ Marketing and communications
- Announce to Twitter.
```
-Pyramid 1.x released.
+Pyramid 2.x released.
PyPI
-https://pypi.org/project/pyramid/1.x/
+https://pypi.org/project/pyramid/2.x/
=== One time only for new version, first pre-release ===
What's New
-https://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/whatsnew-1.X.html
+https://docs.pylonsproject.org/projects/pyramid/en/2.X-branch/whatsnew-2.X.html
=== For all subsequent pre-releases ===
Changes
-https://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/changes.html#version-yyyy-mm-dd
+https://docs.pylonsproject.org/projects/pyramid/en/2.X-branch/changes.html#version-yyyy-mm-dd
Issues
https://github.com/Pylons/pyramid/issues
@@ -161,21 +159,21 @@ https://github.com/Pylons/pyramid/issues
- Announce to maillist.
```
-Pyramid 1.X.X has been released.
+Pyramid 2.X.X has been released.
The full changelog is here:
-https://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/changes.html
+https://docs.pylonsproject.org/projects/pyramid/en/2.X-branch/changes.html
-What's New In Pyramid 1.X:
-https://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/whatsnew-1.X.html
+What's New In Pyramid 2.X:
+https://docs.pylonsproject.org/projects/pyramid/en/2.X-branch/whatsnew-2.X.html
-1.X release documentation (across all alphas and betas, as well as when it gets
+2.X release documentation (across all alphas and betas, as well as when it gets
to final release):
-https://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/
+https://docs.pylonsproject.org/projects/pyramid/en/2.X-branch/
You can install it via PyPI:
- pip install Pyramid==1.X
+ pip install Pyramid==2.X
Enjoy, and please report any issues you find to the issue tracker at
https://github.com/Pylons/pyramid/issues
diff --git a/TODO.txt b/TODO.txt
index 000a3ed09..ebcf4c926 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -133,6 +133,3 @@ Probably Bad Ideas
c.add_view(..)
- _fix_registry should dictify the registry being fixed.
-
-- Apply a prefix to the userid principal to avoid poisoning the principal
- namespace. See https://github.com/Pylons/pyramid/issues/2060
diff --git a/contributing.md b/contributing.md
index 3b096db5d..534b399c6 100644
--- a/contributing.md
+++ b/contributing.md
@@ -14,6 +14,8 @@ Git branches and their purpose and status at the time of this writing are listed
* [master](https://github.com/Pylons/pyramid/) - The branch on which further
development takes place. The default branch on GitHub.
+* [2.0-branch](https://github.com/Pylons/pyramid/tree/2.0-branch) - The branch
+ classified as "alpha".
* [1.10-branch](https://github.com/Pylons/pyramid/tree/1.10-branch) - The branch
classified as "stable" or "latest".
* [1.9-branch](https://github.com/Pylons/pyramid/tree/1.9-branch) - The oldest
diff --git a/docs/api/authentication.rst b/docs/api/authentication.rst
index f3a25ee64..a1a8dfea1 100644
--- a/docs/api/authentication.rst
+++ b/docs/api/authentication.rst
@@ -26,7 +26,7 @@ Authentication Policies
~~~~~~~~~~~~~~~~~~~~~~~
Authentication policies have been deprecated by the new security system. See
-:ref:`upgrading_auth` for more information.
+:ref:`upgrading_auth_20` for more information.
.. autoclass:: AuthTktAuthenticationPolicy
:members:
diff --git a/docs/api/request.rst b/docs/api/request.rst
index 8704a2fe7..f6a3b62a6 100644
--- a/docs/api/request.rst
+++ b/docs/api/request.rst
@@ -176,7 +176,7 @@
``unauthenticated_userid`` has been deprecated in version 2.0. Use
:attr:`authenticated_userid` or :attr:`identity` instead. See
- :ref:`upgrading_auth` for more information.
+ :ref:`upgrading_auth_20` for more information.
A property which returns a value which represents the *claimed* (not
verified) :term:`userid` of the credentials present in the
@@ -194,7 +194,7 @@
.. deprecated:: 2.0
The new security policy has removed the concept of principals. See
- :ref:`upgrading_auth` for more information.
+ :ref:`upgrading_auth_20` for more information.
A property which returns the list of 'effective' :term:`principal`
identifiers for this request. This list typically includes the
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 7137f14a4..85d12b571 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -48,12 +48,8 @@ Glossary
builds on Python's ``distutils`` to provide easier building,
distribution, and installation of libraries and applications.
- distribute
- `Distribute <https://pypi.org/project/distribute/>`_ is a fork of :term:`Setuptools` which runs on both Python 2 and Python 3.
- It is now in legacy state because :term:`Setuptools` now runs on both Python 2 and 3.
-
pkg_resources
- A module which ships with :term:`Setuptools` and :term:`distribute` that
+ A module which ships with :term:`Setuptools` that
provides an API for addressing "asset files" within a Python
:term:`package`. Asset files are static files, template files, etc;
basically anything non-Python-source that lives in a Python package can
@@ -87,23 +83,19 @@ Glossary
:term:`package`.
project
- (Setuptools/distutils terminology). A directory on disk which
- contains a ``setup.py`` file and one or more Python packages. The
- ``setup.py`` file contains code that allows the package(s) to be
- installed, distributed, and tested.
+ Setuptools / Python packaging terminology.
+ A directory on disk which contains a ``setup.py`` and / or ``pyproject.toml`` file and one or more Python packages.
+ The project files contain metadata that allow the package(s) to be installed, distributed, and tested.
distribution
- (Setuptools/distutils terminology). A file representing an
+ Setuptools / Python packaging terminology. A file representing an
installable library or application. Distributions are usually
- files that have the suffix of ``.egg``, ``.tar.gz``, or ``.zip``.
- Distributions are the target of Setuptools-related commands such as
- ``easy_install``.
+ archives that have the suffix of ``.whl``, ``.tar.gz``, or ``.zip``.
+ Distributions are the target of packaging-related commands such as ``pip install``.
entry point
- A :term:`Setuptools` indirection, defined within a Setuptools
- :term:`distribution` setup.py. It is usually a name which refers
- to a function somewhere in a package which is held by the
- distribution.
+ A :term:`Setuptools` indirection, defined within a Setuptools :term:`distribution` (usually in ``setup.py`` or ``setup.cfg``).
+ It is usually a name which refers to a function somewhere in a package which is held by the distribution.
dotted Python name
A reference to a Python object by name using a string, in the form
@@ -1083,7 +1075,7 @@ Glossary
https://docs.pylonsproject.org/projects/waitress/en/latest/ for detailed
information.
- Green Unicorn
+ gunicorn
Aka ``gunicorn``, a fast :term:`WSGI` server that runs on Unix under
Python 2.6+ or Python 3.4+. See https://gunicorn.org/ for detailed
information.
@@ -1128,15 +1120,6 @@ Glossary
The :term:`Python Packaging Authority`'s recommended tool for installing
Python packages.
- pyvenv
- The :term:`Python Packaging Authority` formerly recommended using the
- ``pyvenv`` command for `creating virtual environments on Python 3.4 and
- 3.5
- <https://packaging.python.org/tutorials/installing-packages/#creating-virtual-environments>`_,
- but it was deprecated in 3.6 in favor of ``python3 -m venv`` on Unix or
- ``python -m venv`` on Windows, which is backward compatible on Python
- 3.3 and greater.
-
virtual environment
An isolated Python environment that allows packages to be installed for
use by a particular application, rather than being installed system wide.
diff --git a/docs/narr/install.rst b/docs/narr/install.rst
index 23bf0cfd3..1801f3c9a 100644
--- a/docs/narr/install.rst
+++ b/docs/narr/install.rst
@@ -5,7 +5,7 @@ Installing :app:`Pyramid`
.. note::
- This installation guide emphasizes the use of Python 3.5 and greater for
+ This installation guide emphasizes the use of Python 3.6 and greater for
simplicity.
@@ -15,13 +15,13 @@ Installing :app:`Pyramid`
Before You Install Pyramid
--------------------------
-Install Python version 3.5 or greater for your operating system, and satisfy
+Install Python version 3.6 or greater for your operating system, and satisfy
the :ref:`requirements-for-installing-packages`, as described in
the following sections.
.. sidebar:: Python Versions
- As of this writing, :app:`Pyramid` is tested against Python 3.5, 3.6, 3.7, 3.8, and 3.9 and PyPy3.
+ As of this writing, :app:`Pyramid` is tested against Python 3.6, 3.7, 3.8, and 3.9 and PyPy3.
:app:`Pyramid` is known to run on all popular Unix-like systems such as Linux,
macOS, and FreeBSD, as well as on Windows platforms. It is also known to
diff --git a/docs/narr/project.rst b/docs/narr/project.rst
index 6493f0fe7..ee75587e9 100644
--- a/docs/narr/project.rst
+++ b/docs/narr/project.rst
@@ -1173,8 +1173,8 @@ One popular production alternative to the default Waitress server is
using the Apache web server rather than any "pure-Python" server like Waitress.
It is fast and featureful. See :ref:`modwsgi_tutorial` for details.
-Another good production alternative is :term:`Green Unicorn` (aka
-``gunicorn``). It's faster than Waitress and slightly easier to configure than
+Another good production alternative is :term:`gunicorn`.
+It's faster than Waitress and slightly easier to configure than
``mod_wsgi``, although it depends, in its default configuration, on having a
buffering HTTP proxy in front of it. It does not, as of this writing, work on
Windows.
diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst
index 2da524d4c..03ad5c8d2 100644
--- a/docs/narr/sessions.rst
+++ b/docs/narr/sessions.rst
@@ -73,68 +73,6 @@ using the :meth:`pyramid.config.Configurator.set_session_factory` method.
In short, use a different session factory implementation (preferably one which keeps session data on the server) for anything but the most basic of applications where "session security doesn't matter", you are sure your application has no cross-site scripting vulnerabilities, and you are confident your secret key will not be exposed.
.. index::
- triple: pickle deprecation; JSON-serializable; ISession interface
-
-.. _pickle_session_deprecation:
-
-Changes to ISession in Pyramid 2.0
-----------------------------------
-
-In :app:`Pyramid` 2.0 the :class:`pyramid.interfaces.ISession` interface was changed to require that session implementations only need to support JSON-serializable data types.
-This is a stricter contract than the previous requirement that all objects be pickleable and it is being done for security purposes.
-This is a backward-incompatible change.
-Previously, if a client-side session implementation was compromised, it left the application vulnerable to remote code execution attacks using specially-crafted sessions that execute code when deserialized.
-
-Please reference the following tickets if detailed information on these changes is needed:
-
-* `2.0 feature request: Require that sessions are JSON serializable #2709 <https://github.com/pylons/pyramid/issues/2709>`_.
-* `deprecate pickleable sessions, recommend json #3353 <https://github.com/pylons/pyramid/pull/3353>`_.
-* `change to use JSONSerializer for SignedCookieSessionFactory #3413 <https://github.com/pylons/pyramid/pull/3413>`_.
-
-For users with compatibility concerns, it's possible to craft a serializer that can handle both formats until you are satisfied that clients have had time to reasonably upgrade.
-Remember that sessions should be short-lived and thus the number of clients affected should be small (no longer than an auth token, at a maximum). An example serializer:
-
-.. code-block:: python
- :linenos:
-
- import pickle
- from pyramid.session import JSONSerializer
- from pyramid.session import SignedCookieSessionFactory
-
-
- class JSONSerializerWithPickleFallback(object):
- def __init__(self):
- self.json = JSONSerializer()
-
- def dumps(self, appstruct):
- """
- Accept a Python object and return bytes.
-
- During a migration, you may want to catch serialization errors here,
- and keep using pickle while finding spots in your app that are not
- storing JSON-serializable objects. You may also want to integrate
- a fall-back to pickle serialization here as well.
- """
- return self.json.dumps(appstruct)
-
- def loads(self, bstruct):
- """Accept bytes and return a Python object."""
- try:
- return self.json.loads(bstruct)
- except ValueError:
- try:
- return pickle.loads(bstruct)
- except Exception:
- # this block should catch at least:
- # ValueError, AttributeError, ImportError; but more to be safe
- raise ValueError
-
- # somewhere in your configuration code
- serializer = JSONSerializerWithPickleFallback()
- session_factory = SignedCookieSessionFactory(..., serializer=serializer)
- config.set_session_factory(session_factory)
-
-.. index::
single: session object
Using a Session Object
@@ -193,7 +131,7 @@ Some gotchas:
- Keys and values of session data must be JSON-serializable.
This means, typically, that they are instances of basic types of objects, such as strings, lists, dictionaries, tuples, integers, etc.
If you place an object in a session data key or value that is not JSON-serializable, an error will be raised when the session is serialized.
- Please also see :ref:`pickle_session_deprecation`.
+ Please also see :ref:`upgrading_session_20`.
- If you place a mutable value (for example, a list or a dictionary) in a
session object, and you subsequently mutate that value, you must call the
diff --git a/docs/narr/upgrading.rst b/docs/narr/upgrading.rst
index e5541e248..d6882809a 100644
--- a/docs/narr/upgrading.rst
+++ b/docs/narr/upgrading.rst
@@ -86,7 +86,6 @@ At the time of a Pyramid version release, each supports all versions of Python
through the end of their lifespans. The end-of-life for a given version of
Python is when security updates are no longer released.
-- `Python 3.5 Lifespan <https://devguide.python.org/#status-of-python-branches>`_ 2020-09-13.
- `Python 3.6 Lifespan <https://devguide.python.org/#status-of-python-branches>`_ 2021-12-23.
- `Python 3.7 Lifespan <https://devguide.python.org/#status-of-python-branches>`_ 2023-06-27.
- `Python 3.8 Lifespan <https://devguide.python.org/#status-of-python-branches>`_ 2024-10-??.
diff --git a/docs/quick_tutorial/requirements.rst b/docs/quick_tutorial/requirements.rst
index fd1726dbd..901f6134d 100644
--- a/docs/quick_tutorial/requirements.rst
+++ b/docs/quick_tutorial/requirements.rst
@@ -19,7 +19,7 @@ virtual environment.)
This *Quick Tutorial* is based on:
-* **Python 3.8**. Pyramid fully supports Python 3.5+.
+* **Python 3.8**. Pyramid fully supports Python 3.6+.
This tutorial uses **Python 3.8**.
* **venv**. We believe in virtual environments.
diff --git a/docs/tutorials/wiki2/authentication.rst b/docs/tutorials/wiki2/authentication.rst
index 4d8723176..414d6c879 100644
--- a/docs/tutorials/wiki2/authentication.rst
+++ b/docs/tutorials/wiki2/authentication.rst
@@ -10,8 +10,7 @@ APIs to add login and logout functionality to our wiki.
We will implement authentication with the following steps:
-* Add a :term:`security policy` and a ``request.user`` computed property
- (``security.py``).
+* Add a :term:`security policy` (``security.py``).
* Add routes for ``/login`` and ``/logout`` (``routes.py``).
* Add login and logout views (``views/auth.py``).
* Add a login template (``login.jinja2``).
@@ -41,10 +40,8 @@ Update ``tutorial/security.py`` with the following content:
:linenos:
:language: python
-Here we've defined:
-
-* A new security policy named ``MySecurityPolicy``, which is implementing most of the :class:`pyramid.interfaces.ISecurityPolicy` interface by tracking a :term:`identity` using a signed cookie implemented by :class:`pyramid.authentication.AuthTktCookieHelper` (lines 8-34).
-* The ``request.user`` computed property is registered for use throughout our application as the authenticated ``tutorial.models.User`` object for the logged-in user (line 42-44).
+Here we've defined a new security policy named ``MySecurityPolicy``, which is implementing most of the :class:`pyramid.interfaces.ISecurityPolicy` interface by tracking a :term:`identity` using a signed cookie implemented by :class:`pyramid.authentication.AuthTktCookieHelper` (lines 8-34).
+The security policy outputs the authenticated ``tutorial.models.User`` object for the logged-in user as the :term:`identity`, which is available as ``request.identity``.
Our new :term:`security policy` defines how our application will remember, forget, and identify users.
It also handles authorization, which we'll cover in the next chapter (if you're wondering why we didn't implement the ``permits`` method yet).
@@ -64,7 +61,7 @@ Identifying the current user is done in a few steps:
#. The result is stored in the ``identity_cache`` which ensures that subsequent invocations return the same identity object for the request.
-Finally, :attr:`pyramid.request.Request.identity` contains either ``None`` or a ``tutorial.models.User`` instance and that value is aliased to ``request.user`` for convenience in our application.
+Finally, :attr:`pyramid.request.Request.identity` contains either ``None`` or a ``tutorial.models.User`` instance.
Note the usage of the ``identity_cache`` is optional, but it has several advantages in most scenarios:
@@ -156,7 +153,7 @@ Only the highlighted lines need to be changed.
If the user either is not logged in or is not in the ``basic`` or ``editor`` roles, then we raise ``HTTPForbidden``, which will trigger our forbidden view to compute a response.
However, we will hook this later to redirect to the login page.
-Also, now that we have ``request.user``, we no longer have to hard-code the creator as the ``editor`` user, so we can finally drop that hack.
+Also, now that we have ``request.identity``, we no longer have to hard-code the creator as the ``editor`` user, so we can finally drop that hack.
These simple checks should protect our views.
@@ -266,7 +263,7 @@ indicated by the highlighted lines.
:emphasize-lines: 2-12
:language: html
-The ``request.user`` will be ``None`` if the user is not authenticated, or a
+The ``request.identity`` will be ``None`` if the user is not authenticated, or a
``tutorial.models.User`` object if the user is authenticated. This check will
make the logout link shown only when the user is logged in, and conversely the
login link is only shown when the user is logged out.
diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst
index 38b9b7373..be3a09664 100644
--- a/docs/tutorials/wiki2/authorization.rst
+++ b/docs/tutorials/wiki2/authorization.rst
@@ -5,7 +5,7 @@ Adding authorization
====================
In the last chapter we built :term:`authentication` into our wiki. We also
-went one step further and used the ``request.user`` object to perform some
+went one step further and used the ``request.identity`` object to perform some
explicit :term:`authorization` checks. This is fine for a lot of applications,
but :app:`Pyramid` provides some facilities for cleaning this up and decoupling
the constraints from the view function itself.
@@ -24,7 +24,7 @@ We will implement access control with the following steps:
Add ACL support
---------------
-A :term:`principal` is a level of abstraction on top of the raw :term:`userid`
+A :term:`principal` is a level of abstraction on top of the raw :term:`identity`
that describes the user in terms of its capabilities, roles, or other
identifiers that are easier to generalize. The permissions are then written
against the principals without focusing on the exact user involved.
diff --git a/docs/tutorials/wiki2/src/authentication/tests/conftest.py b/docs/tutorials/wiki2/src/authentication/tests/conftest.py
index 2db65f887..4ac4c60a8 100644
--- a/docs/tutorials/wiki2/src/authentication/tests/conftest.py
+++ b/docs/tutorials/wiki2/src/authentication/tests/conftest.py
@@ -4,10 +4,9 @@ import alembic.command
import os
from pyramid.paster import get_appsettings
from pyramid.scripting import prepare
-from pyramid.testing import DummyRequest
+from pyramid.testing import DummyRequest, testConfig
import pytest
import transaction
-from webob.cookies import Cookie
import webtest
from tutorial import main
@@ -89,37 +88,45 @@ def app_request(app, tm, dbsession):
drawbacks in tests as it's harder to mock data and is heavier.
"""
- env = prepare(registry=app.registry)
- request = env['request']
- request.host = 'example.com'
+ with prepare(registry=app.registry) as env:
+ request = env['request']
+ request.host = 'example.com'
- # without this, request.dbsession will be joined to the same transaction
- # manager but it will be using a different sqlalchemy.orm.Session using
- # a separate database transaction
- request.dbsession = dbsession
- request.tm = tm
+ # without this, request.dbsession will be joined to the same transaction
+ # manager but it will be using a different sqlalchemy.orm.Session using
+ # a separate database transaction
+ request.dbsession = dbsession
+ request.tm = tm
- yield request
- env['closer']()
+ yield request
@pytest.fixture
-def dummy_request(app, tm, dbsession):
+def dummy_request(tm, dbsession):
"""
A lightweight dummy request.
- This request is ultra-lightweight and should be used only when the
- request itself is not a large focus in the call-stack.
-
- It is way easier to mock and control side-effects using this object.
+ This request is ultra-lightweight and should be used only when the request
+ itself is not a large focus in the call-stack. It is much easier to mock
+ and control side-effects using this object, however:
- It does not have request extensions applied.
- Threadlocals are not properly pushed.
"""
request = DummyRequest()
- request.registry = app.registry
request.host = 'example.com'
request.dbsession = dbsession
request.tm = tm
return request
+
+@pytest.fixture
+def dummy_config(dummy_request):
+ """
+ A dummy :class:`pyramid.config.Configurator` object. This allows for
+ mock configuration, including configuration for ``dummy_request``, as well
+ as pushing the appropriate threadlocals.
+
+ """
+ with testConfig(request=dummy_request) as config:
+ yield config
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/security.py b/docs/tutorials/wiki2/src/authentication/tutorial/security.py
index a4843f286..e0d8ed965 100644
--- a/docs/tutorials/wiki2/src/authentication/tutorial/security.py
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/security.py
@@ -40,5 +40,3 @@ def includeme(config):
config.set_default_csrf_options(require_csrf=True)
config.set_security_policy(MySecurityPolicy(settings['auth.secret']))
- config.add_request_method(
- lambda request: request.identity, 'user', property=True)
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/authentication/tutorial/templates/layout.jinja2
index 64a1db0c5..55f4a85dc 100644
--- a/docs/tutorials/wiki2/src/authentication/tutorial/templates/layout.jinja2
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/templates/layout.jinja2
@@ -33,13 +33,13 @@
</div>
<div class="col-md-10">
<div class="content">
- {% if request.user is none %}
+ {% if not request.is_authenticated %}
<p class="pull-right">
<a href="{{ request.route_url('login') }}">Login</a>
</p>
{% else %}
<form class="pull-right" action="{{ request.route_url('logout') }}" method="post">
- {{request.user.name}}
+ {{request.identity.name}}
<input type="hidden" name="csrf_token" value="{{ get_csrf_token() }}">
<button class="btn btn-link" type="submit">Logout</button>
</form>
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/views/auth.py b/docs/tutorials/wiki2/src/authentication/tutorial/views/auth.py
index e1a564415..807ff3464 100644
--- a/docs/tutorials/wiki2/src/authentication/tutorial/views/auth.py
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/views/auth.py
@@ -53,7 +53,7 @@ def logout(request):
@forbidden_view_config(renderer='tutorial:templates/403.jinja2')
def forbidden_view(exc, request):
- if request.user is None:
+ if not request.is_authenticated:
next_url = request.route_url('login', _query={'next': request.url})
return HTTPSeeOther(location=next_url)
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py b/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py
index 378ce0ae9..4fb715737 100644
--- a/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py
@@ -45,7 +45,7 @@ def view_page(request):
def edit_page(request):
pagename = request.matchdict['pagename']
page = request.dbsession.query(models.Page).filter_by(name=pagename).one()
- user = request.user
+ user = request.identity
if user is None or (user.role != 'editor' and page.creator != user):
raise HTTPForbidden
if request.method == 'POST':
@@ -60,7 +60,7 @@ def edit_page(request):
@view_config(route_name='add_page', renderer='tutorial:templates/edit.jinja2')
def add_page(request):
- user = request.user
+ user = request.identity
if user is None or user.role not in ('editor', 'basic'):
raise HTTPForbidden
pagename = request.matchdict['pagename']
@@ -70,7 +70,7 @@ def add_page(request):
if request.method == 'POST':
body = request.params['body']
page = models.Page(name=pagename, data=body)
- page.creator = request.user
+ page.creator = request.identity
request.dbsession.add(page)
next_url = request.route_url('view_page', pagename=pagename)
return HTTPSeeOther(location=next_url)
diff --git a/docs/tutorials/wiki2/src/authorization/tests/conftest.py b/docs/tutorials/wiki2/src/authorization/tests/conftest.py
index 2db65f887..4ac4c60a8 100644
--- a/docs/tutorials/wiki2/src/authorization/tests/conftest.py
+++ b/docs/tutorials/wiki2/src/authorization/tests/conftest.py
@@ -4,10 +4,9 @@ import alembic.command
import os
from pyramid.paster import get_appsettings
from pyramid.scripting import prepare
-from pyramid.testing import DummyRequest
+from pyramid.testing import DummyRequest, testConfig
import pytest
import transaction
-from webob.cookies import Cookie
import webtest
from tutorial import main
@@ -89,37 +88,45 @@ def app_request(app, tm, dbsession):
drawbacks in tests as it's harder to mock data and is heavier.
"""
- env = prepare(registry=app.registry)
- request = env['request']
- request.host = 'example.com'
+ with prepare(registry=app.registry) as env:
+ request = env['request']
+ request.host = 'example.com'
- # without this, request.dbsession will be joined to the same transaction
- # manager but it will be using a different sqlalchemy.orm.Session using
- # a separate database transaction
- request.dbsession = dbsession
- request.tm = tm
+ # without this, request.dbsession will be joined to the same transaction
+ # manager but it will be using a different sqlalchemy.orm.Session using
+ # a separate database transaction
+ request.dbsession = dbsession
+ request.tm = tm
- yield request
- env['closer']()
+ yield request
@pytest.fixture
-def dummy_request(app, tm, dbsession):
+def dummy_request(tm, dbsession):
"""
A lightweight dummy request.
- This request is ultra-lightweight and should be used only when the
- request itself is not a large focus in the call-stack.
-
- It is way easier to mock and control side-effects using this object.
+ This request is ultra-lightweight and should be used only when the request
+ itself is not a large focus in the call-stack. It is much easier to mock
+ and control side-effects using this object, however:
- It does not have request extensions applied.
- Threadlocals are not properly pushed.
"""
request = DummyRequest()
- request.registry = app.registry
request.host = 'example.com'
request.dbsession = dbsession
request.tm = tm
return request
+
+@pytest.fixture
+def dummy_config(dummy_request):
+ """
+ A dummy :class:`pyramid.config.Configurator` object. This allows for
+ mock configuration, including configuration for ``dummy_request``, as well
+ as pushing the appropriate threadlocals.
+
+ """
+ with testConfig(request=dummy_request) as config:
+ yield config
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/security.py b/docs/tutorials/wiki2/src/authorization/tutorial/security.py
index 4f79195ef..18f0bd4c7 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/security.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/security.py
@@ -59,5 +59,3 @@ def includeme(config):
config.set_default_csrf_options(require_csrf=True)
config.set_security_policy(MySecurityPolicy(settings['auth.secret']))
- config.add_request_method(
- lambda request: request.identity, 'user', property=True)
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja2
index 64a1db0c5..55f4a85dc 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja2
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja2
@@ -33,13 +33,13 @@
</div>
<div class="col-md-10">
<div class="content">
- {% if request.user is none %}
+ {% if not request.is_authenticated %}
<p class="pull-right">
<a href="{{ request.route_url('login') }}">Login</a>
</p>
{% else %}
<form class="pull-right" action="{{ request.route_url('logout') }}" method="post">
- {{request.user.name}}
+ {{request.identity.name}}
<input type="hidden" name="csrf_token" value="{{ get_csrf_token() }}">
<button class="btn btn-link" type="submit">Logout</button>
</form>
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views/auth.py b/docs/tutorials/wiki2/src/authorization/tutorial/views/auth.py
index e1a564415..807ff3464 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/views/auth.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/views/auth.py
@@ -53,7 +53,7 @@ def logout(request):
@forbidden_view_config(renderer='tutorial:templates/403.jinja2')
def forbidden_view(exc, request):
- if request.user is None:
+ if not request.is_authenticated:
next_url = request.route_url('login', _query={'next': request.url})
return HTTPSeeOther(location=next_url)
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py b/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py
index 214788357..4a2a66c84 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py
@@ -56,7 +56,7 @@ def add_page(request):
if request.method == 'POST':
body = request.params['body']
page = models.Page(name=pagename, data=body)
- page.creator = request.user
+ page.creator = request.identity
request.dbsession.add(page)
next_url = request.route_url('view_page', pagename=pagename)
return HTTPSeeOther(location=next_url)
diff --git a/docs/tutorials/wiki2/src/basiclayout/tests/conftest.py b/docs/tutorials/wiki2/src/basiclayout/tests/conftest.py
index 2db65f887..4ac4c60a8 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tests/conftest.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tests/conftest.py
@@ -4,10 +4,9 @@ import alembic.command
import os
from pyramid.paster import get_appsettings
from pyramid.scripting import prepare
-from pyramid.testing import DummyRequest
+from pyramid.testing import DummyRequest, testConfig
import pytest
import transaction
-from webob.cookies import Cookie
import webtest
from tutorial import main
@@ -89,37 +88,45 @@ def app_request(app, tm, dbsession):
drawbacks in tests as it's harder to mock data and is heavier.
"""
- env = prepare(registry=app.registry)
- request = env['request']
- request.host = 'example.com'
+ with prepare(registry=app.registry) as env:
+ request = env['request']
+ request.host = 'example.com'
- # without this, request.dbsession will be joined to the same transaction
- # manager but it will be using a different sqlalchemy.orm.Session using
- # a separate database transaction
- request.dbsession = dbsession
- request.tm = tm
+ # without this, request.dbsession will be joined to the same transaction
+ # manager but it will be using a different sqlalchemy.orm.Session using
+ # a separate database transaction
+ request.dbsession = dbsession
+ request.tm = tm
- yield request
- env['closer']()
+ yield request
@pytest.fixture
-def dummy_request(app, tm, dbsession):
+def dummy_request(tm, dbsession):
"""
A lightweight dummy request.
- This request is ultra-lightweight and should be used only when the
- request itself is not a large focus in the call-stack.
-
- It is way easier to mock and control side-effects using this object.
+ This request is ultra-lightweight and should be used only when the request
+ itself is not a large focus in the call-stack. It is much easier to mock
+ and control side-effects using this object, however:
- It does not have request extensions applied.
- Threadlocals are not properly pushed.
"""
request = DummyRequest()
- request.registry = app.registry
request.host = 'example.com'
request.dbsession = dbsession
request.tm = tm
return request
+
+@pytest.fixture
+def dummy_config(dummy_request):
+ """
+ A dummy :class:`pyramid.config.Configurator` object. This allows for
+ mock configuration, including configuration for ``dummy_request``, as well
+ as pushing the appropriate threadlocals.
+
+ """
+ with testConfig(request=dummy_request) as config:
+ yield config
diff --git a/docs/tutorials/wiki2/src/installation/tests/conftest.py b/docs/tutorials/wiki2/src/installation/tests/conftest.py
index 2db65f887..4ac4c60a8 100644
--- a/docs/tutorials/wiki2/src/installation/tests/conftest.py
+++ b/docs/tutorials/wiki2/src/installation/tests/conftest.py
@@ -4,10 +4,9 @@ import alembic.command
import os
from pyramid.paster import get_appsettings
from pyramid.scripting import prepare
-from pyramid.testing import DummyRequest
+from pyramid.testing import DummyRequest, testConfig
import pytest
import transaction
-from webob.cookies import Cookie
import webtest
from tutorial import main
@@ -89,37 +88,45 @@ def app_request(app, tm, dbsession):
drawbacks in tests as it's harder to mock data and is heavier.
"""
- env = prepare(registry=app.registry)
- request = env['request']
- request.host = 'example.com'
+ with prepare(registry=app.registry) as env:
+ request = env['request']
+ request.host = 'example.com'
- # without this, request.dbsession will be joined to the same transaction
- # manager but it will be using a different sqlalchemy.orm.Session using
- # a separate database transaction
- request.dbsession = dbsession
- request.tm = tm
+ # without this, request.dbsession will be joined to the same transaction
+ # manager but it will be using a different sqlalchemy.orm.Session using
+ # a separate database transaction
+ request.dbsession = dbsession
+ request.tm = tm
- yield request
- env['closer']()
+ yield request
@pytest.fixture
-def dummy_request(app, tm, dbsession):
+def dummy_request(tm, dbsession):
"""
A lightweight dummy request.
- This request is ultra-lightweight and should be used only when the
- request itself is not a large focus in the call-stack.
-
- It is way easier to mock and control side-effects using this object.
+ This request is ultra-lightweight and should be used only when the request
+ itself is not a large focus in the call-stack. It is much easier to mock
+ and control side-effects using this object, however:
- It does not have request extensions applied.
- Threadlocals are not properly pushed.
"""
request = DummyRequest()
- request.registry = app.registry
request.host = 'example.com'
request.dbsession = dbsession
request.tm = tm
return request
+
+@pytest.fixture
+def dummy_config(dummy_request):
+ """
+ A dummy :class:`pyramid.config.Configurator` object. This allows for
+ mock configuration, including configuration for ``dummy_request``, as well
+ as pushing the appropriate threadlocals.
+
+ """
+ with testConfig(request=dummy_request) as config:
+ yield config
diff --git a/docs/tutorials/wiki2/src/models/tests/conftest.py b/docs/tutorials/wiki2/src/models/tests/conftest.py
index 2db65f887..4ac4c60a8 100644
--- a/docs/tutorials/wiki2/src/models/tests/conftest.py
+++ b/docs/tutorials/wiki2/src/models/tests/conftest.py
@@ -4,10 +4,9 @@ import alembic.command
import os
from pyramid.paster import get_appsettings
from pyramid.scripting import prepare
-from pyramid.testing import DummyRequest
+from pyramid.testing import DummyRequest, testConfig
import pytest
import transaction
-from webob.cookies import Cookie
import webtest
from tutorial import main
@@ -89,37 +88,45 @@ def app_request(app, tm, dbsession):
drawbacks in tests as it's harder to mock data and is heavier.
"""
- env = prepare(registry=app.registry)
- request = env['request']
- request.host = 'example.com'
+ with prepare(registry=app.registry) as env:
+ request = env['request']
+ request.host = 'example.com'
- # without this, request.dbsession will be joined to the same transaction
- # manager but it will be using a different sqlalchemy.orm.Session using
- # a separate database transaction
- request.dbsession = dbsession
- request.tm = tm
+ # without this, request.dbsession will be joined to the same transaction
+ # manager but it will be using a different sqlalchemy.orm.Session using
+ # a separate database transaction
+ request.dbsession = dbsession
+ request.tm = tm
- yield request
- env['closer']()
+ yield request
@pytest.fixture
-def dummy_request(app, tm, dbsession):
+def dummy_request(tm, dbsession):
"""
A lightweight dummy request.
- This request is ultra-lightweight and should be used only when the
- request itself is not a large focus in the call-stack.
-
- It is way easier to mock and control side-effects using this object.
+ This request is ultra-lightweight and should be used only when the request
+ itself is not a large focus in the call-stack. It is much easier to mock
+ and control side-effects using this object, however:
- It does not have request extensions applied.
- Threadlocals are not properly pushed.
"""
request = DummyRequest()
- request.registry = app.registry
request.host = 'example.com'
request.dbsession = dbsession
request.tm = tm
return request
+
+@pytest.fixture
+def dummy_config(dummy_request):
+ """
+ A dummy :class:`pyramid.config.Configurator` object. This allows for
+ mock configuration, including configuration for ``dummy_request``, as well
+ as pushing the appropriate threadlocals.
+
+ """
+ with testConfig(request=dummy_request) as config:
+ yield config
diff --git a/docs/tutorials/wiki2/src/tests/tests/conftest.py b/docs/tutorials/wiki2/src/tests/tests/conftest.py
index 1c8fb16d0..5ef28acd1 100644
--- a/docs/tutorials/wiki2/src/tests/tests/conftest.py
+++ b/docs/tutorials/wiki2/src/tests/tests/conftest.py
@@ -4,7 +4,7 @@ import alembic.command
import os
from pyramid.paster import get_appsettings
from pyramid.scripting import prepare
-from pyramid.testing import DummyRequest
+from pyramid.testing import DummyRequest, testConfig
import pytest
import transaction
from webob.cookies import Cookie
@@ -130,37 +130,45 @@ def app_request(app, tm, dbsession):
drawbacks in tests as it's harder to mock data and is heavier.
"""
- env = prepare(registry=app.registry)
- request = env['request']
- request.host = 'example.com'
+ with prepare(registry=app.registry) as env:
+ request = env['request']
+ request.host = 'example.com'
- # without this, request.dbsession will be joined to the same transaction
- # manager but it will be using a different sqlalchemy.orm.Session using
- # a separate database transaction
- request.dbsession = dbsession
- request.tm = tm
+ # without this, request.dbsession will be joined to the same transaction
+ # manager but it will be using a different sqlalchemy.orm.Session using
+ # a separate database transaction
+ request.dbsession = dbsession
+ request.tm = tm
- yield request
- env['closer']()
+ yield request
@pytest.fixture
-def dummy_request(app, tm, dbsession):
+def dummy_request(tm, dbsession):
"""
A lightweight dummy request.
- This request is ultra-lightweight and should be used only when the
- request itself is not a large focus in the call-stack.
-
- It is way easier to mock and control side-effects using this object.
+ This request is ultra-lightweight and should be used only when the request
+ itself is not a large focus in the call-stack. It is much easier to mock
+ and control side-effects using this object, however:
- It does not have request extensions applied.
- Threadlocals are not properly pushed.
"""
request = DummyRequest()
- request.registry = app.registry
request.host = 'example.com'
request.dbsession = dbsession
request.tm = tm
return request
+
+@pytest.fixture
+def dummy_config(dummy_request):
+ """
+ A dummy :class:`pyramid.config.Configurator` object. This allows for
+ mock configuration, including configuration for ``dummy_request``, as well
+ as pushing the appropriate threadlocals.
+
+ """
+ with testConfig(request=dummy_request) as config:
+ yield config
diff --git a/docs/tutorials/wiki2/src/tests/tests/test_views.py b/docs/tutorials/wiki2/src/tests/tests/test_views.py
index 007184af8..e93b04b3c 100644
--- a/docs/tutorials/wiki2/src/tests/tests/test_views.py
+++ b/docs/tutorials/wiki2/src/tests/tests/test_views.py
@@ -1,9 +1,17 @@
+from pyramid.testing import DummySecurityPolicy
+
from tutorial import models
def makeUser(name, role):
return models.User(name=name, role=role)
+
+def setUser(config, user):
+ config.set_security_policy(
+ DummySecurityPolicy(identity=user)
+ )
+
def makePage(name, data, creator):
return models.Page(name=name, data=data, creator=creator)
@@ -12,7 +20,11 @@ class Test_view_wiki:
from tutorial.views.default import view_wiki
return view_wiki(request)
- def test_it(self, dummy_request):
+ def _addRoutes(self, config):
+ config.add_route('view_page', '/{pagename}')
+
+ def test_it(self, dummy_config, dummy_request):
+ self._addRoutes(dummy_config)
response = self._callFUT(dummy_request)
assert response.location == 'http://example.com/FrontPage'
@@ -25,13 +37,19 @@ class Test_view_page:
from tutorial.routes import PageResource
return PageResource(page)
- def test_it(self, dummy_request, dbsession):
+ def _addRoutes(self, config):
+ config.add_route('edit_page', '/{pagename}/edit_page')
+ config.add_route('add_page', '/add_page/{pagename}')
+ config.add_route('view_page', '/{pagename}')
+
+ def test_it(self, dummy_config, dummy_request, dbsession):
# add a page to the db
user = makeUser('foo', 'editor')
page = makePage('IDoExist', 'Hello CruelWorld IDoExist', user)
dbsession.add_all([page, user])
# create a request asking for the page we've created
+ self._addRoutes(dummy_config)
dummy_request.context = self._makeContext(page)
# call the view we're testing and check its behavior
@@ -56,18 +74,24 @@ class Test_add_page:
from tutorial.routes import NewPage
return NewPage(pagename)
- def test_get(self, dummy_request, dbsession):
- dummy_request.user = makeUser('foo', 'editor')
+ def _addRoutes(self, config):
+ config.add_route('add_page', '/add_page/{pagename}')
+ config.add_route('view_page', '/{pagename}')
+
+ def test_get(self, dummy_config, dummy_request, dbsession):
+ setUser(dummy_config, makeUser('foo', 'editor'))
+ self._addRoutes(dummy_config)
dummy_request.context = self._makeContext('AnotherPage')
info = self._callFUT(dummy_request)
assert info['pagedata'] == ''
assert info['save_url'] == 'http://example.com/add_page/AnotherPage'
- def test_submit_works(self, dummy_request, dbsession):
+ def test_submit_works(self, dummy_config, dummy_request, dbsession):
dummy_request.method = 'POST'
dummy_request.POST['body'] = 'Hello yo!'
dummy_request.context = self._makeContext('AnotherPage')
- dummy_request.user = makeUser('foo', 'editor')
+ setUser(dummy_config, makeUser('foo', 'editor'))
+ self._addRoutes(dummy_config)
self._callFUT(dummy_request)
page = (
dbsession.query(models.Page)
@@ -85,24 +109,30 @@ class Test_edit_page:
from tutorial.routes import PageResource
return PageResource(page)
- def test_get(self, dummy_request, dbsession):
+ def _addRoutes(self, config):
+ config.add_route('edit_page', '/{pagename}/edit_page')
+ config.add_route('view_page', '/{pagename}')
+
+ def test_get(self, dummy_config, dummy_request, dbsession):
user = makeUser('foo', 'editor')
page = makePage('abc', 'hello', user)
dbsession.add_all([page, user])
+ self._addRoutes(dummy_config)
dummy_request.context = self._makeContext(page)
info = self._callFUT(dummy_request)
assert info['pagename'] == 'abc'
assert info['save_url'] == 'http://example.com/abc/edit_page'
- def test_submit_works(self, dummy_request, dbsession):
+ def test_submit_works(self, dummy_config, dummy_request, dbsession):
user = makeUser('foo', 'editor')
page = makePage('abc', 'hello', user)
dbsession.add_all([page, user])
+ self._addRoutes(dummy_config)
dummy_request.method = 'POST'
dummy_request.POST['body'] = 'Hello yo!'
- dummy_request.user = user
+ setUser(dummy_config, user)
dummy_request.context = self._makeContext(page)
response = self._callFUT(dummy_request)
assert response.location == 'http://example.com/abc'
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/security.py b/docs/tutorials/wiki2/src/tests/tutorial/security.py
index 4f79195ef..18f0bd4c7 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/security.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/security.py
@@ -59,5 +59,3 @@ def includeme(config):
config.set_default_csrf_options(require_csrf=True)
config.set_security_policy(MySecurityPolicy(settings['auth.secret']))
- config.add_request_method(
- lambda request: request.identity, 'user', property=True)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2
index 64a1db0c5..55f4a85dc 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2
+++ b/docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2
@@ -33,13 +33,13 @@
</div>
<div class="col-md-10">
<div class="content">
- {% if request.user is none %}
+ {% if not request.is_authenticated %}
<p class="pull-right">
<a href="{{ request.route_url('login') }}">Login</a>
</p>
{% else %}
<form class="pull-right" action="{{ request.route_url('logout') }}" method="post">
- {{request.user.name}}
+ {{request.identity.name}}
<input type="hidden" name="csrf_token" value="{{ get_csrf_token() }}">
<button class="btn btn-link" type="submit">Logout</button>
</form>
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views/auth.py b/docs/tutorials/wiki2/src/tests/tutorial/views/auth.py
index e1a564415..807ff3464 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/views/auth.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/views/auth.py
@@ -53,7 +53,7 @@ def logout(request):
@forbidden_view_config(renderer='tutorial:templates/403.jinja2')
def forbidden_view(exc, request):
- if request.user is None:
+ if not request.is_authenticated:
next_url = request.route_url('login', _query={'next': request.url})
return HTTPSeeOther(location=next_url)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views/default.py b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py
index 214788357..4a2a66c84 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/views/default.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py
@@ -56,7 +56,7 @@ def add_page(request):
if request.method == 'POST':
body = request.params['body']
page = models.Page(name=pagename, data=body)
- page.creator = request.user
+ page.creator = request.identity
request.dbsession.add(page)
next_url = request.route_url('view_page', pagename=pagename)
return HTTPSeeOther(location=next_url)
diff --git a/docs/tutorials/wiki2/src/views/tests/conftest.py b/docs/tutorials/wiki2/src/views/tests/conftest.py
index 2db65f887..4ac4c60a8 100644
--- a/docs/tutorials/wiki2/src/views/tests/conftest.py
+++ b/docs/tutorials/wiki2/src/views/tests/conftest.py
@@ -4,10 +4,9 @@ import alembic.command
import os
from pyramid.paster import get_appsettings
from pyramid.scripting import prepare
-from pyramid.testing import DummyRequest
+from pyramid.testing import DummyRequest, testConfig
import pytest
import transaction
-from webob.cookies import Cookie
import webtest
from tutorial import main
@@ -89,37 +88,45 @@ def app_request(app, tm, dbsession):
drawbacks in tests as it's harder to mock data and is heavier.
"""
- env = prepare(registry=app.registry)
- request = env['request']
- request.host = 'example.com'
+ with prepare(registry=app.registry) as env:
+ request = env['request']
+ request.host = 'example.com'
- # without this, request.dbsession will be joined to the same transaction
- # manager but it will be using a different sqlalchemy.orm.Session using
- # a separate database transaction
- request.dbsession = dbsession
- request.tm = tm
+ # without this, request.dbsession will be joined to the same transaction
+ # manager but it will be using a different sqlalchemy.orm.Session using
+ # a separate database transaction
+ request.dbsession = dbsession
+ request.tm = tm
- yield request
- env['closer']()
+ yield request
@pytest.fixture
-def dummy_request(app, tm, dbsession):
+def dummy_request(tm, dbsession):
"""
A lightweight dummy request.
- This request is ultra-lightweight and should be used only when the
- request itself is not a large focus in the call-stack.
-
- It is way easier to mock and control side-effects using this object.
+ This request is ultra-lightweight and should be used only when the request
+ itself is not a large focus in the call-stack. It is much easier to mock
+ and control side-effects using this object, however:
- It does not have request extensions applied.
- Threadlocals are not properly pushed.
"""
request = DummyRequest()
- request.registry = app.registry
request.host = 'example.com'
request.dbsession = dbsession
request.tm = tm
return request
+
+@pytest.fixture
+def dummy_config(dummy_request):
+ """
+ A dummy :class:`pyramid.config.Configurator` object. This allows for
+ mock configuration, including configuration for ``dummy_request``, as well
+ as pushing the appropriate threadlocals.
+
+ """
+ with testConfig(request=dummy_request) as config:
+ yield config
diff --git a/docs/tutorials/wiki2/tests.rst b/docs/tutorials/wiki2/tests.rst
index 1bf38d988..dce14cf9b 100644
--- a/docs/tutorials/wiki2/tests.rst
+++ b/docs/tutorials/wiki2/tests.rst
@@ -69,6 +69,9 @@ Per-test fixtures
- ``dummy_request`` - a :class:`pyramid.testing.DummyRequest` object that is very lightweight.
This is a great object to pass to view functions that have minimal side-effects as it'll be fast and simple.
+- ``dummy_config`` — a :class:`pyramid.config.Configurator` object used as configuration by ``dummy_request``.
+ Useful for mocking configuration like routes and security policies.
+
Modifying the fixtures
----------------------
@@ -109,8 +112,8 @@ Integration tests
=================
We can directly execute the view code, bypassing :app:`Pyramid` and testing just the code that we've written.
-These tests use dummy requests that we'll prepare appropriately to set the conditions each view expects.
-For example, setting ``request.user``, or adding some dummy data to the session.
+These tests use dummy requests that we'll prepare appropriately to set the conditions each view expects, such as adding dummy data to the session.
+We'll be using ``dummy_config`` to configure the necessary routes, as well as setting the security policy as :class:`pyramid.testing.DummySecurityPolicy` to mock ``dummy_request.identity``.
Update ``tests/test_views.py`` such that it appears as follows:
diff --git a/docs/whatsnew-1.10.rst b/docs/whatsnew-1.10.rst
index 53eed6f87..6e960f4d2 100644
--- a/docs/whatsnew-1.10.rst
+++ b/docs/whatsnew-1.10.rst
@@ -26,7 +26,7 @@ Feature Additions
See https://github.com/Pylons/pyramid/pull/3326
- Added :class:`pyramid.session.JSONSerializer`.
- See :ref:`pickle_session_deprecation` for more information about this feature.
+ See :ref:`upgrading_session_20` for more information about this feature.
See https://github.com/Pylons/pyramid/pull/3353
- Modify the builtin session implementations to support ``SameSite`` options on cookies and set the default to ``'Lax'``.
@@ -69,7 +69,7 @@ Feature Additions
Deprecations
------------
-- The :class:`pyramid.interfaces.ISession` interface will move to require JSON-serializable objects in :app:`Pyramid` 2.0. See :ref:`pickle_session_deprecation` for more information about this change.
+- The :class:`pyramid.interfaces.ISession` interface will move to require JSON-serializable objects in :app:`Pyramid` 2.0. See :ref:`upgrading_session_20` for more information about this change.
See https://github.com/Pylons/pyramid/pull/3353
- The :func:`pyramid.session.signed_serialize` and :func:`pyramid.session.signed_deserialize` functions will be removed in :app:`Pyramid` 2.0, along with the removal of :func:`pyramid.session.UnencryptedCookieSessionFactoryConfig` which was deprecated in :app:`Pyramid` 1.5.
diff --git a/docs/whatsnew-1.5.rst b/docs/whatsnew-1.5.rst
index 753dfd355..417b8c7b5 100644
--- a/docs/whatsnew-1.5.rst
+++ b/docs/whatsnew-1.5.rst
@@ -315,7 +315,7 @@ The feature additions in Pyramid 1.5 follow.
passing the predicate factory directly, you can pass a dotted name which
refers to the factory.
-- :func:`pyramid.path.package_name` no longer thows an exception when resolving
+- :func:`pyramid.path.package_name` no longer throws an exception when resolving
the package name for namespace packages that have no ``__file__`` attribute.
- An authorization API has been added as a method of the request:
diff --git a/docs/whatsnew-2.0.rst b/docs/whatsnew-2.0.rst
index 906529d6b..e97198b23 100644
--- a/docs/whatsnew-2.0.rst
+++ b/docs/whatsnew-2.0.rst
@@ -1,69 +1,178 @@
What's New in Pyramid 2.0
=========================
-This article explains the new features in :app:`Pyramid` version 2.0 as
-compared to its predecessor, :app:`Pyramid` 1.10. It also documents backwards
-incompatibilities between the two versions and deprecations added to
-:app:`Pyramid` 2.0, as well as software dependency changes and notable
-documentation additions.
+This article explains the new features in :app:`Pyramid` version 2.0 as compared to its predecessor, :app:`Pyramid` 1.10.
+It also documents backwards incompatibilities between the two versions and deprecations added to :app:`Pyramid` 2.0, as well as software dependency changes and notable documentation additions.
+
+.. note::
+
+ This is the first release of :app:`Pyramid` that does not support Python 2, which is now End-of-Life and no longer receiving critical security updates by the PSF.
Feature Additions
-----------------
The feature additions in Pyramid 2.0 are as follows:
-- The authentication and authorization policies of Pyramid 1.x have been merged
- into a single :term:`security policy` in Pyramid 2.0. For details on how to
- migrate to the new security policy, see :ref:`upgrading_auth`.
- Authentication and authorization policies can still be used and will continue
- to function normally for the time being.
+- The authentication and authorization policies of Pyramid 1.x have been merged into a single :term:`security policy` in Pyramid 2.0.
+ For details on how to migrate to the new security policy, see :ref:`upgrading_auth_20`.
+ Authentication and authorization policies can still be used and will continue to function normally for the time being.
+
+ New security APIs have been added to support an overhaul of the authentication and authorization system.
+ Read :ref:`upgrading_auth_20` for information about using this new system.
+
+ - :meth:`pyramid.config.Configurator.set_security_policy`
+ - :class:`pyramid.interfaces.ISecurityPolicy`
+ - :attr:`pyramid.request.Request.identity`
+ - :class:`pyramid.authentication.AuthTktCookieHelper` (available in Pyramid 1.x)
+ - :class:`pyramid.authentication.SessionAuthenticationHelper`
+ - :class:`pyramid.authorization.ACLHelper`
+
+ See https://github.com/Pylons/pyramid/pull/3465
+
+- Exposed :data:`pyramid.authorization.ALL_PERMISSIONS` and :data:`pyramid.authorization.DENY_ALL` such that all of the ACL-related constants are now importable from the ``pyramid.authorization`` namespace.
+ See https://github.com/Pylons/pyramid/pull/3563
+
+- Changed the default ``serializer`` on :class:`pyramid.session.SignedCookieSessionFactory` to use :class:`pyramid.session.JSONSerializer` instead of :class:`pyramid.session.PickleSerializer`.
+ Read :ref:`upgrading_session_20` for more information about why this change was made.
+ See https://github.com/Pylons/pyramid/pull/3413
+
+- It is now possible to control whether a route pattern contains a trailing slash when it is composed with a route prefix using
+ ``config.include(..., route_prefix=...)`` or ``with config.route_prefix_context(...)``.
+ This can be done by specifying an empty pattern and setting the new argument ``inherit_slash=True``.
+ For example:
+
+ .. code-block:: python
+
+ with config.route_prefix_context('/users'):
+ config.add_route('users', '', inherit_slash=True)
+
+ In the example, the resulting pattern will be ``/users``.
+ Similarly, if the route prefix were ``/users/`` then the final pattern would be ``/users/``.
+ If the ``pattern`` was ``'/'``, then the final pattern would always be ``/users/``.
+ This new setting is only available if the pattern supplied to ``add_route`` is the empty string (``''``).
+ See https://github.com/Pylons/pyramid/pull/3420
+
+- A new parameter, ``allow_no_origin``, was added to :meth:`pyramid.config.Configurator.set_default_csrf_options` as well as :func:`pyramid.csrf.check_csrf_origin`.
+ This option controls whether a request is rejected if it has no ``Origin`` or ``Referer`` header - often the result of a user configuring their browser not to send a ``Referer`` header for privacy reasons even on same-domain requests.
+ The default is to reject requests without a known origin.
+ It is also possible to allow the special ``Origin: null`` header by adding it to the ``pyramid.csrf_trusted_origins`` list in the settings.
+ See https://github.com/Pylons/pyramid/pull/3512 and https://github.com/Pylons/pyramid/pull/3518
+
+- A new parameter, ``check_origin``, was added to :meth:`pyramid.config.Configurator.set_default_csrf_options` which disables origin checking entirely.
+ See https://github.com/Pylons/pyramid/pull/3518
+
+- Added :class:`pyramid.interfaces.IPredicateInfo` which defines the object passed to predicate factories as their second argument.
+ See https://github.com/Pylons/pyramid/pull/3514
+
+- Added support for serving pre-compressed static assets by using the ``content_encodings`` argument of :meth:`pyramid.config.Configurator.add_static_view` and :func:`pyramid.static.static_view`.
+ See https://github.com/Pylons/pyramid/pull/3537
+
+- Fix ``DeprecationWarning`` emitted by using the ``imp`` module.
+ See https://github.com/Pylons/pyramid/pull/3553
+
+- Properties created via ``config.add_request_method(..., property=True)`` or ``request.set_property`` used to be readonly.
+ They can now be overridden via ``request.foo = ...`` and until the value is deleted it will return the overridden value.
+ This is most useful when mocking request properties in testing.
+ See https://github.com/Pylons/pyramid/pull/3559
+
+- Finished callbacks are now executed as part of the ``closer`` that is invoked as part of :func:`pyramid.scripting.prepare` and :func:`pyramid.paster.bootstrap`.
+ See https://github.com/Pylons/pyramid/pull/3561
+
+- Added :class:`pyramid.request.RequestLocalCache` which can be used to create simple objects that are shared across requests and can be used to store per-request data.
+ This is useful when the source of data is external to the request itself.
+ Often a reified property is used on a request via :meth:`pyramid.config.Configurator.add_request_method`, or :class:`pyramid.decorator.reify`.
+ These work great when the data is generated on-demand when accessing the request property.
+ However, often the case is that the data is generated when accessing some other system and then we want to cache the data for the duration of the request.
+ See https://github.com/Pylons/pyramid/pull/3561
+
+- No longer define ``pyramid.request.Request.json_body`` which is already provided by WebOb.
+ This allows the attribute to now be settable.
+ See https://github.com/Pylons/pyramid/pull/3447
+
+- Improve debugging info from :class:`pyramid.view.view_config` decorator.
+ See https://github.com/Pylons/pyramid/pull/3483
+
+- ``pserve`` now outputs verbose messaging to `stderr` instead of `stdout` to circumvent buffering issues that exist by default on `stdout`.
+ See https://github.com/Pylons/pyramid/pull/3593
Deprecations
------------
-- Authentication and authorization policies have been deprecated in favor of
- the new :term:`security policy`.
+- Deprecated the authentication and authorization interfaces and principal-based support.
+ See :ref:`upgrading_auth_20` for information on equivalent APIs and notes on upgrading.
+ The following APIs are deprecated as a result of this change:
+
+ - :meth:`pyramid.config.Configurator.set_authentication_policy`
+ - :meth:`pyramid.config.Configurator.set_authorization_policy`
+ - :class:`pyramid.interfaces.IAuthenticationPolicy`
+ - :class:`pyramid.interfaces.IAuthorizationPolicy`
+ - :attr:`pyramid.request.Request.effective_principals`
+ - :attr:`pyramid.request.Request.unauthenticated_userid`
+ - :class:`pyramid.authentication.AuthTktAuthenticationPolicy`
+ - :class:`pyramid.authentication.RemoteUserAuthenticationPolicy`
+ - :class:`pyramid.authentication.RepozeWho1AuthenticationPolicy`
+ - :class:`pyramid.authentication.SessionAuthenticationPolicy`
+ - :class:`pyramid.authentication.BasicAuthAuthenticationPolicy`
+ - :class:`pyramid.authorization.ACLAuthorizationPolicy`
+ - The ``effective_principals`` view and route predicates.
+
+- Deprecated :func:`pyramid.security.principals_allowed_by_permission``.
+ This method continues to work with the deprecated :class:`pyramid.interfaces.IAuthorizationPolicy` interface but will not work with the new :class:`pyramid.interfaces.ISecurityPolicy`.
+ See https://github.com/Pylons/pyramid/pull/3465
+
+- Deprecated several ACL-related aspects of :mod:`pyramid.security`.
+ Equivalent objects should now be imported from the :mod:`pyramid.authorization` module.
+ This includes:
-.. _upgrading_auth:
+ - :attr:`pyramid.security.Everyone`
+ - :attr:`pyramid.security.Authenticated`
+ - :attr:`pyramid.security.ALL_PERMISSIONS`
+ - :attr:`pyramid.security.DENY_ALL`
+ - :attr:`pyramid.security.ACLAllowed`
+ - :attr:`pyramid.security.ACLDenied`
+
+ See https://github.com/Pylons/pyramid/pull/3563
+
+- Deprecated :class:`pyramid.session.PickleSerializer`.
+ See :ref:`upgrading_session_20` for more information, as well as
+ https://github.com/pylons/pyramid/issues/2709,
+ https://github.com/pylons/pyramid/pull/3353,
+ and https://github.com/pylons/pyramid/pull/3413
+
+.. _upgrading_auth_20:
Upgrading Authentication/Authorization
--------------------------------------
-The authentication and authorization policies of Pyramid 1.x have been merged
-into a single :term:`security policy` in Pyramid 2.0. Authentication and
-authorization policies can still be used and will continue to function
-normally, however they have been deprecated and support may be removed in
-upcoming versions.
-
-The new security policy should implement
-:class:`pyramid.interfaces.ISecurityPolicy` and can be set via the
-``security_policy`` argument of :class:`pyramid.config.Configurator` or
-:meth:`pyramid.config.Configurator.set_security_policy`.
-
-The policy contains ``authenticated_userid`` and ``remember``,
-with the same method signatures as in the legacy authentication policy. It
-also contains ``forget``, but now with keyword arguments in the method
-signature.
-
-The new security policy adds the concept of an :term:`identity`, which is an
-object representing the user associated with the current request. The identity
-can be accessed via :attr:`pyramid.request.Request.identity`.
+.. note::
+ It's important to note that the principal and ACL features within :app:`Pyramid` are not going away, nor deprecated, nor removed.
+ Most ACL features are deprecated in their current locations and moved into the :mod:`pyramid.authorization` module.
+ The main change is that they are now more optional than before and modifications were made to make the top-level APIs less opinionated as well as simpler.
+
+:app:`Pyramid` provides a simple set of APIs for plugging in allowed/denied semantics in your application.
+
+The authentication and authorization policies of Pyramid 1.x have been merged into a single :term:`security policy` in Pyramid 2.0.
+Authentication and authorization policies can still be used and will continue to function normally, however they have been deprecated and support may be removed in upcoming versions.
+
+The new security policy should implement :class:`pyramid.interfaces.ISecurityPolicy` and can be set via the ``security_policy`` argument of :class:`pyramid.config.Configurator` or :meth:`pyramid.config.Configurator.set_security_policy`.
+
+The policy contains :meth:`pyramid.interfaces.ISecurityPolicy.authenticated_userid` and :meth:`pyramid.interfaces.ISecurityPolicy.remember`, with the same method signatures as in the legacy authentication policy.
+It also contains :meth:`pyramid.interfaces.ISecurityPolicy.forget`, but now accepting keyword arguments in the method signature.
+
+The new security policy adds the concept of an :term:`identity`, which is an object representing the user associated with the current request.
+The identity can be accessed via :attr:`pyramid.request.Request.identity`.
The object can be of any shape, such as a simple ID string or an ORM object.
-The concept of :term:`principals <principal>` has been removed; the
-``permits`` method is passed an identity object. This change gives much more
-flexibility in authorization implementations, especially those that do not
-match the ACL pattern. If you were previously using
-:class:`pyramid.authorization.ACLAuthorizationPolicy`, you can achieve the same
-results by writing your own ``permits`` method using
-:class:`pyramid.authorization.ACLHelper`. For more details on implementing an
-ACL, see :ref:`assigning_acls`.
-
-Pyramid does not provide any built-in security policies. Similiar
-functionality of the authentication and authorization policies is now provided
-by helpers, which can be utilized to implement your own security policy. The
-functionality of the legacy authentication policies roughly correspond to the
-following helpers:
+The concept of :term:`principals <principal>` has been removed from the request object, security policy, and view/route predicates.
+Principals are replaced by ``identity``.
+The :meth:`pyramid.interfaces.ISecurityPolicy.permits` method is provided the ``request``, ``context``, and ``permissions`` and may now use the ``identity`` object, or derive principals, in any way it deems necessary for the application without being restricted to a list of principals represented by strings.
+This change gives much more flexibility in authorization implementations, especially those that do not match the ACL pattern.
+If you were previously using :class:`pyramid.authorization.ACLAuthorizationPolicy`, you can achieve the same results by writing your own ``permits`` method using :class:`pyramid.authorization.ACLHelper`.
+For more details on implementing an ACL, see :ref:`assigning_acls`.
+
+Pyramid does not provide any built-in security policies.
+Similiar functionality of the authentication and authorization policies is now provided by helpers, which can be utilized to implement your own security policy.
+The functionality of the legacy authentication policies roughly correspond to the following helpers:
+----------------------------------------------------------------+-------------------------------------------------------------------+
| Authentication Policy | Security Policy Helper |
@@ -81,16 +190,189 @@ following helpers:
| :class:`pyramid.authentication.RepozeWho1AuthenticationPolicy` | No equivalent. |
+----------------------------------------------------------------+-------------------------------------------------------------------+
-For further documentation on implementing security policies, see
-:ref:`writing_security_policy`.
+Upgrading from Built-in Policies
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Let's assume your application is using the built-in authentication and authorization policies, like :class:`pyramid.authentication.AuthTktAuthenticationPolicy`.
+For example:
+
+.. code-block:: python
+ :linenos:
+
+ def groupfinder(userid, request):
+ # do some db lookups to verify userid, then return
+ # None if not recognized, or a list of principals
+ if userid == 'editor':
+ return ['group:editor']
+
+ authn_policy = AuthTktAuthenticationPolicy('seekrit', callback=groupfinder)
+ authz_policy = ACLAuthorizationPolicy()
+ config.set_authentication_policy(authn_policy)
+ config.set_authorization_policy(authz_policy)
+
+We can easily write our own :class:`pyramid.interfaces.ISecurityPolicy` implementation:
-.. _behavior_of_legacy_auth:
+.. code-block:: python
+ :linenos:
-Behavior of the Legacy System
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ from pyramid.authentication import AuthTktCookieHelper
+ from pyramid.authorization import ACLHelper, Authenticated, Everyone
+
+ class MySecurityPolicy:
+ def __init__(self, secret):
+ self.helper = AuthTktCookieHelper(secret)
+
+ def identity(self, request):
+ # define our simple identity as None or a dict with userid and principals keys
+ identity = self.helper.identify(request)
+ if identity is None:
+ return None
+ userid = identity['userid'] # identical to the deprecated request.unauthenticated_userid
+
+ # verify the userid, just like we did before with groupfinder
+ principals = groupfinder(userid, request)
+
+ # assuming the userid is valid, return a map with userid and principals
+ if principals is not None:
+ return {
+ 'userid': userid,
+ 'principals': principals,
+ }
+
+ def authenticated_userid(self, request):
+ # defer to the identity logic to determine if the user id logged in
+ # and return None if they are not
+ identity = request.identity
+ if identity is not None:
+ return identity['userid']
+
+ def permits(self, request, context, permission):
+ # use the identity to build a list of principals, and pass them
+ # to the ACLHelper to determine allowed/denied
+ identity = request.identity
+ principals = set([Everyone])
+ if identity is not None:
+ principals.add(Authenticated)
+ principals.add(identity['userid'])
+ principals.update(identity['principals'])
+ return ACLHelper().permits(context, principals, permission)
+
+ def remember(self, request, userid, **kw):
+ return self.helper.remember(request, userid, **kw)
+
+ def forget(self, request, **kw):
+ return self.helper.forget(request, **kw)
+
+ config.set_security_policy(MySecurityPolicy('seekrit'))
+
+This is a little bit more verbose than before, but it is easy to write, and is significantly more extensible for more advanced applications.
+
+- Look at the new :class:`pyramid.request.RequestLocalCache` as well for help in caching the identity for improved performance.
+- Look at the improved :ref:`wiki2_adding_authorization` tutorial for another example of a security policy.
+
+For further documentation on implementing security policies, see :ref:`writing_security_policy`.
+
+Upgrading from Third-Party Policies
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A generic :term:`security policy` can be written to work with legacy authentication and authorization policies.
+Note that some new features like the identity may not be as extensible and nice to use when taking this approach but it can be done to ease the transition:
+
+.. code-block:: python
+ :linenos:
+
+ class ShimSecurityPolicy:
+ def __init__(self, authn_policy, authz_policy):
+ self.authn_policy = authn_policy
+ self.authz_policy = authz_policy
+
+ def authenticated_userid(self, request):
+ return self.authn_policy.authenticated_userid(request)
+
+ def permits(self, request, context, permission):
+ principals = self.authn_policy.effective_principals(request)
+ return self.authz_policy.permits(context, principals, permission)
+
+ def remember(self, request, userid, **kw):
+ return self.authn_policy.remember(request, userid, **kw)
+
+ def forget(self, request, **kw):
+ return self.authz_policy.forget(request, **kw)
+
+Compatibility with Legacy Authentication/Authorization Policies and APIs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you are upgrading from an application that is using the legacy authentication and authorization policies and APIs, things will continue to function normally.
+The new system is backward-compatible and the APIs still exist.
+It is highly encouraged to upgrade in order to embrace the new features.
+The legacy APIs are deprecated and may be removed in the future.
-Legacy authentication and authorization policies will continue to function as normal, as well as all related :class:`pyramid.request.Request` properties.
The new :attr:`pyramid.request.Request.identity` property will output the same result as :attr:`pyramid.request.Request.authenticated_userid`.
-If using a security policy, :attr:`pyramid.request.Request.unauthenticated_userid` will return the same value as :attr:`pyramid.request.Request.authenticated_userid`.
-:attr:`pyramid.request.Request.effective_principals` will always return a one-element list containing the :data:`pyramid.authorization.Everyone` principal, as there is no equivalent in the new security policy.
+If you try to use the new APIs with an application that is using the legacy authentication and authorization policies, then there are some issues to be aware of:
+
+- :attr:`pyramid.request.Request.unauthenticated_userid` will return the same value as :attr:`pyramid.request.Request.authenticated_userid`.
+- :attr:`pyramid.request.Request.effective_principals` will always return a one-element list containing the :data:`pyramid.authorization.Everyone` principal.
+
+.. index::
+ triple: pickle deprecation; JSON-serializable; ISession interface
+
+.. _upgrading_session_20:
+
+Upgrading Session Serialization
+-------------------------------
+
+In :app:`Pyramid` 2.0 the :class:`pyramid.interfaces.ISession` interface was changed to require that session implementations only need to support JSON-serializable data types.
+This is a stricter contract than the previous requirement that all objects be pickleable and it is being done for security purposes.
+This is a backward-incompatible change.
+Previously, if a client-side session implementation was compromised, it left the application vulnerable to remote code execution attacks using specially-crafted sessions that execute code when deserialized.
+
+Please reference the following tickets if detailed information on these changes is needed:
+
+- `2.0 feature request: Require that sessions are JSON serializable #2709 <https://github.com/pylons/pyramid/issues/2709>`_.
+- `deprecate pickleable sessions, recommend json #3353 <https://github.com/pylons/pyramid/pull/3353>`_.
+- `change to use JSONSerializer for SignedCookieSessionFactory #3413 <https://github.com/pylons/pyramid/pull/3413>`_.
+
+For users with compatibility concerns, it's possible to craft a serializer that can handle both formats until you are satisfied that clients have had time to reasonably upgrade.
+Remember that sessions should be short-lived and thus the number of clients affected should be small (no longer than an auth token, at a maximum).
+An example serializer:
+
+.. code-block:: python
+ :linenos:
+
+ import pickle
+ from pyramid.session import JSONSerializer
+ from pyramid.session import SignedCookieSessionFactory
+
+
+ class JSONSerializerWithPickleFallback(object):
+ def __init__(self):
+ self.json = JSONSerializer()
+
+ def dumps(self, appstruct):
+ """
+ Accept a Python object and return bytes.
+
+ During a migration, you may want to catch serialization errors here,
+ and keep using pickle while finding spots in your app that are not
+ storing JSON-serializable objects. You may also want to integrate
+ a fall-back to pickle serialization here as well.
+ """
+ return self.json.dumps(appstruct)
+
+ def loads(self, bstruct):
+ """Accept bytes and return a Python object."""
+ try:
+ return self.json.loads(bstruct)
+ except ValueError:
+ try:
+ return pickle.loads(bstruct)
+ except Exception:
+ # this block should catch at least:
+ # ValueError, AttributeError, ImportError; but more to be safe
+ raise ValueError
+
+ # somewhere in your configuration code
+ serializer = JSONSerializerWithPickleFallback()
+ session_factory = SignedCookieSessionFactory(..., serializer=serializer)
+ config.set_session_factory(session_factory)
diff --git a/pyproject.toml b/pyproject.toml
index 36187efd1..eddde46d9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,5 +1,6 @@
[build-system]
requires = ["setuptools", "wheel"]
+build-backend = "setuptools.build_meta"
[tool.black]
line-length = 79
diff --git a/setup.py b/setup.py
index dcd2fe468..04b78cbeb 100644
--- a/setup.py
+++ b/setup.py
@@ -22,7 +22,7 @@ def readfile(name):
README = readfile('README.rst')
CHANGES = readfile('CHANGES.rst')
-VERSION = '2.0.dev0'
+VERSION = '2.0b0'
install_requires = [
'hupper >= 1.5', # ignore_files support
@@ -41,7 +41,6 @@ tests_require = [
'zope.component >= 4.0', # py3 compat
]
-
docs_extras = [
'Sphinx >= 3.0.0', # Force RTD to use >= 3.0.0
'docutils',
@@ -74,7 +73,6 @@ setup(
"Intended Audience :: Developers",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
@@ -100,7 +98,7 @@ setup(
package_dir={'': 'src'},
include_package_data=True,
zip_safe=False,
- python_requires='>=3.5',
+ python_requires='>=3.6',
install_requires=install_requires,
extras_require={'testing': testing_extras, 'docs': docs_extras},
tests_require=tests_require,
diff --git a/src/pyramid/authorization.py b/src/pyramid/authorization.py
index eadd46f85..2bef53ad9 100644
--- a/src/pyramid/authorization.py
+++ b/src/pyramid/authorization.py
@@ -60,7 +60,7 @@ class ACLAuthorizationPolicy:
.. deprecated:: 2.0
Authorization policies have been deprecated by the new security system.
- See :ref:`upgrading_auth` for more information.
+ See :ref:`upgrading_auth_20` for more information.
"""
diff --git a/src/pyramid/config/security.py b/src/pyramid/config/security.py
index 8939cf62e..59e8d0799 100644
--- a/src/pyramid/config/security.py
+++ b/src/pyramid/config/security.py
@@ -60,8 +60,8 @@ class SecurityConfiguratorMixin:
"""
.. deprecated:: 2.0
- Authentication policies have been replaced by
- security policies. See :ref:`upgrading_auth` for more information.
+ Authentication policies have been replaced by security policies.
+ See :ref:`upgrading_auth_20` for more information.
Override the :app:`Pyramid` :term:`authentication policy` in the
current configuration. The ``policy`` argument must be an instance
@@ -121,8 +121,8 @@ class SecurityConfiguratorMixin:
"""
.. deprecated:: 2.0
- Authentication policies have been replaced by
- security policies. See :ref:`upgrading_auth` for more information.
+ Authentication policies have been replaced by security policies.
+ See :ref:`upgrading_auth_20` for more information.
Override the :app:`Pyramid` :term:`authorization policy` in the
current configuration. The ``policy`` argument must be an instance
diff --git a/src/pyramid/interfaces.py b/src/pyramid/interfaces.py
index 0b7a07040..6704f7c7c 100644
--- a/src/pyramid/interfaces.py
+++ b/src/pyramid/interfaces.py
@@ -116,7 +116,7 @@ class IResponse(Interface):
identity = Attribute(
"""An object containing authentication information related to the
current request. The object's type and meaning is defined by the
- configured security policy."""
+ configured :term:`security policy`."""
)
authenticated_userid = Attribute(
@@ -245,7 +245,11 @@ class IResponse(Interface):
is_authenticated = Attribute(
"""A boolean indicating whether the request has an authenticated
- user, as determined by the security policy in use."""
+ user, as determined by the security policy in use.
+
+ The value is determined by the result of
+ :attr:`pyramid.request.Request.authenticated_userid`.
+ """
)
last_modified = Attribute(
@@ -506,6 +510,9 @@ class ISecurityPolicy(Interface):
def authenticated_userid(request):
"""Return a :term:`userid` string identifying the trusted and
verified user, or ``None`` if unauthenticated.
+
+ If the result is ``None``, then
+ :attr:`pyramid.request.Request.is_authenticated` will return ``False``.
"""
def permits(request, context, permission):
@@ -535,7 +542,7 @@ class IAuthenticationPolicy(Interface):
.. deprecated:: 2.0
Authentication policies have been removed in favor of security
- policies. See :ref:`upgrading_auth` for more information.
+ policies. See :ref:`upgrading_auth_20` for more information.
"""
@@ -595,7 +602,7 @@ class IAuthorizationPolicy(Interface):
.. deprecated:: 2.0
Authentication policies have been removed in favor of security
- policies. See :ref:`upgrading_auth` for more information.
+ policies. See :ref:`upgrading_auth_20` for more information.
"""
@@ -1139,7 +1146,7 @@ class ISession(IDict):
support types that can be serialized using JSON. It's recommended to
switch any session implementations to support only JSON and to only
store primitive types in sessions. See
- :ref:`pickle_session_deprecation` for more information about why this
+ :ref:`upgrading_session_20` for more information about why this
change was made.
.. versionchanged:: 1.9
diff --git a/src/pyramid/security.py b/src/pyramid/security.py
index 9cdafaf6f..a0ab39701 100644
--- a/src/pyramid/security.py
+++ b/src/pyramid/security.py
@@ -85,7 +85,7 @@ def principals_allowed_by_permission(context, permission):
.. deprecated:: 2.0
The new security policy has removed the concept of principals. See
- :ref:`upgrading_auth` for more information.
+ :ref:`upgrading_auth_20` for more information.
Provided a ``context`` (a resource object), and a ``permission``
string, if an :term:`authorization policy` is
@@ -286,8 +286,8 @@ class AuthenticationAPIMixin:
.. deprecated:: 2.0
``unauthenticated_userid`` does not have an equivalent in the new
- security system. Use :attr:`.authenticated_userid` or
- :attr:`.identity` instead. See :ref:`upgrading_auth` for more
+ security system. Use :attr:`.authenticated_userid` or
+ :attr:`.identity` instead. See :ref:`upgrading_auth_20` for more
information.
Return an object which represents the *claimed* (not verified) user
@@ -322,7 +322,7 @@ class AuthenticationAPIMixin:
.. deprecated:: 2.0
The new security policy has removed the concept of principals. See
- :ref:`upgrading_auth` for more information.
+ :ref:`upgrading_auth_20` for more information.
Return the list of 'effective' :term:`principal` identifiers
for the ``request``. If no :term:`authentication policy` is in effect,
diff --git a/src/pyramid/session.py b/src/pyramid/session.py
index 8c27aa674..032d58a8b 100644
--- a/src/pyramid/session.py
+++ b/src/pyramid/session.py
@@ -51,7 +51,7 @@ class PickleSerializer:
Pyramid will require JSON-serializable objects in :app:`Pyramid` 2.0.
- Please see :ref:`pickle_session_deprecation`.
+ Please see :ref:`upgrading_session_20`.
A serializer that uses the pickle protocol to dump Python data to bytes.
@@ -456,7 +456,7 @@ def SignedCookieSessionFactory(
In :app:`Pyramid` 2.0 the default ``serializer`` option changed to
use :class:`pyramid.session.JSONSerializer`. See
- :ref:`pickle_session_deprecation` for more information about why this
+ :ref:`upgrading_session_20` for more information about why this
change was made.
.. versionadded: 1.5a3
diff --git a/tox.ini b/tox.ini
index d4a1b24ac..f90bd9166 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,7 @@
[tox]
envlist =
lint,
- py35,py36,py37,py38,py39,pypy3,
+ py36,py37,py38,py39,pypy3,
py38-cover,coverage,
docs