[Zope-Checkins] CVS: Zope3/lib/python/Zope/RestrictedPython/tests - __init__.py:1.1.2.1 restricted_module.py:1.1.2.1 security_in_syntax.py:1.1.2.1 testRestrictions.py:1.1.2.1

Fred L. Drake, Jr. fdrake@acm.org
Wed, 27 Feb 2002 17:29:57 -0500


Update of /cvs-repository/Zope3/lib/python/Zope/RestrictedPython/tests
In directory cvs.zope.org:/tmp/cvs-serv17295/tests

Added Files:
      Tag: Zope-3x-branch
	__init__.py restricted_module.py security_in_syntax.py 
	testRestrictions.py 
Log Message:
Python 2.2 version of RestrictedPython (mostly cleanup).

=== Added File Zope3/lib/python/Zope/RestrictedPython/tests/__init__.py ===
'''Python package.'''


=== Added File Zope3/lib/python/Zope/RestrictedPython/tests/restricted_module.py ===
from __future__ import nested_scopes

def print0():
    print 'Hello, world!',
    return printed

def print1():
    print 'Hello,',
    print 'world!',
    return printed

def printStuff():
    print 'a', 'b', 'c',
    return printed

def printToNone():
    x = None
    print >>x, 'Hello, world!',
    return printed

def printLines():
    # This failed before Zope 2.4.0a2
    r = range(3)
    for n in r:
        for m in r:
            print m + n * len(r),
        print
    return printed

def primes():
    # Somewhat obfuscated code on purpose
    print filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0,
    map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,20))),
    return printed

def allowed_read(ob):
    print ob.allowed
    print ob.s
    print ob[0]
    print ob[2]
    print ob[3:-1]
    print len(ob)
    return printed

def allowed_default_args(ob):
    def f(a=ob.allowed, s=ob.s):
        return a, s


def allowed_simple():
    q = {'x':'a'}
    q['y'] = 'b'
    q.update({'z': 'c'})
    r = ['a']
    r.append('b')
    r[2:2] = ['c']
    s = 'a'
    s = s[:100] + 'b'
    s += 'c'
    _ = q
    
    return q['x'] + q['y'] + q['z'] + r[0] + r[1] + r[2] + s

def allowed_write(ob):
    ob.writeable = 1
    #ob.writeable += 1
    [1 for ob.writeable in 1,2]
    ob['safe'] = 2
    #ob['safe'] += 2
    [1 for ob['safe'] in 1,2]

def denied_print(ob):
    print >> ob, 'Hello, world!',

def denied_getattr(ob):
    #ob.disallowed += 1
    ob.disallowed = 1
    return ob.disallowed

def denied_default_args(ob):
    def f(d=ob.disallowed):
        return d

def denied_setattr(ob):
    ob.allowed = -1

def denied_setattr2(ob):
    #ob.allowed += -1
    ob.allowed = -1

def denied_setattr3(ob):
    [1 for ob.allowed in 1,2]

def denied_getitem(ob):
    ob[1]

def denied_getitem2(ob):
    #ob[1] += 1
    ob[1]
    
def denied_setitem(ob):
    ob['x'] = 2

def denied_setitem2(ob):
    #ob[0] += 2
    ob['x'] = 2

def denied_setitem3(ob):
    [1 for ob['x'] in 1,2]

def denied_setslice(ob):
    ob[0:1] = 'a'

def denied_setslice2(ob):
    #ob[0:1] += 'a'
    ob[0:1] = 'a'

def denied_setslice3(ob):
    [1 for ob[0:1] in 1,2]

##def strange_attribute():
##    # If a guard has attributes with names that don't start with an
##    # underscore, those attributes appear to be an attribute of
##    # anything.
##    return [].attribute_of_anything

def order_of_operations():
    return 3 * 4 * -2 + 2 * 12

def rot13(ss):
    mapping = {}
    orda = ord('a')
    ordA = ord('A')
    for n in range(13):
        c1 = chr(orda + n)
        c2 = chr(orda + n + 13)
        c3 = chr(ordA + n)
        c4 = chr(ordA + n + 13)
        mapping[c1] = c2
        mapping[c2] = c1
        mapping[c3] = c4
        mapping[c4] = c3
    del c1, c2, c3, c4, orda, ordA
    res = ''
    for c in ss:
        res = res + mapping.get(c, c)
    return res

def nested_scopes_1():
    # Fails if 'a' is consumed by the first function.
    a = 1
    def f1():
        return a
    def f2():
        return a
    return f1() + f2()



=== Added File Zope3/lib/python/Zope/RestrictedPython/tests/security_in_syntax.py ===

# These are all supposed to raise a SyntaxError when using
# compile_restricted() but not when using compile().
# Each function in this module is compiled using compile_restricted().

def overrideGuardWithFunction():
    def _getattr(o): return o

def overrideGuardWithLambda():
    lambda o, _getattr=None: o

def overrideGuardWithClass():
    class _getattr:
        pass

def overrideGuardWithName():
    _getattr = None

def overrideGuardWithArgument():
    def f(_getattr=None):
        pass

def reserved_names():
    printed = ''

def bad_name():
    __ = 12

def bad_attr():
    some_ob._some_attr = 15

def no_exec():
    exec 'q = 1'


=== Added File Zope3/lib/python/Zope/RestrictedPython/tests/testRestrictions.py ===

import sys, os
import unittest

from string import rfind
from types import FunctionType

from Zope.RestrictedPython import compile_restricted
from Zope.RestrictedPython.PrintCollector import PrintCollector
from Zope.RestrictedPython.Eval import RestrictionCapableEval
from Zope.RestrictedPython.tests import restricted_module, security_in_syntax

if __name__=='__main__':
    here = os.path.dirname(sys.argv[0])
else:
    here = os.path.dirname(__file__)

def _getindent(line):
    """Returns the indentation level of the given line."""
    indent = 0
    for c in line:
        if c == ' ': indent = indent + 1
        elif c == '\t': indent = indent + 8
        else: break
    return indent

def find_source(fn, func):
    """Given a func_code object, this function tries to find and return
    the python source code of the function.  Originally written by
    Harm van der Heijden (H.v.d.Heijden@phys.tue.nl)"""
    f = open(fn,"r")
    for i in range(func.co_firstlineno):
        line = f.readline()
    ind = _getindent(line)
    msg = ""
    while line:
        msg = msg + line
        line = f.readline()
        # the following should be <= ind, but then we get
        # confused by multiline docstrings. Using == works most of
        # the time... but not always!
        if _getindent(line) == ind: break
    f.close()
    return fn, msg

def create_rmodule():
    global rmodule
    fn = os.path.join(here, 'restricted_module.py')
    f = open(fn, 'r')
    source = f.read()
    f.close()
    # Sanity check
    compile(source, fn, 'exec')
    # Now compile it for real
    code = compile_restricted(source, fn, 'exec')
    rmodule = {'__builtins__':{'__import__':__import__, 'None':None}}
    builtins = getattr(__builtins__, '__dict__', __builtins__)
    for name in ('map', 'reduce', 'int', 'pow', 'range', 'filter',
                 'len', 'chr', 'ord',
                 ):
        rmodule[name] = builtins[name]
    exec code in rmodule

create_rmodule()

class AccessDenied (Exception): pass

DisallowedObject = []

class RestrictedObject:
    disallowed = DisallowedObject
    allowed = 1
    _ = 2
    __ = 3
    _some_attr = 4
    __some_other_attr__ = 5
    s = 'Another day, another test...'
    __writeable_attrs__ = ('writeable',)

    def __getitem__(self, idx):
        if idx == 'protected':
            raise AccessDenied
        elif idx == 0 or idx == 'safe':
            return 1
        elif idx == 1:
            return DisallowedObject
        else:
            return self.s[idx]

    def __getslice__(self, lo, hi):
        return self.s[lo:hi]

    def __len__(self):
        return len(self.s)

    def __setitem__(self, idx, v):
        if idx == 'safe':
            self.safe = v
        else:
            raise AccessDenied

    def __setslice__(self, lo, hi, value):
        raise AccessDenied

    write = DisallowedObject


def guarded_getattr(ob, name):
    v = getattr(ob, name)
    if v is DisallowedObject:
        raise AccessDenied
    return v

SliceType = type(slice(0))
def guarded_getitem(ob, index):
    if type(index) is SliceType and index.step is None:
        start = index.start
        stop = index.stop
        if start is None:
            start = 0
        if stop is None:
            v = ob[start:]
        else:
            v = ob[start:stop]
    else:
        v = ob[index]
    if v is DisallowedObject:
        raise AccessDenied
    return v


class TestGuard:
    '''A guard class'''
    def __init__(self, _ob, write=None):
        self.__dict__['_ob'] = _ob

    # Write guard methods

    def __setattr__(self, name, value):
        _ob = self.__dict__['_ob']
        writeable = getattr(_ob, '__writeable_attrs__', ())
        if name not in writeable:
            raise AccessDenied
        if name[:5] == 'func_':
            raise AccessDenied
        setattr(_ob, name, value)

    def __setitem__(self, index, value):
        _ob = self.__dict__['_ob']
        _ob[index] = value

    def __setslice__(self, lo, hi, value):
        _ob = self.__dict__['_ob']
        _ob[lo:hi] = value

##    attribute_of_anything = 98.6

class RestrictionTests(unittest.TestCase):
    def execFunc(self, name, *args, **kw):
        func = rmodule[name]
        func.func_globals.update({'_getattr_': guarded_getattr,
                                  '_getitem_': guarded_getitem,
                                  '_write_': TestGuard,
                                  '_print_': PrintCollector})
        return func(*args, **kw)

    def checkPrint(self):
        for i in range(2):
            res = self.execFunc('print%s' % i)
            assert res == 'Hello, world!', res

    def checkPrintToNone(self):
        try:
            res = self.execFunc('printToNone')
        except AttributeError:
            # Passed.  "None" has no "write" attribute.
            pass
        else:
            assert 0, res

    def checkPrintStuff(self):
        res = self.execFunc('printStuff')
        assert res == 'a b c', res

    def checkPrintLines(self):
        res = self.execFunc('printLines')
        assert res == '0 1 2\n3 4 5\n6 7 8\n', res

    def checkPrimes(self):
        res = self.execFunc('primes')
        assert res == '[2, 3, 5, 7, 11, 13, 17, 19]', res

    def checkAllowedSimple(self):
        res = self.execFunc('allowed_simple')
        assert res == 'abcabcabc', res

    def checkAllowedRead(self):
        self.execFunc('allowed_read', RestrictedObject())

    def checkAllowedWrite(self):
        self.execFunc('allowed_write', RestrictedObject())

    def checkAllowedArgs(self):
        self.execFunc('allowed_default_args', RestrictedObject())

    def checkDenied(self):
        for k in rmodule.keys():
            if k[:6] == 'denied':
                try:
                    self.execFunc(k, RestrictedObject())
                except AccessDenied:
                    # Passed the test
                    pass
                else:
                    raise AssertionError, '%s() did not trip security' % k

    def checkSyntaxSecurity(self):
        # Ensures that each of the functions in security_in_syntax.py
        # throws a SyntaxError when using compile_restricted.
        fn = os.path.join(here, 'security_in_syntax.py')
        f = open(fn, 'r')
        source = f.read()
        f.close()
        # Unrestricted compile.
        code = compile(source, fn, 'exec')
        m = {'__builtins__':None}
        exec code in m
        for k, v in m.items():
            if hasattr(v, 'func_code'):
                filename, source = find_source(fn, v.func_code)
                # Now compile it with restrictions
                try:
                    code = compile_restricted(source, filename, 'exec')
                except SyntaxError:
                    # Passed the test.
                    pass
                else:
                    raise AssertionError, '%s should not have compiled' % k

##    def checkStrangeAttribute(self):
##        res = self.execFunc('strange_attribute')
##        assert res == 98.6, res

    def checkOrderOfOperations(self):
        res = self.execFunc('order_of_operations')
        assert (res == 0), res

    def checkRot13(self):
        res = self.execFunc('rot13', 'Zope is k00l')
        assert (res == 'Mbcr vf x00y'), res

    def checkNestedScopes1(self):
        res = self.execFunc('nested_scopes_1')
        assert (res == 2), res

    def checkUnrestrictedEval(self):
        expr = RestrictionCapableEval("{'a':[m.pop()]}['a'] + [m[0]]")
        v = [12, 34]
        expect = v[:]
        expect.reverse()
        res = expr.eval({'m':v})
        assert res == expect, res
        v = [12, 34]
        res = expr(m=v)
        assert res == expect

    def checkStackSize(self):
        for k, rfunc in rmodule.items():
            if not k.startswith('_') and hasattr(rfunc, 'func_code'):
                rss = rfunc.func_code.co_stacksize
                ss = getattr(restricted_module, k).func_code.co_stacksize
                self.failUnless(
                    rss >= ss, 'The stack size estimate for %s() '
                    'should have been at least %d, but was only %d'
                    % (k, ss, rss))

def test_suite():
    return unittest.makeSuite(RestrictionTests, 'check')

def main():
    alltests=test_suite()
    runner = unittest.TextTestRunner()
    runner.run(alltests)

def debug():
   test_suite().debug()

def pdebug():
    import pdb
    pdb.run('debug()')

if __name__=='__main__':
    if len(sys.argv) > 1:
        globals()[sys.argv[1]]()
    else:
        main()