[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.
e.g.:
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)
self.paranoia_add(game)
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?
Thanks.
--
David Ripton dripton at ripton.net
More information about the Twisted-Python
mailing list