
:LastChangedDate: $LastChangedDate$
:LastChangedRevision: $LastChangedRevision$
:LastChangedBy: $LastChangedBy$

Authentication with Perspective Broker
======================================








Overview
--------



The examples shown in :doc:`Using Perspective Broker <pb-usage>` demonstrate how to do basic remote method calls, but provided no
facilities for authentication. In this context, authentication is about who
gets which remote references, and how to restrict access to the "right" 
set of people or programs.




As soon as you have a program which offers services to multiple users,
where those users should not be allowed to interfere with each other, you
need to think about authentication. Many services use the idea of an "account" , and rely upon fact that each user has access to only one
account. Twisted uses a system called :doc:`cred <cred>` to
handle authentication issues, and Perspective Broker has code to make it
easy to implement the most common use cases.





Compartmentalizing Services
---------------------------



Imagine how you would write a chat server using PB. The first step might
be a ``ChatServer`` object which had a bunch of 
``pb.RemoteReference`` s that point at user clients. Pretend that
those clients offered a ``remote_print`` method which lets the
server print a message on the user's console. In that case, the server might
look something like this:





.. code-block:: python

    
    class ChatServer(pb.Referenceable):
    
        def __init__(self):
            self.groups = {} # indexed by name
            self.users = {} # indexed by name
        def remote_joinGroup(self, username, groupname):
            if groupname not in self.groups:
                self.groups[groupname] = []
            self.groups[groupname].append(self.users[username])
        def remote_sendMessage(self, from_username, groupname, message):
            group = self.groups[groupname]
            if group:
                # send the message to all members of the group
                for user in group:
                    user.callRemote("print",
                                    "<%s> says: %s" % (from_username,
                                                             message))




For now, assume that all clients have somehow acquired a 
``pb.RemoteReference`` to this ``ChatServer`` object,
perhaps using ``pb.Root`` and ``getRootObject`` as
described in the :doc:`previous chapter <pb-usage>` . In this
scheme, when a user sends a message to the group, their client runs
something like the following:





.. code-block:: python

    
    remotegroup.callRemote("sendMessage", "alice", "Hi, my name is alice.")






Incorrect Arguments
~~~~~~~~~~~~~~~~~~~



You've probably seen the first problem: users can trivially spoof each
other. We depend upon the user to pass a correct value in their
"username" argument, and have no way to tell if they're lying or not.
There is nothing to prevent Alice from modifying her client to do:





.. code-block:: python

    
    remotegroup.callRemote("sendMessage", "bob", "i like pork")




much to the horror of Bob's vegetarian friends. [#]_ 




(In general, learn to get suspicious if you see any argument of a
remotely-invokable method described as "must be X" )




The best way to fix this is to keep track of the user's name locally,
rather than asking them to send it to the server with each message. The best
place to keep state is in an object, so this suggests we need a per-user
object. Rather than choosing an obvious name [#]_ , let's call this the 
``User`` class.





.. code-block:: python

    
    class User(pb.Referenceable):
        def __init__(self, username, server, clientref):
            self.name = username
            self.server = server
            self.remote = clientref
        def remote_joinGroup(self, groupname):
            self.server.joinGroup(groupname, self)
        def remote_sendMessage(self, groupname, message):
            self.server.sendMessage(self.name, groupname, message)
        def send(self, message):
            self.remote.callRemote("print", message)
    
    class ChatServer:
        def __init__(self):
            self.groups = {} # indexed by name
        def joinGroup(self, groupname, user):
            if groupname not in self.groups:
                self.groups[groupname] = []
            self.groups[groupname].append(user)
        def sendMessage(self, from_username, groupname, message):
            group = self.groups[groupname]
            if group:
                # send the message to all members of the group
                for user in group:
                    user.send("<%s> says: %s" % (from_username, message))




Again, assume that each remote client gets access to a single 
``User`` object, which is created with the proper username.




Note how the ``ChatServer`` object has no remote access: it
isn't even ``pb.Referenceable`` anymore. This means that all access
to it must be mediated through other objects, with code that is under your
control.




As long as Alice only has access to her own ``User`` object, she
can no longer spoof Bob. The only way for her to invoke 
``ChatServer.sendMessage`` is to call her ``User`` 
object's ``remote_sendMessage`` method, and that method uses its
own state to provide the ``from_username`` argument. It doesn't
give her any way to change that state.




This restriction is important. The ``User`` object is able to
maintain its own integrity because there is a wall between the object and
the client: the client cannot inspect or modify internal state, like the 
``.name`` attribute. The only way through this wall is via remote
method invocations, and the only control Alice has over those invocations is
when they get invoked and what arguments they are given.



.. note::
   
   
   No object can maintain its integrity against local threats: by design,
   Python offers no mechanism for class instances to hide their attributes, and
   once an intruder has a copy of ``self.__dict__`` , they can do
   everything the original object was able to do.
   
   






Unforgeable References
~~~~~~~~~~~~~~~~~~~~~~



Now suppose you wanted to implement group parameters, for example a mode
in which nobody was allowed to talk about mattresses because some users were
sensitive and calming them down after someone said "mattress" is a
hassle that's best avoided altogether. Again, per-group state implies a
per-group object. We'll go out on a limb and call this the 
``Group`` object:





.. code-block:: python

    
    class User(pb.Referenceable):
        def __init__(self, username, server, clientref):
            self.name = username
            self.server = server
            self.remote = clientref
        def remote_joinGroup(self, groupname, allowMattress=True):
            return self.server.joinGroup(groupname, self, allowMattress)
        def send(self, message):
            self.remote.callRemote("print", message)
    
    class Group(pb.Referenceable):
        def __init__(self, groupname, allowMattress):
            self.name = groupname
            self.allowMattress = allowMattress
            self.users = []
        def remote_send(self, from_user, message):
            if not self.allowMattress and "mattress" in message:
                raise ValueError("Don't say that word")
            for user in self.users:
                user.send("<%s> says: %s" % (from_user.name, message))
        def addUser(self, user):
            self.users.append(user)
    
    class ChatServer:
        def __init__(self):
            self.groups = {} # indexed by name
        def joinGroup(self, groupname, user, allowMattress):
            if groupname not in self.groups:
                self.groups[groupname] = Group(groupname, allowMattress)
            self.groups[groupname].addUser(user)
            return self.groups[groupname]





This example takes advantage of the fact that 
``pb.Referenceable`` objects sent over a wire can be returned to
you, and they will be turned into references to the same object that you
originally sent. The client cannot modify the object in any way: all they
can do is point at it and invoke its ``remote_*`` methods. Thus,
you can be sure that the ``.name`` attribute remains the same as
you left it. In this case, the client code would look something like
this:





.. code-block:: python

    
    class ClientThing(pb.Referenceable):
        def remote_print(self, message):
            print(message)
        def join(self):
            d = self.remoteUser.callRemote("joinGroup", "#twisted",
                                           allowMattress=False)
            d.addCallback(self.gotGroup)
        def gotGroup(self, group):
            group.callRemote("send", self.remoteUser, "hi everybody")




The ``User`` object is sent from the server side, and is turned
into a ``pb.RemoteReference`` when it arrives at the client. The
client sends it back to ``Group.remote_send`` , and PB turns it back
into a reference to the original ``User`` when it gets there. 
``Group.remote_send`` can then use its ``.name`` attribute
as the sender of the message.



.. note::
   
   
   
   Third party references (there aren't any)
   
   
   
   
   This technique also relies upon the fact that the 
   ``pb.Referenceable`` reference can *only* come from someone
   who holds a corresponding ``pb.RemoteReference`` . The design of the
   serialization mechanism (implemented in :py:mod:`twisted.spread.jelly` : pb, jelly, spread.. get it?  Look for "banana" , too.  What other networking framework
   can claim API names based on sandwich ingredients?) makes it impossible for
   a client to obtain a reference that they weren't explicitly given.
   References passed over the wire are given id numbers and recorded in a
   per-connection dictionary. If you didn't give them the reference, the id
   number won't be in the dict, and no amount of guessing by a malicious client
   will give them anything else. The dict goes away when the connection is
   dropped, further limiting the scope of those references.
   
   
   
   
   Furthermore, it is not possible for Bob to send *his* 
   ``User`` reference to Alice (perhaps over some other PB channel
   just between the two of them). Outside the context of Bob's connection to
   the server, that reference is just a meaningless number. To prevent
   confusion, PB will tell you if you try to give it away: when you try to hand
   a ``pb.RemoteReference`` to a third party, you'll get an exception
   (implemented with an assert in pb.py:364 RemoteReference.jellyFor).
   
   
   
   
   This helps the security model somewhat: only the client you gave the
   reference to can cause any damage with it. Of course, the client might be a
   brainless zombie, simply doing anything some third party wants. When it's
   not proxying ``callRemote`` invocations, it's probably terrorizing
   the living and searching out human brains for sustenance. In short, if you
   don't trust them, don't give them that reference.
   
   
   
   
   And remember that everything you've ever given them over that connection
   can come back to you. If expect the client to invoke your method with some
   object A that you sent to them earlier, and instead they send you object B
   (that you also sent to them earlier), and you don't check it somehow, then
   you've just opened up a security hole (we'll see an example of this
   shortly). It may be better to keep such objects in a dictionary on the
   server side, and have the client send you an index string instead. Doing it
   that way makes it obvious that they can send you anything they want, and
   improves the chances that you'll remember to implement the right checks.
   (This is exactly what PB is doing underneath, with a per-connection
   dictionary of ``Referenceable`` objects, indexed by a number).
   
   
   
   
   And, of course, you have to make sure you don't accidentally hand out a
   reference to the wrong object.
   
   
   





But again, note the vulnerability. If Alice holds a 
``RemoteReference`` to *any* object on the server side that
has a ``.name`` attribute, she can use that name as a spoofed"from" parameter. As a simple example, what if her client code looked
like:





.. code-block:: python

    
    class ClientThing(pb.Referenceable):
        def join(self):
            d = self.remoteUser.callRemote("joinGroup", "#twisted")
            d.addCallback(self.gotGroup)
        def gotGroup(self, group):
            group.callRemote("send", from_user=group, "hi everybody")




This would let her send a message that appeared to come from "#twisted" rather than "Alice" . If she joined a group that
happened to be named "bob" (perhaps it is the "How To Be Bob" 
channel, populated by Alice and countless others, a place where they can
share stories about their best impersonating-Bob moments), then she would be
able to emit a message that looked like "<bob> says: hi there" ,
and she has accomplished her lifelong goal.






Argument Typechecking
~~~~~~~~~~~~~~~~~~~~~



There are two techniques to close this hole. The first is to have your
remotely-invokable methods do type-checking on their arguments: if 
``Group.remote_send`` asserted ``isinstance(from_user, User)`` then Alice couldn't use non-User objects to do her spoofing,
and hopefully the rest of the system is designed well enough to prevent her
from obtaining access to somebody else's User object.






Objects as Capabilities
~~~~~~~~~~~~~~~~~~~~~~~



The second technique is to avoid having the client send you the objects
altogether. If they don't send you anything, there is nothing to verify. In
this case, you would have to have a per-user-per-group object, in which the 
``remote_send`` method would only take a single 
``message`` argument. The ``UserGroup`` object is created
with references to the only ``User`` and ``Group`` objects
that it will ever use, so no lookups are needed:





.. code-block:: python

    
    class UserGroup(pb.Referenceable):
        def __init__(self, user, group):
            self.user = user
            self.group = group
        def remote_send(self, message):
            self.group.send(self.user.name, message)
    
    class Group:
        def __init__(self, groupname, allowMattress):
            self.name = groupname
            self.allowMattress = allowMattress
            self.users = []
        def send(self, from_user, message):
            if not self.allowMattress and "mattress" in message:
                raise ValueError("Don't say that word")
            for user in self.users:
                user.send("<%s> says: %s" % (from_user.name, message))
        def addUser(self, user):
            self.users.append(user)




The only message-sending m                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   