| """Bastionification utility. |
| |
| A bastion (for another object -- the 'original') is an object that has |
| the same methods as the original but does not give access to its |
| instance variables. Bastions have a number of uses, but the most |
| obvious one is to provide code executing in restricted mode with a |
| safe interface to an object implemented in unrestricted mode. |
| |
| The bastionification routine has an optional second argument which is |
| a filter function. Only those methods for which the filter method |
| (called with the method name as argument) returns true are accessible. |
| The default filter method returns true unless the method name begins |
| with an underscore. |
| |
| There are a number of possible implementations of bastions. We use a |
| 'lazy' approach where the bastion's __getattr__() discipline does all |
| the work for a particular method the first time it is used. This is |
| usually fastest, especially if the user doesn't call all available |
| methods. The retrieved methods are stored as instance variables of |
| the bastion, so the overhead is only occurred on the first use of each |
| method. |
| |
| Detail: the bastion class has a __repr__() discipline which includes |
| the repr() of the original object. This is precomputed when the |
| bastion is created. |
| |
| """ |
| |
| __all__ = ["BastionClass", "Bastion"] |
| |
| from types import MethodType |
| |
| |
| class BastionClass: |
| |
| """Helper class used by the Bastion() function. |
| |
| You could subclass this and pass the subclass as the bastionclass |
| argument to the Bastion() function, as long as the constructor has |
| the same signature (a get() function and a name for the object). |
| |
| """ |
| |
| def __init__(self, get, name): |
| """Constructor. |
| |
| Arguments: |
| |
| get - a function that gets the attribute value (by name) |
| name - a human-readable name for the original object |
| (suggestion: use repr(object)) |
| |
| """ |
| self._get_ = get |
| self._name_ = name |
| |
| def __repr__(self): |
| """Return a representation string. |
| |
| This includes the name passed in to the constructor, so that |
| if you print the bastion during debugging, at least you have |
| some idea of what it is. |
| |
| """ |
| return "<Bastion for %s>" % self._name_ |
| |
| def __getattr__(self, name): |
| """Get an as-yet undefined attribute value. |
| |
| This calls the get() function that was passed to the |
| constructor. The result is stored as an instance variable so |
| that the next time the same attribute is requested, |
| __getattr__() won't be invoked. |
| |
| If the get() function raises an exception, this is simply |
| passed on -- exceptions are not cached. |
| |
| """ |
| attribute = self._get_(name) |
| self.__dict__[name] = attribute |
| return attribute |
| |
| |
| def Bastion(object, filter = lambda name: name[:1] != '_', |
| name=None, bastionclass=BastionClass): |
| """Create a bastion for an object, using an optional filter. |
| |
| See the Bastion module's documentation for background. |
| |
| Arguments: |
| |
| object - the original object |
| filter - a predicate that decides whether a function name is OK; |
| by default all names are OK that don't start with '_' |
| name - the name of the object; default repr(object) |
| bastionclass - class used to create the bastion; default BastionClass |
| |
| """ |
| |
| # Note: we define *two* ad-hoc functions here, get1 and get2. |
| # Both are intended to be called in the same way: get(name). |
| # It is clear that the real work (getting the attribute |
| # from the object and calling the filter) is done in get1. |
| # Why can't we pass get1 to the bastion? Because the user |
| # would be able to override the filter argument! With get2, |
| # overriding the default argument is no security loophole: |
| # all it does is call it. |
| # Also notice that we can't place the object and filter as |
| # instance variables on the bastion object itself, since |
| # the user has full access to all instance variables! |
| |
| def get1(name, object=object, filter=filter): |
| """Internal function for Bastion(). See source comments.""" |
| if filter(name): |
| attribute = getattr(object, name) |
| if type(attribute) == MethodType: |
| return attribute |
| raise AttributeError, name |
| |
| def get2(name, get1=get1): |
| """Internal function for Bastion(). See source comments.""" |
| return get1(name) |
| |
| if name is None: |
| name = `object` |
| return bastionclass(get2, name) |
| |
| |
| def _test(): |
| """Test the Bastion() function.""" |
| class Original: |
| def __init__(self): |
| self.sum = 0 |
| def add(self, n): |
| self._add(n) |
| def _add(self, n): |
| self.sum = self.sum + n |
| def total(self): |
| return self.sum |
| o = Original() |
| b = Bastion(o) |
| testcode = """if 1: |
| b.add(81) |
| b.add(18) |
| print "b.total() =", b.total() |
| try: |
| print "b.sum =", b.sum, |
| except: |
| print "inaccessible" |
| else: |
| print "accessible" |
| try: |
| print "b._add =", b._add, |
| except: |
| print "inaccessible" |
| else: |
| print "accessible" |
| try: |
| print "b._get_.func_defaults =", map(type, b._get_.func_defaults), |
| except: |
| print "inaccessible" |
| else: |
| print "accessible" |
| \n""" |
| exec testcode |
| print '='*20, "Using rexec:", '='*20 |
| import rexec |
| r = rexec.RExec() |
| m = r.add_module('__main__') |
| m.b = b |
| r.r_exec(testcode) |
| |
| |
| if __name__ == '__main__': |
| _test() |