summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt11
-rw-r--r--TODO.txt5
-rw-r--r--docs/api/request.rst41
-rw-r--r--docs/api/security.rst2
-rw-r--r--docs/glossary.rst23
-rw-r--r--docs/narr/security.rst94
-rw-r--r--docs/tutorials/wiki/design.rst4
-rw-r--r--docs/tutorials/wiki2/design.rst3
-rw-r--r--pyramid/authentication.py24
-rw-r--r--pyramid/interfaces.py57
-rw-r--r--pyramid/security.py25
-rw-r--r--pyramid/testing.py4
-rw-r--r--pyramid/tests/test_security.py21
13 files changed, 209 insertions, 105 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index bcd4498c5..c7c829fb6 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -96,6 +96,13 @@ Bug Fixes
- Fix route generation for static view asset specifications having no path.
See https://github.com/Pylons/pyramid/pull/1377
+Deprecations
+------------
+
+- Renamed the ``principal`` argument to ``pyramid.security.remember()`` to
+ ``userid`` in order to clarify its intended purpose.
+ See https://github.com/Pylons/pyramid/pull/1399
+
Docs
----
@@ -106,6 +113,10 @@ Docs
- Clarify a previously-implied detail of the ``ISession.invalidate`` API
documentation.
+- Improve and clarify the documentation on what Pyramid defines as a
+ ``principal`` and a ``userid`` in its security APIs.
+ See https://github.com/Pylons/pyramid/pull/1399
+
Scaffolds
---------
diff --git a/TODO.txt b/TODO.txt
index 62b8c39f4..e738b58d8 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -125,7 +125,10 @@ Future
- 1.7: Change ``pyramid.authentication.AuthTktAuthenticationPolicy`` default
``hashalg`` to ``sha512``.
-- 1.8 Remove set_request_property.
+- 1.8: Remove set_request_property.
+
+- 1.9: Remove extra code enabling ``pyramid.security.remember(principal=...)``
+ and force use of ``userid``.
Probably Bad Ideas
------------------
diff --git a/docs/api/request.rst b/docs/api/request.rst
index 77d80f6d6..dd68fa09c 100644
--- a/docs/api/request.rst
+++ b/docs/api/request.rst
@@ -167,37 +167,40 @@
.. 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``.
+ A property which returns the :term:`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 :term:`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.
+ verified) :term:`userid` 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 :term:`userid` exists in persistent storage.
+ Even if the :term:`userid` does not exist in persistent storage, this
+ value will be the value of the :term:`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.
+ identifiers for this request. This list typically includes the
+ :term:`userid` of the currently authenticated user if a user is
+ currently authenticated, but this depends on the
+ :term:`authentication policy` in effect. 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)
diff --git a/docs/api/security.rst b/docs/api/security.rst
index 814b68e5a..88086dbbf 100644
--- a/docs/api/security.rst
+++ b/docs/api/security.rst
@@ -16,7 +16,7 @@ Authentication API Functions
.. autofunction:: forget
-.. autofunction:: remember
+.. autofunction:: remember(request, userid, **kwargs)
Authorization API Functions
---------------------------
diff --git a/docs/glossary.rst b/docs/glossary.rst
index ef7e9a9ae..01300a0be 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -286,13 +286,22 @@ Glossary
:term:`authorization policy`.
principal
- A *principal* is a string or unicode object representing a userid
- or a group id. It is provided by an :term:`authentication
- policy`. For example, if a user had the user id "bob", and Bob
- was part of two groups named "group foo" and "group bar", the
- request might have information attached to it that would
- indicate that Bob was represented by three principals: "bob",
- "group foo" and "group bar".
+ A *principal* is a string or unicode object representing an
+ entity, typically a user or group. Principals are provided by an
+ :term:`authentication policy`. For example, if a user had the
+ :term:`userid` `"bob"`, and was part of two groups named `"group foo"`
+ and "group bar", the request might have information attached to
+ it that would indicate that Bob was represented by three
+ principals: `"bob"`, `"group foo"` and `"group bar"`.
+
+ userid
+ A *userid* is a string or unicode object used to identify and
+ authenticate a real-world user (or client). A userid is
+ supplied to an :term:`authentication policy` in order to discover
+ the user's :term:`principals <principal>`. The default behavior
+ of the authentication policies :app:`Pyramid` provides is to
+ return the user's userid as a principal, but this is not strictly
+ necessary in custom policies that define their principals differently.
authorization policy
An authorization policy in :app:`Pyramid` terms is a bit of
diff --git a/docs/narr/security.rst b/docs/narr/security.rst
index 8db23a33b..2dc0c76af 100644
--- a/docs/narr/security.rst
+++ b/docs/narr/security.rst
@@ -6,13 +6,28 @@
Security
========
-:app:`Pyramid` provides an optional declarative authorization system
-that can prevent a :term:`view` from being invoked based on an
+:app:`Pyramid` provides an optional, declarative, security system.
+Security in :app:`Pyramid` is separated into authentication and
+authorization. The two systems communicate via :term:`principal`
+identifiers. Authentication is merely the mechanism by which credentials
+provided in the :term:`request` are resolved to one or more
+:term:`principal` identifiers. These identifiers represent the users and
+groups that are in effect during the request. Authorization then determines
+access based on the :term:`principal` identifiers, the requested
+:term:`permission`, and a :term:`context`.
+
+The :app:`Pyramid` authorization system
+can prevent a :term:`view` from being invoked based on an
:term:`authorization policy`. Before a view is invoked, the
authorization system can use the credentials in the :term:`request`
along with the :term:`context` resource to determine if access will be
allowed. Here's how it works at a high level:
+- A user may or may not have previously visited the application and
+ supplied authentication credentials, including a :term:`userid`. If
+ so, the application may have called
+ :func:`pyramid.security.remember` to remember these.
+
- A :term:`request` is generated when a user visits the application.
- Based on the request, a :term:`context` resource is located through
@@ -25,7 +40,9 @@ allowed. Here's how it works at a high level:
context as well as other attributes of the request.
- If an :term:`authentication policy` is in effect, it is passed the
- request; it returns some number of :term:`principal` identifiers.
+ request. It will return some number of :term:`principal` identifiers.
+ To do this, the policy would need to determine the authenticated
+ :term:`userid` present in the request.
- If an :term:`authorization policy` is in effect and the :term:`view
configuration` associated with the view callable that was found has
@@ -41,15 +58,6 @@ allowed. Here's how it works at a high level:
- If the authorization policy denies access, the view callable is not
invoked; instead the :term:`forbidden view` is invoked.
-Security in :app:`Pyramid`, unlike many systems, cleanly and explicitly
-separates authentication and authorization. Authentication is merely the
-mechanism by which credentials provided in the :term:`request` are
-resolved to one or more :term:`principal` identifiers. These identifiers
-represent the users and groups in effect during the request.
-Authorization then determines access based on the :term:`principal`
-identifiers, the :term:`view callable` being invoked, and the
-:term:`context` resource.
-
Authorization is enabled by modifying your application to include an
:term:`authentication policy` and :term:`authorization policy`.
:app:`Pyramid` comes with a variety of implementations of these
@@ -104,7 +112,8 @@ For example:
The above configuration enables a policy which compares the value of an "auth
ticket" cookie passed in the request's environment which contains a reference
-to a single :term:`principal` against the principals present in any
+to a single :term:`userid` and matches that userid's
+:term:`principals <principal>` against the principals present in any
:term:`ACL` found in the resource tree when attempting to call some
:term:`view`.
@@ -595,36 +604,53 @@ that implements the following interface:
""" An object representing a Pyramid authentication policy. """
def authenticated_userid(self, request):
- """ Return the authenticated userid or ``None`` if no
- authenticated userid can be found. This method of the policy
- should ensure that a record exists in whatever persistent store is
- used related to the user (the user should not have been deleted);
- if a record associated with the current id does not exist in a
- persistent store, it should return ``None``."""
+ """ Return the authenticated :term:`userid` or ``None`` if
+ no authenticated userid can be found. This method of the
+ policy should ensure that a record exists in whatever
+ persistent store is used related to the user (the user
+ should not have been deleted); if a record associated with
+ the current id does not exist in a persistent store, it
+ should return ``None``.
+
+ """
def unauthenticated_userid(self, request):
- """ Return the *unauthenticated* userid. This method performs the
- same duty as ``authenticated_userid`` but is permitted to return the
- userid based only on data present in the request; it needn't (and
- shouldn't) check any persistent store to ensure that the user record
- related to the request userid exists."""
+ """ Return the *unauthenticated* userid. This method
+ performs the same duty as ``authenticated_userid`` but is
+ permitted to return the userid based only on data present
+ in the request; it needn't (and shouldn't) check any
+ persistent store to ensure that the user record related to
+ the request userid exists.
+
+ This method is intended primarily a helper to assist the
+ ``authenticated_userid`` method in pulling credentials out
+ of the request data, abstracting away the specific headers,
+ query strings, etc that are used to authenticate the request.
+
+ """
def effective_principals(self, request):
""" Return a sequence representing the effective principals
- including the userid and any groups belonged to by the current
- user, including 'system' groups such as
- ``pyramid.security.Everyone`` and
- ``pyramid.security.Authenticated``. """
+ typically including the :term:`userid` and any groups belonged
+ to by the current user, always including 'system' groups such
+ as ``pyramid.security.Everyone`` and
+ ``pyramid.security.Authenticated``.
- def remember(self, request, principal, **kw):
+ """
+
+ def remember(self, request, userid, **kw):
""" Return a set of headers suitable for 'remembering' the
- principal named ``principal`` when set in a response. An
- individual authentication policy and its consumers can decide
- on the composition and meaning of **kw. """
-
+ :term:`userid` named ``userid`` when set in a response. An
+ individual authentication policy and its consumers can
+ decide on the composition and meaning of **kw.
+
+ """
+
def forget(self, request):
""" Return a set of headers suitable for 'forgetting' the
- current user on subsequent requests. """
+ current user on subsequent requests.
+
+ """
After you do so, you can pass an instance of such a class into the
:class:`~pyramid.config.Configurator.set_authentication_policy` method
diff --git a/docs/tutorials/wiki/design.rst b/docs/tutorials/wiki/design.rst
index eb785dd1c..28380bd66 100644
--- a/docs/tutorials/wiki/design.rst
+++ b/docs/tutorials/wiki/design.rst
@@ -53,10 +53,10 @@ Security
We'll eventually be adding security to our application. The components we'll
use to do this are below.
-- USERS, a dictionary mapping usernames to their
+- USERS, a dictionary mapping :term:`userids <userid>` to their
corresponding passwords.
-- GROUPS, a dictionary mapping usernames to a
+- GROUPS, a dictionary mapping :term:`userids <userid>` to a
list of groups to which they belong to.
- ``groupfinder``, an *authorization callback* that looks up
diff --git a/docs/tutorials/wiki2/design.rst b/docs/tutorials/wiki2/design.rst
index df2c83398..ff7413668 100644
--- a/docs/tutorials/wiki2/design.rst
+++ b/docs/tutorials/wiki2/design.rst
@@ -53,7 +53,8 @@ Security
We'll eventually be adding security to our application. The components we'll
use to do this are below.
-- USERS, a dictionary mapping users names to their corresponding passwords.
+- USERS, a dictionary mapping users names (the user's :term:`userids
+ <userid>`) to their corresponding passwords.
- GROUPS, a dictionary mapping user names to a list of groups they belong to.
diff --git a/pyramid/authentication.py b/pyramid/authentication.py
index b84981bbc..f4c211ffa 100644
--- a/pyramid/authentication.py
+++ b/pyramid/authentication.py
@@ -335,11 +335,11 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy):
effective_principals.extend(groups)
return effective_principals
- def remember(self, request, principal, **kw):
- """ Store the ``principal`` as ``repoze.who.userid``.
+ def remember(self, request, userid, **kw):
+ """ Store the ``userid`` as ``repoze.who.userid``.
The identity to authenticated to :mod:`repoze.who`
- will contain the given principal as ``userid``, and
+ will contain the given userid as ``userid``, and
provide all keyword arguments as additional identity
keys. Useful keys could be ``max_age`` or ``userdata``.
"""
@@ -348,7 +348,7 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy):
return []
environ = request.environ
identity = kw
- identity['repoze.who.userid'] = principal
+ identity['repoze.who.userid'] = userid
return identifier.remember(environ, identity)
def forget(self, request):
@@ -404,7 +404,7 @@ class RemoteUserAuthenticationPolicy(CallbackAuthenticationPolicy):
""" The ``REMOTE_USER`` value found within the ``environ``."""
return request.environ.get(self.environ_key)
- def remember(self, request, principal, **kw):
+ def remember(self, request, userid, **kw):
""" A no-op. The ``REMOTE_USER`` does not provide a protocol for
remembering the user. This will be application-specific and can
be done somewhere else or in a subclass."""
@@ -652,7 +652,7 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy):
if result:
return result['userid']
- def remember(self, request, principal, **kw):
+ def remember(self, request, userid, **kw):
""" Accepts the following kw args: ``max_age=<int-seconds>,
``tokens=<sequence-of-ascii-strings>``.
@@ -660,7 +660,7 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy):
the response.
"""
- return self.cookie.remember(request, principal, **kw)
+ return self.cookie.remember(request, userid, **kw)
def forget(self, request):
""" A list of headers which will delete appropriate cookies."""
@@ -1061,13 +1061,13 @@ class SessionAuthenticationPolicy(CallbackAuthenticationPolicy):
self.userid_key = prefix + 'userid'
self.debug = debug
- def remember(self, request, principal, **kw):
- """ Store a principal in the session."""
- request.session[self.userid_key] = principal
+ def remember(self, request, userid, **kw):
+ """ Store a userid in the session."""
+ request.session[self.userid_key] = userid
return []
def forget(self, request):
- """ Remove the stored principal from the session."""
+ """ Remove the stored userid from the session."""
if self.userid_key in request.session:
del request.session[self.userid_key]
return []
@@ -1132,7 +1132,7 @@ class BasicAuthAuthenticationPolicy(CallbackAuthenticationPolicy):
if credentials:
return credentials[0]
- def remember(self, request, principal, **kw):
+ def remember(self, request, userid, **kw):
""" A no-op. Basic authentication does not provide a protocol for
remembering the user. Credentials are sent on every request.
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index c5a70dbfd..2b56262c0 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -439,36 +439,55 @@ class IViewMapperFactory(Interface):
class IAuthenticationPolicy(Interface):
""" An object representing a Pyramid authentication policy. """
+
def authenticated_userid(request):
- """ Return the authenticated userid or ``None`` if no authenticated
- userid can be found. This method of the policy should ensure that a
- record exists in whatever persistent store is used related to the
- user (the user should not have been deleted); if a record associated
- with the current id does not exist in a persistent store, it should
- return ``None``."""
+ """ Return the authenticated :term:`userid` or ``None`` if
+ no authenticated userid can be found. This method of the
+ policy should ensure that a record exists in whatever
+ persistent store is used related to the user (the user
+ should not have been deleted); if a record associated with
+ the current id does not exist in a persistent store, it
+ should return ``None``.
+
+ """
def unauthenticated_userid(request):
- """ Return the *unauthenticated* userid. This method performs the
- same duty as ``authenticated_userid`` but is permitted to return the
- userid based only on data present in the request; it needn't (and
- shouldn't) check any persistent store to ensure that the user record
- related to the request userid exists."""
+ """ Return the *unauthenticated* userid. This method
+ performs the same duty as ``authenticated_userid`` but is
+ permitted to return the userid based only on data present
+ in the request; it needn't (and shouldn't) check any
+ persistent store to ensure that the user record related to
+ the request userid exists.
+
+ This method is intended primarily a helper to assist the
+ ``authenticated_userid`` method in pulling credentials out
+ of the request data, abstracting away the specific headers,
+ query strings, etc that are used to authenticate the request.
+
+ """
def effective_principals(request):
""" Return a sequence representing the effective principals
- including the userid and any groups belonged to by the current
- user, including 'system' groups such as Everyone and
- Authenticated. """
+ typically including the :term:`userid` and any groups belonged
+ to by the current user, always including 'system' groups such
+ as ``pyramid.security.Everyone`` and
+ ``pyramid.security.Authenticated``.
- def remember(request, principal, **kw):
+ """
+
+ def remember(request, userid, **kw):
""" Return a set of headers suitable for 'remembering' the
- principal named ``principal`` when set in a response. An
- individual authentication policy and its consumers can decide
- on the composition and meaning of ``**kw.`` """
+ :term:`userid` named ``userid`` when set in a response. An
+ individual authentication policy and its consumers can
+ decide on the composition and meaning of **kw.
+
+ """
def forget(request):
""" Return a set of headers suitable for 'forgetting' the
- current user on subsequent requests. """
+ current user on subsequent requests.
+
+ """
class IAuthorizationPolicy(Interface):
""" An object representing a Pyramid authorization policy. """
diff --git a/pyramid/security.py b/pyramid/security.py
index 041155563..cbb4b895f 100644
--- a/pyramid/security.py
+++ b/pyramid/security.py
@@ -17,6 +17,8 @@ Authenticated = 'system.Authenticated'
Allow = 'Allow'
Deny = 'Deny'
+_marker = object()
+
class AllPermissionsList(object):
""" Stand in 'permission list' to represent all permissions """
def __iter__(self):
@@ -115,12 +117,12 @@ deprecated(
'"effective_principals" attribute of the Pyramid request instead.'
)
-def remember(request, principal, **kw):
+def remember(request, userid=_marker, **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
+ implied by the data passed as ``userid`` 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
@@ -138,11 +140,28 @@ def remember(request, principal, **kw):
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.
+
+ .. deprecated:: 1.6
+ Renamed the ``principal`` argument to ``userid`` to clarify its
+ purpose.
"""
+ if userid is _marker:
+ principal = kw.pop('principal', _marker)
+ if principal is _marker:
+ raise TypeError(
+ 'remember() missing 1 required positional argument: '
+ '\'userid\'')
+ else:
+ deprecated(
+ 'principal',
+ 'The "principal" argument was deprecated in Pyramid 1.6. '
+ 'It will be removed in Pyramid 1.9. Use the "userid" '
+ 'argument instead.')
+ userid = principal
policy = _get_authentication_policy(request)
if policy is None:
return []
- return policy.remember(request, principal, **kw)
+ return policy.remember(request, userid, **kw)
def forget(request):
"""
diff --git a/pyramid/testing.py b/pyramid/testing.py
index 8cbd8b82b..f77889e72 100644
--- a/pyramid/testing.py
+++ b/pyramid/testing.py
@@ -79,8 +79,8 @@ class DummySecurityPolicy(object):
effective_principals.extend(self.groupids)
return effective_principals
- def remember(self, request, principal, **kw):
- self.remembered = principal
+ def remember(self, request, userid, **kw):
+ self.remembered = userid
return self.remember_result
def forget(self, request):
diff --git a/pyramid/tests/test_security.py b/pyramid/tests/test_security.py
index 6f08a100c..6d75ac8e3 100644
--- a/pyramid/tests/test_security.py
+++ b/pyramid/tests/test_security.py
@@ -134,9 +134,9 @@ class TestRemember(unittest.TestCase):
def tearDown(self):
testing.tearDown()
- def _callFUT(self, *arg):
+ def _callFUT(self, *arg, **kwarg):
from pyramid.security import remember
- return remember(*arg)
+ return remember(*arg, **kwarg)
def test_no_authentication_policy(self):
request = _makeRequest()
@@ -159,6 +159,19 @@ class TestRemember(unittest.TestCase):
result = self._callFUT(request, 'me')
self.assertEqual(result, [('X-Pyramid-Test', 'me')])
+ def test_with_deprecated_principal_arg(self):
+ request = _makeRequest()
+ registry = request.registry
+ _registerAuthenticationPolicy(registry, 'yo')
+ result = self._callFUT(request, principal='me')
+ self.assertEqual(result, [('X-Pyramid-Test', 'me')])
+
+ def test_with_missing_arg(self):
+ request = _makeRequest()
+ registry = request.registry
+ _registerAuthenticationPolicy(registry, 'yo')
+ self.assertRaises(TypeError, lambda: self._callFUT(request))
+
class TestForget(unittest.TestCase):
def setUp(self):
testing.setUp()
@@ -462,8 +475,8 @@ class DummyAuthenticationPolicy:
def authenticated_userid(self, request):
return self.result
- def remember(self, request, principal, **kw):
- headers = [(_TEST_HEADER, principal)]
+ def remember(self, request, userid, **kw):
+ headers = [(_TEST_HEADER, userid)]
self._header_remembered = headers[0]
return headers