Coding standards and documentation improvements in auth_nb.py

This commit is contained in:
Stephan Erb 2008-12-26 15:39:18 +00:00
parent 7427399a2a
commit de73b76771
1 changed files with 162 additions and 102 deletions

View File

@ -14,10 +14,13 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
'''
Provides library with all Non-SASL and SASL authentication mechanisms.
Provides plugs for SASL and NON-SASL authentication mechanisms.
Can be used both for client and transport authentication.
See client_nb.py
'''
from protocol import *
from protocol import NS_SASL, NS_SESSION, NS_STREAMS, NS_BIND, NS_AUTH
from protocol import Node, NodeProcessed, isResultNode, Iq, Protocol, JID
from client import PlugIn
import sha
import base64
@ -41,14 +44,20 @@ except ImportError:
GSS_STATE_STEP = 0
GSS_STATE_WRAP = 1
SASL_FAILURE = 'failure'
SASL_SUCCESS = 'success'
SASL_UNSUPPORTED = 'not-supported'
SASL_IN_PROCESS = 'in-process'
def challenge_splitter(data):
''' Helper function that creates a dict from challenge string.
Sample chalenge string:
Sample challenge string:
username="example.org",realm="somerealm",\
nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",\
nc=00000001,qop="auth,auth-int,auth-conf",charset=utf-8
in the above example:
Expected result for challan:
dict['qop'] = ('auth','auth-int','auth-conf')
dict['realm'] = 'somerealm'
'''
@ -102,43 +111,64 @@ def challenge_splitter(data):
quotes_open = False
return dict_
class SASL(PlugIn):
''' Implements SASL authentication. '''
'''
Implements SASL authentication. Can be plugged into NonBlockingClient
to start authentication.
'''
def __init__(self, username, password, on_sasl):
'''
:param user: XMPP username
:param password: XMPP password
:param on_sasl: Callback, will be called after each SASL auth-step.
'''
PlugIn.__init__(self)
self.username = username
self.password = password
self.on_sasl = on_sasl
self.realm = None
def plugin(self, owner):
if 'version' not in self._owner.Dispatcher.Stream._document_attrs:
self.startsasl='not-supported'
self.startsasl = SASL_UNSUPPORTED
elif self._owner.Dispatcher.Stream.features:
try:
self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
self.FeaturesHandler(self._owner.Dispatcher,
self._owner.Dispatcher.Stream.features)
except NodeProcessed:
pass
else: self.startsasl=None
else:
self.startsasl = None
def plugout(self):
''' Remove SASL handlers from owner's dispatcher. Used internally. '''
self._owner.UnregisterHandler('features', self.FeaturesHandler,
xmlns=NS_STREAMS)
self._owner.UnregisterHandler('challenge', self.SASLHandler,
xmlns=NS_SASL)
self._owner.UnregisterHandler('failure', self.SASLHandler, xmlns=NS_SASL)
self._owner.UnregisterHandler('success', self.SASLHandler, xmlns=NS_SASL)
def auth(self):
''' Start authentication. Result can be obtained via "SASL.startsasl" attribute and will be
either "success" or "failure". Note that successfull auth will take at least
two Dispatcher.Process() calls. '''
'''
Start authentication. Result can be obtained via "SASL.startsasl"
attribute and will be either SASL_SUCCESS or SASL_FAILURE.
Note that successfull auth will take at least two Dispatcher.Process()
calls.
'''
if self.startsasl:
pass
elif self._owner.Dispatcher.Stream.features:
try:
self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
self.FeaturesHandler(self._owner.Dispatcher,
self._owner.Dispatcher.Stream.features)
except NodeProcessed:
pass
else: self._owner.RegisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS)
def plugout(self):
''' Remove SASL handlers from owner's dispatcher. Used internally. '''
self._owner.UnregisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS)
self._owner.UnregisterHandler('challenge', self.SASLHandler, xmlns=NS_SASL)
self._owner.UnregisterHandler('failure', self.SASLHandler, xmlns=NS_SASL)
self._owner.UnregisterHandler('success', self.SASLHandler, xmlns=NS_SASL)
else:
self._owner.RegisterHandler('features',
self.FeaturesHandler, xmlns=NS_STREAMS)
def FeaturesHandler(self, conn, feats):
''' Used to determine if server supports SASL auth. Used internally. '''
@ -147,8 +177,10 @@ class SASL(PlugIn):
log.error('SASL not supported by server')
return
self.mecs = []
for mec in feats.getTag('mechanisms', namespace=NS_SASL).getTags('mechanism'):
for mec in feats.getTag('mechanisms', namespace=NS_SASL).getTags(
'mechanism'):
self.mecs.append(mec.getData())
self._owner.RegisterHandler('challenge', self.SASLHandler, xmlns=NS_SASL)
self._owner.RegisterHandler('failure', self.SASLHandler, xmlns=NS_SASL)
self._owner.RegisterHandler('success', self.SASLHandler, xmlns=NS_SASL)
@ -177,19 +209,20 @@ class SASL(PlugIn):
payload=[base64.encodestring(sasl_data).replace('\n','')])
self.mechanism = 'PLAIN'
else:
self.startsasl='failure'
self.startsasl = SASL_FAILURE
log.error('I can only use DIGEST-MD5, GSSAPI and PLAIN mecanisms.')
return
self.startsasl='in-process'
self._owner.send(node.__str__())
self.startsasl = SASL_IN_PROCESS
self._owner.send(str(node))
raise NodeProcessed
def SASLHandler(self, conn, challenge):
''' Perform next SASL auth step. Used internally. '''
if challenge.getNamespace() != NS_SASL:
return
### Handle Auth result
if challenge.getName() == 'failure':
self.startsasl = 'failure'
self.startsasl = SASL_FAILURE
try:
reason = challenge.getChildren()[0]
except Exception:
@ -203,26 +236,30 @@ class SASL(PlugIn):
self.on_sasl()
raise NodeProcessed
elif challenge.getName() == 'success':
self.startsasl='success'
self.startsasl = SASL_SUCCESS
log.info('Successfully authenticated with remote server.')
handlers = self._owner.Dispatcher.dumpHandlers()
# Bosh specific dispatcher replugging
# save old features. They will be used in case we won't get response on
# stream restart after SASL auth (happens with XMPP over BOSH with Openfire)
# stream restart after SASL auth (happens with XMPP over BOSH with
# Openfire)
old_features = self._owner.Dispatcher.Stream.features
self._owner.Dispatcher.PlugOut()
dispatcher_nb.Dispatcher().PlugIn(self._owner, after_SASL=True,
old_features=old_features)
self._owner.Dispatcher.restoreHandlers(handlers)
self._owner.User = self.username
if self.on_sasl:
self.on_sasl()
raise NodeProcessed
########################################3333
### Perform auth step
incoming_data = challenge.getData()
data=base64.decodestring(incoming_data)
log.info('Got challenge:' + data)
if self.mechanism == 'GSSAPI':
if self.gss_step == GSS_STATE_STEP:
rc = kerberos.authGSSClientStep(self.gss_vc, incoming_data)
@ -239,6 +276,8 @@ class SASL(PlugIn):
self._owner.send(Node('response', attrs={'xmlns':NS_SASL},
payload=response).__str__())
raise NodeProcessed
# magic foo...
chal = challenge_splitter(data)
if not self.realm and 'realm' in chal:
self.realm = chal['realm']
@ -265,27 +304,30 @@ class SASL(PlugIn):
resp['response'] = response
resp['charset'] = 'utf-8'
sasl_data = ''
for key in ('charset', 'username', 'realm', 'nonce', 'nc', 'cnonce', 'digest-uri', 'response', 'qop'):
if key in ['nc','qop','response','charset']:
for key in ('charset', 'username', 'realm', 'nonce', 'nc', 'cnonce',
'digest-uri', 'response', 'qop'):
if key in ('nc','qop','response','charset'):
sasl_data += "%s=%s," % (key, resp[key])
else:
sasl_data += '%s="%s",' % (key, resp[key])
########################################3333
node = Node('response', attrs={'xmlns':NS_SASL},
payload=[base64.encodestring(sasl_data[:-1]).replace('\r','').replace('\n','')])
self._owner.send(node.__str__())
payload=[base64.encodestring(sasl_data[:-1]).replace('\r','').
replace('\n','')])
self._owner.send(str(node))
elif 'rspauth' in chal:
self._owner.send(Node('response', attrs={'xmlns':NS_SASL}).__str__())
self._owner.send(str(Node('response', attrs={'xmlns':NS_SASL})))
else:
self.startsasl='failure'
self.startsasl = SASL_FAILURE
log.error('Failed SASL authentification: unknown challenge')
if self.on_sasl:
self.on_sasl()
raise NodeProcessed
class NonBlockingNonSASL(PlugIn):
''' Implements old Non-SASL (JEP-0078) authentication used
in jabberd1.4 and transport authentication.
'''
Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and
transport authentication.
'''
def __init__(self, user, password, resource, on_auth):
''' Caches username, password and resource for auth. '''
@ -296,19 +338,22 @@ class NonBlockingNonSASL(PlugIn):
self.on_auth = on_auth
def plugin(self, owner):
''' Determine the best auth method (digest/0k/plain) and use it for auth.
Returns used method name on success. Used internally. '''
'''
Determine the best auth method (digest/0k/plain) and use it for auth.
Returns used method name on success. Used internally.
'''
log.info('Querying server about possible auth methods')
self.owner = owner
owner.Dispatcher.SendAndWaitForResponse(
Iq('get', NS_AUTH, payload=[Node('username', payload=[self.user])]), func=self._on_username
)
Iq('get', NS_AUTH, payload=[Node('username', payload=[self.user])]),
func=self._on_username)
def _on_username(self, resp):
if not isResultNode(resp):
log.error('No result node arrived! Aborting...')
return self.on_auth(None)
iq=Iq(typ='set',node=resp)
query = iq.getTag('query')
query.setTagData('username',self.user)
@ -317,7 +362,8 @@ class NonBlockingNonSASL(PlugIn):
if query.getTag('digest'):
log.info("Performing digest authentication")
query.setTagData('digest',
sha.new(self.owner.Dispatcher.Stream._document_attrs['id']+self.password).hexdigest())
sha.new(self.owner.Dispatcher.Stream._document_attrs['id'] +
self.password).hexdigest())
if query.getTag('password'):
query.delChild('password')
self._method = 'digest'
@ -336,24 +382,29 @@ class NonBlockingNonSASL(PlugIn):
query.setTagData('hash', hash_)
self._method='0k'
else:
log.warn("Sequre methods unsupported, performing plain text authentication")
log.warn("Sequre methods unsupported, performing plain text \
authentication")
query.setTagData('password', self.password)
self._method = 'plain'
resp = self.owner.Dispatcher.SendAndWaitForResponse(iq,func=self._on_auth)
def _on_auth(self, resp):
if isResultNode(resp):
log.info('Sucessfully authenticated with remove host.')
log.info('Sucessfully authenticated with remote host.')
self.owner.User = self.user
self.owner.Resource = self.resource
self.owner._registered_name=self.owner.User+'@'+self.owner.Server+'/'+self.owner.Resource
self.owner._registered_name = self.owner.User+'@'+self.owner.Server+\
'/'+self.owner.Resource
return self.on_auth(self._method)
log.error('Authentication failed!')
return self.on_auth(None)
class NonBlockingBind(PlugIn):
''' Bind some JID to the current connection to allow router know of our location.'''
'''
Bind some JID to the current connection to allow router know of our
location. Must be plugged after successful SASL auth.
'''
def __init__(self):
PlugIn.__init__(self)
self.bound = None
@ -362,14 +413,19 @@ class NonBlockingBind(PlugIn):
''' Start resource binding, if allowed at this time. Used internally. '''
if self._owner.Dispatcher.Stream.features:
try:
self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
self.FeaturesHandler(self._owner.Dispatcher,
self._owner.Dispatcher.Stream.features)
except NodeProcessed:
pass
else:
self._owner.RegisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS)
self._owner.RegisterHandler('features', self.FeaturesHandler,
xmlns=NS_STREAMS)
def FeaturesHandler(self, conn, feats):
''' Determine if server supports resource binding and set some internal attributes accordingly. '''
'''
Determine if server supports resource binding and set some internal
attributes accordingly.
'''
if not feats.getTag('bind', namespace=NS_BIND):
log.error('Server does not requested binding.')
# we try to bind resource anyway
@ -384,10 +440,13 @@ class NonBlockingBind(PlugIn):
def plugout(self):
''' Remove Bind handler from owner's dispatcher. Used internally. '''
self._owner.UnregisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS)
self._owner.UnregisterHandler('features', self.FeaturesHandler,
xmlns=NS_STREAMS)
def NonBlockingBind(self, resource=None, on_bound=None):
''' Perform binding. Use provided resource name or random (if not provided). '''
''' Perform binding.
Use provided resource name or random (if not provided).
'''
self.on_bound = on_bound
self._resource = resource
if self._resource:
@ -397,9 +456,9 @@ class NonBlockingBind(PlugIn):
self._owner.onreceive(None)
self._owner.Dispatcher.SendAndWaitForResponse(
Protocol('iq',typ='set',
payload=[Node('bind', attrs={'xmlns':NS_BIND}, payload=self._resource)]),
func=self._on_bound)
Protocol('iq',typ='set', payload=[Node('bind', attrs={'xmlns':NS_BIND},
payload=self._resource)]), func=self._on_bound)
def _on_bound(self, resp):
if isResultNode(resp):
self.bound.append(resp.getTag('bind').getTagData('jid'))
@ -408,7 +467,8 @@ class NonBlockingBind(PlugIn):
self._owner.User = jid.getNode()
self._owner.Resource = jid.getResource()
self._owner.SendAndWaitForResponse(Protocol('iq', typ='set',
payload=[Node('session', attrs={'xmlns':NS_SESSION})]), func=self._on_session)
payload=[Node('session', attrs={'xmlns':NS_SESSION})]),
func=self._on_session)
elif resp:
log.error('Binding failed: %s.' % resp.getTag('error'))
self.on_bound(None)