[Zope-Checkins] CVS: Zope3/lib/python/Zope/Security - CheckerRegistry.py:1.1.2.1 Checker.py:1.1.2.3 Proxy.py:1.1.2.11

Jim Fulton jim@zope.com
Thu, 18 Apr 2002 20:29:10 -0400


Update of /cvs-repository/Zope3/lib/python/Zope/Security
In directory cvs.zope.org:/tmp/cvs-serv16151/lib/python/Zope/Security

Modified Files:
      Tag: SecurityProxy-branch
	Checker.py Proxy.py 
Added Files:
      Tag: SecurityProxy-branch
	CheckerRegistry.py 
Log Message:
Refactored Checker constructor to simply take a function to compute a
permission from a name.

Provided some convenience checker factory functions.

Moved checker registry into a separate module.


=== Added File Zope3/lib/python/Zope/Security/CheckerRegistry.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
"""

$Id: CheckerRegistry.py,v 1.1.2.1 2002/04/19 00:29:08 jim Exp $
"""

from ISecurityProxyFactory import ISecurityProxyFactory
from _Proxy import _Proxy
from types import InstanceType, ClassType, FunctionType, MethodType, ModuleType
from Zope.Exceptions import DuplicationError
from Checker import Checker, NamesChecker

NoProxy = object()

# _checkers is a mapping.
#
#  - Keys are types
#
#  - Values are
#
#    o None => rock
#    o a Checker
#    o a function returning None or a Checker
#
_checkers = {}
getChecker = _checkers.get

_defaultChecker = Checker({}.get)

def _instanceChecker(inst):
    checker = _checkers.get(inst.__class__, _defaultChecker)
    if checker is _defaultChecker and isinstance(inst, Exception):
        return NoProxy # XXX we should be more careful
    return checker

def _classChecker(class_):
    checker = _checkers.get(class_, _typeChecker)
    if checker is _typeChecker and issubclass(class_, Exception):
        return NoProxy # XXX we should be more careful

    return checker

_callableChecker = NamesChecker(['__str__', '__repr__', '__hash__',
                                 '__call__'])
_typeChecker = NamesChecker(['__str__', '__repr__', '__hash__'])

def _moduleChecker(module):
    return _checkers.get(module, _typeChecker)


_default_checkers = {
    dict: NamesChecker(['__getitem__', 'get', 'has_key', '__len__',
                         'keys', 'values', 'items']),
    list: NamesChecker(['__getitem__', 'index', 'count', '__len__']),
    # YAGNI: () a rock
    tuple: NamesChecker(['__getitem__', '__len__']),
    int: NoProxy,
    float: NoProxy,
    long: NoProxy,
    complex: NoProxy,
    type(None): NoProxy,
    str: NoProxy, # Woo hoo
    unicode: NoProxy,
    InstanceType: _instanceChecker,
    _Proxy: NoProxy,
    ClassType: _classChecker,
    FunctionType: _callableChecker,
    MethodType: _callableChecker,
    type: _typeChecker,
    ModuleType: _moduleChecker,
    # XXX bool
    }


def _clear():
    _checkers.clear()
    _checkers.update(_default_checkers)

from Zope.Testing.CleanUp import addCleanUp
addCleanUp(_clear)

def defineChecker(type_, checker):
    if type_ in _checkers:
        raise DuplicationError(type_)
    _checkers[type_] = checker


=== Zope3/lib/python/Zope/Security/Checker.py 1.1.2.2 => 1.1.2.3 ===
-from Zope.Exceptions import Unauthorized, Forbidden
+from Zope.Exceptions import Unauthorized, Forbidden, DuplicationError
 # XXX SecurityManagement needs to move out of App
 from Zope.App.Security.SecurityManagement import getSecurityManager
 
+from Interface.IInterface import IInterface
+
+# Marker for public attributes
+CheckerPublic = object()
 
 class Checker:
 
     __implements__ =  IChecker
 
-    def __init__(self, database):
+    def __init__(self, permission_func):
         """Create a checker
 
-        An optional database may be provided. If it is provides, then
-        it is a sequence of name-tester and permission pairs. A name
-        tester is a callable object that takes a name and returns a
-        boolean indicating whether the name matches.
+        A callable must be provided for computing permissions for
+        names. The callable will be called with attribute names and
+        must return a permission id, None, or the special marker,
+        CheckerPublic. If None is returned, then access to the name is
+        forbidden. If CheckerPublic is returned, then access will be
+        granted without checking a permission.
         """
         
-        self.__database = database
-        
+        self.__permission_func = permission_func
+
+
+    def permission_id(self, name):
+        """Return the result of calling the permission func
+        """
+        return self.__permission_func(name)
 
     ############################################################
     # Implementation methods for interface
@@ -26,33 +37,100 @@
 
     def check_getattr(self, object, name):
         'See Zope.Security.IChecker.IChecker'
-        checkDatabase(self.__database, name, object)
+        check(self.__permission_func, name, object)
 
-    def check(self, object, name):
-        checkDatabase(self.__database, name, object)
+    def check(self, object, name):        
+        check(self.__permission_func, name, object)
 
     def proxy(self, value):
         'See Zope.Security.IChecker.IChecker'
         # Now we need to create a proxy
         return Proxy(value)
         
-
     #
     ############################################################
 
-def checkDatabase(database, name, object):
+
+def check(permission_func, name, object):
     # We have the information we need already
-    for check, permission in database:
-        if check(name):
-            if permission is None:
-                return
-            manager = getSecurityManager()
-            if manager.checkPermission(permission, object):
-                return
-            else:
-                raise Unauthorized(name=name)
+    permission = permission_func(name)
+    if permission:
+        if permission is CheckerPublic:
+            return # Public
+        manager = getSecurityManager()
+        if manager.checkPermission(permission, object):
+            return
+        else:
+            raise Unauthorized(name=name)
 
     raise Forbidden(name)
 
+
+def NamesChecker(names, permission_id=CheckerPublic, **__kw__):
+    """Return a checker that grants access to a set of names.
+
+    A sequence of names is given as the first argument. If a second
+    argument, permission_id, is given, it is the permission required
+    to access the names.  Additional names and persmission ids can be
+    supplied as keyword arguments.
+    """
+
+    data = {}
+    data.update(__kw__)
+    for name in names:
+        if data.get(name, permission_id) is not permission_id:
+            raise DuplicationError(name)
+        data[name] = permission_id
+
+    return Checker(data.get)
+
+def MultiChecker(specs):
+    """Create a checker from a sequence of specifications
+
+    A specification is:
+
+    - A two-tuple with:
+
+      o a sequence of names or an interface
+
+      o a permission id
+
+      All the names in the sequence of names or the interface are
+      protected by the permission.
+
+    - A dictionoid (having anitems method), with items that are
+      name/permission-id pairs.
+    """
+    data = {}
+
+    for spec in specs:
+        if type(spec) is tuple:
+            names, permission_id = spec
+            if IInterface.isImplementedBy(names):
+                names = names.names(1)
+            for name in names:
+                if data.get(name, permission_id) is not permission_id:
+                    raise DuplicationError(name)
+                data[name] = permission_id
+        else:
+            for name, permission_id in spec.items():
+                if data.get(name, permission_id) is not permission_id:
+                    raise DuplicationError(name)
+                data[name] = permission_id
+
+    return Checker(data.get)
+
+def NonPrivateChecker(permission_id = CheckerPublic):
+    
+    def check(name, permission_id=permission_id):
+        if name.startswith('_'):
+            return None
+        return permission_id
+    
+    return Checker(check)
+
 # Import this last due to module dependencies
 from Proxy import Proxy
+
+
+


=== Zope3/lib/python/Zope/Security/Proxy.py 1.1.2.10 => 1.1.2.11 ===
 
 def Proxy(object, checker=None):
-    if checker is None:
-        checker = _checkers.get(type(object), _defaultChecker)
-        if checker is None:
+    if checker is None:        
+        checker = getChecker(type(object), _defaultChecker)
+        if checker is NoProxy:
             return object
+
         if not isinstance(checker, Checker):
             checker = checker(object)
-            if checker is None:
+            if checker is NoProxy:
                 return object
+
     else:
         # Maybe someone passed us a proxy and a checker
         if type(object) is _Proxy:
@@ -40,83 +42,7 @@
 
 Proxy.__implements__ = ISecurityProxyFactory
 
-def namesChecker(names, permission=None):
-    d = {}
-    for name in names:
-        d[name] = 1
-    return Checker([(d.has_key, permission)])
-
-
-# Ugh.
-#
-# _checkers is a mapping.
-#
-#  - Keys are types
-#
-#  - Values are
-#
-#    o None => rock
-#    o a Checker
-#    o a function returning None or a Checker
-#
-
 from Checker import Checker
+_defaultChecker = Checker({}.get)
 
-_defaultChecker = Checker(())
-
-def _instanceChecker(inst):
-    if isinstance(inst, Exception):
-        return None # XXX we should be more careful
-
-    return _checkers.get(inst.__class__, _defaultChecker)
-
-def _classChecker(class_):
-    if issubclass(class_, Exception):
-        return None # XXX we should be more careful
-
-    return _typeChecker
-
-_callableChecker = namesChecker(['__str__', '__repr__', '__hash__',
-                                 '__call__'])
-_typeChecker = namesChecker(['__str__', '__repr__', '__hash__'])
-
-def _moduleChecker(module):
-    return _checkers.get(module, _typeChecker)
-
-
-_default_checkers = {
-    dict: namesChecker(['__getitem__', 'get', 'has_key', '__len__',
-                         'keys', 'values', 'items']),
-    list: namesChecker(['__getitem__', 'index', 'count', '__len__']),
-    # YAGNI: () a rock
-    tuple: namesChecker(['__getitem__', '__len__']),
-    int: None,
-    float: None,
-    long: None,
-    complex: None,
-    type(None): None,
-    str: None, # Woo hoo
-    unicode: None,
-    InstanceType: _instanceChecker,
-    _Proxy: None,
-    ClassType: _classChecker,
-    FunctionType: _callableChecker,
-    MethodType: _callableChecker,
-    type: _typeChecker,
-    ModuleType: _moduleChecker,
-    # XXX bool
-    }
-
-
-_checkers = {}
-def _clear():
-    _checkers.clear()
-    _checkers.update(_default_checkers)
-
-from Zope.Testing.CleanUp import addCleanUp
-addCleanUp(_clear)
-
-def defineChecker(type_, checker):
-    if type_ in _checkers:
-        raise DuplicationError(type_)
-    _checkers[type_] = checker
+from CheckerRegistry import getChecker, NoProxy