[Twisted-Python] Lies, Damn Lies, and Stable Interfaces

Glyph Lefkowitz glyph at twistedmatrix.com
Mon Jun 16 04:28:46 MDT 2003


There are some new modules in CVS right now, and I would like some 
feedback on them.

The modules in question are a sketch of The Wave of the Future of 
authentication in Twisted, A.K.A. "New Cred".  A good place to start 
looking is sandbox/webhappyrealm.py

Unfortunately, this sets a bad precedent, since old cred was 
"semi-stable".  However, I think that the break with declared stability 
is a small price to pay for the _VASTLY_ improved interfaces which this 
package provides.  Since not too many people were using the old cred 
deeply, and the new interfaces are much smaller, hopefully we can 
migrate to the new code as quickly as possible.

Of course, no new Twisted functionality would be complete without a 
byzantine new set of terms.  In this case, the terms are hopefully more 
descriptive and more memorable; as a bonus, they do not conflict with 
the old names, so we can keep both versions of cred on-line until such 
time as the deprecation warnings can be resolved.

This message is primarily about those new names.  I hope that this 
provides enough fodder for someone with more time to produce a 
nice-looking HOWTO.  Without further ado, here are the new words you 
need to integrate into your basal nervous system:


#############################

Portal
======

This is the ineffable mystery at the core of login.  There is one 
concrete implementation of Portal, and no interface - it does a very 
simple task.  A Portal associates one (1) Realm with a collection of 
CredentialChecker instances.  (More on those later.)

The closest analogue of this in Old Cred was the Authorizer.

If you are writing a protocol that needs to authenticate against 
something, you will need a reference to a Portal, and to nothing else.  
This has only 2 methods -

	login(credentials, mind, *interfaces)

The docstring is quite expansive (see twisted.cred.portal), but in 
brief, this is what you call when you need to call in order to connect 
a user to the system.  The result is a deferred which fires a tuple of:

	- interface (which was one of the interfaces passed in the *interfaces 
tuple)
	- an object that implements that interface (an Aspect of an Avatar)	- 
logout, a 0-argument callable which disconnects the connection that was 
established by this call to login

	registerChecker(checker, *credentialInterfaces)

which adds a CredentialChecker to the portal.

Mind
====

Masters of Perspective Broker already know this object as the ill-named 
"client object".  There is no "mind" class, or even interface, but it 
is an object which serves an important role - any notifications which 
are to be relayed to an authenticated client are passed through a 
'mind'.

The name may seem rather unusual, but considering that a Mind is 
representative of the entity on the "other end" of a network connection 
that is both receiving updates and issuing commands, I believe it is 
appropriate.

Although many protocols will not use this, it serves an important role. 
  It is provided as an argument both to the Portal and to the Realm, 
although a CredentialChecker should interact with a client program 
exclusively through a Credentials instance.

Unlike the original Perspective Broker "client object", a Mind's 
implementation is most often dictated by the protocol that is 
connecting rather than the Realm.  A Realm which requires a particular 
interface to issue notifications will need to wrap the Protocol's mind 
implementation with an adapter in order to get one that conforms to its 
expected interface - however, Perspective Broker will likely continue 
to use the model where the client object has a pre-specified remote 
interface.

(If you don't quite understand this, it's fine.  It's hard to explain, 
and it's not used in simple usages of cred, so feel free to pass None 
until you find yourself requiring something like this.)

CredentialChecker
=================

This is an object which resolves some Credentials to an avatar ID.  
Some examples of CredentialChecker implementations would be: 
InMemoryUsernamePassword, ApacheStyleHTAccessFile, 
UNIXPasswordDatabase, SSHPublicKeyChecker.  A credential checker 
stipulates some requirements of the credentials it can check by 
specifying a credentialInterfaces attribute, which is a list of 
interfaces.  Credentials passed to its requestAvatarId method must 
implement one of those interfaces.

For the most part, these things will just check usernames and passwords 
and produce the username as the result, but hopefully we will be seeing 
some public-key, challenge-response, and certificate based credential 
checker mechanisms soon.  (If somebody were to write an 
ActiveDirectory-compatible LDAP login, they would be my best friend for 
EVER.)

A credential checker should raise an error if it cannot authenticate 
the user, and return '' for anonymous access.

Credentials
===========

Oddly enough, this represents some credentials that the user presents.  
Usually this will just be a small static blob of data, but in some 
cases it will actually be an object connected to a network protocol.  
For example, a username/password pair is static, but a 
challenge/response server is an active state-machine that will require 
several method calls in order to determine a result.

Realm
=====

A realm is an interface which connects your universe of "business 
objects" to the authentication system.  This is similar to the Old Cred 
"Service", but the name "Service" will be phased out when referring to 
cred - another planned refactoring is to move 
twisted.internet.app.ApplicationService into its own module and more 
heavily emphasize its use in start-up and shut-down.

IRealm is another one-method interface:

	requestAvatar(avatarId, mind, *interfaces)

This method will typically be called from 'Portal.login'.  The avatarId 
is the one returned by a CredentialChecker.

The important thing to realize about this method is that if it is being 
called, _the user has already authenticated_.  Therefore, if possible, 
the Realm should create a new user if one does not already exist 
whenever possible.  Of course, sometimes this will be impossible 
without more information, and that is the case that the interfaces 
argument is for.

Some protocols can only accept a fixed set of interfaces.  However, 
others (most notably PB) can deal with more arbitrary data being 
returned.  An online game may call Portal.login with:

     login(PBChallengeResponse(...), clientSideThingy,
           IServerPlayerInWorld, IUserChooser, ICharacterCreationThing)

This specifies that the client would most prefer a player in the world, 
but, failing that, a UserChooser (selection interface for a second step 
to get a PlayerInWorld) or a CharacterCreationThing (interface to 
create a new character in the world before returning it) would be 
acceptable.

Since requestAvatar should be called from a Deferred callback, it may 
return a Deferred or a synchronous result.

At the moment, there is only an interface for the Realm.  However, it 
is expected that a utility class will be written in the near future to 
facilitate log-in methods, a re-directing method similar to 
"Perspective.attached" for Avatars, and at least somewhat automated 
compositing of Realms.  However, in writing the code for cred and 
guard, there has been no need for such a thing yet.

Avatar
======

This object has the dubious distinction of appearing nowhere in the 
code; in fact, very few things will ever touch an actual Avatar, and it 
is not clear that it will have an interface.  However, _aspects_ of an 
avatar are returned in deferreds from the above methods, which is to 
say, things that implement particular interfaces which communicate with 
various protocols that may access your Realm.

This is (hopefully, obviously) similar to a 'Perspective'.  However, an 
Avatar separates 2 concerns which were muddied previously - _access_ to 
a realm and _storage of user data_ within a realm.  The avatar itself 
stores the data, and the aspects interface to it.  The avatar itself 
may of course implement its own aspects, but it is suggested in most 
cases to register adapters that do this.  (Perspective Broker will keep 
its name, because the Avatar Aspect that communicates with the remote 
object protocol will still be called a Perspective.)

#############################


While it's understandable to want to wait for this interface to fully 
stabilize, we suggest that you start porting code to it _now_, 
especially if you're already tracking CVS.  The old cred was, through 
the continued efforts of many developers, experimentally determined to 
be a piece of crap.  I humbly suggest that this interface is not only 
not a piece of crap, but totally awesome, and actually usable in real 
applications.  Not only is it a vastly better design, it makes use of 
Deferreds in such a way that it is almost effortless to provide or 
manipulate an implementation of one of these interfaces.  (As a 
yardstick, the code for woven.guard was 30% shorter and the example was 
almost unmeasureably shorter - sandbox/webhappyrealm.py is already 
doing things that were impossible under the old model.)

I hope this explanation was helpful.  Please give feedback.





More information about the Twisted-Python mailing list