[Zope3-checkins] CVS: Zope3/src/zope/security - checker.py:1.24 proxy.py:1.6

Steve Alexander steve@cat-box.net
Wed, 28 May 2003 08:55:59 -0400


Update of /cvs-repository/Zope3/src/zope/security
In directory cvs.zope.org:/tmp/cvs-serv18143/src/zope/security

Modified Files:
	checker.py proxy.py 
Log Message:
Merged some of the work done by SteveA and MariusG on the
stevea-decorators-branch.

* Refactored acting on WATCH_CHECKERS into a reusable and unobtrusive
  mixin class. Execution speed will be faster when WATCH_CHECKERS is false.

* Improved and clearer implementation of ProxyFactory.

* Added comprehensive test of ProxyFactory.

* Made an explicit TrustedCheckerBase marker type to show the connection
  between the checker module and the proxy module.

* Added a note about the poor naming of _always_available. The name
  _available_by_default would better reflect actual use. That is, it is
  possible to make an _always_available name unavailable.

* Added a BasicTypes_examples dict that can be imported into unit tests
  that want to check whether basic types are handled properly.

* Added comprehensive test of ProxyFactory.


=== Zope3/src/zope/security/checker.py 1.23 => 1.24 ===
--- Zope3/src/zope/security/checker.py:1.23	Thu May 22 15:40:32 2003
+++ Zope3/src/zope/security/checker.py	Wed May 28 08:55:28 2003
@@ -20,7 +20,7 @@
 import types
 import datetime
 
-from zope.interface import directlyProvides, Interface
+from zope.interface import directlyProvides, Interface, implements
 from zope.interface.interfaces import IInterface, IInterfaceSpecification
 from zope.interface.declarations import ObjectSpecification
 from zope.interface.declarations import ProvidesSpecification
@@ -30,9 +30,10 @@
 from zope.security.interfaces import IChecker
 from zope.security.interfaces import ISecurityProxyFactory
 from zope.security.management import getSecurityManager
-from zope.security._proxy import _Proxy as Proxy
-from zope.exceptions \
-     import Unauthorized, ForbiddenAttribute, DuplicationError
+from zope.security._proxy import _Proxy as Proxy, getChecker
+from zope.exceptions import Unauthorized, ForbiddenAttribute, DuplicationError
+
+__metaclass__ = type
 
 if os.environ.get('ZOPE_WATCH_CHECKERS'):
     WATCH_CHECKERS = True
@@ -45,29 +46,36 @@
 
     The proxy checker is looked up if not provided.
     """
-
+    if type(object) is Proxy:
+        if checker is None or checker is getChecker(object):
+            return object
+        else:
+            # We have a proxy, but someone asked us to change its checker.
+            # Let's raise an exception.
+            #
+            # Other reasonable actions would be to either keep the existing
+            # proxy, or to create a new one with the given checker.
+            # The latter might be a security hole though, if untrusted code
+            # can call ProxyFactory.
+            raise TypeError("Tried to use ProxyFactory to change a Proxy's"
+                            " checker.")
     if checker is None:
         checker = getattr(object, '__Security_checker__', None)
 
-    if checker is None:
-
-        checker = selectChecker(object)
         if checker is None:
-            return object
-
-    else:
-        # Maybe someone passed us a proxy and a checker
-        if type(object) is Proxy:
-            # XXX should we keep the existing proxy or create a new one.
-            return object
+            checker = selectChecker(object)
+            if checker is None:
+                return object
 
     return Proxy(object, checker)
 
 directlyProvides(ProxyFactory, ISecurityProxyFactory)
 
-class Checker:
+class TrustedCheckerBase:
+    """Marker type used by zope.security.proxy.trustedRemoveSecurityProxy"""
 
-    __implements__ =  IChecker
+class Checker(TrustedCheckerBase):
+    implements(IChecker)
 
     def __init__(self, permission_func,
                  setattr_permission_func=lambda name: None
@@ -117,64 +125,36 @@
     def check_setattr(self, object, name):
         'See IChecker'
 
-        if WATCH_CHECKERS:
-            print >> sys.stderr, ('Checking %r.%s:' % (object, name)),
-
-        # We have the information we need already
         permission = self._setattr_permission_func(name)
-        if permission:
+        if permission is not None:
             if permission is CheckerPublic:
-                if WATCH_CHECKERS:
-                    print >> sys.stderr, 'Public.'
                 return # Public
             manager = getSecurityManager()
             if manager.checkPermission(permission, object):
-                if WATCH_CHECKERS:
-                    print >> sys.stderr, 'Granted.'
                 return
             else:
-                if WATCH_CHECKERS:
-                    print >> sys.stderr, 'Unauthorized.'
                 __traceback_supplement__ = (TracebackSupplement, object)
                 raise Unauthorized(name=name)
 
-        if WATCH_CHECKERS:
-            print >> sys.stderr, 'Forbidden.'
-
         __traceback_supplement__ = (TracebackSupplement, object)
         raise ForbiddenAttribute(name)
 
     def check(self, object, name):
         'See IChecker'
 
-        if WATCH_CHECKERS:
-            print >> sys.stderr, ('Checking %r.%s:' % (object, name)),
-
-        # We have the information we need already
         permission = self._permission_func(name)
-        if permission:
+        if permission is not None:
             if permission is CheckerPublic:
-                if WATCH_CHECKERS:
-                    print >> sys.stderr, 'Public.'
                 return # Public
             manager = getSecurityManager()
             if manager.checkPermission(permission, object):
-                if WATCH_CHECKERS:
-                    print >> sys.stderr, 'Granted.'
                 return
             else:
-                if WATCH_CHECKERS:
-                    print >> sys.stderr, 'Unauthorized.'
                 __traceback_supplement__ = (TracebackSupplement, object)
                 raise Unauthorized(name=name)
         elif name in _always_available:
-            if WATCH_CHECKERS:
-                print >> sys.stderr, 'Always available.'
             return
 
-        if WATCH_CHECKERS:
-            print >> sys.stderr, 'Forbidden.'
-
         __traceback_supplement__ = (TracebackSupplement, object)
         raise ForbiddenAttribute(name)
 
@@ -190,6 +170,70 @@
 
         return Proxy(value, checker)
 
+class CheckerLoggingMixin:
+    """Debugging mixin for Checker.
+
+    Prints verbose debugging information about every performed check to
+    sys.stderr.
+
+    This class relies on the class it's mixed into having permission_id
+    and setattr_permission_id methods.
+    """
+
+    def check(self, object, name):
+        print >> sys.stderr, ('Checking %r.%s:' % (object, name)),
+        try:
+            super(CheckerLoggingMixin, self).check(object, name)
+            if name in _always_available:
+                print >> sys.stderr, 'Always available.'
+            elif self.permission_id(name) is CheckerPublic:
+                print >> sys.stderr, 'Public.'
+            else:
+                print >> sys.stderr, 'Granted.'
+        except Unauthorized:
+            print >> sys.stderr, 'Unauthorized.'
+            raise
+        except ForbiddenAttribute:
+            print >> sys.stderr, 'Forbidden.'
+            raise
+
+    def check_getattr(self, object, name):
+        print >> sys.stderr, ('Checking get %r.%s:' % (object, name)),
+        try:
+            super(CheckerLoggingMixin, self).check(object, name)
+            if name in _always_available:
+                print >> sys.stderr, 'Always available.'
+            elif self.permission_id(name) is CheckerPublic:
+                print >> sys.stderr, 'Public.'
+            else:
+                print >> sys.stderr, 'Granted.'
+        except Unauthorized:
+            print >> sys.stderr, 'Unauthorized.'
+            raise
+        except ForbiddenAttribute:
+            print >> sys.stderr, 'Forbidden.'
+            raise
+
+    def check_setattr(self, object, name):
+        print >> sys.stderr, ('Checking set %r.%s:' % (object, name)),
+        try:
+            super(CheckerLoggingMixin, self).check_setattr(object, name)
+            if self.setattr_permission_id(name) is CheckerPublic:
+                print >> sys.stderr, 'Public.'
+            else:
+                print >> sys.stderr, 'Granted.'
+        except Unauthorized:
+            print >> sys.stderr, 'Unauthorized.'
+            raise
+        except ForbiddenAttribute:
+            print >> sys.stderr, 'Forbidden.'
+            raise
+
+if WATCH_CHECKERS:
+    class Checker(CheckerLoggingMixin, Checker):
+        pass
+
+
 # Helper class for __traceback_supplement__
 class TracebackSupplement:
 
@@ -397,7 +441,9 @@
     return _checkers.get(module, _typeChecker)
 
 
-
+# The variable '_always_available' should really be called
+# '_available_by_default', as that would better reflect its meaning.
+# XXX: Fix the name.
 _always_available = ['__lt__', '__le__', '__eq__',
                      '__gt__', '__ge__', '__ne__',
                      '__hash__', '__nonzero__',
@@ -426,6 +472,23 @@
     datetime.date: NoProxy,
     datetime.time: NoProxy,
 }
+# Available for tests. Located here so it can be kept in sync with BasicTypes.
+BasicTypes_examples = {
+    object: object(),
+    int: 65536,
+    float: -1.4142,
+    long: 65536l,
+    complex: -1.4142j,
+    types.NoneType: None,
+    str: 'abc',
+    unicode: u'uabc',
+    type(True): True,
+    datetime.timedelta: datetime.timedelta(3),
+    datetime.datetime: datetime.datetime(2003, 1, 1),
+    datetime.date: datetime.date(2003, 1, 1),
+    datetime.time: datetime.time(23, 58)
+}
+
 
 class _Sequence(object):
     def __len__(self): return 0


=== Zope3/src/zope/security/proxy.py 1.5 => 1.6 ===
--- Zope3/src/zope/security/proxy.py:1.5	Tue May 20 16:28:50 2003
+++ Zope3/src/zope/security/proxy.py	Wed May 28 08:55:28 2003
@@ -18,28 +18,26 @@
 
 from zope.security._proxy import getObject, getChecker
 from zope.security._proxy import _Proxy as Proxy
-from zope.security.checker import Checker as _trustedChecker
+from zope.security.checker import TrustedCheckerBase
 
 # This import represents part of the API for this module
 from zope.security.checker import ProxyFactory
 
 def trustedRemoveSecurityProxy(object):
-    """Remove a security proxy if the proxy's checker came from a trusted source.
+    """Remove a security proxy if its checker came from a trusted source.
 
-    The rational is that it's OK to do this since the caller is
-    trusted and the proxy can always be recreated by callingt the
+    The rationale is that it is OK to do this since the caller is
+    trusted and the proxy can always be recreated by calling the
     proxy factory and getting back a proxy with the same checker.
 
     XXX More thought needs to be given to assuring this contact.
-    
     """
     if ((type(object) is Proxy) and
-        isinstance(getChecker(object), _trustedChecker)
+        isinstance(getChecker(object), TrustedCheckerBase)
         ):
         return getObject(object)
 
     return object
-
 
 def getTestProxyItems(proxy):
     """Try to get checker names and permissions for testing