Representing Users with Different Capabilities

It can often be useful to authenticate against an external service. You can avoid making users maintain an additional username and password by using a credentials checker that tries to log into a POP mail server, a web application, or some other service where you know your users will already have accounts. However, this approach creates a potential problem. Your realm is going to be asked for avatars after users have been authenticated by the credentials checker. But since the credentials checker is using an external service, rather than a local data source, for authentication, the realm may not have any information about the avatars it's asked for. While you might be able to spontaneously create accounts based on the avatar ID, you will frequently need additional information before users can start using their accounts.

You can handle this scenario by taking advantage of the way twisted.cred supports multiple avatar interfaces. Your realm can use different types of avatars to represent users with different capabilities . One interface can identify users from whom you need additional information, and another interface can identify users who have already provided the necessary information and are ready to use your application.

6.3.1. How Do I Do That?

Write a class implementing portal.IRealm that accepts two different avatar interfaces in requestAvatar. Use one avatar interface for users who need to supply additional information before they can start using your service. This interface should provide a way for users to submit the required information. Use a second avatar interface to provide the normal user actions and data. Example 6-3 demonstrates this technique.

Example 6-3. multiavatar.py




from twisted.cred import portal, checkers, credentials, error as credError

from twisted.protocols import basic

from twisted.internet import protocol, reactor, defer

from zope.interface import Interface, implements



class INamedUserAvatar(Interface):

 "should have attributes username and fullname"



class NamedUserAvatar:

 implements(INamedUserAvatar)

 def _ _init_ _(self, username, fullname):

 self.username = username

 self.fullname = fullname



class INewUserAvatar(Interface):

 "should have username attribute only"

 def setName(self, fullname):

 raise NotImplementedError



class NewUserAvatar:

 implements(INewUserAvatar)

 def _ _init_ _(self, username, userDb):

 self.username = username

 self.userDb = userDb



 def setName(self, fullname):

 self.userDb[self.username] = fullname

 return NamedUserAvatar(self.username, fullname)



class MultiAvatarRealm:

 implements(portal.IRealm)



 def _ _init_ _(self, users):

 self.users = users



 def requestAvatar(self, avatarId, mind, *interfaces):

 logout = lambda: None

 if INamedUserAvatar in interfaces and self.users.has_key(avatarId):

 fullname = self.users[avatarId]

 return (INamedUserAvatar,

 NamedUserAvatar(avatarId, fullname),



 logout)

 elif INewUserAvatar in interfaces:

 avatar = NewUserAvatar(avatarId, self.users)

 return (INewUserAvatar, avatar, logout)

 else:

 raise KeyError("None of the requested interfaces is supported")



class PasswordDictChecker(object):

 implements(checkers.ICredentialsChecker)

 credentialInterfaces = (credentials.IUsernamePassword,)



 def _ _init_ _(self, passwords):

 "passwords: a dict-like object mapping usernames to passwords"

 self.passwords = passwords



 def requestAvatarId(self, credentials):

 username = credentials.username

 if self.passwords.has_key(username):

 if credentials.password == self.passwords[username]:

 return defer.succeed(username)

 else:

 return defer.fail(

 credError.UnauthorizedLogin("Bad password"))

 else:

 return defer.fail(

 credError.UnauthorizedLogin("No such user"))



class NamedUserLoginProtocol(basic.LineReceiver):

 def lineReceived(self, line):

 cmd = getattr(self, 'handle_' + self.currentCommand)

 cmd(line.strip( ))



 def connectionMade(self):

 self.transport.write("User Name: ")

 self.currentCommand = 'user'



 def handle_user(self, username):

 self.username = username

 self.transport.write("Password: ")

 self.currentCommand = 'pass'



 def handle_pass(self, password):

 creds = credentials.UsernamePassword(self.username, password)

 avatarInterfaces = (INamedUserAvatar, INewUserAvatar)

 self.factory.portal.login(creds, None, *avatarInterfaces).addCallback(

 self._loginSucceeded).addErrback(

 self._loginFailed)



 def _loginSucceeded(self, avatarInfo):

 avatar, avatarInterface, self.logout = avatarInfo

 if avatarInterface == INewUserAvatar:

 self.transport.write("What's your full name? ")

 self.currentCommand = "fullname"

 self.avatar = avatar

 else:

 self._gotNamedUser(avatar)



 def handle_fullname(self, fullname):

 namedUserAvatar = self.avatar.setName(fullname)

 self._gotNamedUser(namedUserAvatar)



 def _gotNamedUser(self, avatar):

 self.transport.write("Welcome %s!
" % avatar.fullname)

 defer.maybeDeferred(self.logout).addBoth(self._logoutFinished)



 def _logoutFinished(self, result):

 self.transport.loseConnection( )



 def _loginFailed(self, failure):

 self.transport.write("Denied: %s.
" % failure.getErrorMessage( ))

 self.transport.loseConnection( )



class NamedUserLoginFactory(protocol.ServerFactory):

 protocol = NamedUserLoginProtocol



 def _ _init_ _(self, portal):

 self.portal = portal



users = {

 'admin': 'Admin User',

 }



passwords = {

 'admin': 'aaa',

 'user1': 'bbb',

 'user2': 'ccc'

 }



portal = portal.Portal(MultiAvatarRealm(users))

portal.registerChecker(PasswordDictChecker(passwords))

factory = NamedUserLoginFactory(portal)

reactor.listenTCP(2323, factory)

reactor.run( )

When you run multiavatar.py, it will start up a server on port 2323. This server uses the same simple protocol as the previous examples in this chapter, but with one addition: if you log in as a user who it hasn't seen before, it will ask you for your full name. This information won't be required for subsequent logins, as it will remember the name you entered the first time:


 $ telnet localhost 2323 

 Trying 127.0.0.1...

 Connected to sparky.

 Escape character is '^]'.

 User Name: user1 

 Password: bbb 

 What's your full name? Abe Fettig 

 Welcome Abe Fettig!

 Connection closed by foreign host.



 $ telnet localhost 2323 

 Trying 127.0.0.1...

 Connected to sparky.

 Escape character is '^]'.

 User Name: user1 

 Password: bbb 

 

 Welcome Abe Fettig!

 Connection closed by foreign host.

 

6.3.2. How Does That Work?

The server protocol and realm in Example 6-3 are both aware that this realm uses two different types of avatar. When the MultiAvatarRealm.requestAvatar method is called, it checks to see whether the avatar is listed in self.users, the dictionary of users it knows about. If it finds the user information, it returns a tuple containing INamedUserAvatar, a NamedUserAvatar object for that user, and a logout function. So far, this is identical to the behavior of TestRealm in Example 6-1 at the beginning of this chapter.

If requestAvatar is called with an unknown avatar ID, though, MultiAvatarRealm returns a different type of avatar. The INewUserAvatar interface and NewUserAvatar class represent the actions available to users from whom the server needs more information. Unlike the regular NamedUserAvatar object, NewUserAvatar doesn't provide a fullname attribute: the server doesn't know this user's name. Instead it provides the setName method, providing a way to store the user's name. As a convenience, setName returns a NamedUserAvatar. This step makes it possible to take a NewUserAvatar object and upgrade to a NamedUserAvatar by supplying the user's full name.

The NamedUserProtocol in Example 6-3 calls portal.login with the user's credentials and two interface arguments, INamedUserAvatar and INewUserAvatar. When it gets the results, it checks the first item in the tuple to see which interface is being used. If it is INewUserAvatar, the server asks the client for his full name, and calls the avatar's setName method. In this case, that's all the information that was missing. The NewUserAvatar stores the user's name for future use, and the server can proceed with the rest of the session (brief as it is).

Getting Started

Building Simple Clients and Servers

Web Clients

Web Servers

Web Services and RPC

Authentication

Mail Clients

Mail Servers

NNTP Clients and Servers

SSH

Services, Processes, and Logging



Twisted Network Programming Essentials
Twisted Network Programming Essentials
ISBN: 0596100329
EAN: 2147483647
Year: 2004
Pages: 107
Authors: Abe Fettig

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net