[Twisted-Python] pb.Copyable, round trip objects, and untrusted clients

David Ripton dripton at ripton.net
Wed May 19 23:54:55 EDT 2004

I'm working on a client-server game, with hidden information, using PB. 
  I don't want to trust the clients.

Because there is hidden information, it's important not to send anything 
to the client that it's player shouldn't see.  Doing that with PB is 
pretty straightforward.  If a class's data is always secret, don't make 
it Copyable.  If parts of a class are secret, censor them in getStateToCopy.

Obviously, the client should only be able to change the server's game 
state via a well-defined error-checked interface.  Also fairly 
straightforward, though error-prone since you need to forsee and test 
against every way to cheat.

There's a third hole to plug, which is passing an object from the server 
to the client as an argument of a client-side remote_* method, and later 
getting it back as an argument to a server-side perspective_* method. 
(e.g. the server passes the client a list of games in progress, and 
later the client passes back the game it wants to join.)  If the client 
changes the object before passing it back, it might trick the server 
into doing the wrong thing with it.

Anyway, it seems easy enough to avoid this problem, by just rewinding 
any changes the client has made to such objects.  At some point before 
sending an object to the client side, add it to a dict, keyed by its id. 
  When an object comes back from the client as an argument in a 
perspective_* call, use its id to lookup the server's copy of the object 
in the dict, and then reassign the current name to the good version of 
the object, losing any changes the client may have made.  In other 
words, the client is treated as if it only passed the object's id back, 
not its __dict__.  But the interface is simpler.


in Server
def __init__:
     self._paranoia = {}  # Maybe a weak dict instead

def paranoid_add(self, obj):
     self._paranoia[id(obj)] = obj
def paranoid_get(self, obj):
     return self._paranoia[id(obj)]

def form_game(self, whatever):
     game = Game.Game(args)

Then in User (a pb.Avatar):
def perspective_join_game(self, game):
     game = self.server.paranoid_get(game)
     self.server.join_game(self.name, game)

This is simple and works, but it's annoyingly repetitive.  And plumbing 
code is infecting the application level.  And I know there are other 
object vs. id caches inside PB, so this feels redundant.  What's the 
right way to do this with Copyable?


David Ripton    dripton at ripton.net

More information about the Twisted-Python mailing list