mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 03:44:55 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			174 lines
		
	
	
	
		
			5.4 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			174 lines
		
	
	
	
		
			5.4 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
"""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.
 | 
						|
 | 
						|
"""
 | 
						|
 | 
						|
 | 
						|
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 =", 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()
 |