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 | """ |
| 28 | |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 29 | |
| 30 | from types import MethodType |
| 31 | |
| 32 | |
| 33 | class BastionClass: |
| 34 | |
| 35 | """Helper class used by the Bastion() function. |
| 36 | |
| 37 | You could subclass this and pass the subclass as the bastionclass |
| 38 | argument to the Bastion() function, as long as the constructor has |
| 39 | the same signature (a get() function and a name for the object). |
| 40 | |
| 41 | """ |
| 42 | |
| 43 | def __init__(self, get, name): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 44 | """Constructor. |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 45 | |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 46 | Arguments: |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 47 | |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 48 | get - a function that gets the attribute value (by name) |
| 49 | name - a human-readable name for the original object |
| 50 | (suggestion: use repr(object)) |
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 | """ |
| 53 | self._get_ = get |
| 54 | self._name_ = name |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 55 | |
| 56 | def __repr__(self): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 57 | """Return a representation string. |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 58 | |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 59 | This includes the name passed in to the constructor, so that |
| 60 | if you print the bastion during debugging, at least you have |
| 61 | some idea of what it is. |
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 | """ |
| 64 | return "<Bastion for %s>" % self._name_ |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 65 | |
| 66 | def __getattr__(self, name): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 67 | """Get an as-yet undefined attribute value. |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 68 | |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 69 | This calls the get() function that was passed to the |
| 70 | constructor. The result is stored as an instance variable so |
| 71 | that the next time the same attribute is requested, |
| 72 | __getattr__() won't be invoked. |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 73 | |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 74 | If the get() function raises an exception, this is simply |
| 75 | passed on -- exceptions are not cached. |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 76 | |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 77 | """ |
| 78 | attribute = self._get_(name) |
| 79 | self.__dict__[name] = attribute |
| 80 | return attribute |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 81 | |
| 82 | |
| 83 | def Bastion(object, filter = lambda name: name[:1] != '_', |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 84 | name=None, bastionclass=BastionClass): |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 85 | """Create a bastion for an object, using an optional filter. |
| 86 | |
| 87 | See the Bastion module's documentation for background. |
| 88 | |
| 89 | Arguments: |
| 90 | |
| 91 | object - the original object |
| 92 | filter - a predicate that decides whether a function name is OK; |
| 93 | by default all names are OK that don't start with '_' |
| 94 | name - the name of the object; default repr(object) |
| 95 | bastionclass - class used to create the bastion; default BastionClass |
| 96 | |
| 97 | """ |
| 98 | |
| 99 | # Note: we define *two* ad-hoc functions here, get1 and get2. |
| 100 | # Both are intended to be called in the same way: get(name). |
| 101 | # It is clear that the real work (getting the attribute |
| 102 | # from the object and calling the filter) is done in get1. |
| 103 | # Why can't we pass get1 to the bastion? Because the user |
| 104 | # would be able to override the filter argument! With get2, |
| 105 | # overriding the default argument is no security loophole: |
| 106 | # all it does is call it. |
| 107 | # Also notice that we can't place the object and filter as |
| 108 | # instance variables on the bastion object itself, since |
| 109 | # the user has full access to all instance variables! |
| 110 | |
| 111 | def get1(name, object=object, filter=filter): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 112 | """Internal function for Bastion(). See source comments.""" |
| 113 | if filter(name): |
| 114 | attribute = getattr(object, name) |
| 115 | if type(attribute) == MethodType: |
| 116 | return attribute |
| 117 | raise AttributeError, name |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 118 | |
| 119 | def get2(name, get1=get1): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 120 | """Internal function for Bastion(). See source comments.""" |
| 121 | return get1(name) |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 122 | |
| 123 | if name is None: |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 124 | name = `object` |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 125 | return bastionclass(get2, name) |
| 126 | |
| 127 | |
| 128 | def _test(): |
| 129 | """Test the Bastion() function.""" |
| 130 | class Original: |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 131 | def __init__(self): |
| 132 | self.sum = 0 |
| 133 | def add(self, n): |
| 134 | self._add(n) |
| 135 | def _add(self, n): |
| 136 | self.sum = self.sum + n |
| 137 | def total(self): |
| 138 | return self.sum |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 139 | o = Original() |
| 140 | b = Bastion(o) |
Guido van Rossum | 6ba66d0 | 1996-08-20 20:21:52 +0000 | [diff] [blame] | 141 | testcode = """if 1: |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 142 | b.add(81) |
| 143 | b.add(18) |
| 144 | print "b.total() =", b.total() |
| 145 | try: |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 146 | print "b.sum =", b.sum, |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 147 | except: |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 148 | print "inaccessible" |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 149 | else: |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 150 | print "accessible" |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 151 | try: |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 152 | print "b._add =", b._add, |
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 | 6ba66d0 | 1996-08-20 20:21:52 +0000 | [diff] [blame] | 157 | try: |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 158 | print "b._get_.func_defaults =", b._get_.func_defaults, |
Guido van Rossum | 6ba66d0 | 1996-08-20 20:21:52 +0000 | [diff] [blame] | 159 | except: |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 160 | print "inaccessible" |
Guido van Rossum | 6ba66d0 | 1996-08-20 20:21:52 +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 | \n""" |
| 164 | exec testcode |
| 165 | print '='*20, "Using rexec:", '='*20 |
| 166 | import rexec |
| 167 | r = rexec.RExec() |
| 168 | m = r.add_module('__main__') |
| 169 | m.b = b |
| 170 | r.r_exec(testcode) |
Guido van Rossum | 601d332 | 1996-06-11 20:12:49 +0000 | [diff] [blame] | 171 | |
| 172 | |
| 173 | if __name__ == '__main__': |
| 174 | _test() |