Skip to content

Commit

Permalink
Merge pull request #36 from zopefoundation/issue8
Browse files Browse the repository at this point in the history
Fix ZOPE_WATCH_CHECKERS in pure-Python mode
  • Loading branch information
jamadden committed Sep 11, 2017
2 parents a19b95f + aac82c4 commit 009f429
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 62 deletions.
6 changes: 4 additions & 2 deletions .travis.yml
Expand Up @@ -9,9 +9,11 @@ matrix:
- python: pypy
- python: pypy3
- python: 2.7
env: PURE_PYTHON=1
env:
- PURE_PYTHON=1
- ZOPE_WATCH_CHECKERS=1
- python: 3.4
env: PURE_PYTHON=1
env: ZOPE_WATCH_CHECKERS=1
install:
- pip install -U pip setuptools
- pip install -U coveralls coverage
Expand Down
3 changes: 3 additions & 0 deletions CHANGES.rst
Expand Up @@ -35,6 +35,9 @@ Changes
- Respect ``PURE_PYTHON`` at runtime. See `issue 33
<https://github.com/zopefoundation/zope.security/issues/33>`_.

- Fix watching checkers (``ZOPE_WATCH_CHECKERS=1``) in pure-Python
mode. See `issue 8 <https://github.com/zopefoundation/zope.security/issues/8>`_.

4.1.1 (2017-05-17)
------------------

Expand Down
34 changes: 30 additions & 4 deletions docs/conf.py
Expand Up @@ -11,8 +11,16 @@
# All configuration values have a default; values that are commented out
# serve to show the default.

import sys, os

import os
import sys
import pkg_resources
sys.path.append(os.path.abspath('../src'))
rqmt = pkg_resources.require('zope.security')[0]

# We can't have ZOPE_WATCH_CHECKERS set because it interferes
# with doctests
os.environ['ZOPE_WATCH_CHECKERS'] = '0'
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
Expand All @@ -28,6 +36,7 @@
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.doctest',
'sphinx.ext.extlinks',
'sphinx.ext.intersphinx',
'sphinx.ext.viewcode',
'repoze.sphinx.autointerface',
Expand All @@ -54,9 +63,9 @@
# built documents.
#
# The short X.Y version.
version = '4.0'
version = '%s.%s' % tuple(map(int, rqmt.version.split('.')[:2]))
# The full version, including alpha/beta/rc tags.
release = '4.0dev'
release = rqmt.version

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down Expand Up @@ -249,4 +258,21 @@


# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'http://docs.python.org/': None}
intersphinx_mapping = {
'https://docs.python.org/': None,
'https://zopeinterface.readthedocs.io/en/latest': None,
'https://zopeproxy.readthedocs.io/en/latest': None,
'https://zopeschema.readthedocs.io/en/latest': None,
}

extlinks = {'issue': ('https://github.com/zopefoundation/zope.datetime/issues/%s',
'issue #'),
'pr': ('https://github.com/zopefoundation/zope.datetime/pull/%s',
'pull request #')}

autodoc_default_flags = [
'members',
'show-inheritance',
]
autoclass_content = 'both'
autodoc_member_order = 'bysource'
55 changes: 35 additions & 20 deletions src/zope/security/checker.py
Expand Up @@ -55,17 +55,17 @@

try:
from zope.exceptions import DuplicationError
except ImportError: #pragma NO COVER
except ImportError: # pragma: no cover
class DuplicationError(Exception):
"""A duplicate registration was attempted"""

if os.environ.get('ZOPE_WATCH_CHECKERS'): #pragma NO COVER
WATCH_CHECKERS = 0

if os.environ.get('ZOPE_WATCH_CHECKERS'):
try:
WATCH_CHECKERS = int(os.environ.get('ZOPE_WATCH_CHECKERS'))
except ValueError:
WATCH_CHECKERS = 1
else:
WATCH_CHECKERS = 0


def ProxyFactory(object, checker=None):
Expand Down Expand Up @@ -256,19 +256,19 @@ def getInfo(self):
cls = self.obj.__class__
if hasattr(cls, "__module__"):
s = "%s.%s" % (cls.__module__, cls.__name__)
else: #pragma NO COVER XXX
else: # pragma: no cover XXX
s = str(cls.__name__)
result.append(" - class: " + s)
except: #pragma NO COVER XXX
except: # pragma: no cover XXX
pass
try:
cls = type(self.obj)
if hasattr(cls, "__module__"):
s = "%s.%s" % (cls.__module__, cls.__name__)
else: #pragma NO COVER XXX
else: # pragma: no cover XXX
s = str(cls.__name__)
result.append(" - type: " + s)
except: #pragma NO COVER XXX
except: # pragma: no cover XXX
pass
return "\n".join(result)

Expand All @@ -282,7 +282,7 @@ class Global(object):
"""

def __init__(self, name, module=None):
if module is None: #pragma NO COVER XXX
if module is None: # pragma: no cover XXX
module = sys._getframe(1).f_locals['__name__']

self.__name__ = name
Expand Down Expand Up @@ -395,6 +395,7 @@ def selectCheckerPy(object):

while not isinstance(checker, Checker):
checker = checker(object)

if checker is NoProxy or checker is None:
return None

Expand Down Expand Up @@ -443,7 +444,7 @@ def undefineChecker(type_):
if _c_available:
try:
import zope.security._zope_security_checker
except (ImportError, AttributeError): #pragma NO COVER PyPy / PURE_PYTHON
except (ImportError, AttributeError): # pragma: no cover PyPy / PURE_PYTHON
_c_available = False

if _c_available:
Expand Down Expand Up @@ -479,6 +480,7 @@ def __init__(self, checker1, checker2):
Checker.__init__(self,
checker1.get_permissions,
checker1.set_permissions)

self._checker2 = checker2

def check(self, object, name):
Expand Down Expand Up @@ -580,11 +582,24 @@ def check_setattr(self, object, name):
raise


if WATCH_CHECKERS: #pragma NO COVER
class Checker(CheckerLoggingMixin, Checker):
verbosity = WATCH_CHECKERS
class CombinedChecker(CheckerLoggingMixin, CombinedChecker):
verbosity = WATCH_CHECKERS
# We have to be careful with the order of inheritance
# here. See https://github.com/zopefoundation/zope.security/issues/8
class WatchingChecker(CheckerLoggingMixin, Checker):
verbosity = WATCH_CHECKERS
class WatchingCombinedChecker(CombinedChecker, WatchingChecker):
verbosity = WATCH_CHECKERS

if WATCH_CHECKERS: # pragma: no cover
# When we make these the default, we also need to be sure
# to update the _defaultChecker's type (if it's not the C
# extension) so that selectCheckerPy can properly recognize
# it as a Checker.
# See https://github.com/zopefoundation/zope.security/issues/8
Checker = WatchingChecker
CombinedChecker = WatchingCombinedChecker

if not _c_available:
_defaultChecker.__class__ = Checker

def _instanceChecker(inst):
return _checkers.get(inst.__class__, _defaultChecker)
Expand Down Expand Up @@ -658,7 +673,7 @@ def update(self, d):
if PYTHON2:
_basic_types[long] = NoProxy
_basic_types[unicode] = NoProxy
else: #pragma NO COVER
else: # pragma: no cover
_basic_types[type({}.values())] = NoProxy
_basic_types[type({}.keys())] = NoProxy
_basic_types[type({}.items())] = NoProxy
Expand All @@ -667,7 +682,7 @@ def update(self, d):
import pytz
except ImportError:
pass
else: #pragma NO COVER
else: # pragma: no cover
_basic_types[type(pytz.UTC)] = NoProxy

BasicTypes = BasicTypes(_basic_types)
Expand All @@ -694,7 +709,7 @@ def update(self, d):
BasicTypes_examples[long] = long(65536)


class _Sequence(object): #pragma NO COVER
class _Sequence(object): # pragma: no cover
def __len__(self): return 0
def __getitem__(self, i): raise IndexError

Expand All @@ -706,7 +721,7 @@ def __getitem__(self, i): raise IndexError
__call__=CheckerPublic,
)

def f(): #pragma NO COVER
def f(): # pragma: no cover
yield f


Expand Down Expand Up @@ -940,7 +955,7 @@ def _clear():

try:
from zope.testing.cleanup import addCleanUp
except ImportError: #pragma NO COVER
except ImportError: # pragma: no cover
pass
else:
addCleanUp(_clear)
18 changes: 16 additions & 2 deletions src/zope/security/tests/__init__.py
@@ -1,2 +1,16 @@
#
# This file is necessary to make this directory a package.
import io


class QuietWatchingChecker(object):
# zope.testrunner does not support setUp/tearDownModule,
# so we use a mixin class to make sure we don't flood stderr
# with pointless printing when testing watching checkers

def setUp(self):
from zope.security import checker
self.__old_file = checker.CheckerLoggingMixin._file
checker.CheckerLoggingMixin._file = io.StringIO() if bytes is not str else io.BytesIO()

def tearDown(self):
from zope.security import checker
checker.CheckerLoggingMixin._file = self.__old_file
48 changes: 34 additions & 14 deletions src/zope/security/tests/test_checker.py
Expand Up @@ -13,8 +13,11 @@
##############################################################################
"""Tests for zope.security.checker
"""

import unittest

from zope.security import checker
from zope.security.tests import QuietWatchingChecker

def _skip_if_not_Py2(testfunc):
import sys
Expand All @@ -28,6 +31,7 @@ def _skip_if_no_btrees(testfunc):
else:
return testfunc


class Test_ProxyFactory(unittest.TestCase):

def _callFUT(self, object, checker=None):
Expand Down Expand Up @@ -199,7 +203,7 @@ def test_w_setattr_forbidden_getattr_allowed(self):


_marker = []
class CheckerTestsBase(object):
class CheckerTestsBase(QuietWatchingChecker):

def _getTargetClass(self):
raise NotImplementedError("Subclasses must define")
Expand Down Expand Up @@ -586,16 +590,20 @@ def test_iteration_of_itertools_groupby(self):
class TestCheckerPy(CheckerTestsBase, unittest.TestCase):

def _getTargetClass(self):
from zope.security.checker import CheckerPy
return CheckerPy
return checker.CheckerPy


class TestChecker(CheckerTestsBase, unittest.TestCase):

def _getTargetClass(self):
from zope.security.checker import Checker
return Checker
return checker.Checker

@unittest.skipIf(checker.Checker is checker.WatchingChecker,
"WatchingChecker is the default")
class TestWatchingChecker(TestChecker):

def _getTargetClass(self):
return checker.WatchingChecker

class TestTracebackSupplement(unittest.TestCase):

Expand Down Expand Up @@ -1116,7 +1124,8 @@ class Foo(object):
self.assertFalse(Foo in _checkers)


class TestCombinedChecker(unittest.TestCase):
class TestCombinedChecker(QuietWatchingChecker,
unittest.TestCase):

def _getTargetClass(self):
from zope.security.checker import CombinedChecker
Expand Down Expand Up @@ -1339,6 +1348,12 @@ def checkPermission(self, obj, perm):
finally:
del thread_local.interaction

@unittest.skipIf(checker.WatchingCombinedChecker is checker.CombinedChecker,
"WatchingCombinedChecker is the default")
class TestWatchingCombinedChecker(TestCombinedChecker):

def _getTargetClass(self):
return checker.WatchingCombinedChecker

class TestCheckerLoggingMixin(unittest.TestCase):

Expand Down Expand Up @@ -1605,23 +1620,26 @@ class Foo(object):

# Pre-geddon tests start here

class TestSecurityPolicy(unittest.TestCase):
class TestSecurityPolicy(QuietWatchingChecker,
unittest.TestCase):

def setUp(self):
super(TestSecurityPolicy, self).setUp()

from zope.security.management import newInteraction
from zope.security.management import setSecurityPolicy
from zope.security.checker import _clear
_clear()
checker._clear()
self.__oldpolicy = setSecurityPolicy(self._makeSecurityPolicy())
newInteraction()

def tearDown(self):
super(TestSecurityPolicy, self).tearDown()

from zope.security.management import endInteraction
from zope.security.management import setSecurityPolicy
from zope.security.checker import _clear
endInteraction()
setSecurityPolicy(self.__oldpolicy)
_clear()
checker._clear()

def _makeSecurityPolicy(self):
from zope.interface import implementer
Expand Down Expand Up @@ -2193,15 +2211,17 @@ def overridingChecker(self):
from zope.security.checker import Checker
return Checker(self.decorationGetMap, self.decorationSetMap)

class TestCombinedCheckerMixin(TestMixinDecoratedChecker, unittest.TestCase):
class TestCombinedCheckerMixin(QuietWatchingChecker,
TestMixinDecoratedChecker,
unittest.TestCase):

def setUp(self):
unittest.TestCase.setUp(self)
super(TestCombinedCheckerMixin, self).setUp()
self.decoratedSetUp()

def tearDown(self):
self.decoratedTearDown()
unittest.TestCase.tearDown(self)
super(TestCombinedCheckerMixin, self).tearDown()

def test_checking(self):
from zope.security.interfaces import Unauthorized
Expand Down

0 comments on commit 009f429

Please sign in to comment.