cred.xhtml   [plain text]


<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>Cred: Pluggable Authentication</title>
  </head>
  <body>
    <h1>Cred: Pluggable Authentication</h1>

    <h2>Goals</h2>

<p>Cred is a pluggable authentication system for servers.  It allows any
number of network protocols to connect and authenticate to a system, and
communicate to those aspects of the system which are meaningful to the specific
protocol.  For example, Twisted's POP3 support passes a <q>username and
password</q> set of credentials to get back a mailbox for the specified email
account.  IMAP does the same, but retrieves a slightly different view of the
same mailbox, enabling those features specific to IMAP which are not available
in other mail protocols.</p>

<p>Cred is designed to allow both the backend implementation of the business
logic - called the <em>avatar</em> - and the authentication database - called
the <em>credential checker</em> - to be decided during deployment. For example,
the same POP3 server should be able to authenticate against the local UNIX
password database or an LDAP server without having to know anything about how
or where mail is stored.  </p>

<p>To sketch out how this works - a <q>Realm</q> corresponds to an application
domain and is in charge of avatars, which are network-accessible business logic
objects.  To connect this to an authentication database, a top-level object
called a <code class="API" base="twisted.cred.portal">Portal</code> stores a
realm, and a number of credential checkers.  Something that wishes to log in,
such as a <code class="API" base="twisted.internet.protocol">Protocol</code>,
stores a reference to the portal. Login consists of passing credentials and a
request interface (e.g. POP3's <code class="API"
base="twisted.protocols.pop3">IMailbox</code>) to the portal. The portal passes
the credentials to the appropriate credential checker, which returns an avatar
ID. The ID is passed to the realm, which returns the appropriate avatar.  For a
Portal that has a realm that creates mailbox objects and a credential checker
that checks /etc/passwd, login consists of passing in a username/password and
the IMailbox interface to the portal. The portal passes this to the /etc/passwd
credential checker, gets back a avatar ID corresponding to an email account,
passes that to the realm and gets back a mailbox object for that email
account.</p>

    <h2>Cred objects</h2>
    <h3>The Portal</h3>
<p>This is the the core of login, the point of integration between all the objects
in the cred system.  There is one 
concrete implementation of Portal, and no interface - it does a very 
simple task.  A <code class="API" base="twisted.cred.portal">Portal</code>
associates one (1) Realm with a collection of 
CredentialChecker instances.  (More on those later.)</p>

<p>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 -</p>

<ul>
<li><code class="API" base="twisted.cred.portal.Portal">login</code><code>(credentials, mind, *interfaces)</code>

<p>The docstring is quite expansive (see <code class="API">twisted.cred.portal</code>), but in 
brief, this is what you call when you need to call in order to connect 
a user to the system.  Typically you only pass in one interface, and the mind is 
<code class="python">None</code>. The interfaces are the possible interfaces the returned
avatar is expected to implement, in order of preference. 
The result is a deferred which fires a tuple of:</p>
    <ul>
	<li>interface the avatar implements (which was one of the interfaces passed in the *interfaces 
tuple)</li>
	<li>an object that implements that interface (an avatar)</li>
    <li>logout, a 0-argument callable which disconnects the connection that was 
established by this call to login</li>
    </ul>
<p>The logout method has to be called when the avatar is logged out. For POP3 this means
when the protocol is disconnected or logged out, etc..</p>
</li>
<li><code class="API" base="twisted.cred.portal.Portal">registerChecker</code><code>(checker, *credentialInterfaces)</code>

<p>which adds a CredentialChecker to the portal. The optional list of interfaces are interfaces of credentials
that the checker is able to check.</p>
</li></ul>

    <h3>The CredentialChecker</h3>
<p>This is an object implementing <code class="API"
base="twisted.cred.checkers">ICredentialChecker</code> which resolves some
Credentials to an avatar ID.  
Some examples of CredentialChecker implementations would be: 
InMemoryUsernamePassword, ApacheStyleHTAccessFile, 
UNIXPasswordDatabase, SSHPublicKeyDatabase.  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.</p>

<p>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.</p>

<p>A credential checker should raise an error if it cannot authenticate 
the user, and return <code class="API">twisted.cred.checkers.ANONYMOUS</code>
for anonymous access.</p>

    <h3>The Credentials</h3>
<p>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.</p>

<p>Twisted comes with a number of credentials interfaces and implementations
in the <code class="API">twisted.cred.credentials</code> module,
such as <code class="API" base="twisted.cred.credentials">IUsernamePassword</code>
and <code class="API" base="twisted.cred.credentials">IUsernameHashedPassword</code>.</p>

    <h3>The Realm</h3>
<p>A realm is an interface which connects your universe of <q>business 
objects</q> to the authentication system.</p>

<p><code class="API" base="twisted.cred.portal">IRealm</code> is another one-method interface:</p>

<ul>
<li><code class="API" base="twisted.cred.portal.IRealm">requestAvatar</code><code>(avatarId, mind, *interfaces)</code>

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

<div class="note">Note that <code>avatarId</code> must always be a string. In
particular, do not use unicode strings. If internationalized support is needed,
it is recommended to use UTF-8, and take care of decoding in the realm.  </div>

<p>The important thing to realize about this method is that if it is being 
called, <em>the user has already authenticated</em>.  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.</p>
</li>
</ul>

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

    <h3>The Avatar</h3>

<p>An avatar is a business logic object for a specific user. For POP3, it's
a mailbox, for a first-person-shooter it's the object that interacts with
the game, the actor as it were. Avatars are specific to an application,
and each avatar represents a single <q>user</q>.</p>

    <h3>The Mind</h3>

<p>As mentioned before, the mind is usually None, so you can skip this
bit if you want.</p>

<p>Masters of Perspective Broker already know this object as the ill-named
<q>client object</q>.  There is no <q>mind</q> 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'. In addition, it
allows passing more information to the realm during login in addition to the
avatar ID.</p>

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

<p>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.</p>

<p>Unlike the original Perspective Broker <q>client object</q>, 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.</p>

<p>(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.)</p>

    <h2>Responsibilities</h2>

    <h3>Server protocol implementation</h3>

<p>The protocol implementor should define the interface the avatar should implement,
and design the protocol to have a portal attached. When a user logs in using the
protocol, a credential object is created, passed to the portal, and an avatar
with the appropriate interface is requested. When the user logs out or the protocol
is disconnected, the avatar should be logged out.</p>

<p>The protocol designer should not hardcode how users are authenticated or the
realm implemented. For example, a POP3 protocol implementation would require a portal whose
realm returns avatars implementing IMailbox and whose credential checker accepts
username/password credentials, but that is all. Here's a sketch of how the code
might look - note that USER and PASS are the protocol commands used to login, and
the DELE command can only be used after you are logged in:</p>

<pre class="python">
from twisted.protocols import basic
from twisted.python import log, components
from twisted.cred import credentials, error
from twisted.internet import defer

class IMailbox(components.Interface):
    """Interface specification for mailbox."""
    def deleteMessage(self, index): pass


class POP3(basic.LineReceiver):
    # ...
    def __init__(self, portal):
        self.portal = portal

    def do_DELE(self, i):
        # uses self.mbox, which is set after login
        i = int(i)-1
        self.mbox.deleteMessage(i)
        self.successResponse()

    def do_USER(self, user):
        self._userIs = user
        self.successResponse('USER accepted, send PASS')

    def do_PASS(self, password):
        if self._userIs is None:
            self.failResponse("USER required before PASS")
            return
        user = self._userIs
        self._userIs = None
        d = defer.maybeDeferred(self.authenticateUserPASS, user, password)
        d.addCallback(self._cbMailbox, user)

    def authenticateUserPASS(self, user, password):
        if self.portal is not None:
            return self.portal.login(
                cred.credentials.UsernamePassword(user, password),
                None,
                IMailbox
            )
        raise error.UnauthorizedLogin()

    def _cbMailbox(self, ial, user):
        interface, avatar, logout = ial

        if interface is not IMailbox:
            self.failResponse('Authentication failed')
            log.err("_cbMailbox() called with an interface other than IMailbox")
            return

        self.mbox = avatar
        self._onLogout = logout
        self.successResponse('Authentication succeeded')
        log.msg("Authenticated login for " + user)
</pre>

   <h3>Application implementation</h3>

<p>The application developer can implement realms and credential checkers. For example,
she might implement a realm that returns IMailbox implementing avatars, using MySQL
for storage, or perhaps a credential checker that uses LDAP for authentication.
In the following example, the Realm for a simple remote object service (using
Twisted's Perspective Broker protocol) is implemented:</p>

<pre class="python">
from twisted.spread import pb
from twisted.cred.portal import IRealm

class SimplePerspective(pb.Avatar):

    def perspective_echo(self, text):
        print 'echoing',text
        return text

    def logout(self):
        print self, "logged out"


class SimpleRealm:
    __implements__ = IRealm

    def requestAvatar(self, avatarId, mind, *interfaces):
        if pb.IPerspective in interfaces:
            avatar = SimplePerspective()
            return pb.IPerspective, avatar, avatar.logout 
        else:
            raise NotImplementedError("no interface")
</pre>

   <h3>Deployment</h3>

<p>Deployment involves tying together a protocol, an appropriate realm and a credential
checker. For example, a POP3 server can be constructed by attaching to it a portal
that wraps the MySQL-based realm and an /etc/passwd credential checker, or perhaps
the LDAP credential checker if that is more useful. The following example shows
how the SimpleRealm in the previous example is deployed using an in-memory credential checker:</p>

<pre class="python">
from twisted.spread import pb
from twisted.internet import reactor
from twisted.cred.portal import Portal
from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse

portal = Portal(SimpleRealm())
checker = InMemoryUsernamePasswordDatabaseDontUse()
checker.addUser("guest", "password")
portal.registerChecker(checker)
reactor.listenTCP(9986, pb.PBServerFactory(portal))
reactor.run()
</pre>

</body></html>