You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
175 lines
5.6 KiB
175 lines
5.6 KiB
"""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()
|
|
|