summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml1
-rw-r--r--CHANGES.txt34
-rw-r--r--CONTRIBUTORS.txt4
-rw-r--r--HACKING.txt32
-rw-r--r--docs/api/request.rst42
-rw-r--r--docs/designdefense.rst2
-rw-r--r--docs/narr/events.rst6
-rw-r--r--docs/narr/extending.rst2
-rw-r--r--docs/narr/hooks.rst104
-rw-r--r--docs/narr/hybrid.rst2
-rw-r--r--docs/narr/i18n.rst8
-rw-r--r--docs/narr/introduction.rst6
-rw-r--r--docs/narr/introspector.rst4
-rw-r--r--docs/narr/logging.rst2
-rw-r--r--docs/narr/paste.rst2
-rw-r--r--docs/narr/resources.rst14
-rw-r--r--docs/narr/scaffolding.rst10
-rw-r--r--docs/narr/security.rst4
-rw-r--r--docs/narr/testing.rst34
-rw-r--r--docs/narr/threadlocals.rst19
-rw-r--r--docs/narr/upgrading.rst4
-rw-r--r--docs/narr/urldispatch.rst10
-rw-r--r--docs/narr/vhosting.rst4
-rw-r--r--docs/narr/viewconfig.rst2
-rw-r--r--docs/narr/views.rst2
-rw-r--r--docs/quick_tutorial/authentication/tutorial/views.py4
-rw-r--r--docs/quick_tutorial/authorization/tutorial/views.py4
-rw-r--r--docs/tutorials/wiki/authorization.rst31
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/views.py8
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/views.py8
-rw-r--r--docs/tutorials/wiki2/authorization.rst21
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/views.py12
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/tests.py21
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views.py13
-rw-r--r--docs/whatsnew-1.5.rst73
-rw-r--r--pyramid/config/predicates.py3
-rw-r--r--pyramid/config/routes.py8
-rw-r--r--pyramid/config/testing.py10
-rw-r--r--pyramid/config/views.py8
-rw-r--r--pyramid/i18n.py3
-rw-r--r--pyramid/request.py15
-rw-r--r--pyramid/security.py326
-rw-r--r--pyramid/testing.py13
-rw-r--r--pyramid/tests/test_config/test_testing.py9
-rw-r--r--pyramid/tests/test_request.py12
-rw-r--r--pyramid/tests/test_security.py410
46 files changed, 855 insertions, 511 deletions
diff --git a/.travis.yml b/.travis.yml
index 9d4324ff8..bc82c8faf 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,6 +6,7 @@ python:
- 2.7
- pypy
- 3.2
+ - 3.3
script: python setup.py test -q
diff --git a/CHANGES.txt b/CHANGES.txt
index bf1c1ea01..d6f5ea792 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -4,6 +4,25 @@ Unreleased
Features
--------
+- An authorization API has been added as a method of the
+ request: ``request.has_permission``.
+
+ ``request.has_permission`` is a method-based alternative to the
+ ``pyramid.security.has_permission`` API and works exactly the same. The
+ older API is now deprecated.
+
+- Property API attributes have been added to the request for easier access to
+ authentication data: ``request.authenticated_userid``,
+ ``request.unauthenticated_userid``, and ``request.effective_principals``.
+
+ These are analogues, respectively, of
+ ``pyramid.security.authenticated_userid``,
+ ``pyramid.security.unauthenticated_userid``, and
+ ``pyramid.security.effective_principals``. They operate exactly the same,
+ except they are attributes of the request instead of functions accepting a
+ request. They are properties, so they cannot be assigned to. The older
+ function-based APIs are now deprecated.
+
- Pyramid's console scripts (``pserve``, ``pviews``, etc) can now be run
directly, allowing custom arguments to be sent to the python interpreter
at runtime. For example::
@@ -108,6 +127,21 @@ Deprecations
the SignedCookieSessionFactory are not. See
https://github.com/Pylons/pyramid/pull/1142
+- The ``pyramid.security.has_permission`` API is now deprecated. Instead, use
+ the newly-added ``has_permission`` method of the request object.
+
+- The ``pyramid.security.effective_principals`` API is now deprecated.
+ Instead, use the newly-added ``effective_principals`` attribute of the
+ request object.
+
+- The ``pyramid.security.authenticated_userid`` API is now deprecated.
+ Instead, use the newly-added ``authenticated_userid`` attribute of the
+ request object.
+
+- The ``pyramid.security.unauthenticated_userid`` API is now deprecated.
+ Instead, use the newly-added ``unauthenticated_userid`` attribute of the
+ request object.
+
1.5a2 (2013-09-22)
==================
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index bfe22e540..63528e662 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -224,3 +224,7 @@ Contributors
- Doug Hellmann, 2013/09/06
- Karl O. Pinc, 2013/09/27
+
+- Matthew Russell, 2013/10/14
+
+- Antti Haapala, 2013/11/15
diff --git a/HACKING.txt b/HACKING.txt
index b32a8a957..12f2d68e2 100644
--- a/HACKING.txt
+++ b/HACKING.txt
@@ -6,9 +6,9 @@ Here are some guidelines about hacking on Pyramid.
Using a Development Checkout
----------------------------
-You'll have to create a development environment to hack on Pyramid, using a
-Pyramid checkout. You can either do this by hand or, if you have ``tox``
-installed (it's on PyPI), you can (ab)use tox to get a working development
+You'll have to create a development environment to hack on Pyramid, using a
+Pyramid checkout. You can either do this by hand or, if you have ``tox``
+installed (it's on PyPI), you can (ab)use tox to get a working development
environment. Each installation method is described below.
By Hand
@@ -25,15 +25,15 @@ By Hand
$ cd ~/hack-on-pyramid
$ virtualenv -ppython2.7 env
- Note that very old versions of virtualenv (virtualenv versions below, say,
+ Note that very old versions of virtualenv (virtualenv versions below, say,
1.10 or thereabouts) require you to pass a ``--no-site-packages`` flag to
get a completely isolated environment.
- You can choose which Python version you want to use by passing a ``-p``
+ You can choose which Python version you want to use by passing a ``-p``
flag to ``virtualenv``. For example, ``virtualenv -ppython2.7``
chooses the Python 2.7 interpreter to be installed.
- From here on in within these instructions, the ``~/hack-on-pyramid/env``
+ From here on in within these instructions, the ``~/hack-on-pyramid/env``
virtual environment you created above will be referred to as ``$VENV``.
To use the instructions in the steps that follow literally, use the
``export VENV=~/hack-on-pyramid/env`` command.
@@ -132,7 +132,7 @@ Coding Style
- PEP8 compliance. Whitespace rules are relaxed: not necessary to put
2 newlines between classes. But 80-column lines, in particular, are
- mandatory. See
+ mandatory. See
http://docs.pylonsproject.org/en/latest/community/codestyle.html for more
information.
@@ -142,14 +142,14 @@ Coding Style
Running Tests
--------------
-- To run all tests for Pyramid on a single Python version, run ``nosetests``
+- To run all tests for Pyramid on a single Python version, run ``nosetests``
from your development virtualenv (See *Using a Development Checkout* above).
- To run individual tests (i.e. during development) you can use a regular
expression with the ``-t`` parameter courtesy of the `nose-selecttests
- <https://pypi.python.org/pypi/nose-selecttests/>`_ plugin that's been
- installed (along with nose itself) via ``python setup.py dev``. The
- easiest usage is to simply provide the verbatim name of the test you're
+ <https://pypi.python.org/pypi/nose-selecttests/>`_ plugin that's been
+ installed (along with nose itself) via ``python setup.py dev``. The
+ easiest usage is to simply provide the verbatim name of the test you're
working on.
- To run the full set of Pyramid tests on all platforms, install ``tox``
@@ -191,8 +191,8 @@ or adds the feature.
To build and review docs (where ``$VENV`` refers to the virtualenv you're
using to develop Pyramid):
-1. After following the steps above in "Using a Development Checkout", cause
- Sphinx and all development requirements to be installed in your
+1. After following the steps above in "Using a Development Checkout", cause
+ Sphinx and all development requirements to be installed in your
virtualenv::
$ cd ~/hack-on-pyramid
@@ -212,9 +212,9 @@ using to develop Pyramid):
$ cd ~/hack-on-pyramid/pyramid/docs
$ make clean html SPHINXBUILD=$VENV/bin/sphinx-build
- The ``SPHINXBUILD=...`` hair is there in order to tell it to use the
- virtualenv Python, which will have both Sphinx and Pyramid (for API
- documentation generation) installed.
+ The ``SPHINXBUILD=...`` argument tells Sphinx to use the virtualenv Python,
+ which will have both Sphinx and Pyramid (for API documentation generation)
+ installed.
4. Open the ``docs/_build/html/index.html`` file to see the resulting HTML
rendering.
diff --git a/docs/api/request.rst b/docs/api/request.rst
index 72abddb68..b7604020e 100644
--- a/docs/api/request.rst
+++ b/docs/api/request.rst
@@ -11,7 +11,9 @@
:exclude-members: add_response_callback, add_finished_callback,
route_url, route_path, current_route_url,
current_route_path, static_url, static_path,
- model_url, resource_url, set_property
+ model_url, resource_url, set_property,
+ effective_principals, authenticated_userid,
+ unauthenticated_userid, has_permission
.. attribute:: context
@@ -161,6 +163,42 @@
request, the value of this attribute will be ``None``. See
:ref:`matched_route`.
+ .. attribute:: authenticated_userid
+
+ .. versionadded:: 1.5
+
+ A property which returns the userid of the currently authenticated user
+ or ``None`` if there is no :term:`authentication policy` in effect or
+ there is no currently authenticated user. This differs from
+ :attr:`~pyramid.request.Request.unauthenticated_userid`, because the
+ effective authentication policy will have ensured that a record
+ associated with the userid exists in persistent storage; if it has
+ not, this value will be ``None``.
+
+ .. attribute:: unauthenticated_userid
+
+ .. versionadded:: 1.5
+
+ A property which returns a value which represents the *claimed* (not
+ verified) user id of the credentials present in the request. ``None`` if
+ there is no :term:`authentication policy` in effect or there is no user
+ data associated with the current request. This differs from
+ :attr:`~pyramid.request.Request.authenticated_userid`, because the
+ effective authentication policy will not ensure that a record associated
+ with the userid exists in persistent storage. Even if the userid
+ does not exist in persistent storage, this value will be the value
+ of the userid *claimed* by the request data.
+
+ .. attribute:: effective_principals
+
+ .. versionadded:: 1.5
+
+ A property which returns the list of 'effective' :term:`principal`
+ identifiers for this request. This will include the userid of the
+ currently authenticated user if a user is currently authenticated. If no
+ :term:`authentication policy` is in effect, this will return a sequence
+ containing only the :attr:`pyramid.security.Everyone` principal.
+
.. method:: invoke_subrequest(request, use_tweens=False)
.. versionadded:: 1.4a1
@@ -215,6 +253,8 @@
request provided by e.g. the ``pshell`` environment. For more
information, see :ref:`subrequest_chapter`.
+ .. automethod:: has_permission
+
.. automethod:: add_response_callback
.. automethod:: add_finished_callback
diff --git a/docs/designdefense.rst b/docs/designdefense.rst
index bbce3e29c..2f3c14881 100644
--- a/docs/designdefense.rst
+++ b/docs/designdefense.rst
@@ -1078,7 +1078,7 @@ The contents of ``app2.py``:
The contents of ``config.py``:
.. code-block:: python
- :linenos:
+ :linenos:
L = []
diff --git a/docs/narr/events.rst b/docs/narr/events.rst
index 2accb3dbe..50484761d 100644
--- a/docs/narr/events.rst
+++ b/docs/narr/events.rst
@@ -172,7 +172,7 @@ track of the information that subscribers will need. Here are some
example custom event classes:
.. code-block:: python
- :linenos:
+ :linenos:
class DocCreated(object):
def __init__(self, doc, request):
@@ -196,7 +196,7 @@ also use custom events with :ref:`subscriber predicates
event with a decorator:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.events import subscriber
from .events import DocCreated
@@ -215,7 +215,7 @@ To fire your custom events use the
accessed as ``request.registry.notify``. For example:
.. code-block:: python
- :linenos:
+ :linenos:
from .events import DocCreated
diff --git a/docs/narr/extending.rst b/docs/narr/extending.rst
index a60a49fea..8462a9da7 100644
--- a/docs/narr/extending.rst
+++ b/docs/narr/extending.rst
@@ -234,7 +234,7 @@ For example, if the original application has the following
``configure_views`` configuration method:
.. code-block:: python
- :linenos:
+ :linenos:
def configure_views(config):
config.add_view('theoriginalapp.views.theview', name='theview')
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index 0c450fad7..f2542f1d7 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -363,7 +363,7 @@ and modify the set of :term:`renderer globals` before they are passed to a
that can be used for this purpose. For example:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.events import subscriber
from pyramid.events import BeforeRender
@@ -963,8 +963,8 @@ For full details, please read the `Venusian documentation
.. _registering_tweens:
-Registering "Tweens"
---------------------
+Registering Tweens
+------------------
.. versionadded:: 1.2
Tweens
@@ -976,26 +976,80 @@ feature that may be used by Pyramid framework extensions, to provide, for
example, Pyramid-specific view timing support bookkeeping code that examines
exceptions before they are returned to the upstream WSGI application. Tweens
behave a bit like :term:`WSGI` :term:`middleware` but they have the benefit of
-running in a context in which they have access to the Pyramid
-:term:`application registry` as well as the Pyramid rendering machinery.
+running in a context in which they have access to the Pyramid :term:`request`,
+:term:`response` and :term:`application registry` as well as the Pyramid
+rendering machinery.
-Creating a Tween Factory
-~~~~~~~~~~~~~~~~~~~~~~~~
+Creating a Tween
+~~~~~~~~~~~~~~~~
-To make use of tweens, you must construct a "tween factory". A tween factory
+To create a tween, you must write a "tween factory". A tween factory
must be a globally importable callable which accepts two arguments:
``handler`` and ``registry``. ``handler`` will be the either the main
Pyramid request handling function or another tween. ``registry`` will be the
Pyramid :term:`application registry` represented by this Configurator. A
-tween factory must return a tween when it is called.
+tween factory must return the tween (a callable object) when it is called.
-A tween is a callable which accepts a :term:`request` object and returns
-a :term:`response` object.
+A tween is called with a single argument, ``request``, which is the
+:term:`request` created by Pyramid's router when it receives a WSGI request.
+A tween should return a :term:`response`, usually the one generated by the
+downstream Pyramid application.
-Here's an example of a tween factory:
+You can write the tween factory as a simple closure-returning function:
.. code-block:: python
- :linenos:
+ :linenos:
+
+ def simple_tween_factory(handler, registry):
+ # one-time configuration code goes here
+
+ def simple_tween(request):
+ # code to be executed for each request before
+ # the actual application code goes here
+
+ response = handler(request)
+
+ # code to be executed for each request after
+ # the actual application code goes here
+
+ return response
+
+ return simple_tween
+
+Alternatively, the tween factory can be a class with the ``__call__`` magic
+method:
+
+.. code-block:: python
+ :linenos:
+
+ class simple_tween_factory(object):
+ def __init__(handler, registry):
+ self.handler = handler
+ self.registry = registry
+
+ # one-time configuration code goes here
+
+ def __call__(self, request):
+ # code to be executed for each request before
+ # the actual application code goes here
+
+ response = self.handler(request)
+
+ # code to be executed for each request after
+ # the actual application code goes here
+
+ return response
+
+The closure style performs slightly better and enables you to conditionally
+omit the tween from the request processing pipeline (see the following timing
+tween example), whereas the class style makes it easier to have shared mutable
+state, and it allows subclassing.
+
+Here's a complete example of a tween that logs the time spent processing each
+request:
+
+.. code-block:: python
+ :linenos:
# in a module named myapp.tweens
@@ -1022,12 +1076,6 @@ Here's an example of a tween factory:
# handler
return handler
-If you remember, a tween is an object which accepts a :term:`request` object
-and which returns a :term:`response` argument. The ``request`` argument to a
-tween will be the request created by Pyramid's router when it receives a WSGI
-request. The response object will be generated by the downstream Pyramid
-application and it should be returned by the tween.
-
In the above example, the tween factory defines a ``timing_tween`` tween and
returns it if ``asbool(registry.settings.get('do_timing'))`` is true. It
otherwise simply returns the handler it was given. The ``registry.settings``
@@ -1053,7 +1101,7 @@ Here's an example of registering a tween factory as an "implicit" tween in a
Pyramid application:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.config import Configurator
config = Configurator()
@@ -1087,7 +1135,7 @@ chain (the tween generated by the very last tween factory added) as its
request handler function. For example:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.config import Configurator
@@ -1132,8 +1180,10 @@ Allowable values for ``under`` or ``over`` (or both) are:
fallbacks if the desired tween is not included, as well as compatibility
with multiple other tweens.
-Effectively, ``under`` means "closer to the main Pyramid application than",
-``over`` means "closer to the request ingress than".
+Effectively, ``over`` means "closer to the request ingress than" and
+``under`` means "closer to the main Pyramid application than".
+You can think of an onion with outer layers over the inner layers,
+the application being under all the layers at the center.
For example, the following call to
:meth:`~pyramid.config.Configurator.add_tween` will attempt to place the
@@ -1329,7 +1379,7 @@ route predicate factory is most often a class with a constructor
method. For example:
.. code-block:: python
- :linenos:
+ :linenos:
class ContentTypePredicate(object):
def __init__(self, val, config):
@@ -1392,7 +1442,7 @@ with a subscriber that subscribes to the :class:`pyramid.events.NewRequest`
event type.
.. code-block:: python
- :linenos:
+ :linenos:
class RequestPathStartsWith(object):
def __init__(self, val, config):
@@ -1421,7 +1471,7 @@ previously registered ``request_path_startswith`` predicate in a call to
:meth:`~pyramid.config.Configurator.add_subscriber`:
.. code-block:: python
- :linenos:
+ :linenos:
# define a subscriber in your code
@@ -1437,7 +1487,7 @@ Here's the same subscriber/predicate/event-type combination used via
:class:`~pyramid.events.subscriber`.
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.events import subscriber
diff --git a/docs/narr/hybrid.rst b/docs/narr/hybrid.rst
index a29ccb2ac..4a3258d35 100644
--- a/docs/narr/hybrid.rst
+++ b/docs/narr/hybrid.rst
@@ -63,7 +63,7 @@ An application that uses only traversal will have view configuration
declarations that look like this:
.. code-block:: python
- :linenos:
+ :linenos:
# config is an instance of pyramid.config.Configurator
diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst
index b62c16ff0..c9b782c08 100644
--- a/docs/narr/i18n.rst
+++ b/docs/narr/i18n.rst
@@ -309,7 +309,7 @@ In particular, add the ``Babel`` and ``lingua`` distributions to the
application's ``setup.py`` file:
.. code-block:: python
- :linenos:
+ :linenos:
setup(name="mypackage",
# ...
@@ -370,7 +370,7 @@ file of a ``pcreate`` -generated :app:`Pyramid` application has stanzas in it
that look something like the following:
.. code-block:: ini
- :linenos:
+ :linenos:
[compile_catalog]
directory = myproject/locale
@@ -398,7 +398,7 @@ that you'd like the domain of your translations to be ``mydomain``
instead, change the ``setup.cfg`` file stanzas to look like so:
.. code-block:: ini
- :linenos:
+ :linenos:
[compile_catalog]
directory = myproject/locale
@@ -1041,7 +1041,7 @@ if no locale can be determined.
Here's an implementation of a simple locale negotiator:
.. code-block:: python
- :linenos:
+ :linenos:
def my_locale_negotiator(request):
locale_name = request.params.get('my_locale')
diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst
index a9c5fdfbd..8acbab3a0 100644
--- a/docs/narr/introduction.rst
+++ b/docs/narr/introduction.rst
@@ -336,7 +336,7 @@ For example, instead of returning a ``Response`` object from a
``render_to_response`` call:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.renderers import render_to_response
@@ -347,7 +347,7 @@ For example, instead of returning a ``Response`` object from a
You can return a Python dictionary:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.view import view_config
@@ -827,7 +827,7 @@ Here's an example of using Pyramid's introspector from within a view
callable:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.view import view_config
from pyramid.response import Response
diff --git a/docs/narr/introspector.rst b/docs/narr/introspector.rst
index 3c0a6744f..a7bde4cf7 100644
--- a/docs/narr/introspector.rst
+++ b/docs/narr/introspector.rst
@@ -24,7 +24,7 @@ Here's an example of using Pyramid's introspector from within a view
callable:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.view import view_config
from pyramid.response import Response
@@ -100,7 +100,7 @@ its ``__getitem__``, ``get``, ``keys``, ``values``, or ``items`` methods.
For example:
.. code-block:: python
- :linenos:
+ :linenos:
route_intr = introspector.get('routes', 'edit_user')
pattern = route_intr['pattern']
diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst
index b3bfb8a1e..75428d513 100644
--- a/docs/narr/logging.rst
+++ b/docs/narr/logging.rst
@@ -179,7 +179,7 @@ file, simply create a logger object using the ``__name__`` builtin and call
methods on it.
.. code-block:: python
- :linenos:
+ :linenos:
import logging
log = logging.getLogger(__name__)
diff --git a/docs/narr/paste.rst b/docs/narr/paste.rst
index 3427b6d53..f1fb70869 100644
--- a/docs/narr/paste.rst
+++ b/docs/narr/paste.rst
@@ -87,7 +87,7 @@ configuration object and *returns* an instance of our application.
.. _defaults_section_of_pastedeploy_file:
-``[DEFAULTS]`` Section of a PasteDeploy ``.ini`` File
+``[DEFAULT]`` Section of a PasteDeploy ``.ini`` File
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can add a ``[DEFAULT]`` section to your PasteDeploy ``.ini`` file. Such
diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst
index b1bb611e5..f3ff1dc4c 100644
--- a/docs/narr/resources.rst
+++ b/docs/narr/resources.rst
@@ -83,7 +83,7 @@ works against resource instances.
Here's a sample resource tree, represented by a variable named ``root``:
.. code-block:: python
- :linenos:
+ :linenos:
class Resource(dict):
pass
@@ -201,7 +201,7 @@ location-aware resources. These APIs include (but are not limited to)
:func:`~pyramid.traversal.resource_path`,
:func:`~pyramid.traversal.resource_path_tuple`, or
:func:`~pyramid.traversal.traverse`, :func:`~pyramid.traversal.virtual_root`,
-and (usually) :func:`~pyramid.security.has_permission` and
+and (usually) :meth:`~pyramid.request.Request.has_permission` and
:func:`~pyramid.security.principals_allowed_by_permission`.
In general, since so much :app:`Pyramid` infrastructure depends on
@@ -695,10 +695,10 @@ The APIs provided by :ref:`location_module` are used against resources.
These can be used to walk down a resource tree, or conveniently locate one
resource "inside" another.
-Some APIs in :ref:`security_module` accept a resource object as a parameter.
-For example, the :func:`~pyramid.security.has_permission` API accepts a
+Some APIs on the :class:`pyramid.request.Request` accept a resource object as a parameter.
+For example, the :meth:`~pyramid.request.Request.has_permission` API accepts a
resource object as one of its arguments; the ACL is obtained from this
-resource or one of its ancestors. Other APIs in the :mod:`pyramid.security`
-module also accept :term:`context` as an argument, and a context is always a
-resource.
+resource or one of its ancestors. Other security related APIs on the
+:class:`pyramid.request.Request` class also accept :term:`context` as an argument,
+and a context is always a resource.
diff --git a/docs/narr/scaffolding.rst b/docs/narr/scaffolding.rst
index 534b2caf4..f924d0d62 100644
--- a/docs/narr/scaffolding.rst
+++ b/docs/narr/scaffolding.rst
@@ -39,9 +39,9 @@ named ``__init__.py`` with something like the following:
from pyramid.scaffolds import PyramidTemplate
- class CoolExtensionTemplate(PyramidTemplate):
- _template_dir = 'coolextension_scaffold'
- summary = 'My cool extension'
+ class CoolExtensionTemplate(PyramidTemplate):
+ _template_dir = 'coolextension_scaffold'
+ summary = 'My cool extension'
Once this is done, within the ``scaffolds`` directory, create a template
directory. Our example used a template directory named
@@ -89,7 +89,7 @@ For example:
[pyramid.scaffold]
coolextension=coolextension.scaffolds:CoolExtensionTemplate
"""
- )
+ )
Run your distribution's ``setup.py develop`` or ``setup.py install``
command. After that, you should be able to see your scaffolding template
@@ -112,7 +112,7 @@ want to have extension scaffolds that can work across Pyramid 1.0.X, 1.1.X,
defining your scaffold template:
.. code-block:: python
- :linenos:
+ :linenos:
try: # pyramid 1.0.X
# "pyramid.paster.paste_script..." doesn't exist past 1.0.X
diff --git a/docs/narr/security.rst b/docs/narr/security.rst
index e85ed823a..9e6fb6c82 100644
--- a/docs/narr/security.rst
+++ b/docs/narr/security.rst
@@ -550,7 +550,7 @@ also contain security debugging information in its body.
Debugging Imperative Authorization Failures
-------------------------------------------
-The :func:`pyramid.security.has_permission` API is used to check
+The :meth:`pyramid.request.Request.has_permission` API is used to check
security within view functions imperatively. It returns instances of
objects that are effectively booleans. But these objects are not raw
``True`` or ``False`` objects, and have information attached to them
@@ -563,7 +563,7 @@ one of :data:`pyramid.security.ACLAllowed`,
``msg`` attribute, which is a string indicating why the permission was
denied or allowed. Introspecting this information in the debugger or
via print statements when a call to
-:func:`~pyramid.security.has_permission` fails is often useful.
+:meth:`~pyramid.request.Request.has_permission` fails is often useful.
.. index::
single: authentication policy (creating)
diff --git a/docs/narr/testing.rst b/docs/narr/testing.rst
index 88d6904c7..5a5bf8fad 100644
--- a/docs/narr/testing.rst
+++ b/docs/narr/testing.rst
@@ -214,11 +214,10 @@ function.
.. code-block:: python
:linenos:
- from pyramid.security import has_permission
from pyramid.httpexceptions import HTTPForbidden
def view_fn(request):
- if not has_permission('edit', request.context, request):
+ if request.has_permission('edit'):
raise HTTPForbidden
return {'greeting':'hello'}
@@ -229,15 +228,16 @@ function.
otherwise it would fail when run normally.
Without doing anything special during a unit test, the call to
-:func:`~pyramid.security.has_permission` in this view function will always
-return a ``True`` value. When a :app:`Pyramid` application starts normally,
-it will populate a :term:`application registry` using :term:`configuration
-declaration` calls made against a :term:`Configurator`. But if this
-application registry is not created and populated (e.g. by initializing the
-configurator with an authorization policy), like when you invoke application
-code via a unit test, :app:`Pyramid` API functions will tend to either fail
-or return default results. So how do you test the branch of the code in this
-view function that raises :exc:`~pyramid.httpexceptions.HTTPForbidden`?
+:meth:`~pyramid.request.Request.has_permission` in this view function will
+always return a ``True`` value. When a :app:`Pyramid` application starts
+normally, it will populate a :term:`application registry` using
+:term:`configuration declaration` calls made against a :term:`Configurator`.
+But if this application registry is not created and populated (e.g. by
+initializing the configurator with an authorization policy), like when you
+invoke application code via a unit test, :app:`Pyramid` API functions will tend
+to either fail or return default results. So how do you test the branch of the
+code in this view function that raises
+:exc:`~pyramid.httpexceptions.HTTPForbidden`?
The testing API provided by :app:`Pyramid` allows you to simulate various
application registry registrations for use under a unit testing framework
@@ -287,12 +287,12 @@ Its third line registers a "dummy" "non-permissive" authorization policy
using the :meth:`~pyramid.config.Configurator.testing_securitypolicy` method,
which is a special helper method for unit testing.
-We then create a :class:`pyramid.testing.DummyRequest` object which simulates
-a WebOb request object API. A :class:`pyramid.testing.DummyRequest` is a
-request object that requires less setup than a "real" :app:`Pyramid` request.
-We call the function being tested with the manufactured request. When the
-function is called, :func:`pyramid.security.has_permission` will call the
-"dummy" authentication policy we've registered through
+We then create a :class:`pyramid.testing.DummyRequest` object which simulates a
+WebOb request object API. A :class:`pyramid.testing.DummyRequest` is a request
+object that requires less setup than a "real" :app:`Pyramid` request. We call
+the function being tested with the manufactured request. When the function is
+called, :meth:`pyramid.request.Request.has_permission` will call the "dummy"
+authentication policy we've registered through
:meth:`~pyramid.config.Configurator.testing_securitypolicy`, which denies
access. We check that the view function raises a
:exc:`~pyramid.httpexceptions.HTTPForbidden` error.
diff --git a/docs/narr/threadlocals.rst b/docs/narr/threadlocals.rst
index a90ee4905..afe56de3e 100644
--- a/docs/narr/threadlocals.rst
+++ b/docs/narr/threadlocals.rst
@@ -29,17 +29,16 @@ of a thread local or a global is usually just a way to avoid passing
some value around between functions, which is itself usually a very
bad idea, at least if code readability counts as an important concern.
-For historical reasons, however, thread local variables are indeed
-consulted by various :app:`Pyramid` API functions. For example,
-the implementation of the :mod:`pyramid.security` function named
-:func:`~pyramid.security.authenticated_userid` retrieves the thread
-local :term:`application registry` as a matter of course to find an
+For historical reasons, however, thread local variables are indeed consulted by
+various :app:`Pyramid` API functions. For example, the implementation of the
+:mod:`pyramid.security` function named
+:func:`~pyramid.security.authenticated_userid` (deprecated as of 1.5) retrieves
+the thread local :term:`application registry` as a matter of course to find an
:term:`authentication policy`. It uses the
-:func:`pyramid.threadlocal.get_current_registry` function to
-retrieve the application registry, from which it looks up the
-authentication policy; it then uses the authentication policy to
-retrieve the authenticated user id. This is how :app:`Pyramid`
-allows arbitrary authentication policies to be "plugged in".
+:func:`pyramid.threadlocal.get_current_registry` function to retrieve the
+application registry, from which it looks up the authentication policy; it then
+uses the authentication policy to retrieve the authenticated user id. This is
+how :app:`Pyramid` allows arbitrary authentication policies to be "plugged in".
When they need to do so, :app:`Pyramid` internals use two API
functions to retrieve the :term:`request` and :term:`application
diff --git a/docs/narr/upgrading.rst b/docs/narr/upgrading.rst
index 64343ca3e..eb3194a65 100644
--- a/docs/narr/upgrading.rst
+++ b/docs/narr/upgrading.rst
@@ -137,7 +137,7 @@ In the above case, it's line #3 in the ``myproj.views`` module (``from
pyramid.view import static``) that is causing the problem:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.view import view_config
@@ -148,7 +148,7 @@ The deprecation warning tells me how to fix it, so I can change the code to
do things the newer way:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.view import view_config
diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst
index 61849c3c0..96ee5758e 100644
--- a/docs/narr/urldispatch.rst
+++ b/docs/narr/urldispatch.rst
@@ -492,7 +492,7 @@ The simplest route declaration which configures a route match to *directly*
result in a particular view callable being invoked:
.. code-block:: python
- :linenos:
+ :linenos:
config.add_route('idea', 'site/{id}')
config.add_view('mypackage.views.site_view', route_name='idea')
@@ -901,7 +901,7 @@ Details of the route matching decision for a particular request to the
which you started the application from. For example:
.. code-block:: text
- :linenos:
+ :linenos:
$ PYRAMID_DEBUG_ROUTEMATCH=true $VENV/bin/pserve development.ini
Starting server in PID 13586.
@@ -1060,7 +1060,7 @@ A custom route predicate may also *modify* the ``match`` dictionary. For
instance, a predicate might do some type conversion of values:
.. code-block:: python
- :linenos:
+ :linenos:
def integers(*segment_names):
def predicate(info, request):
@@ -1086,7 +1086,7 @@ To avoid the try/except uncertainty, the route pattern can contain regular
expressions specifying requirements for that marker. For instance:
.. code-block:: python
- :linenos:
+ :linenos:
def integers(*segment_names):
def predicate(info, request):
@@ -1128,7 +1128,7 @@ name. The ``pattern`` attribute is the route pattern. An example of using
the route in a set of route predicates:
.. code-block:: python
- :linenos:
+ :linenos:
def twenty_ten(info, request):
if info['route'].name in ('ymd', 'ym', 'y'):
diff --git a/docs/narr/vhosting.rst b/docs/narr/vhosting.rst
index d37518052..53f6888b3 100644
--- a/docs/narr/vhosting.rst
+++ b/docs/narr/vhosting.rst
@@ -109,7 +109,7 @@ An example of an Apache ``mod_proxy`` configuration that will host the
is below:
.. code-block:: apache
- :linenos:
+ :linenos:
NameVirtualHost *:80
@@ -130,7 +130,7 @@ For a :app:`Pyramid` application running under :term:`mod_wsgi`,
the same can be achieved using ``SetEnv``:
.. code-block:: apache
- :linenos:
+ :linenos:
<Location />
SetEnv HTTP_X_VHM_ROOT /cms
diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst
index 7c76116f7..e5a2c1ade 100644
--- a/docs/narr/viewconfig.rst
+++ b/docs/narr/viewconfig.rst
@@ -435,7 +435,7 @@ configured view.
If specified, this value should be a :term:`principal` identifier or a
sequence of principal identifiers. If the
- :func:`pyramid.security.effective_principals` method indicates that every
+ :meth:`pyramid.request.Request.effective_principals` method indicates that every
principal named in the argument list is present in the current request, this
predicate will return True; otherwise it will return False. For example:
``effective_principals=pyramid.security.Authenticated`` or
diff --git a/docs/narr/views.rst b/docs/narr/views.rst
index b2dd549ce..a746eb043 100644
--- a/docs/narr/views.rst
+++ b/docs/narr/views.rst
@@ -536,7 +536,7 @@ The following types work as view callables in this style:
e.g.:
.. code-block:: python
- :linenos:
+ :linenos:
from pyramid.response import Response
diff --git a/docs/quick_tutorial/authentication/tutorial/views.py b/docs/quick_tutorial/authentication/tutorial/views.py
index 3038b6d9b..ab46eb2dd 100644
--- a/docs/quick_tutorial/authentication/tutorial/views.py
+++ b/docs/quick_tutorial/authentication/tutorial/views.py
@@ -2,8 +2,8 @@ from pyramid.httpexceptions import HTTPFound
from pyramid.security import (
remember,
forget,
- authenticated_userid
)
+
from pyramid.view import (
view_config,
view_defaults
@@ -16,7 +16,7 @@ from .security import USERS
class TutorialViews:
def __init__(self, request):
self.request = request
- self.logged_in = authenticated_userid(request)
+ self.logged_in = request.authenticated_userid
@view_config(route_name='home')
def home(self):
diff --git a/docs/quick_tutorial/authorization/tutorial/views.py b/docs/quick_tutorial/authorization/tutorial/views.py
index 92c1946ba..43d14455a 100644
--- a/docs/quick_tutorial/authorization/tutorial/views.py
+++ b/docs/quick_tutorial/authorization/tutorial/views.py
@@ -2,8 +2,8 @@ from pyramid.httpexceptions import HTTPFound
from pyramid.security import (
remember,
forget,
- authenticated_userid
)
+
from pyramid.view import (
view_config,
view_defaults,
@@ -17,7 +17,7 @@ from .security import USERS
class TutorialViews:
def __init__(self, request):
self.request = request
- self.logged_in = authenticated_userid(request)
+ self.logged_in = request.authenticated_userid
@view_config(route_name='home')
def home(self):
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst
index 460a852e0..93cd0c18e 100644
--- a/docs/tutorials/wiki/authorization.rst
+++ b/docs/tutorials/wiki/authorization.rst
@@ -56,10 +56,10 @@ returns one of these values:
return ``None``.
For example, ``groupfinder('editor', request )`` returns ``['group:editor']``,
-``groupfinder('viewer', request)`` returns ``[]``, and ``groupfinder('admin', request)``
-returns ``None``. We will use ``groupfinder()`` as an :term:`authentication policy`
-"callback" that will provide the :term:`principal` or principals
-for a user.
+``groupfinder('viewer', request)`` returns ``[]``, and ``groupfinder('admin',
+request)`` returns ``None``. We will use ``groupfinder()`` as an
+:term:`authentication policy` "callback" that will provide the
+:term:`principal` or principals for a user.
In a production system, user and group
data will most often come from a database, but here we use "dummy"
@@ -149,8 +149,8 @@ to the ``@view_config`` decorator for ``add_page()`` and
``edit_page()``, for example:
.. code-block:: python
- :linenos:
- :emphasize-lines: 3
+ :linenos:
+ :emphasize-lines: 3
@view_config(name='add_page', context='.models.Wiki',
renderer='templates/edit.pt',
@@ -251,18 +251,6 @@ in ``views.py``.
Return a logged_in flag to the renderer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Add the following line to the import at the head of
-``tutorial/tutorial/views.py``:
-
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 11-15
- :linenos:
- :emphasize-lines: 4
- :language: python
-
-(Only the highlighted line and a trailing comma on the preceding
-line need to be added.)
-
Add a ``logged_in`` parameter to the return value of
``view_page()``, ``edit_page()`` and ``add_page()``,
like this:
@@ -274,14 +262,13 @@ like this:
return dict(page = page,
content = content,
edit_url = edit_url,
- logged_in = authenticated_userid(request))
+ logged_in = request.authenticated_userid)
(Only the highlighted line and a trailing comma on the preceding
line need to be added.)
-:meth:`~pyramid.security.authenticated_userid()` will return ``None``
-if the user is not authenticated, or a user id if the user is
-authenticated.
+The :meth:`pyramid.request.Request.authenticated_userid` will be ``None`` if
+the user is not authenticated, or a user id if the user is authenticated.
Add a "Logout" link when logged in
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views.py b/docs/tutorials/wiki/src/authorization/tutorial/views.py
index 77956b1e3..62e96e0e7 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/views.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/views.py
@@ -11,9 +11,9 @@ from pyramid.view import (
from pyramid.security import (
remember,
forget,
- authenticated_userid,
)
+
from .security import USERS
from .models import Page
@@ -45,7 +45,7 @@ def view_page(context, request):
edit_url = request.resource_url(context, 'edit_page')
return dict(page = context, content = content, edit_url = edit_url,
- logged_in = authenticated_userid(request))
+ logged_in = request.authenticated_userid)
@view_config(name='add_page', context='.models.Wiki',
renderer='templates/edit.pt',
@@ -65,7 +65,7 @@ def add_page(context, request):
page.__parent__ = context
return dict(page=page, save_url=save_url,
- logged_in=authenticated_userid(request))
+ logged_in=request.authenticated_userid)
@view_config(name='edit_page', context='.models.Page',
renderer='templates/edit.pt',
@@ -77,7 +77,7 @@ def edit_page(context, request):
return dict(page=context,
save_url=request.resource_url(context, 'edit_page'),
- logged_in=authenticated_userid(request))
+ logged_in=request.authenticated_userid)
@view_config(context='.models.Wiki', name='login',
renderer='templates/login.pt')
diff --git a/docs/tutorials/wiki/src/tests/tutorial/views.py b/docs/tutorials/wiki/src/tests/tutorial/views.py
index 77956b1e3..62e96e0e7 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/views.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/views.py
@@ -11,9 +11,9 @@ from pyramid.view import (
from pyramid.security import (
remember,
forget,
- authenticated_userid,
)
+
from .security import USERS
from .models import Page
@@ -45,7 +45,7 @@ def view_page(context, request):
edit_url = request.resource_url(context, 'edit_page')
return dict(page = context, content = content, edit_url = edit_url,
- logged_in = authenticated_userid(request))
+ logged_in = request.authenticated_userid)
@view_config(name='add_page', context='.models.Wiki',
renderer='templates/edit.pt',
@@ -65,7 +65,7 @@ def add_page(context, request):
page.__parent__ = context
return dict(page=page, save_url=save_url,
- logged_in=authenticated_userid(request))
+ logged_in=request.authenticated_userid)
@view_config(name='edit_page', context='.models.Page',
renderer='templates/edit.pt',
@@ -77,7 +77,7 @@ def edit_page(context, request):
return dict(page=context,
save_url=request.resource_url(context, 'edit_page'),
- logged_in=authenticated_userid(request))
+ logged_in=request.authenticated_userid)
@view_config(context='.models.Wiki', name='login',
renderer='templates/login.pt')
diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst
index cf20db6d7..1e5d0dcbf 100644
--- a/docs/tutorials/wiki2/authorization.rst
+++ b/docs/tutorials/wiki2/authorization.rst
@@ -221,7 +221,7 @@ Add the following import statements to the
head of ``tutorial/tutorial/views.py``:
.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 9-16,18,24-25
+ :lines: 9-19
:linenos:
:emphasize-lines: 3,6-9,11
:language: python
@@ -274,17 +274,6 @@ added to ``views.py``.
Return a logged_in flag to the renderer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Add the following line to the import at the head of
-``tutorial/tutorial/views.py``:
-
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 14-18
- :linenos:
- :emphasize-lines: 4
- :language: python
-
-(Only the highlighted line needs to be added.)
-
Add a ``logged_in`` parameter to the return value of
``view_page()``, ``edit_page()`` and ``add_page()``,
like this:
@@ -296,12 +285,12 @@ like this:
return dict(page = page,
content = content,
edit_url = edit_url,
- logged_in = authenticated_userid(request))
+ logged_in = request.authenticated_userid)
(Only the highlighted line needs to be added.)
-The :meth:`~pyramid.security.authenticated_userid` method will return None
-if the user is not authenticated.
+The :meth:`~pyramid.request.Request.authenticated_userid` property will be
+``None`` if the user is not authenticated.
Add a "Logout" link when logged in
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -349,7 +338,7 @@ when we're done:
.. literalinclude:: src/authorization/tutorial/views.py
:linenos:
- :emphasize-lines: 11,14-18,25,31,37,58,61,73,76,88,91-117,119-123
+ :emphasize-lines: 11,14-19,25,31,37,58,61,73,76,88,91-117,119-123
:language: python
(Only the highlighted lines need to be added.)
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views.py b/docs/tutorials/wiki2/src/authorization/tutorial/views.py
index b6dbbf5f6..e954d5a31 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/views.py
@@ -14,15 +14,15 @@ from pyramid.view import (
from pyramid.security import (
remember,
forget,
- authenticated_userid,
)
+from .security import USERS
+
from .models import (
DBSession,
Page,
)
-from .security import USERS
# regular expression used to find WikiWords
wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
@@ -55,7 +55,7 @@ def view_page(request):
content = wikiwords.sub(check, content)
edit_url = request.route_url('edit_page', pagename=pagename)
return dict(page=page, content=content, edit_url=edit_url,
- logged_in=authenticated_userid(request))
+ logged_in=request.authenticated_userid)
@view_config(route_name='add_page', renderer='templates/edit.pt',
permission='edit')
@@ -70,7 +70,7 @@ def add_page(request):
save_url = request.route_url('add_page', pagename=pagename)
page = Page(name='', data='')
return dict(page=page, save_url=save_url,
- logged_in=authenticated_userid(request))
+ logged_in=request.authenticated_userid)
@view_config(route_name='edit_page', renderer='templates/edit.pt',
permission='edit')
@@ -84,8 +84,8 @@ def edit_page(request):
pagename=pagename))
return dict(
page=page,
- save_url = request.route_url('edit_page', pagename=pagename),
- logged_in=authenticated_userid(request),
+ save_url=request.route_url('edit_page', pagename=pagename),
+ logged_in=request.authenticated_userid
)
@view_config(route_name='login', renderer='templates/login.pt')
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests.py b/docs/tutorials/wiki2/src/tests/tutorial/tests.py
index 4ee30685e..c50e05b6d 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/tests.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/tests.py
@@ -26,27 +26,6 @@ def _registerRoutes(config):
config.add_route('add_page', 'add_page/{pagename}')
-class PageModelTests(unittest.TestCase):
-
- def setUp(self):
- self.session = _initTestingDB()
-
- def tearDown(self):
- self.session.remove()
-
- def _getTargetClass(self):
- from tutorial.models import Page
- return Page
-
- def _makeOne(self, name='SomeName', data='some data'):
- return self._getTargetClass()(name, data)
-
- def test_constructor(self):
- instance = self._makeOne()
- self.assertEqual(instance.name, 'SomeName')
- self.assertEqual(instance.data, 'some data')
-
-
class ViewWikiTests(unittest.TestCase):
def setUp(self):
self.config = testing.setUp()
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views.py b/docs/tutorials/wiki2/src/tests/tutorial/views.py
index b6dbbf5f6..41bea4785 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/views.py
@@ -14,15 +14,15 @@ from pyramid.view import (
from pyramid.security import (
remember,
forget,
- authenticated_userid,
)
+from .security import USERS
+
from .models import (
DBSession,
Page,
)
-from .security import USERS
# regular expression used to find WikiWords
wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
@@ -55,7 +55,7 @@ def view_page(request):
content = wikiwords.sub(check, content)
edit_url = request.route_url('edit_page', pagename=pagename)
return dict(page=page, content=content, edit_url=edit_url,
- logged_in=authenticated_userid(request))
+ logged_in=request.authenticated_userid)
@view_config(route_name='add_page', renderer='templates/edit.pt',
permission='edit')
@@ -70,7 +70,7 @@ def add_page(request):
save_url = request.route_url('add_page', pagename=pagename)
page = Page(name='', data='')
return dict(page=page, save_url=save_url,
- logged_in=authenticated_userid(request))
+ logged_in=request.authenticated_userid)
@view_config(route_name='edit_page', renderer='templates/edit.pt',
permission='edit')
@@ -84,8 +84,8 @@ def edit_page(request):
pagename=pagename))
return dict(
page=page,
- save_url = request.route_url('edit_page', pagename=pagename),
- logged_in=authenticated_userid(request),
+ save_url=request.route_url('edit_page', pagename=pagename),
+ logged_in=request.authenticated_userid
)
@view_config(route_name='login', renderer='templates/login.pt')
@@ -121,4 +121,3 @@ def logout(request):
headers = forget(request)
return HTTPFound(location = request.route_url('view_wiki'),
headers = headers)
-
diff --git a/docs/whatsnew-1.5.rst b/docs/whatsnew-1.5.rst
index 57f93cbff..23613896a 100644
--- a/docs/whatsnew-1.5.rst
+++ b/docs/whatsnew-1.5.rst
@@ -316,6 +316,48 @@ The feature additions in Pyramid 1.5 follow.
- :func:`pyramid.path.package_name` no longer thows 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:
+ :meth:`pyramid.request.Request.has_permission`. It is a method-based
+ alternative to the :func:`pyramid.security.has_permission` API and works
+ exactly the same. The older API is now deprecated.
+
+- Property API attributes have been added to the request for easier access to
+ authentication data: :attr:`pyramid.request.Request.authenticated_userid`,
+ :attr:`pyramid.request.Request.unauthenticated_userid`, and
+ :attr:`pyramid.request.Request.effective_principals`. These are analogues,
+ respectively, of :func:`pyramid.security.authenticated_userid`,
+ :func:`pyramid.security.unauthenticated_userid`, and
+ :func:`pyramid.security.effective_principals`. They operate exactly the
+ same, except they are attributes of the request instead of functions
+ accepting a request. They are properties, so they cannot be assigned to.
+ The older function-based APIs are now deprecated.
+
+- Pyramid's console scripts (``pserve``, ``pviews``, etc) can now be run
+ directly, allowing custom arguments to be sent to the python interpreter
+ at runtime. For example::
+
+ python -3 -m pyramid.scripts.pserve development.ini
+
+- Added a specific subclass of :class:`pyramid.httpexceptions.HTTPBadRequest`
+ named :class:`pyramid.exceptions.BadCSRFToken` which will now be raised in
+ response to failures in the ``check_csrf_token`` view predicate. See
+ https://github.com/Pylons/pyramid/pull/1149
+
+- Added a new ``SignedCookieSessionFactory`` which is very similar to the
+ ``UnencryptedCookieSessionFactoryConfig`` but with a clearer focus on
+ signing content. The custom serializer arguments to this function should
+ only focus on serializing, unlike its predecessor which required the
+ serializer to also perform signing.
+ See https://github.com/Pylons/pyramid/pull/1142
+
+- Added a new ``BaseCookieSessionFactory`` which acts as a generic cookie
+ factory that can be used by framework implementors to create their own
+ session implementations. It provides a reusable API which focuses strictly
+ on providing a dictionary-like object that properly handles renewals,
+ timeouts, and conformance with the ``ISession`` API.
+ See https://github.com/Pylons/pyramid/pull/1142
+
+
Other Backwards Incompatibilities
---------------------------------
@@ -404,6 +446,13 @@ Other Backwards Incompatibilities
Pyramid narrative documentation instead of providing renderer globals values
to the configurator.
+- The key/values in the ``_query`` parameter of
+ :meth:`pyramid.request.Request.route_url` and the ``query`` parameter of
+ :meth:`pyramid.request.Request.resource_url` (and their variants), used to
+ encode a value of ``None`` as the string ``'None'``, leaving the resulting
+ query string to be ``a=b&key=None``. The value is now dropped in this
+ situation, leaving a query string of ``a=b&key=``. See
+ https://github.com/Pylons/pyramid/issues/1119
Deprecations
------------
@@ -417,12 +466,36 @@ Deprecations
a deprecation warning when used. It had been docs-deprecated in 1.4
but did not issue a deprecation warning when used.
+- :func:`pyramid.security.has_permission` is now deprecated in favor of using
+ :meth:`pyramid.request.Request.has_permission`.
+
+- The :func:`pyramid.security.authenticated_userid`,
+ :func:`pyramid.security.unauthenticated_userid`, and
+ :func:`pyramid.security.effective_principals` functions have been
+ deprecated. Use :attr:`pyramid.request.Request.authenticated_userid`,
+ :attr:`pyramid.request.Request.unauthenticated_userid` and
+ :attr:`pyramid.request.Request.effective_principals` instead.
+
+- Deprecate the ``pyramid.interfaces.ITemplateRenderer`` interface. It was
+ ill-defined and became unused when Mako and Chameleon template bindings were
+ split into their own packages.
+
+- The ``pyramid.session.UnencryptedCookieSessionFactoryConfig`` API has been
+ deprecated and is superseded by the
+ ``pyramid.session.SignedCookieSessionFactory``. Note that while the cookies
+ generated by the ``UnencryptedCookieSessionFactoryConfig``
+ are compatible with cookies generated by old releases, cookies generated by
+ the SignedCookieSessionFactory are not. See
+ https://github.com/Pylons/pyramid/pull/1142
+
Documentation Enhancements
--------------------------
- A new documentation chapter named :ref:`quick_tour` was added. It describes
starting out with Pyramid from a high level.
+- Added a :ref:`quick_tutorial` to go with the Quick Tour
+
- Many other enhancements.
diff --git a/pyramid/config/predicates.py b/pyramid/config/predicates.py
index c8f66e83d..967f2eeee 100644
--- a/pyramid/config/predicates.py
+++ b/pyramid/config/predicates.py
@@ -13,7 +13,6 @@ from pyramid.traversal import (
from pyramid.urldispatch import _compile_route
from pyramid.util import object_description
from pyramid.session import check_csrf_token
-from pyramid.security import effective_principals
from .util import as_sorted_tuple
@@ -288,7 +287,7 @@ class EffectivePrincipalsPredicate(object):
phash = text
def __call__(self, context, request):
- req_principals = effective_principals(request)
+ req_principals = request.effective_principals
if is_nonstr_iter(req_principals):
rpset = set(req_principals)
if self.val.issubset(rpset):
diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py
index 4de4663a8..4fd207600 100644
--- a/pyramid/config/routes.py
+++ b/pyramid/config/routes.py
@@ -237,10 +237,10 @@ class RoutesConfiguratorMixin(object):
If specified, this value should be a :term:`principal` identifier or
a sequence of principal identifiers. If the
- :func:`pyramid.security.effective_principals` method indicates that
- every principal named in the argument list is present in the current
- request, this predicate will return True; otherwise it will return
- False. For example:
+ :attr:`pyramid.request.Request.effective_principals` property
+ indicates that every principal named in the argument list is present
+ in the current request, this predicate will return True; otherwise it
+ will return False. For example:
``effective_principals=pyramid.security.Authenticated`` or
``effective_principals=('fred', 'group:admins')``.
diff --git a/pyramid/config/testing.py b/pyramid/config/testing.py
index 2ab85b1f5..5df726a31 100644
--- a/pyramid/config/testing.py
+++ b/pyramid/config/testing.py
@@ -47,14 +47,14 @@ class TestingConfiguratorMixin(object):
``groupids`` argument. The authentication policy will return
the userid identifier implied by the ``userid`` argument and
the group ids implied by the ``groupids`` argument when the
- :func:`pyramid.security.authenticated_userid` or
- :func:`pyramid.security.effective_principals` APIs are
+ :attr:`pyramid.request.Request.authenticated_userid` or
+ :attr:`pyramid.request.Request.effective_principals` APIs are
used.
This function is most useful when testing code that uses
- the APIs named :func:`pyramid.security.has_permission`,
- :func:`pyramid.security.authenticated_userid`,
- :func:`pyramid.security.effective_principals`, and
+ the APIs named :meth:`pyramid.request.Request.has_permission`,
+ :attr:`pyramid.request.Request.authenticated_userid`,
+ :attr:`pyramid.request.Request.effective_principals`, and
:func:`pyramid.security.principals_allowed_by_permission`.
.. versionadded:: 1.4
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index 0165f96f1..72dc3f414 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -1024,10 +1024,10 @@ class ViewsConfiguratorMixin(object):
If specified, this value should be a :term:`principal` identifier or
a sequence of principal identifiers. If the
- :func:`pyramid.security.effective_principals` method indicates that
- every principal named in the argument list is present in the current
- request, this predicate will return True; otherwise it will return
- False. For example:
+ :attr:`pyramid.request.Request.effective_principals` property
+ indicates that every principal named in the argument list is present
+ in the current request, this predicate will return True; otherwise it
+ will return False. For example:
``effective_principals=pyramid.security.Authenticated`` or
``effective_principals=('fred', 'group:admins')``.
diff --git a/pyramid/i18n.py b/pyramid/i18n.py
index cdedbc877..6ffd93e8f 100644
--- a/pyramid/i18n.py
+++ b/pyramid/i18n.py
@@ -107,7 +107,8 @@ def default_locale_negotiator(request):
- First, the negotiator looks for the ``_LOCALE_`` attribute of
the request object (possibly set by a view or a listener for an
- :term:`event`).
+ :term:`event`). If the attribute exists and it is not ``None``,
+ its value will be used.
- Then it looks for the ``request.params['_LOCALE_']`` value.
diff --git a/pyramid/request.py b/pyramid/request.py
index 2cf0613f7..188e968ac 100644
--- a/pyramid/request.py
+++ b/pyramid/request.py
@@ -21,6 +21,10 @@ from pyramid.compat import (
from pyramid.decorator import reify
from pyramid.i18n import LocalizerRequestMixin
from pyramid.response import Response
+from pyramid.security import (
+ AuthenticationAPIMixin,
+ AuthorizationAPIMixin,
+ )
from pyramid.url import URLMethodsMixin
from pyramid.util import InstancePropertyMixin
@@ -136,8 +140,15 @@ class CallbackMethodsMixin(object):
callback(self)
@implementer(IRequest)
-class Request(BaseRequest, URLMethodsMixin, CallbackMethodsMixin,
- InstancePropertyMixin, LocalizerRequestMixin):
+class Request(
+ BaseRequest,
+ URLMethodsMixin,
+ CallbackMethodsMixin,
+ InstancePropertyMixin,
+ LocalizerRequestMixin,
+ AuthenticationAPIMixin,
+ AuthorizationAPIMixin,
+ ):
"""
A subclass of the :term:`WebOb` Request class. An instance of
this class is created by the :term:`router` and is provided to a
diff --git a/pyramid/security.py b/pyramid/security.py
index 3e25f9b2f..58fa9332a 100644
--- a/pyramid/security.py
+++ b/pyramid/security.py
@@ -1,3 +1,4 @@
+from zope.deprecation import deprecated
from zope.interface import providedBy
from pyramid.interfaces import (
@@ -30,79 +31,143 @@ DENY_ALL = (Deny, Everyone, ALL_PERMISSIONS)
NO_PERMISSION_REQUIRED = '__no_permission_required__'
-def has_permission(permission, context, request):
- """ Provided a permission (a string or unicode object), a context
- (a :term:`resource` instance) and a request object, return an
- instance of :data:`pyramid.security.Allowed` if the permission
- is granted in this context to the user implied by the
- request. Return an instance of :mod:`pyramid.security.Denied`
- if this permission is not granted in this context to this user.
- This function delegates to the current authentication and
- authorization policies. Return
- :data:`pyramid.security.Allowed` unconditionally if no
- authentication policy has been configured in this application."""
+def _get_registry(request):
try:
reg = request.registry
except AttributeError:
reg = get_current_registry() # b/c
- authn_policy = reg.queryUtility(IAuthenticationPolicy)
- if authn_policy is None:
- return Allowed('No authentication policy in use.')
+ return reg
- authz_policy = reg.queryUtility(IAuthorizationPolicy)
- if authz_policy is None:
- raise ValueError('Authentication policy registered without '
- 'authorization policy') # should never happen
- principals = authn_policy.effective_principals(request)
- return authz_policy.permits(context, principals, permission)
+def _get_authentication_policy(request):
+ registry = _get_registry(request)
+ return registry.queryUtility(IAuthenticationPolicy)
-def authenticated_userid(request):
- """ Return the userid of the currently authenticated user or
- ``None`` if there is no :term:`authentication policy` in effect or
- there is no currently authenticated user."""
- try:
- reg = request.registry
- except AttributeError:
- reg = get_current_registry() # b/c
+def has_permission(permission, context, request):
+ """
+ A function that calls
+ :meth:`pyramid.request.Request.has_permission` and returns its result.
+
+ .. deprecated:: 1.5
+ Use :meth:`pyramid.request.Request.has_permission` instead.
+
+ .. versionchanged:: 1.5a3
+ If context is None, then attempt to use the context attribute
+ of self, if not set then the AttributeError is propergated.
+ """
+ return request.has_permission(permission, context)
+
+deprecated(
+ 'has_permission',
+ 'As of Pyramid 1.5 the "pyramid.security.has_permission" API is now '
+ 'deprecated. It will be removed in Pyramd 1.8. Use the '
+ '"has_permission" method of the Pyramid request instead.'
+ )
- policy = reg.queryUtility(IAuthenticationPolicy)
- if policy is None:
- return None
- return policy.authenticated_userid(request)
+
+def authenticated_userid(request):
+ """
+ A function that returns the value of the property
+ :attr:`pyramid.request.Request.authenticated_userid`.
+
+ .. deprecated:: 1.5
+ Use :attr:`pyramid.request.Request.authenticated_userid` instead.
+ """
+ return request.authenticated_userid
+
+deprecated(
+ 'authenticated_userid',
+ 'As of Pyramid 1.5 the "pyramid.security.authenticated_userid" API is now '
+ 'deprecated. It will be removed in Pyramd 1.8. Use the '
+ '"authenticated_userid" attribute of the Pyramid request instead.'
+ )
def unauthenticated_userid(request):
- """ Return an object which represents the *claimed* (not verified) user
- id of the credentials present in the request. ``None`` if there is no
- :term:`authentication policy` in effect or there is no user data
- associated with the current request. This differs from
- :func:`~pyramid.security.authenticated_userid`, because the effective
- authentication policy will not ensure that a record associated with the
- userid exists in persistent storage."""
- try:
- reg = request.registry
- except AttributeError:
- reg = get_current_registry() # b/c
+ """
+ A function that returns the value of the property
+ :attr:`pyramid.request.Request.unauthenticated_userid`.
+
+ .. deprecated:: 1.5
+ Use :attr:`pyramid.request.Request.unauthenticated_userid` instead.
+ """
+ return request.unauthenticated_userid
+
+deprecated(
+ 'unauthenticated_userid',
+ 'As of Pyramid 1.5 the "pyramid.security.unauthenticated_userid" API is '
+ 'now deprecated. It will be removed in Pyramd 1.8. Use the '
+ '"unauthenticated_userid" attribute of the Pyramid request instead.'
+ )
+
+def effective_principals(request):
+ """
+ A function that returns the value of the property
+ :attr:`pyramid.request.Request.effective_principals`.
+
+ .. deprecated:: 1.5
+ Use :attr:`pyramid.request.Request.effective_principals` instead.
+ """
+ return request.effective_principals
+
+deprecated(
+ 'effective_principals',
+ 'As of Pyramid 1.5 the "pyramid.security.effective_principals" API is '
+ 'now deprecated. It will be removed in Pyramd 1.8. Use the '
+ '"effective_principals" attribute of the Pyramid request instead.'
+ )
+
+def remember(request, principal, **kw):
+ """
+ Returns a sequence of header tuples (e.g. ``[('Set-Cookie', 'foo=abc')]``)
+ on this request's response.
+ These headers are suitable for 'remembering' a set of credentials
+ implied by the data passed as ``principal`` and ``*kw`` using the
+ current :term:`authentication policy`. Common usage might look
+ like so within the body of a view function (``response`` is
+ assumed to be a :term:`WebOb` -style :term:`response` object
+ computed previously by the view code)::
- policy = reg.queryUtility(IAuthenticationPolicy)
+ .. code-block:: python
+
+ from pyramid.security import remember
+ headers = remember(request, 'chrism', password='123', max_age='86400')
+ response = request.response
+ response.headerlist.extend(headers)
+ return response
+
+ If no :term:`authentication policy` is in use, this function will
+ always return an empty sequence. If used, the composition and
+ meaning of ``**kw`` must be agreed upon by the calling code and
+ the effective authentication policy.
+ """
+ policy = _get_authentication_policy(request)
if policy is None:
- return None
- return policy.unauthenticated_userid(request)
+ return []
+ return policy.remember(request, principal, **kw)
-def effective_principals(request):
- """ Return the list of 'effective' :term:`principal` identifiers
- for the ``request``. This will include the userid of the
- currently authenticated user if a user is currently
- authenticated. If no :term:`authentication policy` is in effect,
- this will return an empty sequence."""
- try:
- reg = request.registry
- except AttributeError:
- reg = get_current_registry() # b/c
+def forget(request):
+ """
+ Return a sequence of header tuples (e.g. ``[('Set-Cookie',
+ 'foo=abc')]``) suitable for 'forgetting' the set of credentials
+ possessed by the currently authenticated user. A common usage
+ might look like so within the body of a view function
+ (``response`` is assumed to be an :term:`WebOb` -style
+ :term:`response` object computed previously by the view code)::
+
+ from pyramid.security import forget
+ headers = forget(request)
+ response.headerlist.extend(headers)
+ return response
- policy = reg.queryUtility(IAuthenticationPolicy)
+ If no :term:`authentication policy` is in use, this function will
+ always return an empty sequence.
+
+ .. deprecated:: 1.5
+ Use :meth:`pyramid.request.Request.get_logout_headers` instead.
+ """
+ policy = _get_authentication_policy(request)
if policy is None:
- return [Everyone]
- return policy.effective_principals(request)
+ return []
+ return policy.forget(request)
def principals_allowed_by_permission(context, permission):
""" Provided a ``context`` (a resource object), and a ``permission``
@@ -140,10 +205,7 @@ def view_execution_permitted(context, request, name=''):
An exception is raised if no view is found.
"""
- try:
- reg = request.registry
- except AttributeError:
- reg = get_current_registry() # b/c
+ reg = _get_registry(request)
provides = [IViewClassifier] + map_(providedBy, (request, context))
view = reg.adapters.lookup(provides, ISecuredView, name=name)
if view is None:
@@ -157,58 +219,6 @@ def view_execution_permitted(context, request, name=''):
(name, context))
return view.__permitted__(context, request)
-def remember(request, principal, **kw):
- """ Return a sequence of header tuples (e.g. ``[('Set-Cookie',
- 'foo=abc')]``) suitable for 'remembering' a set of credentials
- implied by the data passed as ``principal`` and ``*kw`` using the
- current :term:`authentication policy`. Common usage might look
- like so within the body of a view function (``response`` is
- assumed to be a :term:`WebOb` -style :term:`response` object
- computed previously by the view code)::
-
- from pyramid.security import remember
- headers = remember(request, 'chrism', password='123', max_age='86400')
- response.headerlist.extend(headers)
- return response
-
- If no :term:`authentication policy` is in use, this function will
- always return an empty sequence. If used, the composition and
- meaning of ``**kw`` must be agreed upon by the calling code and
- the effective authentication policy."""
- try:
- reg = request.registry
- except AttributeError:
- reg = get_current_registry() # b/c
- policy = reg.queryUtility(IAuthenticationPolicy)
- if policy is None:
- return []
- else:
- return policy.remember(request, principal, **kw)
-
-def forget(request):
- """ Return a sequence of header tuples (e.g. ``[('Set-Cookie',
- 'foo=abc')]``) suitable for 'forgetting' the set of credentials
- possessed by the currently authenticated user. A common usage
- might look like so within the body of a view function
- (``response`` is assumed to be an :term:`WebOb` -style
- :term:`response` object computed previously by the view code)::
-
- from pyramid.security import forget
- headers = forget(request)
- response.headerlist.extend(headers)
- return response
-
- If no :term:`authentication policy` is in use, this function will
- always return an empty sequence."""
- try:
- reg = request.registry
- except AttributeError:
- reg = get_current_registry() # b/c
- policy = reg.queryUtility(IAuthenticationPolicy)
- if policy is None:
- return []
- else:
- return policy.forget(request)
class PermitsResult(int):
def __new__(cls, s, *args):
@@ -294,3 +304,89 @@ class ACLAllowed(ACLPermitsResult):
summary is available as the ``msg`` attribute."""
boolval = 1
+class AuthenticationAPIMixin(object):
+
+ def _get_authentication_policy(self):
+ reg = _get_registry(self)
+ return reg.queryUtility(IAuthenticationPolicy)
+
+ @property
+ def authenticated_userid(self):
+ """ Return the userid of the currently authenticated user or
+ ``None`` if there is no :term:`authentication policy` in effect or
+ there is no currently authenticated user.
+
+ .. versionadded:: 1.5
+ """
+ policy = self._get_authentication_policy()
+ if policy is None:
+ return None
+ return policy.authenticated_userid(self)
+
+ @property
+ def unauthenticated_userid(self):
+ """ Return an object which represents the *claimed* (not verified) user
+ id of the credentials present in the request. ``None`` if there is no
+ :term:`authentication policy` in effect or there is no user data
+ associated with the current request. This differs from
+ :attr:`~pyramid.request.Request.authenticated_userid`, because the
+ effective authentication policy will not ensure that a record
+ associated with the userid exists in persistent storage.
+
+ .. versionadded:: 1.5
+ """
+ policy = self._get_authentication_policy()
+ if policy is None:
+ return None
+ return policy.unauthenticated_userid(self)
+
+ @property
+ def effective_principals(self):
+ """ Return the list of 'effective' :term:`principal` identifiers
+ for the ``request``. This will include the userid of the
+ currently authenticated user if a user is currently
+ authenticated. If no :term:`authentication policy` is in effect,
+ this will return an empty sequence.
+
+ .. versionadded:: 1.5
+ """
+ policy = self._get_authentication_policy()
+ if policy is None:
+ return [Everyone]
+ return policy.effective_principals(self)
+
+class AuthorizationAPIMixin(object):
+
+ def has_permission(self, permission, context=None):
+ """ Given a permission and an optional context, returns an instance of
+ :data:`pyramid.security.Allowed` if the permission is granted to this
+ request with the provided context, or the context already associated
+ with the request. Otherwise, returns an instance of
+ :data:`pyramid.security.Denied`. This method delegates to the current
+ authentication and authorization policies. Returns
+ :data:`pyramid.security.Allowed` unconditionally if no authentication
+ policy has been registered for this request. If ``context`` is not
+ supplied or is supplied as ``None``, the context used is the
+ ``request.context`` attribute.
+
+ :param permission: Does this request have the given permission?
+ :type permission: unicode, str
+ :param context: A resource object or ``None``
+ :type context: object
+ :returns: `pyramid.security.PermitsResult`
+
+ .. versionadded:: 1.5
+
+ """
+ if context is None:
+ context = self.context
+ reg = _get_registry(self)
+ authn_policy = reg.queryUtility(IAuthenticationPolicy)
+ if authn_policy is None:
+ return Allowed('No authentication policy in use.')
+ authz_policy = reg.queryUtility(IAuthorizationPolicy)
+ if authz_policy is None:
+ raise ValueError('Authentication policy registered without '
+ 'authorization policy') # should never happen
+ principals = authn_policy.effective_principals(self)
+ return authz_policy.permits(context, principals, permission)
diff --git a/pyramid/testing.py b/pyramid/testing.py
index 4590c55f8..b3460d8aa 100644
--- a/pyramid/testing.py
+++ b/pyramid/testing.py
@@ -27,6 +27,8 @@ from pyramid.registry import Registry
from pyramid.security import (
Authenticated,
Everyone,
+ AuthenticationAPIMixin,
+ AuthorizationAPIMixin,
)
from pyramid.threadlocal import (
@@ -280,10 +282,15 @@ class DummySession(dict):
token = self.new_csrf_token()
return token
-
@implementer(IRequest)
-class DummyRequest(URLMethodsMixin, CallbackMethodsMixin, InstancePropertyMixin,
- LocalizerRequestMixin):
+class DummyRequest(
+ URLMethodsMixin,
+ CallbackMethodsMixin,
+ InstancePropertyMixin,
+ LocalizerRequestMixin,
+ AuthenticationAPIMixin,
+ AuthorizationAPIMixin,
+ ):
""" A DummyRequest object (incompletely) imitates a :term:`request` object.
The ``params``, ``environ``, ``headers``, ``path``, and
diff --git a/pyramid/tests/test_config/test_testing.py b/pyramid/tests/test_config/test_testing.py
index 1089f09fc..05561bfe9 100644
--- a/pyramid/tests/test_config/test_testing.py
+++ b/pyramid/tests/test_config/test_testing.py
@@ -1,6 +1,7 @@
import unittest
from pyramid.compat import text_
+from pyramid.security import AuthenticationAPIMixin, AuthorizationAPIMixin
from pyramid.tests.test_config import IDummy
class TestingConfiguratorMixinTests(unittest.TestCase):
@@ -196,13 +197,9 @@ from zope.interface import implementer
class DummyEvent:
pass
-class DummyRequest:
- subpath = ()
- matchdict = None
+class DummyRequest(AuthenticationAPIMixin, AuthorizationAPIMixin):
def __init__(self, environ=None):
if environ is None:
environ = {}
self.environ = environ
- self.params = {}
- self.cookies = {}
-
+
diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py
index 6cd72fc59..ed41b62ff 100644
--- a/pyramid/tests/test_request.py
+++ b/pyramid/tests/test_request.py
@@ -6,9 +6,10 @@ from pyramid.compat import (
text_,
bytes_,
native_,
- iteritems_,
- iterkeys_,
- itervalues_,
+ )
+from pyramid.security import (
+ AuthenticationAPIMixin,
+ AuthorizationAPIMixin,
)
class TestRequest(unittest.TestCase):
@@ -53,6 +54,11 @@ class TestRequest(unittest.TestCase):
cls = self._getTargetClass()
self.assertEqual(cls.ResponseClass, Response)
+ def test_implements_security_apis(self):
+ apis = (AuthenticationAPIMixin, AuthorizationAPIMixin)
+ r = self._makeOne()
+ self.assertTrue(isinstance(r, apis))
+
def test_charset_defaults_to_utf8(self):
r = self._makeOne({'PATH_INFO':'/'})
self.assertEqual(r.charset, 'UTF-8')
diff --git a/pyramid/tests/test_security.py b/pyramid/tests/test_security.py
index e530e33ca..6f08a100c 100644
--- a/pyramid/tests/test_security.py
+++ b/pyramid/tests/test_security.py
@@ -1,14 +1,13 @@
import unittest
-from pyramid.testing import cleanUp
-
+from pyramid import testing
class TestAllPermissionsList(unittest.TestCase):
def setUp(self):
- cleanUp()
+ testing.setUp()
def tearDown(self):
- cleanUp()
+ testing.tearDown()
def _getTargetClass(self):
from pyramid.security import AllPermissionsList
@@ -103,13 +102,101 @@ class TestACLDenied(unittest.TestCase):
self.assertTrue('<ACLDenied instance at ' in repr(denied))
self.assertTrue("with msg %r>" % msg in repr(denied))
-class TestViewExecutionPermitted(unittest.TestCase):
+class TestPrincipalsAllowedByPermission(unittest.TestCase):
def setUp(self):
- cleanUp()
+ testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def _callFUT(self, *arg):
+ from pyramid.security import principals_allowed_by_permission
+ return principals_allowed_by_permission(*arg)
+
+ def test_no_authorization_policy(self):
+ from pyramid.security import Everyone
+ context = DummyContext()
+ result = self._callFUT(context, 'view')
+ self.assertEqual(result, [Everyone])
+
+ def test_with_authorization_policy(self):
+ from pyramid.threadlocal import get_current_registry
+ registry = get_current_registry()
+ _registerAuthorizationPolicy(registry, 'yo')
+ context = DummyContext()
+ result = self._callFUT(context, 'view')
+ self.assertEqual(result, 'yo')
+
+class TestRemember(unittest.TestCase):
+ def setUp(self):
+ testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def _callFUT(self, *arg):
+ from pyramid.security import remember
+ return remember(*arg)
+
+ def test_no_authentication_policy(self):
+ request = _makeRequest()
+ result = self._callFUT(request, 'me')
+ self.assertEqual(result, [])
+ def test_with_authentication_policy(self):
+ request = _makeRequest()
+ registry = request.registry
+ _registerAuthenticationPolicy(registry, 'yo')
+ result = self._callFUT(request, 'me')
+ self.assertEqual(result, [('X-Pyramid-Test', 'me')])
+
+ def test_with_authentication_policy_no_reg_on_request(self):
+ from pyramid.threadlocal import get_current_registry
+ registry = get_current_registry()
+ request = _makeRequest()
+ del request.registry
+ _registerAuthenticationPolicy(registry, 'yo')
+ result = self._callFUT(request, 'me')
+ self.assertEqual(result, [('X-Pyramid-Test', 'me')])
+
+class TestForget(unittest.TestCase):
+ def setUp(self):
+ testing.setUp()
+
def tearDown(self):
- cleanUp()
+ testing.tearDown()
+ def _callFUT(self, *arg):
+ from pyramid.security import forget
+ return forget(*arg)
+
+ def test_no_authentication_policy(self):
+ request = _makeRequest()
+ result = self._callFUT(request)
+ self.assertEqual(result, [])
+
+ def test_with_authentication_policy(self):
+ request = _makeRequest()
+ _registerAuthenticationPolicy(request.registry, 'yo')
+ result = self._callFUT(request)
+ self.assertEqual(result, [('X-Pyramid-Test', 'logout')])
+
+ def test_with_authentication_policy_no_reg_on_request(self):
+ from pyramid.threadlocal import get_current_registry
+ registry = get_current_registry()
+ request = _makeRequest()
+ del request.registry
+ _registerAuthenticationPolicy(registry, 'yo')
+ result = self._callFUT(request)
+ self.assertEqual(result, [('X-Pyramid-Test', 'logout')])
+
+class TestViewExecutionPermitted(unittest.TestCase):
+ def setUp(self):
+ testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
def _callFUT(self, *arg, **kw):
from pyramid.security import view_execution_permitted
return view_execution_permitted(*arg, **kw)
@@ -140,7 +227,7 @@ class TestViewExecutionPermitted(unittest.TestCase):
reg = get_current_registry()
reg.registerUtility(settings, ISettings)
context = DummyContext()
- request = DummyRequest({})
+ request = testing.DummyRequest({})
class DummyView(object):
pass
view = DummyView()
@@ -159,7 +246,7 @@ class TestViewExecutionPermitted(unittest.TestCase):
reg = get_current_registry()
reg.registerUtility(settings, ISettings)
context = DummyContext()
- request = DummyRequest({})
+ request = testing.DummyRequest({})
self.assertRaises(TypeError, self._callFUT, context, request, '')
def test_with_permission(self):
@@ -171,232 +258,197 @@ class TestViewExecutionPermitted(unittest.TestCase):
context = DummyContext()
directlyProvides(context, IContext)
self._registerSecuredView('', True)
- request = DummyRequest({})
+ request = testing.DummyRequest({})
directlyProvides(request, IRequest)
result = self._callFUT(context, request, '')
- self.assertTrue(result is True)
-
-class TestHasPermission(unittest.TestCase):
- def setUp(self):
- cleanUp()
-
- def tearDown(self):
- cleanUp()
-
- def _callFUT(self, *arg):
- from pyramid.security import has_permission
- return has_permission(*arg)
-
- def test_no_authentication_policy(self):
- request = _makeRequest()
- result = self._callFUT('view', None, request)
- self.assertEqual(result, True)
- self.assertEqual(result.msg, 'No authentication policy in use.')
-
- def test_authentication_policy_no_authorization_policy(self):
- request = _makeRequest()
- _registerAuthenticationPolicy(request.registry, None)
- self.assertRaises(ValueError, self._callFUT, 'view', None, request)
-
- def test_authn_and_authz_policies_registered(self):
- request = _makeRequest()
- _registerAuthenticationPolicy(request.registry, None)
- _registerAuthorizationPolicy(request.registry, 'yo')
- self.assertEqual(self._callFUT('view', None, request), 'yo')
-
- def test_no_registry_on_request(self):
- from pyramid.threadlocal import get_current_registry
- request = DummyRequest({})
- registry = get_current_registry()
- _registerAuthenticationPolicy(registry, None)
- _registerAuthorizationPolicy(registry, 'yo')
- self.assertEqual(self._callFUT('view', None, request), 'yo')
+ self.assertTrue(result)
class TestAuthenticatedUserId(unittest.TestCase):
def setUp(self):
- cleanUp()
-
- def tearDown(self):
- cleanUp()
+ testing.setUp()
- def _callFUT(self, request):
- from pyramid.security import authenticated_userid
- return authenticated_userid(request)
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_backward_compat_delegates_to_mixin(self):
+ from zope.deprecation import __show__
+ try:
+ __show__.off()
+ request = _makeFakeRequest()
+ from pyramid.security import authenticated_userid
+ self.assertEqual(
+ authenticated_userid(request),
+ 'authenticated_userid'
+ )
+ finally:
+ __show__.on()
def test_no_authentication_policy(self):
request = _makeRequest()
- result = self._callFUT(request)
- self.assertEqual(result, None)
+ self.assertEqual(request.authenticated_userid, None)
def test_with_authentication_policy(self):
request = _makeRequest()
_registerAuthenticationPolicy(request.registry, 'yo')
- result = self._callFUT(request)
- self.assertEqual(result, 'yo')
+ self.assertEqual(request.authenticated_userid, 'yo')
def test_with_authentication_policy_no_reg_on_request(self):
from pyramid.threadlocal import get_current_registry
- request = DummyRequest({})
registry = get_current_registry()
+ request = _makeRequest()
+ del request.registry
_registerAuthenticationPolicy(registry, 'yo')
- result = self._callFUT(request)
- self.assertEqual(result, 'yo')
+ self.assertEqual(request.authenticated_userid, 'yo')
-class TestUnauthenticatedUserId(unittest.TestCase):
+class TestUnAuthenticatedUserId(unittest.TestCase):
def setUp(self):
- cleanUp()
-
- def tearDown(self):
- cleanUp()
+ testing.setUp()
- def _callFUT(self, request):
- from pyramid.security import unauthenticated_userid
- return unauthenticated_userid(request)
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_backward_compat_delegates_to_mixin(self):
+ from zope.deprecation import __show__
+ try:
+ __show__.off()
+ request = _makeFakeRequest()
+ from pyramid.security import unauthenticated_userid
+ self.assertEqual(
+ unauthenticated_userid(request),
+ 'unauthenticated_userid',
+ )
+ finally:
+ __show__.on()
def test_no_authentication_policy(self):
request = _makeRequest()
- result = self._callFUT(request)
- self.assertEqual(result, None)
+ self.assertEqual(request.unauthenticated_userid, None)
def test_with_authentication_policy(self):
request = _makeRequest()
_registerAuthenticationPolicy(request.registry, 'yo')
- result = self._callFUT(request)
- self.assertEqual(result, 'yo')
+ self.assertEqual(request.unauthenticated_userid, 'yo')
def test_with_authentication_policy_no_reg_on_request(self):
from pyramid.threadlocal import get_current_registry
- request = DummyRequest({})
registry = get_current_registry()
+ request = _makeRequest()
+ del request.registry
_registerAuthenticationPolicy(registry, 'yo')
- result = self._callFUT(request)
- self.assertEqual(result, 'yo')
+ self.assertEqual(request.unauthenticated_userid, 'yo')
class TestEffectivePrincipals(unittest.TestCase):
def setUp(self):
- cleanUp()
-
- def tearDown(self):
- cleanUp()
+ testing.setUp()
- def _callFUT(self, request):
- from pyramid.security import effective_principals
- return effective_principals(request)
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_backward_compat_delegates_to_mixin(self):
+ request = _makeFakeRequest()
+ from zope.deprecation import __show__
+ try:
+ __show__.off()
+ from pyramid.security import effective_principals
+ self.assertEqual(
+ effective_principals(request),
+ 'effective_principals'
+ )
+ finally:
+ __show__.on()
def test_no_authentication_policy(self):
from pyramid.security import Everyone
request = _makeRequest()
- result = self._callFUT(request)
- self.assertEqual(result, [Everyone])
+ self.assertEqual(request.effective_principals, [Everyone])
def test_with_authentication_policy(self):
request = _makeRequest()
_registerAuthenticationPolicy(request.registry, 'yo')
- result = self._callFUT(request)
- self.assertEqual(result, 'yo')
+ self.assertEqual(request.effective_principals, 'yo')
def test_with_authentication_policy_no_reg_on_request(self):
from pyramid.threadlocal import get_current_registry
registry = get_current_registry()
- request = DummyRequest({})
+ request = _makeRequest()
+ del request.registry
_registerAuthenticationPolicy(registry, 'yo')
- result = self._callFUT(request)
- self.assertEqual(result, 'yo')
+ self.assertEqual(request.effective_principals, 'yo')
-class TestPrincipalsAllowedByPermission(unittest.TestCase):
- def setUp(self):
- cleanUp()
-
- def tearDown(self):
- cleanUp()
-
- def _callFUT(self, *arg):
- from pyramid.security import principals_allowed_by_permission
- return principals_allowed_by_permission(*arg)
-
- def test_no_authorization_policy(self):
- from pyramid.security import Everyone
- context = DummyContext()
- result = self._callFUT(context, 'view')
- self.assertEqual(result, [Everyone])
-
- def test_with_authorization_policy(self):
- from pyramid.threadlocal import get_current_registry
- registry = get_current_registry()
- _registerAuthorizationPolicy(registry, 'yo')
- context = DummyContext()
- result = self._callFUT(context, 'view')
- self.assertEqual(result, 'yo')
-
-class TestRemember(unittest.TestCase):
+class TestHasPermission(unittest.TestCase):
def setUp(self):
- cleanUp()
+ testing.setUp()
def tearDown(self):
- cleanUp()
+ testing.tearDown()
- def _callFUT(self, *arg):
- from pyramid.security import remember
- return remember(*arg)
+ def _makeOne(self):
+ from pyramid.security import AuthorizationAPIMixin
+ from pyramid.registry import Registry
+ mixin = AuthorizationAPIMixin()
+ mixin.registry = Registry()
+ mixin.context = object()
+ return mixin
+
+ def test_delegates_to_mixin(self):
+ from zope.deprecation import __show__
+ try:
+ __show__.off()
+ mixin = self._makeOne()
+ from pyramid.security import has_permission
+ self.called_has_permission = False
+
+ def mocked_has_permission(*args, **kw):
+ self.called_has_permission = True
+
+ mixin.has_permission = mocked_has_permission
+ has_permission('view', object(), mixin)
+ self.assertTrue(self.called_has_permission)
+ finally:
+ __show__.on()
def test_no_authentication_policy(self):
- request = _makeRequest()
- result = self._callFUT(request, 'me')
- self.assertEqual(result, [])
+ request = self._makeOne()
+ result = request.has_permission('view')
+ self.assertTrue(result)
+ self.assertEqual(result.msg, 'No authentication policy in use.')
- def test_with_authentication_policy(self):
- request = _makeRequest()
- registry = request.registry
- _registerAuthenticationPolicy(registry, 'yo')
- result = self._callFUT(request, 'me')
- self.assertEqual(result, 'yo')
+ def test_with_no_authorization_policy(self):
+ request = self._makeOne()
+ _registerAuthenticationPolicy(request.registry, None)
+ self.assertRaises(ValueError,
+ request.has_permission, 'view', context=None)
- def test_with_authentication_policy_no_reg_on_request(self):
+ def test_with_authn_and_authz_policies_registered(self):
+ request = self._makeOne()
+ _registerAuthenticationPolicy(request.registry, None)
+ _registerAuthorizationPolicy(request.registry, 'yo')
+ self.assertEqual(request.has_permission('view', context=None), 'yo')
+
+ def test_with_no_reg_on_request(self):
from pyramid.threadlocal import get_current_registry
registry = get_current_registry()
- request = DummyRequest({})
- _registerAuthenticationPolicy(registry, 'yo')
- result = self._callFUT(request, 'me')
- self.assertEqual(result, 'yo')
-
-class TestForget(unittest.TestCase):
- def setUp(self):
- cleanUp()
-
- def tearDown(self):
- cleanUp()
-
- def _callFUT(self, *arg):
- from pyramid.security import forget
- return forget(*arg)
+ request = self._makeOne()
+ del request.registry
+ _registerAuthenticationPolicy(registry, None)
+ _registerAuthorizationPolicy(registry, 'yo')
+ self.assertEqual(request.has_permission('view'), 'yo')
- def test_no_authentication_policy(self):
- request = _makeRequest()
- result = self._callFUT(request)
- self.assertEqual(result, [])
+ def test_with_no_context_passed(self):
+ request = self._makeOne()
+ self.assertTrue(request.has_permission('view'))
- def test_with_authentication_policy(self):
- request = _makeRequest()
- _registerAuthenticationPolicy(request.registry, 'yo')
- result = self._callFUT(request)
- self.assertEqual(result, 'yo')
+ def test_with_no_context_passed_or_on_request(self):
+ request = self._makeOne()
+ del request.context
+ self.assertRaises(AttributeError, request.has_permission, 'view')
- def test_with_authentication_policy_no_reg_on_request(self):
- from pyramid.threadlocal import get_current_registry
- registry = get_current_registry()
- request = DummyRequest({})
- _registerAuthenticationPolicy(registry, 'yo')
- result = self._callFUT(request)
- self.assertEqual(result, 'yo')
+_TEST_HEADER = 'X-Pyramid-Test'
class DummyContext:
def __init__(self, *arg, **kw):
self.__dict__.update(kw)
-class DummyRequest:
- def __init__(self, environ):
- self.environ = environ
-
class DummyAuthenticationPolicy:
def __init__(self, result):
self.result = result
@@ -411,10 +463,14 @@ class DummyAuthenticationPolicy:
return self.result
def remember(self, request, principal, **kw):
- return self.result
+ headers = [(_TEST_HEADER, principal)]
+ self._header_remembered = headers[0]
+ return headers
def forget(self, request):
- return self.result
+ headers = [(_TEST_HEADER, 'logout')]
+ self._header_forgotten = headers[0]
+ return headers
class DummyAuthorizationPolicy:
def __init__(self, result):
@@ -440,8 +496,24 @@ def _registerAuthorizationPolicy(reg, result):
def _makeRequest():
from pyramid.registry import Registry
- request = DummyRequest({})
+ request = testing.DummyRequest(environ={})
request.registry = Registry()
+ request.context = object()
return request
+def _makeFakeRequest():
+ class FakeRequest(testing.DummyRequest):
+ @property
+ def authenticated_userid(req):
+ return 'authenticated_userid'
+
+ @property
+ def unauthenticated_userid(req):
+ return 'unauthenticated_userid'
+
+ @property
+ def effective_principals(req):
+ return 'effective_principals'
+
+ return FakeRequest({})