| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 1 | """Bastionification utility. | 
 | 2 |  | 
 | 3 | A bastion (for another object -- the 'original') is an object that has | 
 | 4 | the same methods as the original but does not give access to its | 
 | 5 | instance variables.  Bastions have a number of uses, but the most | 
 | 6 | obvious one is to provide code executing in restricted mode with a | 
 | 7 | safe interface to an object implemented in unrestricted mode. | 
 | 8 |  | 
 | 9 | The bastionification routine has an optional second argument which is | 
 | 10 | a filter function.  Only those methods for which the filter method | 
 | 11 | (called with the method name as argument) returns true are accessible. | 
 | 12 | The default filter method returns true unless the method name begins | 
 | 13 | with an underscore. | 
 | 14 |  | 
 | 15 | There are a number of possible implementations of bastions.  We use a | 
 | 16 | 'lazy' approach where the bastion's __getattr__() discipline does all | 
 | 17 | the work for a particular method the first time it is used.  This is | 
 | 18 | usually fastest, especially if the user doesn't call all available | 
 | 19 | methods.  The retrieved methods are stored as instance variables of | 
 | 20 | the bastion, so the overhead is only occurred on the first use of each | 
 | 21 | method. | 
 | 22 |  | 
 | 23 | Detail: the bastion class has a __repr__() discipline which includes | 
 | 24 | the repr() of the original object.  This is precomputed when the | 
 | 25 | bastion is created. | 
 | 26 |  | 
 | 27 | """ | 
| Brett Cannon | 4c1f881 | 2008-05-10 02:27:04 +0000 | [diff] [blame] | 28 | from warnings import warnpy3k | 
 | 29 | warnpy3k("the Bastion module has been removed in Python 3.0", stacklevel=2) | 
 | 30 | del warnpy3k | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 31 |  | 
| Skip Montanaro | e99d5ea | 2001-01-20 19:54:20 +0000 | [diff] [blame] | 32 | __all__ = ["BastionClass", "Bastion"] | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 33 |  | 
 | 34 | from types import MethodType | 
 | 35 |  | 
 | 36 |  | 
 | 37 | class BastionClass: | 
 | 38 |  | 
 | 39 |     """Helper class used by the Bastion() function. | 
 | 40 |  | 
 | 41 |     You could subclass this and pass the subclass as the bastionclass | 
 | 42 |     argument to the Bastion() function, as long as the constructor has | 
 | 43 |     the same signature (a get() function and a name for the object). | 
 | 44 |  | 
 | 45 |     """ | 
 | 46 |  | 
 | 47 |     def __init__(self, get, name): | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 48 |         """Constructor. | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 49 |  | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 50 |         Arguments: | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 51 |  | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 52 |         get - a function that gets the attribute value (by name) | 
 | 53 |         name - a human-readable name for the original object | 
 | 54 |                (suggestion: use repr(object)) | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 55 |  | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 56 |         """ | 
 | 57 |         self._get_ = get | 
 | 58 |         self._name_ = name | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 59 |  | 
 | 60 |     def __repr__(self): | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 61 |         """Return a representation string. | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 62 |  | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 63 |         This includes the name passed in to the constructor, so that | 
 | 64 |         if you print the bastion during debugging, at least you have | 
 | 65 |         some idea of what it is. | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 66 |  | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 67 |         """ | 
 | 68 |         return "<Bastion for %s>" % self._name_ | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 69 |  | 
 | 70 |     def __getattr__(self, name): | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 71 |         """Get an as-yet undefined attribute value. | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 72 |  | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 73 |         This calls the get() function that was passed to the | 
 | 74 |         constructor.  The result is stored as an instance variable so | 
 | 75 |         that the next time the same attribute is requested, | 
 | 76 |         __getattr__() won't be invoked. | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 77 |  | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 78 |         If the get() function raises an exception, this is simply | 
 | 79 |         passed on -- exceptions are not cached. | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 80 |  | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 81 |         """ | 
 | 82 |         attribute = self._get_(name) | 
 | 83 |         self.__dict__[name] = attribute | 
 | 84 |         return attribute | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 85 |  | 
 | 86 |  | 
 | 87 | def Bastion(object, filter = lambda name: name[:1] != '_', | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 88 |             name=None, bastionclass=BastionClass): | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 89 |     """Create a bastion for an object, using an optional filter. | 
 | 90 |  | 
 | 91 |     See the Bastion module's documentation for background. | 
 | 92 |  | 
 | 93 |     Arguments: | 
 | 94 |  | 
 | 95 |     object - the original object | 
 | 96 |     filter - a predicate that decides whether a function name is OK; | 
 | 97 |              by default all names are OK that don't start with '_' | 
 | 98 |     name - the name of the object; default repr(object) | 
 | 99 |     bastionclass - class used to create the bastion; default BastionClass | 
 | 100 |  | 
 | 101 |     """ | 
 | 102 |  | 
| Georg Brandl | e2d827d | 2007-04-12 07:01:19 +0000 | [diff] [blame] | 103 |     raise RuntimeError, "This code is not secure in Python 2.2 and later" | 
| Guido van Rossum | 34a2e08 | 2003-01-06 15:43:34 +0000 | [diff] [blame] | 104 |  | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 105 |     # Note: we define *two* ad-hoc functions here, get1 and get2. | 
 | 106 |     # Both are intended to be called in the same way: get(name). | 
 | 107 |     # It is clear that the real work (getting the attribute | 
 | 108 |     # from the object and calling the filter) is done in get1. | 
 | 109 |     # Why can't we pass get1 to the bastion?  Because the user | 
 | 110 |     # would be able to override the filter argument!  With get2, | 
 | 111 |     # overriding the default argument is no security loophole: | 
 | 112 |     # all it does is call it. | 
 | 113 |     # Also notice that we can't place the object and filter as | 
 | 114 |     # instance variables on the bastion object itself, since | 
 | 115 |     # the user has full access to all instance variables! | 
 | 116 |  | 
 | 117 |     def get1(name, object=object, filter=filter): | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 118 |         """Internal function for Bastion().  See source comments.""" | 
 | 119 |         if filter(name): | 
 | 120 |             attribute = getattr(object, name) | 
 | 121 |             if type(attribute) == MethodType: | 
 | 122 |                 return attribute | 
 | 123 |         raise AttributeError, name | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 124 |  | 
 | 125 |     def get2(name, get1=get1): | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 126 |         """Internal function for Bastion().  See source comments.""" | 
 | 127 |         return get1(name) | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 128 |  | 
 | 129 |     if name is None: | 
| Walter Dörwald | 70a6b49 | 2004-02-12 17:35:32 +0000 | [diff] [blame] | 130 |         name = repr(object) | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 131 |     return bastionclass(get2, name) | 
 | 132 |  | 
 | 133 |  | 
 | 134 | def _test(): | 
 | 135 |     """Test the Bastion() function.""" | 
 | 136 |     class Original: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 137 |         def __init__(self): | 
 | 138 |             self.sum = 0 | 
 | 139 |         def add(self, n): | 
 | 140 |             self._add(n) | 
 | 141 |         def _add(self, n): | 
 | 142 |             self.sum = self.sum + n | 
 | 143 |         def total(self): | 
 | 144 |             return self.sum | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 145 |     o = Original() | 
 | 146 |     b = Bastion(o) | 
| Guido van Rossum | 6ba66d0 | 1996-08-20 20:21:52 +0000 | [diff] [blame] | 147 |     testcode = """if 1: | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 148 |     b.add(81) | 
 | 149 |     b.add(18) | 
 | 150 |     print "b.total() =", b.total() | 
 | 151 |     try: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 152 |         print "b.sum =", b.sum, | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 153 |     except: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 154 |         print "inaccessible" | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 155 |     else: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 156 |         print "accessible" | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 157 |     try: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 158 |         print "b._add =", b._add, | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 159 |     except: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 160 |         print "inaccessible" | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 161 |     else: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 162 |         print "accessible" | 
| Guido van Rossum | 6ba66d0 | 1996-08-20 20:21:52 +0000 | [diff] [blame] | 163 |     try: | 
| Jeremy Hylton | 1a34c87 | 2001-01-19 03:30:22 +0000 | [diff] [blame] | 164 |         print "b._get_.func_defaults =", map(type, b._get_.func_defaults), | 
| Guido van Rossum | 6ba66d0 | 1996-08-20 20:21:52 +0000 | [diff] [blame] | 165 |     except: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 166 |         print "inaccessible" | 
| Guido van Rossum | 6ba66d0 | 1996-08-20 20:21:52 +0000 | [diff] [blame] | 167 |     else: | 
| Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 168 |         print "accessible" | 
| Guido van Rossum | 6ba66d0 | 1996-08-20 20:21:52 +0000 | [diff] [blame] | 169 |     \n""" | 
 | 170 |     exec testcode | 
 | 171 |     print '='*20, "Using rexec:", '='*20 | 
 | 172 |     import rexec | 
 | 173 |     r = rexec.RExec() | 
 | 174 |     m = r.add_module('__main__') | 
 | 175 |     m.b = b | 
 | 176 |     r.r_exec(testcode) | 
| Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 177 |  | 
 | 178 |  | 
 | 179 | if __name__ == '__main__': | 
 | 180 |     _test() |