1
from hashlib import md5
2
from urllib2 import parse_http_list, parse_keqv_list
5
from twisted.internet import reactor
6
from twisted.internet.ssl import ClientContextFactory
7
from twisted.python.failure import Failure
8
from twisted.web.client import HTTPClientFactory, HTTPPageGetter
9
from twisted.web.error import Error
11
from juju.errors import ProviderError
14
def _parse_auth_info(auth_info):
15
method, info_str = auth_info.split(' ', 1)
16
if method != "Digest":
17
raise ProviderError("Unknown authentication method: %s" % method)
18
items = parse_http_list(info_str)
19
info = parse_keqv_list(items)
27
"Authentication request missing required key: %s" % e)
28
algorithm = info.get("algorithm", "MD5")
29
if algorithm != "MD5":
30
raise ProviderError("Unsupported digest algorithm: %s" % algorithm)
31
if "auth" not in qop.split(","):
32
raise ProviderError("Unsupported quality-of-protection: %s" % qop)
33
return realm, nonce, "auth", algorithm
36
def _digest_squish(*fields):
37
return md5(":".join(fields)).hexdigest()
40
class DigestAuthenticator(object):
42
def __init__(self, username, password):
47
def authenticate(self, method, url, auth_info):
48
realm, nonce, qop, algorithm = _parse_auth_info(auth_info)
49
ha1 = _digest_squish(self._user, realm, self._pass)
50
ha2 = _digest_squish(method, url)
52
self._nonce_count += 1
53
nc = "%08x" % self._nonce_count
54
response = _digest_squish(ha1, nonce, nc, cnonce, qop, ha2)
56
'Digest username="%s", realm="%s", nonce="%s", uri="%s", '
57
'algorithm="%s", response="%s", qop="%s", nc="%s", cnonce="%s"'
58
% (self._user, realm, nonce, url, algorithm, response, qop,
62
def _connect(factory):
63
if factory.scheme == 'https':
65
factory.host, factory.port, factory, ClientContextFactory())
67
reactor.connectTCP(factory.host, factory.port, factory)
70
class _AuthPageGetter(HTTPPageGetter):
72
handleStatus_204 = lambda self: self.handleStatus_200()
74
def handleStatus_401(self):
75
if not self.factory.authenticated:
76
(auth_info,) = self.headers["www-authenticate"]
77
self.factory.authenticate(auth_info)
78
_connect(self.factory)
80
self.handleStatusDefault()
81
self.factory.noPage(Failure(Error(self.status, self.message)))
83
self.transport.loseConnection()
86
class _AuthClientFactory(HTTPClientFactory):
88
protocol = _AuthPageGetter
91
def __init__(self, url, authenticator, **kwargs):
92
HTTPClientFactory.__init__(self, url, **kwargs)
93
self._authenticator = authenticator
95
def authenticate(self, auth_info):
96
self.headers["authorization"] = self._authenticator.authenticate(
97
self.method, self.url, auth_info)
98
self.authenticated = True
101
def get_page_auth(url, authenticator, method="GET", postdata=None):
102
factory = _AuthClientFactory(
103
url, authenticator, method=method, postdata=postdata)
105
return factory.deferred