Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 601d332

Browse files
committed
Bastionification utility (useful for rexec clients)
1 parent 4cc4ab1 commit 601d332

1 file changed

Lines changed: 162 additions & 0 deletions

File tree

Lib/Bastion.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
"""Bastionification utility.
2+
3+
A bastion (for another object -- the 'original') is an object that has
4+
the same methods as the original but does not give access to its
5+
instance variables. Bastions have a number of uses, but the most
6+
obvious one is to provide code executing in restricted mode with a
7+
safe interface to an object implemented in unrestricted mode.
8+
9+
The bastionification routine has an optional second argument which is
10+
a filter function. Only those methods for which the filter method
11+
(called with the method name as argument) returns true are accessible.
12+
The default filter method returns true unless the method name begins
13+
with an underscore.
14+
15+
There are a number of possible implementations of bastions. We use a
16+
'lazy' approach where the bastion's __getattr__() discipline does all
17+
the work for a particular method the first time it is used. This is
18+
usually fastest, especially if the user doesn't call all available
19+
methods. The retrieved methods are stored as instance variables of
20+
the bastion, so the overhead is only occurred on the first use of each
21+
method.
22+
23+
Detail: the bastion class has a __repr__() discipline which includes
24+
the repr() of the original object. This is precomputed when the
25+
bastion is created.
26+
27+
"""
28+
29+
__version__ = '$Revision$'
30+
# $Source$
31+
32+
33+
from types import MethodType
34+
35+
36+
class BastionClass:
37+
38+
"""Helper class used by the Bastion() function.
39+
40+
You could subclass this and pass the subclass as the bastionclass
41+
argument to the Bastion() function, as long as the constructor has
42+
the same signature (a get() function and a name for the object).
43+
44+
"""
45+
46+
def __init__(self, get, name):
47+
"""Constructor.
48+
49+
Arguments:
50+
51+
get - a function that gets the attribute value (by name)
52+
name - a human-readable name for the original object
53+
(suggestion: use repr(object))
54+
55+
"""
56+
self._get_ = get
57+
self._name_ = name
58+
59+
def __repr__(self):
60+
"""Return a representation string.
61+
62+
This includes the name passed in to the constructor, so that
63+
if you print the bastion during debugging, at least you have
64+
some idea of what it is.
65+
66+
"""
67+
return "<Bastion for %s>" % self._name_
68+
69+
def __getattr__(self, name):
70+
"""Get an as-yet undefined attribute value.
71+
72+
This calls the get() function that was passed to the
73+
constructor. The result is stored as an instance variable so
74+
that the next time the same attribute is requested,
75+
__getattr__() won't be invoked.
76+
77+
If the get() function raises an exception, this is simply
78+
passed on -- exceptions are not cached.
79+
80+
"""
81+
attribute = self._get_(name)
82+
self.__dict__[name] = attribute
83+
return attribute
84+
85+
86+
def Bastion(object, filter = lambda name: name[:1] != '_',
87+
name=None, bastionclass=BastionClass):
88+
"""Create a bastion for an object, using an optional filter.
89+
90+
See the Bastion module's documentation for background.
91+
92+
Arguments:
93+
94+
object - the original object
95+
filter - a predicate that decides whether a function name is OK;
96+
by default all names are OK that don't start with '_'
97+
name - the name of the object; default repr(object)
98+
bastionclass - class used to create the bastion; default BastionClass
99+
100+
"""
101+
102+
# Note: we define *two* ad-hoc functions here, get1 and get2.
103+
# Both are intended to be called in the same way: get(name).
104+
# It is clear that the real work (getting the attribute
105+
# from the object and calling the filter) is done in get1.
106+
# Why can't we pass get1 to the bastion? Because the user
107+
# would be able to override the filter argument! With get2,
108+
# overriding the default argument is no security loophole:
109+
# all it does is call it.
110+
# Also notice that we can't place the object and filter as
111+
# instance variables on the bastion object itself, since
112+
# the user has full access to all instance variables!
113+
114+
def get1(name, object=object, filter=filter):
115+
"""Internal function for Bastion(). See source comments."""
116+
if filter(name):
117+
attribute = getattr(object, name)
118+
if type(attribute) == MethodType:
119+
return attribute
120+
raise AttributeError, name
121+
122+
def get2(name, get1=get1):
123+
"""Internal function for Bastion(). See source comments."""
124+
return get1(name)
125+
126+
if name is None:
127+
name = `object`
128+
return bastionclass(get2, name)
129+
130+
131+
def _test():
132+
"""Test the Bastion() function."""
133+
class Original:
134+
def __init__(self):
135+
self.sum = 0
136+
def add(self, n):
137+
self._add(n)
138+
def _add(self, n):
139+
self.sum = self.sum + n
140+
def total(self):
141+
return self.sum
142+
o = Original()
143+
b = Bastion(o)
144+
b.add(81)
145+
b.add(18)
146+
print "b.total() =", b.total()
147+
try:
148+
print "b.sum =", b.sum,
149+
except:
150+
print "inaccessible"
151+
else:
152+
print "accessible"
153+
try:
154+
print "b._add =", b._add,
155+
except:
156+
print "inaccessible"
157+
else:
158+
print "accessible"
159+
160+
161+
if __name__ == '__main__':
162+
_test()

0 commit comments

Comments
 (0)