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