| 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 | from warnings import warnpy3k
|
|---|
| 29 | warnpy3k("the Bastion module has been removed in Python 3.0", stacklevel=2)
|
|---|
| 30 | del warnpy3k
|
|---|
| 31 |
|
|---|
| 32 | __all__ = ["BastionClass", "Bastion"]
|
|---|
| 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):
|
|---|
| 48 | """Constructor.
|
|---|
| 49 |
|
|---|
| 50 | Arguments:
|
|---|
| 51 |
|
|---|
| 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))
|
|---|
| 55 |
|
|---|
| 56 | """
|
|---|
| 57 | self._get_ = get
|
|---|
| 58 | self._name_ = name
|
|---|
| 59 |
|
|---|
| 60 | def __repr__(self):
|
|---|
| 61 | """Return a representation string.
|
|---|
| 62 |
|
|---|
| 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.
|
|---|
| 66 |
|
|---|
| 67 | """
|
|---|
| 68 | return "<Bastion for %s>" % self._name_
|
|---|
| 69 |
|
|---|
| 70 | def __getattr__(self, name):
|
|---|
| 71 | """Get an as-yet undefined attribute value.
|
|---|
| 72 |
|
|---|
| 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.
|
|---|
| 77 |
|
|---|
| 78 | If the get() function raises an exception, this is simply
|
|---|
| 79 | passed on -- exceptions are not cached.
|
|---|
| 80 |
|
|---|
| 81 | """
|
|---|
| 82 | attribute = self._get_(name)
|
|---|
| 83 | self.__dict__[name] = attribute
|
|---|
| 84 | return attribute
|
|---|
| 85 |
|
|---|
| 86 |
|
|---|
| 87 | def Bastion(object, filter = lambda name: name[:1] != '_',
|
|---|
| 88 | name=None, bastionclass=BastionClass):
|
|---|
| 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 |
|
|---|
| 103 | raise RuntimeError, "This code is not secure in Python 2.2 and later"
|
|---|
| 104 |
|
|---|
| 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):
|
|---|
| 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
|
|---|
| 124 |
|
|---|
| 125 | def get2(name, get1=get1):
|
|---|
| 126 | """Internal function for Bastion(). See source comments."""
|
|---|
| 127 | return get1(name)
|
|---|
| 128 |
|
|---|
| 129 | if name is None:
|
|---|
| 130 | name = repr(object)
|
|---|
| 131 | return bastionclass(get2, name)
|
|---|
| 132 |
|
|---|
| 133 |
|
|---|
| 134 | def _test():
|
|---|
| 135 | """Test the Bastion() function."""
|
|---|
| 136 | class Original:
|
|---|
| 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
|
|---|
| 145 | o = Original()
|
|---|
| 146 | b = Bastion(o)
|
|---|
| 147 | testcode = """if 1:
|
|---|
| 148 | b.add(81)
|
|---|
| 149 | b.add(18)
|
|---|
| 150 | print "b.total() =", b.total()
|
|---|
| 151 | try:
|
|---|
| 152 | print "b.sum =", b.sum,
|
|---|
| 153 | except:
|
|---|
| 154 | print "inaccessible"
|
|---|
| 155 | else:
|
|---|
| 156 | print "accessible"
|
|---|
| 157 | try:
|
|---|
| 158 | print "b._add =", b._add,
|
|---|
| 159 | except:
|
|---|
| 160 | print "inaccessible"
|
|---|
| 161 | else:
|
|---|
| 162 | print "accessible"
|
|---|
| 163 | try:
|
|---|
| 164 | print "b._get_.func_defaults =", map(type, b._get_.func_defaults),
|
|---|
| 165 | except:
|
|---|
| 166 | print "inaccessible"
|
|---|
| 167 | else:
|
|---|
| 168 | print "accessible"
|
|---|
| 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)
|
|---|
| 177 |
|
|---|
| 178 |
|
|---|
| 179 | if __name__ == '__main__':
|
|---|
| 180 | _test()
|
|---|