[Python-Dev] Capabilities

Ka-Ping Yee ping@zesty.ca
Sat, 29 Mar 2003 18:31:18 -0600 (CST)

On Mon, 10 Mar 2003, Jim Fulton wrote:
> > Maybe every Python object should have a flag which
> > can be set to prevent introspection -- like the current
> > restricted execution mechanism, but on a per-object
> > basis. Then any object could be used as a capability.
> Yes, but not a very useful one.  For example, given a file,
> you often want to create a "file read" capability which is
> an object that allows reading the file but not writing the file.
> Just preventing introspection isn't enough.

All right.  Let me provide an example; maybe this can help ground
the discussion a bit.  We seem to be doing a lot of dancing around
the issue of what a capability is.

In my view, it's a red herring to discuss whether or not a
particular object "is a capability" or not.  It's like asking
whether something is an "object".  Capabilities are a discipline
under which objects are used -- it's better to think of them as
a technique or a style of programming.

What is at issue here (IMHO) is "how might Python change to
facilitate this style of programming?"

(The analogy with object-oriented programming holds here also.
Even if Python didn't have a "class" keyword, you could still
program in an object-oriented style.  In fact, the C implementation
of Python is clearly object-oriented, even though C has no features
specifically designed for OOP.  But adding "class" made it a lot
easier to do a particular style of object-oriented programming in
Python.  Unfortunately, the particular style encouraged by Python's
"class" keyword doesn't work so well for capability-style programming,
because all instance state is public.  But Python's "class" is not
the only way to do object-oriented programming -- see below.)

Okay, at last to the example, then.

Here is one way to program in a capability style using today's
Python, relying on no changes to the interpreter.  This example
defines a "class" called DirectoryReader that provides read-only
access to only a particular subtree of the filesystem.

    import os

    class Namespace:
        def __init__(self, *args, **kw):
            for value in args:
                self.__dict__[value.__name__] = value
            for name, value in kw.items():
                self.__dict__[name] = value

    class ReadOnly(Namespace):
        def __setattr__(self, name, value):
            raise TypeError('read-only namespace')

    def FileReader(path, name):
        self = Namespace(file=open(path, 'r'))

        def __repr__():
            return '<FileReader %r>' % name

        def reset():

        return ReadOnly(__repr__, reset, self.file.read, self.file.close)

    def DirectoryReader(path, name):
        def __repr__():
            return '<DirectoryReader %r>' % name

        def list():
            return os.listdir(path)

        def readfile(name):
            fullpath = os.path.join(path, name)
            if os.path.isfile(fullpath):
                return FileReader(fullpath, name)

        def getdir(name):
            fullpath = os.path.join(path, name)
            if os.path.isdir(fullpath):
                return DirectoryReader(fullpath, name)

        return ReadOnly(__repr__, list, readfile, getdir)

Now, if we pass an instance of DirectoryReader to code running in
restricted mode, i think this is actually secure.

Specifically, the only introspective attributes we have to disallow, in
order for these objects to enforce their intended restrictions, are
im_self and func_globals.  Of course, we still have to hide __import__ and
sys.modules if we want to prevent code from obtaining access to the
filesystem in other ways.

Hiding __dict__, while it has no impact on restricting filesystem access,
allows us to pass the same DirectoryReader object to two clients without
inadvertently creating a communication channel between them.

-- ?!ng