On Sat, 19 Nov 2005 23:00:31 +0000, Phil Mayers <[EMAIL PROTECTED]> wrote:
David Reid wrote:
But in Basic and Digest auth you don't have the username until you get
the response to your challenge. So this is where IAuthorizer comes in
it handles all the steps prior to having something that you can use to
build a credentials.
I'm no cred expert, and I dislike it conceptually, but as far as I can tell
it's got all the facilities it needs. HTTP is slightly more complex so I'll
start with a SASL-ised imap-like example, to see if I've got the right idea:
[snip - cool example protocol with cred integration]
HTTP is a bit of a pain because of the "connectionless" basis. The RFCs for
the hacky mechanisms like "Negotiate" (the MS-ism for kerberos over HTTP)
and such show that. Digest would want some kind of stateless version -
you're effectively authenticating requests as opposed to the connection, but
the basic principle is the same. I'm sure you know all this.
So am I missing something? It looks like cred needs no extending?
Nope. You're dead on. Cred can do everything necessary to handle Digest auth
as-is. Digest auth isn't even the most complex scheme it supports.
I wrote the attached quick example of authentication that involves repeated
challenges and per-user private authentication-required state. It is mainly
the same as your example, with the addition of support for a kind of credential
that requires cooperation from both the authentication database and the
protocol.
Jean-Paul
import random
from zope.interface import implements, Interface, Attribute
from Crypto.PublicKey import RSA
from twisted.protocols import basic
from twisted.internet import defer, stdio, reactor
from twisted.cred import portal
class IDemonstration(Interface):
name = Attribute("The name of this avatar")
class DemonstrationAvatar(object):
def __init__(self, name):
self.name = name
class DemonstrationRealm(object):
def requestAvatar(self, avatarId, mind, *interfaces):
for iface in interfaces:
if iface is IDemonstration:
return IDemonstration, DemonstrationAvatar(avatarId), reactor.stop
raise NotImplementedError()
class IDemonstrationCredentials(Interface):
def getUsername():
pass
def setUsername():
pass
def nextChallenge():
pass
def setResponse():
pass
def verifyChallengeResponse():
pass
class DemonstrationChecker(object):
credentialInterfaces = (IDemonstrationCredentials,)
def __init__(self, secretDatabase):
self.keys = secretDatabase
def requestAvatarId(self, creds):
d = creds.getUsername()
d.addCallback(self.gotUsername, creds)
return d
def gotUsername(self, username, creds):
creds.setPrivateKey(self.keys[username])
d = creds.verifyChallengeResponse()
d.addCallback(self.verified, username)
return d
def verified(self, result, username):
if result:
return username
raise ecred.UnauthorizedLogin()
class DemonstrationCredentials(object):
implements(IDemonstrationCredentials)
waitingForUsername = None
waitingForPrivateKey = None
waitingForAuthentication = None
username = None
privateKey = None
authenticated = False
def __init__(self, phases):
self.phases = phases
self.challengesLeft = phases
self.challenges = []
self.responses = []
def getUsername(self):
if self.username is not None:
return defer.succeed(self.username)
self.waitingForUsername = defer.Deferred()
return self.waitingForUsername
def nextChallenge(self):
if self.username is None:
return defer.succeed("USERNAME")
if self.privateKey is None:
self.waitingForPrivateKey = defer.Deferred()
self.waitingForPrivateKey.addCallback(lambda ign: self.nextChallenge())
return self.waitingForPrivateKey
if self.challengesLeft:
self.challengesLeft -= 1
self.challenges.append(str(random.random()))
return defer.succeed(self.privateKey.encrypt(self.challenges[-1], random.randrange(2 ** 32)))
return defer.succeed(None)
def setPrivateKey(self, pkey):
self.privateKey = pkey
if self.waitingForPrivateKey is not None:
d, self.waitingForPrivateKey = self.waitingForPrivateKey, None
d.callback(None)
def setResponse(self, resp):
if self.username is None:
self.username = resp
if self.waitingForUsername is not None:
d, self.waitingForUsername = self.waitingForUsername, None
d.callback(resp)
else:
self.responses.append(resp)
if len(self.responses) == self.phases:
self.checkResponse()
def checkResponse(self):
if self.responses == self.challenges:
self.correct = True
else:
self.correct = False
self.authenticated = True
if self.waitingForAuthentication is not None:
d, self.waitingForAuthentication = self.waitingForAuthentication, None
d.callback(self.correct)
def verifyChallengeResponse(self):
if self.authenticated:
return defer.succeed(self.correct)
self.waitingForAuthentication = defer.Deferred()
return self.waitingForAuthentication
class ComplexAuthenticationProtocol(basic.LineReceiver):
from os import linesep as delimiter
state = None
def __init__(self, portal):
self.portal = portal
def connectionMade(self):
creds = DemonstrationCredentials(3)
self.creds = creds
self.state = 'authing'
d = self.creds.nextChallenge()
d.addCallback(self.sendChallenge)
self.portal.login(creds, None, IDemonstration).addCallbacks(self.loggedIn, self.loginFailed)
def lineReceived(self, line):
getattr(self, 'state_' + self.state)(line)
def loggedIn(self, (i, a, l)):
self.avatar = a
self.logout = l
self.state = 'authed'
self.transport.write('GREAT JOB: ')
def loginFailed(self, err):
self.sendLine('ACCESS DENIED')
self.transport.loseConnection()
reactor.callLater(0, reactor.stop)
def state_authed(self, line):
self.sendLine(self.avatar.name + '> ' + line)
def connectionLost(self, reason):
if self.state == 'authed':
self.avatar = None
self.logout()
def state_authing(self, line):
self.creds.setResponse(line)
d = self.creds.nextChallenge()
d.addCallback(self.sendChallenge)
def sendChallenge(self, challenge):
if challenge is None:
# Cycle is complete, wait for portal.login()
# Deferred to fire.
self.transport.write('AUTHENTICATING...')
return
self.transport.write("AUTH %r: " % (challenge,))
def main():
r = DemonstrationRealm()
c = DemonstrationChecker({
"exarkun": RSA.construct((
161859819151819697098254488452824564010217320876817821541898950013341404364938401540928478590407075283248536244195214627226564129026378628713469358115556366932798572246155146009693612210610619147184316601341319915404987601523926979999190949843548551482325311823684520186363883408904082828455778237782407401107L,
65537L))})
p = portal.Portal(r, [c])
stdio.StandardIO(ComplexAuthenticationProtocol(p))
reactor.run()
if __name__ == '__main__':
main()
_______________________________________________
Twisted-web mailing list
[email protected]
http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-web