123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- """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
- """
- raise RuntimeError, "This code is not secure in Python 2.2 and 2.3"
- # 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 = repr(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()
|