Michael Merickel
2017-06-15 21300198ee62eb00b757a77f2792329ff2d882a0
fix p.security.ACLPermitsResult to subclass p.security.PermitsResult

The ``IAuthorizationPolicy`` is expected to return an instance of
``PermitsResult`` and the ``ACLPermitsResult`` now subclasses this to
form a consistent class hierarchy.

Similarly the ``ACLDenied`` subclasses ``Denied`` and ``ACLAllowed``
subclasses ``Allowed`` for consistency.
4 files modified
143 ■■■■■ changed files
docs/api/security.rst 24 ●●●●● patch | view | raw | blame | history
pyramid/interfaces.py 6 ●●●●● patch | view | raw | blame | history
pyramid/security.py 109 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_security.py 4 ●●●● patch | view | raw | blame | history
docs/api/security.rst
@@ -80,15 +80,23 @@
    'george', 'read')`` that means deny access.  A sequence of ACEs
    makes up an ACL.  It is a string, and its actual value is "Deny".
.. autoclass:: ACLDenied
   :members:
.. autoclass:: ACLAllowed
   :members:
.. autoclass:: Denied
   :members:
   :members: msg
   .. automethod:: __new__
.. autoclass:: Allowed
   :members:
   :members: msg
   .. automethod:: __new__
.. autoclass:: ACLDenied
   :members: msg
   .. automethod:: __new__
.. autoclass:: ACLAllowed
   :members: msg
   .. automethod:: __new__
pyramid/interfaces.py
@@ -503,8 +503,10 @@
class IAuthorizationPolicy(Interface):
    """ An object representing a Pyramid authorization policy. """
    def permits(context, principals, permission):
        """ Return ``True`` if any of the ``principals`` is allowed the
        ``permission`` in the current ``context``, else return ``False``
        """ Return an instance of :class:`pyramid.security.Allowed` if any
        of the ``principals`` is allowed the ``permission`` in the current
        ``context``, else return an instance of
        :class:`pyramid.security.Denied`.
        """
    def principals_allowed_by_permission(context, permission):
pyramid/security.py
@@ -245,6 +245,14 @@
class PermitsResult(int):
    def __new__(cls, s, *args):
        """
        Create a new instance.
        :param fmt: A format string explaining the reason for denial.
        :param args: Arguments are stored and used with the format string
                      to generate the ``msg``.
        """
        inst = int.__new__(cls, cls.boolval)
        inst.s = s
        inst.args = args
@@ -252,6 +260,7 @@
    @property
    def msg(self):
        """ A string indicating why the result was generated."""
        return self.s % self.args
    def __str__(self):
@@ -263,24 +272,52 @@
                                                    self.msg)
class Denied(PermitsResult):
    """ An instance of ``Denied`` is returned when a security-related
    """
    An instance of ``Denied`` is returned when a security-related
    API or other :app:`Pyramid` code denies an action unrelated to
    an ACL check.  It evaluates equal to all boolean false types.  It
    has an attribute named ``msg`` describing the circumstances for
    the deny."""
    the deny.
    """
    boolval = 0
class Allowed(PermitsResult):
    """ An instance of ``Allowed`` is returned when a security-related
    """
    An instance of ``Allowed`` is returned when a security-related
    API or other :app:`Pyramid` code allows an action unrelated to
    an ACL check.  It evaluates equal to all boolean true types.  It
    has an attribute named ``msg`` describing the circumstances for
    the allow."""
    the allow.
    """
    boolval = 1
class ACLPermitsResult(int):
class ACLPermitsResult(PermitsResult):
    def __new__(cls, ace, acl, permission, principals, context):
        inst = int.__new__(cls, cls.boolval)
        """
        Create a new instance.
        :param ace: The :term:`ACE` that matched, triggering the result.
        :param acl: The :term:`ACL` containing ``ace``.
        :param permission: The required :term:`permission`.
        :param principals: The list of :term:`principals <principal>` provided.
        :param context: The :term:`context` providing the :term:`lineage`
                        searched.
        """
        fmt = ('%s permission %r via ACE %r in ACL %r on context %r for '
               'principals %r')
        inst = PermitsResult.__new__(
            cls,
            fmt,
            cls.__name__,
            permission,
            ace,
            acl,
            context,
            principals,
        )
        inst.permission = permission
        inst.ace = ace
        inst.acl = acl
@@ -288,44 +325,31 @@
        inst.context = context
        return inst
    @property
    def msg(self):
        s = ('%s permission %r via ACE %r in ACL %r on context %r for '
             'principals %r')
        return s % (self.__class__.__name__,
                    self.permission,
                    self.ace,
                    self.acl,
                    self.context,
                    self.principals)
class ACLDenied(ACLPermitsResult, Denied):
    """
    An instance of ``ACLDenied`` is a specialization of
    :class:`pyramid.security.Denied` that represents that a security check
    made explicitly against ACL was denied.  It evaluates equal to all
    boolean false types.  It also has the following attributes: ``acl``,
    ``ace``, ``permission``, ``principals``, and ``context``.  These
    attributes indicate the security values involved in the request.  Its
    ``__str__`` method prints a summary of these attributes for debugging
    purposes. The same summary is available as the ``msg`` attribute.
    def __str__(self):
        return self.msg
    """
    def __repr__(self):
        return '<%s instance at %s with msg %r>' % (self.__class__.__name__,
                                                    id(self),
                                                    self.msg)
class ACLAllowed(ACLPermitsResult, Allowed):
    """
    An instance of ``ACLAllowed`` is a specialization of
    :class:`pyramid.security.Allowed` that represents that a security check
    made explicitly against ACL was allowed.  It evaluates equal to all
    boolean true types.  It also has the following attributes: ``acl``,
    ``ace``, ``permission``, ``principals``, and ``context``.  These
    attributes indicate the security values involved in the request.  Its
    ``__str__`` method prints a summary of these attributes for debugging
    purposes. The same summary is available as the ``msg`` attribute.
class ACLDenied(ACLPermitsResult):
    """ An instance of ``ACLDenied`` represents that a security check made
    explicitly against ACL was denied.  It evaluates equal to all boolean
    false types.  It also has the following attributes: ``acl``, ``ace``,
    ``permission``, ``principals``, and ``context``.  These attributes
    indicate the security values involved in the request.  Its __str__ method
    prints a summary of these attributes for debugging purposes.  The same
    summary is available as the ``msg`` attribute."""
    boolval = 0
class ACLAllowed(ACLPermitsResult):
    """ An instance of ``ACLAllowed`` represents that a security check made
    explicitly against ACL was allowed.  It evaluates equal to all boolean
    true types.  It also has the following attributes: ``acl``, ``ace``,
    ``permission``, ``principals``, and ``context``.  These attributes
    indicate the security values involved in the request.  Its __str__ method
    prints a summary of these attributes for debugging purposes.  The same
    summary is available as the ``msg`` attribute."""
    boolval = 1
    """
class AuthenticationAPIMixin(object):
@@ -395,7 +419,8 @@
        :type permission: unicode, str
        :param context: A resource object or ``None``
        :type context: object
        :returns: `pyramid.security.PermitsResult`
        :returns: Either :class:`pyramid.security.Allowed` or
                  :class:`pyramid.security.Denied`.
        .. versionadded:: 1.5
pyramid/tests/test_security.py
@@ -92,9 +92,11 @@
        return klass(*arg, **kw)
    def test_it(self):
        from pyramid.security import Allowed
        msg = ("ACLAllowed permission 'permission' via ACE 'ace' in ACL 'acl' "
               "on context 'ctx' for principals 'principals'")
        allowed = self._makeOne('ace', 'acl', 'permission', 'principals', 'ctx')
        self.assertIsInstance(allowed, Allowed)
        self.assertTrue(msg in allowed.msg)
        self.assertEqual(allowed, True)
        self.assertTrue(allowed)
@@ -112,9 +114,11 @@
        return klass(*arg, **kw)
    def test_it(self):
        from pyramid.security import Denied
        msg = ("ACLDenied permission 'permission' via ACE 'ace' in ACL 'acl' "
               "on context 'ctx' for principals 'principals'")
        denied = self._makeOne('ace', 'acl', 'permission', 'principals', 'ctx')
        self.assertIsInstance(denied, Denied)
        self.assertTrue(msg in denied.msg)
        self.assertEqual(denied, False)
        self.assertFalse(denied)