Example 5-8 in Chapter 5 demonstrated Perspective Broker without any authentication in place. In the real world, though, you probably don't want to give just anyone remote access to objects in your application. You usually need to have an authentication layer in place to control who gets in, and what they're allowed to do.
Perspective Broker lets you perform authentication by integrating with twisted.cred. Adding authentication makes Perspective Broker live up to its name: it can provide different perspectives to different users, hiding or exposing objects and methods depending on the user's permissions.
6.4.1. How Do I Do That?
Create a realm that returns pb.Avatar objects. Each avatar should have methods prefixed with perspective_ that the user of that avatar will be allowed to run. Connect the realm to a Portal, add credentials checkers, and pass the Portal to a pb.PBServerFactory. Example 6-4 demonstrates a Perspective Broker server with authentication.
Example 6-4. pbcred.py
from twisted.spread import pb from twisted.cred import checkers, portal from zope.interface import implements class TodoList: def _ _init_ _(self): self.items =  def listItems(self): return self.items[:] # return copy of list def addItem(self, item): self.items.append(item) return len(self.items) - 1 def deleteItem(self, index): del(self.items[index]) class UserPerspective(pb.Avatar): def _ _init_ _(self, todoList): self.todoList = todoList def perspective_listItems(self): return self.todoList.listItems( ) class AdminPerspective(UserPerspective): def _ _init_ _(self, todoList): self.todoList = todoList def perspective_addItem(self, item): return self.todoList.addItem(item) def perspective_deleteItem(self, index): return self.todoList.deleteItem(index) class TestRealm(object): implements(portal.IRealm) def _ _init_ _(self, todoList): self.todoList = todoList def requestAvatar(self, avatarId, mind, *interfaces): if not pb.IPerspective in interfaces: raise NotImplementedError, "No supported avatar interface." else: if avatarId == 'admin': avatar = AdminPerspective(self.todoList) else: avatar = UserPerspective(self.todoList) return pb.IPerspective, avatar, lambda: None if __name__ == "_ _main_ _": import sys from twisted.internet import reactor p = portal.Portal(TestRealm(TodoList( ))) p.registerChecker( checkers.InMemoryUsernamePasswordDatabaseDontUse( admin='aaa', guest='bbb')) reactor.listenTCP(8789, pb.PBServerFactory(p)) reactor.run( )
To test this server, write a Perspective Broker client that supports authentication. Create a PBClientFactory and call login (instead of geTRootObject) with your user's credentials as the only argument. Example 6-5 is a pb client that uses a username and password for authentication.
Example 6-5. pbcred_client.py
from twisted.spread import pb from twisted.internet import reactor from twisted.cred import credentials class PbAuthTester(object): def _ _init_ _(self, credentials): self.credentials = credentials self.server = None def runTests(self): self.connect( ).addCallback( lambda _: self.listItems( )).addCallback( lambda _: self.addItem( )).addErrback( self._catchFailure).addCallback( lambda _: reactor.stop( )) def connect(self): factory = pb.PBClientFactory( ) reactor.connectTCP("localhost", 8789, factory) return factory.login(self.credentials).addCallback(self._connected) def _connected(self, rootObj): self.server = rootObj def listItems(self): return self.server.callRemote("listItems").addCallback(self._gotList) def _gotList(self, list): print "%i items in TODO list." % (len(list)) def addItem(self): return self.server.callRemote( "addItem", "Buy groceries").addCallback( self._addedItem) def _addedItem(self, result): print "Added 1 item." def _catchFailure(self, failure): print "Error:", failure.getErrorMessage( ) if __name__ == "_ _main_ _": import sys username, password = sys.argv[1:3] creds = credentials.UsernamePassword(username, password) tester = PbAuthTester(creds) tester.runTests( ) reactor.run( )
Run pbcred.py to start the server. pbcred_client.py takes a username and password as arguments. Try it with the name and password admin and aaa. It will list the number of current items on the server, and then add an item:
$ python pbcred_client.py admin aaa Getting item list... 0 items in list. Adding new item... Added 1 item.
Then try again using the username guest and password bbb. You'll be able to get the item count, but you'll get an error trying to add an item:
$ python pbcred_client.py guest bbb Getting item list... 1 items in list. Adding new item... Error: UserPerspective instance has no attribute 'perspective_addItem'
This is because the guest user does not get the same perspective as admin. The guest user's perspective is read-only, and doesn't include methods for adding or deleting items.
6.4.2. How Does That Work?
The server provides the four standard objects needed for Twisted authentication: avatars, a realm, a portal, and credentials checkers. The TestRealm returns either a UserPerspective avatar or a AdminPerspective avatar, depending on the user's name. The admin user will get an AdminPerspective avatar, which includes the additional methods perspective_addItem and perspective_deleteItem. UserPerspective and AdminPerspective both inherit from pb.Avatar, and provide methods prefixed with perspective_. pb.Avatar objects implement the pb.IPerspective interface; the Perspective Broker server will pass this interface to Portal.login to request a pb.Avatar.
For the sake of simplicity, Example 6-5 uses the InMemoryPasswordDatabaseDontUse credentials checker class, which is included in twisted.cred.checkers for use in test programs only. In production, you'd use a real credentials checker that authenticated usernames and passwords against a file, database, or another service.
To use authentication, the client calls PBClient.login instead of calling PBClient.getRootObject directly. The login function attempts to log in using a set of credentials and then returns a reference to a pb.Avatar object on the server representing that user's perspective.
Building Simple Clients and Servers
Web Services and RPC
NNTP Clients and Servers
Services, Processes, and Logging