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() |
---|