Skip to content

Commit

Permalink
start reworking security policy
Browse files Browse the repository at this point in the history
  • Loading branch information
merwok committed Dec 14, 2019
1 parent 4a46827 commit 0168300
Show file tree
Hide file tree
Showing 18 changed files with 140 additions and 106 deletions.
1 change: 0 additions & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ ignore =
# W504: line break after binary operator (flake8 is not PEP8 compliant)
W504
exclude =
src/pyramid/compat.py
tests/fixtures
tests/pkgs
tests/test_config/pkgs
Expand Down
4 changes: 0 additions & 4 deletions TODO.txt
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,6 @@ Probably Bad Ideas

- Add functionality that mocks the behavior of ``repoze.browserid``.

- Consider implementing the API outlined in
http://plope.com/pyramid_auth_design_api_postmortem, phasing out the
current auth-n-auth abstractions in a backwards compatible way.

- Maybe add ``add_renderer_globals`` method to Configurator.

- Supply ``X-Vhm-Host`` support (probably better to do what paste#prefix
Expand Down
18 changes: 9 additions & 9 deletions docs/api/request.rst
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,11 @@

.. attribute:: authenticated_userid

.. deprecated:: 2.0
.. versionchanged:: 2.0

``authenticated_userid`` has been replaced by
:attr:`authenticated_identity` in the new security system. See
:ref:`upgrading_auth` for more information.
``authenticated_userid`` uses security policy or authn pol
see also :attr:`authenticated_identity` and
:ref:`upgrading_auth` for more information.

A property which returns the :term:`userid` of the currently
authenticated user or ``None`` if there is no :term:`authentication
Expand All @@ -184,9 +184,9 @@

.. deprecated:: 2.0

``unauthenticated_userid`` has been replaced by
:attr:`authenticated_identity` in the new security system. See
:ref:`upgrading_auth` for more information.
``unauthenticated_userid`` has been replaced by
:attr:`authenticated_identity` in the new security system. See
:ref:`upgrading_auth` for more information.

A property which returns a value which represents the *claimed* (not
verified) :term:`userid` of the credentials present in the
Expand All @@ -203,8 +203,8 @@

.. deprecated:: 2.0

The new security policy has removed the concept of principals. See
:ref:`upgrading_auth` for more information.
The new security policy has removed the concept of principals. See
:ref:`upgrading_auth` for more information.

A property which returns the list of 'effective' :term:`principal`
identifiers for this request. This list typically includes the
Expand Down
4 changes: 2 additions & 2 deletions docs/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,8 @@ Glossary

identity
An identity is an object identifying the user associated with the
current request. The identity can be any object, but should implement a
``__str__`` method that outputs a corresponding :term:`userid`.
current request. The identity can be any object, but security policies
should ensure that it represents a valid user (not deleted or deactivated).

security policy
A security policy in :app:`Pyramid` terms is an object implementing the
Expand Down
70 changes: 42 additions & 28 deletions docs/narr/security.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,19 @@ A simple security policy might look like the following:
from pyramid.security import Allowed, Denied
class SessionSecurityPolicy:
def identify(self, request):
def authenticated_userid(self, request):
""" Return the user ID stored in the session. """
return request.session.get('userid')
def permits(self, request, context, identity, permission):
def identify(self, request):
""" Return app-specific user object. """
userid = self.authenticated_userid(request)
if userid is not None:
return models.Users.get(id=userid)
def permits(self, request, context, permission):
""" Allow access to everything if signed in. """
identity = self.identify(request)
if identity is not None:
return Allowed('User is signed in.')
else:
Expand All @@ -87,7 +94,7 @@ A simple security policy might look like the following:
request.session['userid'] = userid
return []
def forget(request):
def forget(request, **kw):
del request.session['userid']
return []
Expand Down Expand Up @@ -136,12 +143,16 @@ For example, our above security policy can leverage these helpers like so:
def __init__(self):
self.helper = SessionAuthenticationHelper()
def authenticated_userid(self, request):
# XXX add code
...
def identify(self, request):
""" Return the user ID stored in the session. """
return self.helper.identify(request)
def permits(self, request, context, identity, permission):
def permits(self, request, context, permission):
""" Allow access to everything if signed in. """
identity = self.identify(request)
if identity is not None:
return Allowed('User is signed in.')
else:
Expand All @@ -150,22 +161,22 @@ For example, our above security policy can leverage these helpers like so:
def remember(request, userid, **kw):
return self.helper.remember(request, userid, **kw)
def forget(request):
return self.helper.forget(request)
def forget(request, **kw):
return self.helper.forget(request, **kw)
Helpers are intended to be used with application-specific code, so perhaps your
authentication also queries the database to ensure the identity is valid.

.. code-block:: python
:linenos:
def identify(self, request):
""" Return the user ID stored in the session. """
user_id = self.helper.identify(request)
if validate_user_id(user_id):
return user_id
else:
return None
def identify(self, request):
# XXX review: use authenticated_userid below or identify?
user_id = self.helper.identify(request)
if validate_user_id(user_id):
return user_id
else:
return None
.. index::
single: permissions
Expand Down Expand Up @@ -237,7 +248,9 @@ might look like so:
from pyramid.security import Allowed, Denied
class SecurityPolicy:
def permits(self, request, context, identity, permission):
def permits(self, request, context, permission):
identity = self.identify(request)
if identity is None:
return Denied('User is not signed in.')
if identity.role == 'admin':
Expand All @@ -246,6 +259,7 @@ might look like so:
allowed = ['read', 'write']
else:
allowed = ['read']
if permission in allowed:
return Allowed(
'Access granted for user %s with role %s.',
Expand Down Expand Up @@ -326,7 +340,7 @@ object. An implementation might look like this:
from pyramid.authorization import ACLHelper
class SecurityPolicy:
def permits(self, request, context, identity, permission):
def permits(self, request, context, permission):
principals = [Everyone]
if identity is not None:
principals.append(Authenticated)
Expand All @@ -352,7 +366,7 @@ For example, an ACL might be attached to the resource for a blog via its class:
(Allow, Everyone, 'view'),
(Allow, 'group:editors', 'add'),
(Allow, 'group:editors', 'edit'),
]
]
Or, if your resources are persistent, an ACL might be specified via the
``__acl__`` attribute of an *instance* of a resource:
Expand All @@ -369,10 +383,10 @@ Or, if your resources are persistent, an ACL might be specified via the
blog = Blog()
blog.__acl__ = [
(Allow, Everyone, 'view'),
(Allow, 'group:editors', 'add'),
(Allow, 'group:editors', 'edit'),
]
(Allow, Everyone, 'view'),
(Allow, 'group:editors', 'add'),
(Allow, 'group:editors', 'edit'),
]
Whether an ACL is attached to a resource's class or an instance of the resource
itself, the effect is the same. It is useful to decorate individual resource
Expand Down Expand Up @@ -425,10 +439,10 @@ Here's an example ACL:
from pyramid.security import Everyone
__acl__ = [
(Allow, Everyone, 'view'),
(Allow, 'group:editors', 'add'),
(Allow, 'group:editors', 'edit'),
]
(Allow, Everyone, 'view'),
(Allow, 'group:editors', 'add'),
(Allow, 'group:editors', 'edit'),
]
The example ACL indicates that the :data:`pyramid.security.Everyone`
principal—a special system-defined principal indicating, literally, everyone—is
Expand Down Expand Up @@ -460,7 +474,7 @@ dictated by the ACL*. So if you have an ACL like this:
__acl__ = [
(Allow, Everyone, 'view'),
(Deny, Everyone, 'view'),
]
]
The ACL helper will *allow* everyone the view permission, even though later in
the ACL you have an ACE that denies everyone the view permission. On the other
Expand All @@ -476,7 +490,7 @@ hand, if you have an ACL like this:
__acl__ = [
(Deny, Everyone, 'view'),
(Allow, Everyone, 'view'),
]
]
The ACL helper will deny everyone the view permission, even though
later in the ACL, there is an ACE that allows everyone.
Expand All @@ -495,7 +509,7 @@ can collapse this into a single ACE, as below.
__acl__ = [
(Allow, Everyone, 'view'),
(Allow, 'group:editors', ('add', 'edit')),
]
]
.. _special_principals:

Expand Down
2 changes: 2 additions & 0 deletions docs/quick_tutorial/authentication.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ Steps
Analysis
========

# TODO update

Unlike many web frameworks, Pyramid includes a built-in but optional security
model for authentication and authorization. This security system is intended to
be flexible and support many needs. In this security model, authentication (who
Expand Down
10 changes: 6 additions & 4 deletions docs/whatsnew-2.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ The new security policy adds the concept of an :term:`identity`, which is an
object representing the user associated with the current request. The identity
can be accessed via :attr:`pyramid.request.Request.authenticated_identity`.
The object can be of any shape, such as a simple ID string or an ORM object,
but should implement a ``__str__`` method that returns a string identifying the
current user, e.g. the ID of the user object in a database. The string
representation is return as
:attr:`pyramid.request.Request.authenticated_userid`.
and should represent an active user.

As in previous version, the property :attr:`pyramid.request.Request.authenticated_userid`
can be used to get a string identifying the current user, for example
the ID of the user object in a database. The value is obtained from the
security policy.
(:attr:`pyramid.request.Request.unauthenticated_userid` has been deprecated.)

The concept of :term:`principals <principal>` has been removed; the
Expand Down
6 changes: 3 additions & 3 deletions src/pyramid/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -1110,7 +1110,7 @@ def forget(self, request):
return self.helper.forget(request)

def unauthenticated_userid(self, request):
return self.helper.identify(request)
return self.helper.authenticated_userid(request)


class SessionAuthenticationHelper:
Expand All @@ -1134,13 +1134,13 @@ def remember(self, request, userid, **kw):
request.session[self.userid_key] = userid
return []

def forget(self, request):
def forget(self, request, **kw):
""" Remove the stored userid from the session."""
if self.userid_key in request.session:
del request.session[self.userid_key]
return []

def identify(self, request):
def authenticated_userid(self, request):
""" Return the stored userid."""
return request.session.get(self.userid_key)

Expand Down
8 changes: 6 additions & 2 deletions src/pyramid/config/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class TestingConfiguratorMixin(object):
# testing API
def testing_securitypolicy(
self,
userid=None,
identity=None,
permissive=True,
remember_result=None,
Expand All @@ -39,6 +40,7 @@ def testing_securitypolicy(
not provided (or it is provided, and is ``None``), the default value
``[]`` (the empty list) will be returned by ``forget``.
XXX rewrite
The behavior of the registered :term:`authentication policy`
depends on the values provided for the ``userid`` and
``groupids`` argument. The authentication policy will return
Expand All @@ -51,19 +53,21 @@ def testing_securitypolicy(
This function is most useful when testing code that uses
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
The ``remember_result`` argument.
.. versionadded:: 1.4
The ``forget_result`` argument.
.. versionchanged:: 2.0
Removed ``groupids`` argument and doc about effective principals.
"""
from pyramid.testing import DummySecurityPolicy

policy = DummySecurityPolicy(
identity, permissive, remember_result, forget_result
userid, identity, permissive, remember_result, forget_result
)
self.registry.registerUtility(policy, ISecurityPolicy)
return policy
Expand Down
17 changes: 9 additions & 8 deletions src/pyramid/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,32 +484,33 @@ def __call__(self, **kw):

class ISecurityPolicy(Interface):
def identify(request):
""" Return an object identifying a trusted and verified user. This
object may be anything, but should implement a ``__str__`` method that
outputs a corresponding :term:`userid`.
""" Return an object identifying a trusted and verified user.
The object may be anything.
"""

def permits(request, context, identity, permission):
def authenticated_userid(request, identity):
""" Return a :term:`userid` string identifying the trusted and
verified user, or ``None`` if unauthenticated.
"""

def permits(request, context, permission):
""" Return an instance of :class:`pyramid.security.Allowed` if a user
of the given identity is allowed the ``permission`` in the current
``context``, else return an instance of
:class:`pyramid.security.Denied`.
"""

def remember(request, userid, **kw):
""" Return a set of headers suitable for 'remembering' the
: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):
def forget(request, **kw):
""" Return a set of headers suitable for 'forgetting' the
current user on subsequent requests.
"""


Expand Down
Loading

0 comments on commit 0168300

Please sign in to comment.