ยปCore Development>Code coverage>Lib/Bastion.py

Python code coverage for Lib/Bastion.py

#countcontent
1n/a"""Bastionification utility.
2n/a
3n/aA bastion (for another object -- the 'original') is an object that has
4n/athe same methods as the original but does not give access to its
5n/ainstance variables. Bastions have a number of uses, but the most
6n/aobvious one is to provide code executing in restricted mode with a
7n/asafe interface to an object implemented in unrestricted mode.
8n/a
9n/aThe bastionification routine has an optional second argument which is
10n/aa filter function. Only those methods for which the filter method
11n/a(called with the method name as argument) returns true are accessible.
12n/aThe default filter method returns true unless the method name begins
13n/awith an underscore.
14n/a
15n/aThere are a number of possible implementations of bastions. We use a
16n/a'lazy' approach where the bastion's __getattr__() discipline does all
17n/athe work for a particular method the first time it is used. This is
18n/ausually fastest, especially if the user doesn't call all available
19n/amethods. The retrieved methods are stored as instance variables of
20n/athe bastion, so the overhead is only occurred on the first use of each
21n/amethod.
22n/a
23n/aDetail: the bastion class has a __repr__() discipline which includes
24n/athe repr() of the original object. This is precomputed when the
25n/abastion is created.
26n/a
271"""
281from warnings import warnpy3k
291warnpy3k("the Bastion module has been removed in Python 3.0", stacklevel=2)
301del warnpy3k
31n/a
321__all__ = ["BastionClass", "Bastion"]
33n/a
341from types import MethodType
35n/a
36n/a
372class BastionClass:
38n/a
39n/a """Helper class used by the Bastion() function.
40n/a
41n/a You could subclass this and pass the subclass as the bastionclass
42n/a argument to the Bastion() function, as long as the constructor has
43n/a the same signature (a get() function and a name for the object).
44n/a
451 """
46n/a
471 def __init__(self, get, name):
48n/a """Constructor.
49n/a
50n/a Arguments:
51n/a
52n/a get - a function that gets the attribute value (by name)
53n/a name - a human-readable name for the original object
54n/a (suggestion: use repr(object))
55n/a
56n/a """
570 self._get_ = get
580 self._name_ = name
59n/a
601 def __repr__(self):
61n/a """Return a representation string.
62n/a
63n/a This includes the name passed in to the constructor, so that
64n/a if you print the bastion during debugging, at least you have
65n/a some idea of what it is.
66n/a
67n/a """
680 return "<Bastion for %s>" % self._name_
69n/a
701 def __getattr__(self, name):
71n/a """Get an as-yet undefined attribute value.
72n/a
73n/a This calls the get() function that was passed to the
74n/a constructor. The result is stored as an instance variable so
75n/a that the next time the same attribute is requested,
76n/a __getattr__() won't be invoked.
77n/a
78n/a If the get() function raises an exception, this is simply
79n/a passed on -- exceptions are not cached.
80n/a
81n/a """
820 attribute = self._get_(name)
830 self.__dict__[name] = attribute
840 return attribute
85n/a
86n/a
871def Bastion(object, filter = lambda name: name[:1] != '_',
881 name=None, bastionclass=BastionClass):
89n/a """Create a bastion for an object, using an optional filter.
90n/a
91n/a See the Bastion module's documentation for background.
92n/a
93n/a Arguments:
94n/a
95n/a object - the original object
96n/a filter - a predicate that decides whether a function name is OK;
97n/a by default all names are OK that don't start with '_'
98n/a name - the name of the object; default repr(object)
99n/a bastionclass - class used to create the bastion; default BastionClass
100n/a
101n/a """
102n/a
1030 raise RuntimeError, "This code is not secure in Python 2.2 and later"
104n/a
105n/a # Note: we define *two* ad-hoc functions here, get1 and get2.
106n/a # Both are intended to be called in the same way: get(name).
107n/a # It is clear that the real work (getting the attribute
108n/a # from the object and calling the filter) is done in get1.
109n/a # Why can't we pass get1 to the bastion? Because the user
110n/a # would be able to override the filter argument! With get2,
111n/a # overriding the default argument is no security loophole:
112n/a # all it does is call it.
113n/a # Also notice that we can't place the object and filter as
114n/a # instance variables on the bastion object itself, since
115n/a # the user has full access to all instance variables!
116n/a
1170 def get1(name, object=object, filter=filter):
118n/a """Internal function for Bastion(). See source comments."""
1190 if filter(name):
1200 attribute = getattr(object, name)
1210 if type(attribute) == MethodType:
1220 return attribute
1230 raise AttributeError, name
124n/a
1250 def get2(name, get1=get1):
126n/a """Internal function for Bastion(). See source comments."""
1270 return get1(name)
128n/a
1290 if name is None:
1300 name = repr(object)
1310 return bastionclass(get2, name)
132n/a
133n/a
1341def _test():
135n/a """Test the Bastion() function."""
1360 class Original:
1370 def __init__(self):
1380 self.sum = 0
1390 def add(self, n):
1400 self._add(n)
1410 def _add(self, n):
1420 self.sum = self.sum + n
1430 def total(self):
1440 return self.sum
1450 o = Original()
1460 b = Bastion(o)
147n/a testcode = """if 1:
148n/a b.add(81)
149n/a b.add(18)
150n/a print "b.total() =", b.total()
151n/a try:
152n/a print "b.sum =", b.sum,
153n/a except:
154n/a print "inaccessible"
155n/a else:
156n/a print "accessible"
157n/a try:
158n/a print "b._add =", b._add,
159n/a except:
160n/a print "inaccessible"
161n/a else:
162n/a print "accessible"
163n/a try:
164n/a print "b._get_.func_defaults =", map(type, b._get_.func_defaults),
165n/a except:
166n/a print "inaccessible"
167n/a else:
168n/a print "accessible"
1690 \n"""
1700 exec testcode
1710 print '='*20, "Using rexec:", '='*20
1720 import rexec
1730 r = rexec.RExec()
1740 m = r.add_module('__main__')
1750 m.b = b
1760 r.r_exec(testcode)
177n/a
178n/a
1791if __name__ == '__main__':
1800 _test()