Hi.
I am trying to get through to Microsoft MapPoint Services using ZSI for
soap handling. I can generate the service classes and also the
soap-requests generated by the service classes seem to be OK. The
problem I am facing is that I can't seem to authenticate myself. I have
made a small change to ZSI.client so that when I get a "401
Unauthorized" response from the remote server I build up a nice
authorization request:
POST /Find-30/FindService.asmx HTTP/1.1
Host: findv3.staging.mappoint.net
Accept-Encoding: identity
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client
Protocol 1.1.4322.573)
SOAPAction: "http://s.mappoint.net/mappoint-30/FindAddress";
Authorization: Digest username="106288", realm="MapPoint",
nonce="91168da8e3a097f41264875211009a194b99a94ffe5bc619415820880a5b",
uri="/Find-30/FindService.asmx",
response="26aa9e36f9ff2a8308030810ab83dad1", qop=auth, nc=0001,
cnonce="623c12f33f0eb883"
Content-length: 0
Expect: 100-continue
The problem is that the server won't authorize me. I have a C# .net
program that does exactly the same I'm trying in python, and it is
authorized nicely:
POST /Find-30/FindService.asmx HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client
Protocol 1.1.4322.573)
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://s.mappoint.net/mappoint-30/FindAddress";
Authorization: Digest
username="106288",realm="MapPoint",nonce="487911f02ed2ef706326675211008a8ec39cfa4fb09304757c8dde417354",uri="/Find-30/FindService.asmx",cnonce="e1ed9880c5e3777a4ba280cec1c9e362",nc=0001,qop="auth",response="b4119a4db73814fd09ae5fec11fc9730"
Content-Length: 523
Expect: 100-continue
Host: findv3.staging.mappoint.net
So I guess the problem is in the Digest calculation. Unfortunately I
don't know much about the issue but I have managed to "steel" this from
urllib2 and changed it a bit to fit my usage (see below).
1. Can I do this another way?
2. Has anybody made a digest MD5 authenticator for ZSI?
3. Whats wrong with my calculation or is it the header??
4. Can I use urllib2 to test the authentication part of a Soap Service.
- My authentication calculator >
import md5
import sha
import re
import time
import random
import os.path
def randombytes(n):
"""Return n random bytes."""
# Use /dev/urandom if it is available. Fall back to random module
# if not. It might be worthwhile to extend this function to use
# other platform-specific mechanisms for getting random bytes.
if os.path.exists("/dev/urandom"):
f = open("/dev/urandom")
s = f.read(n)
f.close()
return s
else:
L = [chr(random.randrange(0, 256)) for i in range(n)]
return "".join(L)
class Challenge:
def __init__(self,challenge):
self.params = {}
self.no_chal=0
if challenge.upper().find('WWW-Authenticate:'.upper())==-1:
self.no_chal=1
rx =
re.compile('WWW-Authenticate:.*qop="(\w+)"',re.IGNORECASE|re.MULTILINE)
m = rx.search(challenge)
if m:
self.params['qop'] = m.group(1)
rx =
re.compile('WWW-Authenticate:.*realm="(\w+)"',re.IGNORECASE|re.MULTILINE)
m = rx.search(challenge)
if m:
self.params['realm'] = m.group(1)
rx =
re.compile('WWW-Authenticate:.*algorithm="(\w+)"',re.IGNORECASE|re.MULTILINE)
m = rx.search(challenge)
if m:
self.params['algorithm'] = m.group(1)
rx =
re.compile('WWW-Authenticate:.*nonce="(\w+)"',re.IGNORECASE|re.MULTILINE)
m = rx.search(challenge)
if m:
self.params['nonce'] = m.group(1)
rx =
re.compile('WWW-Authenticate:.*opaque="(\w+)"',re.IGNORECASE|re.MULTILINE)
m = rx.search(challenge)
if m:
self.params['opaque'] = m.group(1)
rx =
re.compile('WWW-Authenticate:.*Digest.*"',re.IGNORECASE|re.MULTILINE)
m = rx.search(challenge)
if m:
self.params['Digest'] = 1
def get(self,keyword,default=None):
if self.params.has_key(keyword):
return self.params[keyword]
else:
return default
def no_challenge(self):
return self.no_chal
class AbstractDigestAuthHandler:
# Digest authentication is specified in RFC 2617.
# XXX The client does not inspect the Authentication-Info header
# in a successful response.
# XXX It should be possible to test this implementation against
# a mock server that just generates a static set of challenges.
# XXX qop="auth-int" supports is shaky
def __init__(self, user, passwd):
self.user = user
self.passwd = passwd
self.retried = 0
self.nonce_count = 0
def reset_retry_count(self):
self.retried = 0
def retry_http_digest_auth(self, req, auth):
token, challenge = auth.split(' ', 1)
chal = parse_keqv_list(parse_http_list(challenge))
auth = self.get_authorization(req, chal)
if auth:
auth_val = 'Digest %s' % auth
if req.headers.get(self.auth_header, None) == auth_val: