PyXR

c:\python24\lib \ Bastion.py



0001 """Bastionification utility.
0002 
0003 A bastion (for another object -- the 'original') is an object that has
0004 the same methods as the original but does not give access to its
0005 instance variables.  Bastions have a number of uses, but the most
0006 obvious one is to provide code executing in restricted mode with a
0007 safe interface to an object implemented in unrestricted mode.
0008 
0009 The bastionification routine has an optional second argument which is
0010 a filter function.  Only those methods for which the filter method
0011 (called with the method name as argument) returns true are accessible.
0012 The default filter method returns true unless the method name begins
0013 with an underscore.
0014 
0015 There are a number of possible implementations of bastions.  We use a
0016 'lazy' approach where the bastion's __getattr__() discipline does all
0017 the work for a particular method the first time it is used.  This is
0018 usually fastest, especially if the user doesn't call all available
0019 methods.  The retrieved methods are stored as instance variables of
0020 the bastion, so the overhead is only occurred on the first use of each
0021 method.
0022 
0023 Detail: the bastion class has a __repr__() discipline which includes
0024 the repr() of the original object.  This is precomputed when the
0025 bastion is created.
0026 
0027 """
0028 
0029 __all__ = ["BastionClass", "Bastion"]
0030 
0031 from types import MethodType
0032 
0033 
0034 class BastionClass:
0035 
0036     """Helper class used by the Bastion() function.
0037 
0038     You could subclass this and pass the subclass as the bastionclass
0039     argument to the Bastion() function, as long as the constructor has
0040     the same signature (a get() function and a name for the object).
0041 
0042     """
0043 
0044     def __init__(self, get, name):
0045         """Constructor.
0046 
0047         Arguments:
0048 
0049         get - a function that gets the attribute value (by name)
0050         name - a human-readable name for the original object
0051                (suggestion: use repr(object))
0052 
0053         """
0054         self._get_ = get
0055         self._name_ = name
0056 
0057     def __repr__(self):
0058         """Return a representation string.
0059 
0060         This includes the name passed in to the constructor, so that
0061         if you print the bastion during debugging, at least you have
0062         some idea of what it is.
0063 
0064         """
0065         return "<Bastion for %s>" % self._name_
0066 
0067     def __getattr__(self, name):
0068         """Get an as-yet undefined attribute value.
0069 
0070         This calls the get() function that was passed to the
0071         constructor.  The result is stored as an instance variable so
0072         that the next time the same attribute is requested,
0073         __getattr__() won't be invoked.
0074 
0075         If the get() function raises an exception, this is simply
0076         passed on -- exceptions are not cached.
0077 
0078         """
0079         attribute = self._get_(name)
0080         self.__dict__[name] = attribute
0081         return attribute
0082 
0083 
0084 def Bastion(object, filter = lambda name: name[:1] != '_',
0085             name=None, bastionclass=BastionClass):
0086     """Create a bastion for an object, using an optional filter.
0087 
0088     See the Bastion module's documentation for background.
0089 
0090     Arguments:
0091 
0092     object - the original object
0093     filter - a predicate that decides whether a function name is OK;
0094              by default all names are OK that don't start with '_'
0095     name - the name of the object; default repr(object)
0096     bastionclass - class used to create the bastion; default BastionClass
0097 
0098     """
0099 
0100     raise RuntimeError, "This code is not secure in Python 2.2 and 2.3"
0101 
0102     # Note: we define *two* ad-hoc functions here, get1 and get2.
0103     # Both are intended to be called in the same way: get(name).
0104     # It is clear that the real work (getting the attribute
0105     # from the object and calling the filter) is done in get1.
0106     # Why can't we pass get1 to the bastion?  Because the user
0107     # would be able to override the filter argument!  With get2,
0108     # overriding the default argument is no security loophole:
0109     # all it does is call it.
0110     # Also notice that we can't place the object and filter as
0111     # instance variables on the bastion object itself, since
0112     # the user has full access to all instance variables!
0113 
0114     def get1(name, object=object, filter=filter):
0115         """Internal function for Bastion().  See source comments."""
0116         if filter(name):
0117             attribute = getattr(object, name)
0118             if type(attribute) == MethodType:
0119                 return attribute
0120         raise AttributeError, name
0121 
0122     def get2(name, get1=get1):
0123         """Internal function for Bastion().  See source comments."""
0124         return get1(name)
0125 
0126     if name is None:
0127         name = repr(object)
0128     return bastionclass(get2, name)
0129 
0130 
0131 def _test():
0132     """Test the Bastion() function."""
0133     class Original:
0134         def __init__(self):
0135             self.sum = 0
0136         def add(self, n):
0137             self._add(n)
0138         def _add(self, n):
0139             self.sum = self.sum + n
0140         def total(self):
0141             return self.sum
0142     o = Original()
0143     b = Bastion(o)
0144     testcode = """if 1:
0145     b.add(81)
0146     b.add(18)
0147     print "b.total() =", b.total()
0148     try:
0149         print "b.sum =", b.sum,
0150     except:
0151         print "inaccessible"
0152     else:
0153         print "accessible"
0154     try:
0155         print "b._add =", b._add,
0156     except:
0157         print "inaccessible"
0158     else:
0159         print "accessible"
0160     try:
0161         print "b._get_.func_defaults =", map(type, b._get_.func_defaults),
0162     except:
0163         print "inaccessible"
0164     else:
0165         print "accessible"
0166     \n"""
0167     exec testcode
0168     print '='*20, "Using rexec:", '='*20
0169     import rexec
0170     r = rexec.RExec()
0171     m = r.add_module('__main__')
0172     m.b = b
0173     r.r_exec(testcode)
0174 
0175 
0176 if __name__ == '__main__':
0177     _test()
0178 

Generated by PyXR 0.9.4
SourceForge.net Logo