"""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. | |
""" | |
from warnings import warnpy3k | |
warnpy3k("the Bastion module has been removed in Python 3.0", stacklevel=2) | |
del warnpy3k | |
__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 later" | |
# 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() |