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 | from warnings import warnpy3k
|
---|
29 | warnpy3k("the Bastion module has been removed in Python 3.0", stacklevel=2)
|
---|
30 | del warnpy3k
|
---|
31 |
|
---|
32 | __all__ = ["BastionClass", "Bastion"]
|
---|
33 |
|
---|
34 | from types import MethodType
|
---|
35 |
|
---|
36 |
|
---|
37 | class BastionClass:
|
---|
38 |
|
---|
39 | """Helper class used by the Bastion() function.
|
---|
40 |
|
---|
41 | You could subclass this and pass the subclass as the bastionclass
|
---|
42 | argument to the Bastion() function, as long as the constructor has
|
---|
43 | the same signature (a get() function and a name for the object).
|
---|
44 |
|
---|
45 | """
|
---|
46 |
|
---|
47 | def __init__(self, get, name):
|
---|
48 | """Constructor.
|
---|
49 |
|
---|
50 | Arguments:
|
---|
51 |
|
---|
52 | get - a function that gets the attribute value (by name)
|
---|
53 | name - a human-readable name for the original object
|
---|
54 | (suggestion: use repr(object))
|
---|
55 |
|
---|
56 | """
|
---|
57 | self._get_ = get
|
---|
58 | self._name_ = name
|
---|
59 |
|
---|
60 | def __repr__(self):
|
---|
61 | """Return a representation string.
|
---|
62 |
|
---|
63 | This includes the name passed in to the constructor, so that
|
---|
64 | if you print the bastion during debugging, at least you have
|
---|
65 | some idea of what it is.
|
---|
66 |
|
---|
67 | """
|
---|
68 | return "<Bastion for %s>" % self._name_
|
---|
69 |
|
---|
70 | def __getattr__(self, name):
|
---|
71 | """Get an as-yet undefined attribute value.
|
---|
72 |
|
---|
73 | This calls the get() function that was passed to the
|
---|
74 | constructor. The result is stored as an instance variable so
|
---|
75 | that the next time the same attribute is requested,
|
---|
76 | __getattr__() won't be invoked.
|
---|
77 |
|
---|
78 | If the get() function raises an exception, this is simply
|
---|
79 | passed on -- exceptions are not cached.
|
---|
80 |
|
---|
81 | """
|
---|
82 | attribute = self._get_(name)
|
---|
83 | self.__dict__[name] = attribute
|
---|
84 | return attribute
|
---|
85 |
|
---|
86 |
|
---|
87 | def Bastion(object, filter = lambda name: name[:1] != '_',
|
---|
88 | name=None, bastionclass=BastionClass):
|
---|
89 | """Create a bastion for an object, using an optional filter.
|
---|
90 |
|
---|
91 | See the Bastion module's documentation for background.
|
---|
92 |
|
---|
93 | Arguments:
|
---|
94 |
|
---|
95 | object - the original object
|
---|
96 | filter - a predicate that decides whether a function name is OK;
|
---|
97 | by default all names are OK that don't start with '_'
|
---|
98 | name - the name of the object; default repr(object)
|
---|
99 | bastionclass - class used to create the bastion; default BastionClass
|
---|
100 |
|
---|
101 | """
|
---|
102 |
|
---|
103 | raise RuntimeError, "This code is not secure in Python 2.2 and later"
|
---|
104 |
|
---|
105 | # Note: we define *two* ad-hoc functions here, get1 and get2.
|
---|
106 | # Both are intended to be called in the same way: get(name).
|
---|
107 | # It is clear that the real work (getting the attribute
|
---|
108 | # from the object and calling the filter) is done in get1.
|
---|
109 | # Why can't we pass get1 to the bastion? Because the user
|
---|
110 | # would be able to override the filter argument! With get2,
|
---|
111 | # overriding the default argument is no security loophole:
|
---|
112 | # all it does is call it.
|
---|
113 | # Also notice that we can't place the object and filter as
|
---|
114 | # instance variables on the bastion object itself, since
|
---|
115 | # the user has full access to all instance variables!
|
---|
116 |
|
---|
117 | def get1(name, object=object, filter=filter):
|
---|
118 | """Internal function for Bastion(). See source comments."""
|
---|
119 | if filter(name):
|
---|
120 | attribute = getattr(object, name)
|
---|
121 | if type(attribute) == MethodType:
|
---|
122 | return attribute
|
---|
123 | raise AttributeError, name
|
---|
124 |
|
---|
125 | def get2(name, get1=get1):
|
---|
126 | """Internal function for Bastion(). See source comments."""
|
---|
127 | return get1(name)
|
---|
128 |
|
---|
129 | if name is None:
|
---|
130 | name = repr(object)
|
---|
131 | return bastionclass(get2, name)
|
---|
132 |
|
---|
133 |
|
---|
134 | def _test():
|
---|
135 | """Test the Bastion() function."""
|
---|
136 | class Original:
|
---|
137 | def __init__(self):
|
---|
138 | self.sum = 0
|
---|
139 | def add(self, n):
|
---|
140 | self._add(n)
|
---|
141 | def _add(self, n):
|
---|
142 | self.sum = self.sum + n
|
---|
143 | def total(self):
|
---|
144 | return self.sum
|
---|
145 | o = Original()
|
---|
146 | b = Bastion(o)
|
---|
147 | testcode = """if 1:
|
---|
148 | b.add(81)
|
---|
149 | b.add(18)
|
---|
150 | print "b.total() =", b.total()
|
---|
151 | try:
|
---|
152 | print "b.sum =", b.sum,
|
---|
153 | except:
|
---|
154 | print "inaccessible"
|
---|
155 | else:
|
---|
156 | print "accessible"
|
---|
157 | try:
|
---|
158 | print "b._add =", b._add,
|
---|
159 | except:
|
---|
160 | print "inaccessible"
|
---|
161 | else:
|
---|
162 | print "accessible"
|
---|
163 | try:
|
---|
164 | print "b._get_.func_defaults =", map(type, b._get_.func_defaults),
|
---|
165 | except:
|
---|
166 | print "inaccessible"
|
---|
167 | else:
|
---|
168 | print "accessible"
|
---|
169 | \n"""
|
---|
170 | exec testcode
|
---|
171 | print '='*20, "Using rexec:", '='*20
|
---|
172 | import rexec
|
---|
173 | r = rexec.RExec()
|
---|
174 | m = r.add_module('__main__')
|
---|
175 | m.b = b
|
---|
176 | r.r_exec(testcode)
|
---|
177 |
|
---|
178 |
|
---|
179 | if __name__ == '__main__':
|
---|
180 | _test()
|
---|