[Zope-Checkins] CVS: Zope3/lib/python/Zope/Security - Checker.py:1.1.2.1 GetDescr.py:1.1.2.1 ISecurityProxyFactory.py:1.1.2.1 Proxy.py:1.1.2.10 RestrictedInterpreter.py:1.1.2.3

Jim Fulton jim@zope.com
Thu, 18 Apr 2002 18:02:24 -0400


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

Modified Files:
      Tag: SecurityProxy-branch
	Proxy.py RestrictedInterpreter.py 
Added Files:
      Tag: SecurityProxy-branch
	Checker.py GetDescr.py ISecurityProxyFactory.py 
Log Message:
Added SecurityProxy checker implementation and checker registry.

I'm going to refactor the way the checker permission database is
expressed and add some convenience factories.



=== Added File Zope3/lib/python/Zope/Security/Checker.py ===
from Zope.Security.IChecker import IChecker
from Zope.Exceptions import Unauthorized, Forbidden
from GetDescr import GetDescr
# XXX SecurityManagement needs to move out of App
from Zope.App.Security.SecurityManagement import getSecurityManager


class Checker:

    __implements__ =  IChecker

    def __init__(self, database):
        """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.
        """
        
        self.__database = database
        

    ############################################################
    # Implementation methods for interface
    # Zope.Security.IChecker.

    def check_getattr(self, object, name):
        'See Zope.Security.IChecker.IChecker'
        checkDatabase(self.__database, name, object)

    def check(self, object, name):
        checkDatabase(self.__database, 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):
    # 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)

    raise Forbidden(name)

# Import this last due to module dependencies
from Proxy import Proxy


=== Added File Zope3/lib/python/Zope/Security/GetDescr.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.
# 
##############################################################################
"""
Get a descriptor corresponding to an attribute.

Revision information:
$Id: GetDescr.py,v 1.1.2.1 2002/04/18 22:02:23 jim Exp $
"""

def GetDescr(obj, name):
    """
    Get the descriptor for obj.<name>, if one exists.

    Arguments:
    obj:  the object; must be a new-style instance
    name: the attribute name; must be a string

    Return a descriptor (something implementing __get__) that controls
    (at least read) access to obj.<name>, or None if obj.<name> is not
    controlled by a descriptor, or if obj.<name> does not exist.

    Examples:

    1. If the attribute is a property, it is definitely returned.
    2. If the attribute is a method, it is returned unless it is
       overridden in obj.__dict__.
    3. If the attribute is a simple value (e.g. an int), either stored
       in obj.__dict__ or as a class attribute, None is returned.
    4. If the attribute doesn't exist, None is returned.
    """
    if not isinstance(obj.__class__, type):
        raise TypeError("obj must be a new-style instance")
    if not isinstance(name, (str, unicode)):
        raise TypeError("name must be a string")
    try:
        d = obj.__dict__
    except AttributError:
        isivar = 0
    else:
        isivar = name in d
    for cls in obj.__class__.__mro__:
        d = cls.__dict__
        if name in d:
            found = d[name]
            break
    else:
        return None
    if not hasattr(found, "__get__"):
        return None
    if not isivar or hasattr(found, "__set__"):
        return found
    return None


=== Added File Zope3/lib/python/Zope/Security/ISecurityProxyFactory.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: ISecurityProxyFactory.py,v 1.1.2.1 2002/04/18 22:02:23 jim Exp $
"""

from Interface import Interface

class ISecurityProxyFactory(Interface):

    def __call__(object, checker=None):
        """Create a security proxy

        If a checker is given, then use it, otherwise, try to figure
        out a checker.
        """



=== Zope3/lib/python/Zope/Security/Proxy.py 1.1.2.9 => 1.1.2.10 ===
-from Zope.Security._Proxy import _Proxy
+##############################################################################
+#
+# 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.
+# 
+##############################################################################
+"""
 
-def Proxy(obj, checker):
-    if obj is None:
-        return None
-    elif isinstance(obj, int):
-        return obj
-    elif isinstance(obj, float):
-        return obj
-    elif isinstance(obj, str):
-        return obj
-    elif isinstance(obj, unicode):
-        return obj
-    elif isinstance(obj, _Proxy):
-        return obj
-    elif isinstance(obj, types.ClassType) and issubclass(obj, Exception):
-        # XXX can't wrap exceptions because we can't raise proxies
-        return obj
+$Id$
+"""
+
+from ISecurityProxyFactory import ISecurityProxyFactory
+from _Proxy import _Proxy
+from types import InstanceType, ClassType, FunctionType, MethodType, ModuleType
+from Zope.Exceptions import DuplicationError
+
+def Proxy(object, checker=None):
+    if checker is None:
+        checker = _checkers.get(type(object), _defaultChecker)
+        if checker is None:
+            return object
+        if not isinstance(checker, Checker):
+            checker = checker(object)
+            if checker is None:
+                return object
     else:
-        return _Proxy(obj, checker)
+        # 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
+
+    return _Proxy(object, checker)
+
+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(())
+
+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


=== Zope3/lib/python/Zope/Security/RestrictedInterpreter.py 1.1.2.2 => 1.1.2.3 ===
             if k not in self.nok_builtin_names:
                 self.builtins[k] = Proxy(v, self.checker)
-        self.builtins['__import__'] = Proxy(self.ri_import, self.checker)
+        self.builtins['__import__'] = Proxy(self.ri_import)
 
     def ri_import(self, name, globals, locals, fromlist):
         # XXX handle fromlist
         return sys.modules[name]
-                
-
-