One of the things provided by the Perspective Broker is an authentication framework, based around the concept of an avatar. We're going to create a Perspective Broker authenticator that is more complex than what is presented in the Twisted Matrix documentation.
In one of the projects I worked on, we needed to have a central authentication server, to which all the other object brokers would authenticate against. Due to the way that Twisted operates, a user needs to authenticate against the broker that they are currently connected to. We need to build a authenticator that can authenticate for itself, or if needed, recursively authenticate against another server.
To achieve this goal, we run into the first hurdle, Twisted doesn't pass a meaningful password around. This changes the order of authentication slightly then, as we now need to authenticate against the main authenticating server first. That server then gives us a cookie, known as authentication_cookie in the code below. This cookie is good for this login session with the main authenticator. When connecting and authenticating against any other broker in the system, we pass the username and this cookie as our credentials.
class UsernamePasswordDatabaseChecker:
interface.implements(checkers.ICredentialsChecker)
credentialInterfaces = (credentials.IUsernamePassword,
credentials.IUsernameHashedPassword)
def __init__( self, login_service ):
self.login_service = login_service
def passcheck( self, account, credentials ):
if not account:
return failure.Failure( NoSuchAccount( ) )
c1 = credentials.checkPassword( account.password )
c2 = credentials.checkPassword( account.authentication_cookie )
if c1:
return login_server.MasterAuthenticationStatus( account.username,
account, 1 )
elif c2:
return login_server.MasterAuthenticationStatus( account.username,
account, 0 )
elif not c1 and not c2 and account.authentication_cookie:
return failure.Failure( AuthenticationCookieMismatch( ) )
elif not c1 and not c2 and not account.authentication_cookie:
return failure.Failure( InvalidPassword( ) )
return failure.Failure( NoSuchAccount( ) )
@reactor.deferred_tasklet
def store_auth_cookie( self, auth_status ):
bcr = reactor.blocking_tasklet( self.login_service.callRemote )
return bcr( "store_authentication_cookie", auth_status )
@reactor.deferred_tasklet
def requestAvatarId(self, credentials ):
if hasattr( self.login_service, "callRemote" ):
bcr = reactor.blocking_tasklet( self.login_server.callRemote )
acct = bcr( "get_account", credentials.username )
self.passcheck( acct, credentials )
status = bcr( "account_status", acct )
rv = self.store_auth_cookie( status )
return rv
else:
rv = self.login_service.authenticate_account( credentials )
return rv
We use one of the function decorators from when we first started integrating Twisted into Stackless python, the deferred_tasklet. If you haven't read that article, the deferred_tasklet decorator wraps the function to run in a seperate Stackless tasklet, while still returning a Deferred that can be used by the rest of the Twisted machinery.
The referenced member login_service is a straight-forward object that implements the function authenticate_account. If the credentials it is passed are good, it returns an object -- this object is the avatar required by the Perspective Broker. If the credentials are bad, it returns None, which then gets translated into an appropriate remote exception.
The core ingredient in this is whether or not UsernamePasswordDatabaseChecker's member login_service has a member method callRemote. In this scenario, we make remote calls to do the authentication. We use another one of function decorators, blocking_tasklet, to do this so that our code can remain synchronous in style.
All in all, using Stackless tasklets to implement this setup results in less lines of code, and code that is straight-forward and easy to understand. The purely Twisted incarnation of this setup result in a crazy amount of nested callbacks that were very difficult to follow during initial testing, let alone a few months later.
The examples for login_service can be provided, if anyone wants, I just need to dig through the CVS repository to find them.
No comments:
Post a Comment