summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2017-09-14 12:17:35 -0500
committerJason Madden <jamadden@gmail.com>2017-09-14 12:17:35 -0500
commit8b93f2bb08c45c18f207f61b6691fd13664f8609 (patch)
tree5511bb48cda0d0303e762b1d53a4b392f8209a9e /docs
parente8818663a59c5c8b146f599ba87666bf1cd7af75 (diff)
downloadzope-security-8b93f2bb08c45c18f207f61b6691fd13664f8609.tar.gz
Document proxy.__class__ troubles with isinstance/issubclassdocs
And suggest workarounds, in a prominent new section about proxy troubles. Link to this from everywhere we talk about proxies. Fixes #26
Diffstat (limited to 'docs')
-rw-r--r--docs/api/proxy.rst4
-rw-r--r--docs/proxy.rst97
2 files changed, 101 insertions, 0 deletions
diff --git a/docs/api/proxy.rst b/docs/api/proxy.rst
index 733d5df..0acb043 100644
--- a/docs/api/proxy.rst
+++ b/docs/api/proxy.rst
@@ -4,6 +4,10 @@
.. currentmodule:: zope.security.proxy
+An introduction to proxies and their uses can be found in :doc:`../proxy`.
+
+.. seealso:: :ref:`proxy-known-issues`
+
.. testsetup::
from zope.component.testing import setUp
diff --git a/docs/proxy.rst b/docs/proxy.rst
index f5d8166..20cf3af 100644
--- a/docs/proxy.rst
+++ b/docs/proxy.rst
@@ -218,3 +218,100 @@ the interpreted program needs to be able to create simple data
containers to hold information computed in the course of the program
execution. Several safe container types are provided for this
purpose.
+
+.. _proxy-known-issues:
+
+Known Issues With Proxies
+=========================
+
+Security proxies (proxies in general) are not perfect in Python. There
+are some things that they cannot transparently proxy.
+
+issubclass and proxies
+----------------------
+
+Security proxies will proxy the return value of ``__class__``: it will
+be a proxy around the real class of the proxied value. This causes
+failures with ``issubclass``:
+
+.. doctest::
+
+ >>> from zope.security.proxy import ProxyFactory
+ >>> class Object(object):
+ ... pass
+ >>> target = Object()
+ >>> target.__class__ is Object
+ True
+ >>> proxy = ProxyFactory(target, None)
+ >>> proxy.__class__
+ <class 'Object'>
+ >>> proxy.__class__ is Object
+ False
+ >>> issubclass(proxy.__class__, Object)
+ Traceback (most recent call last):
+ ...
+ TypeError: issubclass() arg 1 must be a class
+
+Although the above is a contrived example, using :class:`abstract base
+classes <abc.ABCMeta>` can cause it to arise quite
+unexpectedly:
+
+.. doctest::
+
+ >>> from collections import Mapping
+ >>> isinstance(proxy, Mapping)
+ Traceback (most recent call last):
+ ...
+ TypeError: issubclass() arg 1 must be a class
+
+logging
+~~~~~~~
+
+Starting with `Python 2.7.7 <https://bugs.python.org/issue21172>`_,
+the :class:`logging.LogRecord` makes exactly the above ``isinstance``
+call:
+
+.. doctest::
+
+ >>> from logging import LogRecord
+ >>> LogRecord("name", 1, "/path/to/file", 1,
+ ... "The message %s", (proxy,), None)
+ Traceback (most recent call last):
+ ...
+ TypeError: issubclass() arg 1 must be a class
+
+`Possible workarounds include <https://github.com/zopefoundation/zope.security/issues/26>`_:
+
+- Carefully removing security proxies of objects before passing them
+ to the logging system.
+- Monkey-patching the logging system to use
+ :func:`zope.security.proxy.isinstance` which does this
+ automatically::
+
+ import zope.security.proxy
+ import logging
+ logging.isinstance = zope.security.proxy.isinstance
+- Using :func:`logging.setLogRecordfactory` to set a custom
+ ``LogRecord`` subclass that unwraps any security proxies before they
+ are given to the super class. Note that this is only available on
+ Python 3. On Python 2, it might be possible to achieve a similar
+ result with a custom :func:`logger class <logging.setLoggerClass>`:
+
+.. doctest::
+
+ >>> from zope.security.proxy import removeSecurityProxy
+ >>> class UnwrappingLogRecord(LogRecord):
+ ... def __init__(self, name, level, pathname, lineno,
+ ... msg, args, exc_info, *largs, **kwargs):
+ ... args = [removeSecurityProxy(x) for x in args]
+ ... LogRecord.__init__(self, name, level, pathname,
+ ... lineno, msg, args, exc_info, *largs, **kwargs)
+ ... def __repr__(self):
+ ... return '<UnwrappingLogRecord>'
+ >>> UnwrappingLogRecord("name", 1, "/path/to/file", 1,
+ ... "The message %s", (proxy,), None)
+ <UnwrappingLogRecord>
+
+
+Each specific application will have to determine what solution is
+correct for its security model.