Using Authentication in a Twisted Server

There are quite a few different classes and interfaces used in twisted.cred. These classes and interfaces work together to form a flexible authentication system. This lab introduces the individual pieces of twisted.cred and shows how to tie them together to add authentication to a server.

6.1.1. How Do I Do That?

Set up a portal.Portal object that takes the username and password provided by a user, verifies her identity, and returns an object representing the actions and data available to that user. To do this you'll need to implement a few different twisted.cred interfaces. Example 6-1 demonstrates how to do this, adding authentication to a simple line-based server protocol.

Example 6-1. simplecred.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 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 INamedUserAvatar(Interface):

 "should have attributes username and fullname"



class NamedUserAvatar:

 implements(INamedUserAvatar)

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

 self.username = username

 self.fullname = fullname



class TestRealm:

 implements(portal.IRealm)



 def _ _init_ _(self, users):

 self.users = users



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

 if INamedUserAvatar in interfaces:

 fullname = self.users[avatarId]

 logout = lambda: None

 return (INamedUserAvatar,

 NamedUserAvatar(avatarId, fullname),



 logout)

 else:

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



class LoginTestProtocol(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)

 self.factory.portal.login(creds, None, INamedUserAvatar).addCallback(

 self._loginSucceeded).addErrback(

 self._loginFailed)



 def _loginSucceeded(self, avatarInfo):

 avatarInterface, avatar, logout = avatarInfo

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

 defer.maybeDeferred(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 LoginTestFactory(protocol.ServerFactory):

 protocol = LoginTestProtocol



 def _ _init_ _(self, portal):

 self.portal = portal



users = {

 'admin': 'Admin User',

 'user1': 'Joe Smith',

 'user2': 'Bob King',

 }



passwords = {

 'admin': 'aaa',

 'user1': 'bbb',

 'user2': 'ccc'

 }



if __name__ == "_ _main_ _":

 p = portal.Portal(TestRealm(users))

 p.registerChecker(PasswordDictChecker(passwords))

 factory = LoginTestFactory(p)

 reactor.listenTCP(2323, factory)

 reactor.run( )

When you run simplecred.py, it will start up a server on port 2323. You can then communicate with the server using Telnet. Each time you make a connection, it will ask for your username and password, attempt to log you in, and then disconnect:


 $ telnet localhost 2323

 Trying 127.0.0.1...

 Connected to sparky.

 Escape character is '^]'.

 User Name: admin

 Password: aaa

 

 Welcome Admin User!

 Connection closed by foreign host.



 $ telnet localhost 2323

 Trying 127.0.0.1...

 Connected to sparky.

 Escape character is '^]'.

 User Name: admin

 Password: 123

 

 Denied: Bad password.

 Connection closed by foreign host.



 $ telnet localhost 2323

 Trying 127.0.0.1...

 Connected to sparky.

 Escape character is '^]'.

 User Name: someotherguy

 Password: pass

 

 Denied: No such user.

 Connection closed by foreign host.

 

6.1.2. How Does That Work?

Before you try to understand how all the classes in Example 6-1 work, you should familiarize yourself with the vocabulary twisted.cred uses to talk about users and authentication. Here are some of the terms you'll see in Example 6-1, and other applications using twisted.cred:

 

Credentials

Information used to identify and authenticate a user. This is typically a username and password, but it can be any data or object used to prove a user's identity. Objects that provide credentials will implement twisted.cred.credentials.ICredentials.

 

Avatar

An object on the server that does things on behalf of a user. The concept of an avatar can seem a little fuzzy at first. Don't think of an avatar as representing a user: think of it as representing actions and data available to a user inside your application.

 

Avatar ID

A string that identifies a specific user, used to get the appropriate Avatar for that user. This is often the login username, but it could be any unique identifier. Examples could be "Joe Smith", "joe@localhost", or "user926344".

 

Credentials Checker

An object that takes credentials and attempts to verify them. If the credentials correctly identify a user, the credentials checker will return an avatar ID. Credential checker objects implement the interface twisted.cred.checker.ICredentialsChecker.

 

Realm

An object that provides access to all the possible avatars in an application. A realm will take an avatar ID identifying a specific user and return an avatar object that will work on behalf of that user. A realm can support multiple types of avatar, allowing the same user to work with different services provided by the server. Realm objects implement the interface twisted.cred.portal.IRealm.

 

Portal

A twisted.cred.portal.Portal object, which unites a realm with a set of credential checkers. Inside a portal, the credential checker objects check incoming credentials, returning an avatar ID if the credentials are valid. The avatar ID is then passed to the realm to create an avatar object that can work on behalf of the user.

Figure 6-1 shows how a portal, realm, and credentials checkers work together.

Figure 6-1. Interaction between a portal, realm, and credentials checkers

It might take you a little while to understand all the classes and interfaces in twisted.cred, and at first you might wonder why it's necessary to have a system with so many moving parts. The answer is that this system is designed to be extremely flexible. The code that verifies credentials is kept separate from the code that creates Avatar objects, so you can switch your authentication backend from a password file to a database table or LDAP server without having to touch the rest of your application. If you originally design your system to use plain-text passwords, and later decide to support hashed passwords, all you have to do is plug in a new CredentialsChecker object. If you write a mail server that supports POP3 now, you can later add IMAP4 access, or a web interface, and you won't have to rewrite your authentication code. Your initial investment in understanding twisted.cred now will pay off in flexible, maintainable code later.

In Example 6-1, PasswordDictChecker implements checkers.ICredentialsChecker to check usernames and passwords. The requestAvatarId method will be called when a user tries to log in. requestAvatarId takes a credentials object that implements one of the interfaces listed in credentialsInterfaces, which in this case is limited to credentials.IUsernamePassword. If PasswordDictChecker was capable of accepting credentials other then a plain-text username and password, it would advertise this by including additional interfaces in the credentialsInterfaces list.

If the username and password supplied by the user matches a username and password in the database, requestAvatarId returns a Deferred that will be called back with a value identifying the user, who at that point has been successfully authenticated. Otherwise, it returns a Deferred that fails with a twisted.cred.error.UnauthorizedLogin exception.

The second half of the authentication backend in Example 6-1 is TestRealm, which implements twisted.cred.portal.IRealm. This class does the work of taking the avatar ID of an authenticated user and returning an avatar capable of doing certain tasks on behalf of that user.

TestRealm.requestAvatar takes three or more arguments: avatarID, mind, and one or more interfaces. The first argument, avatarID, is a string identifying the user for whom an avatar should be returned. The second argument, mind, is supposed to represent the actual user on the other end of the connection. In practice, though, mind is used only by Perspective Broker servers. For other applications, you can feel free to ignore it. The interfaces list passed to requestAvatar tell the realm what interface the avatar needs to support. (If there is more than one interface argument, it means that the application calling requestAvatar is capable of working with multiple interfaces. See Example 6-3.) It returns a tuple containing three values: the interface that the avatar supports, the avatar itself, and a logout function that can be called to shut down the avatar when the user is done with the session.

The TestRealm class in Example 6-1 provides access to a single type of avatar, NamedUserAvatar, which implements the interface INamedUser. NamedUserAvatar doesn't do a whole lot: it provides two attributes, username and fullname, that can be used to get some basic information about the user.

TestRealm.requestAvatar first checks to make sure that at least one of the interfaces requested by the client is INamedUser, the only interface which it supports. Then it looks up the full name of the user in its list of users, and uses the full name to construct a NamedUserAvatar object. Then it returns a tuple containing three values: the matching avatar interface (in this case always INamedUser), the avatar object itself, and a logout function. As the server in Example 6-1 isn't even keeping track of the current avatars, it doesn't have take any action when a user is logged out. So it uses an anonymous logout function that doesn't do anything.

A Portal object combines a realm with credentials checkers to form a complete authentication system. When you initialize a Portal, pass it an object that implements IRealm. Then call registerChecker with an object that implements credentials.ICredentialsChecker. You can call registerChecker multiple times if you have separate credentials checkers for handling different credentials interfaces.

Portal provides a single method, login, that encapsulates the whole authentication process. Portal.login takes a set of credentials as the first argument, a mind object as the second argument (which is rarely used, and usually just the value None), and one or more interfaces as the third and following arguments. It looks through its available set of ICredentialsChecker objects (which were added with registerChecker) and finds one that can handle the provided type of credentials. Then it calls the checker's requestAvatarId method to authenticate the user. Next, the Portal takes the avatar ID returned by the credentials checker and the list of interfaces passed to login and uses them to calls its realm's requestAvatar method. The resulting tuple of avatar interface, avatar, and logout function gets called back as the result of login.

When you connect to the server in Example 6-1, LoginTestProtocol asks for a username and password. When these are provided, it wraps them in a credentials.UsernamePassword object and passes it to self.factory.portal.login along with None (a placeholder for the mind argument) and INamedUserAvatar, which specifies the kind of avatar LoginTestProtocol wants to get back. If login is successful, it will call back with a NamedUserAvatar object; otherwise, it will errback with an error describing why the login failed.





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

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