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