775
778
:param headers: The headers for the authentication error response.
776
779
:return: None or the response for the authenticated request.
778
server_header = headers.get(self.auth_reqed_header, None)
781
server_header = headers.get(self.auth_required_header, None)
779
782
if server_header is None:
780
783
# The http error MUST have the associated
781
784
# header. This must never happen in production code.
782
raise KeyError('%s not found' % self.auth_reqed_header)
784
auth = self.get_auth(request)
785
raise KeyError('%s not found' % self.auth_required_header)
787
auth = self.get_auth(request).copy()
788
if auth.get('user', None) is None:
789
# Without a known user, we can't authenticate
785
792
if self.auth_match(server_header, auth):
786
client_header = self.build_auth_header(auth)
787
if client_header == request.get_header(self.auth_header, None):
793
# auth_match may have modified auth (by adding the
794
# password or changing the realm, for example)
795
old = self.get_auth(request)
796
if request.get_header(self.auth_header, None) is not None \
797
and old.get('user') == auth.get('user') \
798
and old.get('realm') == auth.get('realm') \
799
and old.get('password') == auth.get('password'):
788
800
# We already tried that, give up
791
self.add_auth_header(request, client_header)
792
request.add_unredirected_header(self.auth_header, client_header)
803
# We will try to authenticate, save the auth so that
804
# the build_auth_header that will be called during
805
# parent.open use the right values
806
self.set_auth(request, auth)
793
807
response = self.parent.open(request)
795
809
self.auth_successful(request, response, auth)
875
883
"""Insert an authentication header if information is available"""
876
884
auth = self.get_auth(request)
877
885
if self.auth_params_reusable(auth):
878
self.add_auth_header(request, self.build_auth_header(auth))
886
self.add_auth_header(request, self.build_auth_header(auth, request))
881
889
https_request = http_request # FIXME: Need test
884
class AbstractBasicAuthHandler(AbstractAuthHandler):
885
"""A custom basic auth handler."""
887
auth_regexp = re.compile('[ \t]*([^ \t]+)[ \t]+realm="([^"]*)"', re.I)
889
def build_auth_header(self, auth):
892
class BasicAuthHandler(AbstractAuthHandler):
893
"""A custom basic authentication handler."""
895
auth_regexp = re.compile('realm="([^"]*)"', re.I)
897
def build_auth_header(self, auth, request):
890
898
raw = '%s:%s' % (auth['user'], auth['password'])
891
899
auth_header = 'Basic ' + raw.encode('base64').strip()
892
900
return auth_header
894
902
def auth_match(self, header, auth):
895
match = self.auth_regexp.search(header)
903
scheme, raw_auth = header.split(None, 1)
904
scheme = scheme.lower()
905
if scheme != 'basic':
908
match = self.auth_regexp.search(raw_auth)
897
scheme, auth['realm'] = match.groups()
898
auth['scheme'] = scheme.lower()
899
if auth['scheme'] != 'basic':
902
if auth.get('password',None) is None:
903
auth['password'] = self.get_password(auth['user'],
910
realm = match.groups()
911
if scheme != 'basic':
914
# Put useful info into auth
915
auth['scheme'] = scheme
916
auth['realm'] = realm
917
if auth.get('password',None) is None:
918
auth['password'] = self.get_password(auth['user'],
907
921
return match is not None
909
923
def auth_params_reusable(self, auth):
910
924
# If the auth scheme is known, it means a previous
911
925
# authentication was succesful, all information is
912
926
# available, no further checks are needed.
913
return auth.get('scheme',None) == 'basic'
916
class HTTPBasicAuthHandler(AbstractBasicAuthHandler):
917
"""Custom basic authentication handler.
927
return auth.get('scheme', None) == 'basic'
930
def get_digest_algorithm_impls(algorithm):
932
if algorithm == 'MD5':
933
H = lambda x: md5.new(x).hexdigest()
934
elif algorithm == 'SHA':
935
H = lambda x: sha.new(x).hexdigest()
937
KD = lambda secret, data: H("%s:%s" % (secret, data))
941
class DigestAuthHandler(AbstractAuthHandler):
942
"""A custom digest authentication handler."""
944
def auth_params_reusable(self, auth):
945
# If the auth scheme is known, it means a previous
946
# authentication was succesful, all information is
947
# available, no further checks are needed.
948
return auth.get('scheme', None) == 'digest'
950
def auth_match(self, header, auth):
951
scheme, raw_auth = header.split(None, 1)
952
scheme = scheme.lower()
953
if scheme != 'digest':
956
# Put the requested authentication info into a dict
957
req_auth = urllib2.parse_keqv_list(urllib2.parse_http_list(raw_auth))
959
# Check that we can handle that authentication
960
qop = req_auth.get('qop', None)
961
if qop != 'auth': # No auth-int so far
964
nonce = req_auth.get('nonce', None)
965
old_nonce = auth.get('nonce', None)
966
if nonce and old_nonce and nonce == old_nonce:
967
# We already tried that
970
algorithm = req_auth.get('algorithm', 'MD5')
971
H, KD = get_digest_algorithm_impls(algorithm)
975
realm = req_auth.get('realm', None)
976
if auth.get('password',None) is None:
977
auth['password'] = self.get_password(auth['user'],
980
# Put useful info into auth
982
auth['scheme'] = scheme
983
auth['algorithm'] = algorithm
984
auth['realm'] = req_auth['realm']
985
auth['nonce'] = req_auth['nonce']
987
auth['opaque'] = req_auth.get('opaque', None)
993
def build_auth_header(self, auth, request):
994
uri = request.get_selector()
995
A1 = '%s:%s:%s' % (auth['user'], auth['realm'], auth['password'])
996
A2 = '%s:%s' % (request.get_method(), uri)
997
nonce = auth['nonce']
1000
H, KD = get_digest_algorithm_impls(auth['algorithm'])
1001
nonce_count = auth.get('nonce_count',0)
1003
ncvalue = '%08x' % nonce_count
1004
cnonce = sha.new("%s:%s:%s:%s" % (nonce_count, nonce,
1005
time.ctime(), urllib2.randombytes(8))
1007
noncebit = '%s:%s:%s:%s:%s' % (nonce, ncvalue, cnonce, qop, H(A2))
1008
response_digest = KD(H(A1), noncebit)
1011
header += 'username="%s", realm="%s", nonce="%s",' % (auth['user'],
1014
header += ' uri="%s", response="%s"' % (uri, response_digest)
1015
opaque = auth.get('opaque', None)
1017
header += ', opaque="%s"' % opaque
1018
header += ', algorithm="%s"' % auth['algorithm']
1019
header += ', qop="%s", nc="%s", cnonce="%s"' % (qop, ncvalue, cnonce)
1021
# We have used the nonce once more, update the count
1022
auth['nonce_count'] = nonce_count
1027
class HTTPAuthHandler(AbstractAuthHandler):
1028
"""Custom http authentication handler.
919
1030
Send the authentication preventively to avoid the roundtrip
920
associated with the 401 error.
1031
associated with the 401 error and keep the revelant info in
1032
the auth request attribute.
923
1035
password_prompt = 'HTTP %(user)s@%(host)s%(realm)s password'
924
auth_reqed_header = 'www-authenticate'
1036
auth_required_header = 'www-authenticate'
925
1037
auth_header = 'Authorization'
927
1039
def get_auth(self, request):
1040
"""Get the auth params from the request"""
928
1041
return request.auth
930
1043
def set_auth(self, request, auth):
1044
"""Set the auth params for the request"""
931
1045
request.auth = auth
933
1047
def http_error_401(self, req, fp, code, msg, headers):
934
1048
return self.auth_required(req, headers)
937
class ProxyBasicAuthHandler(AbstractBasicAuthHandler):
938
"""Custom proxy basic authentication handler.
1051
class ProxyAuthHandler(AbstractAuthHandler):
1052
"""Custom proxy authentication handler.
940
1054
Send the authentication preventively to avoid the roundtrip
941
associated with the 407 error.
1055
associated with the 407 error and keep the revelant info in
1056
the proxy_auth request attribute..
944
1059
password_prompt = 'Proxy %(user)s@%(host)s%(realm)s password'
945
auth_reqed_header = 'proxy-authenticate'
1060
auth_required_header = 'proxy-authenticate'
946
1061
# FIXME: the correct capitalization is Proxy-Authorization,
947
1062
# but python-2.4 urllib2.Request insist on using capitalize()
948
1063
# instead of title().
949
1064
auth_header = 'Proxy-authorization'
951
1066
def get_auth(self, request):
1067
"""Get the auth params from the request"""
952
1068
return request.proxy_auth
954
1070
def set_auth(self, request, auth):
1071
"""Set the auth params for the request"""
955
1072
request.proxy_auth = auth
957
1074
def http_error_407(self, req, fp, code, msg, headers):
958
1075
return self.auth_required(req, headers)
1078
class HTTPBasicAuthHandler(BasicAuthHandler, HTTPAuthHandler):
1079
"""Custom http basic authentication handler"""
1082
class ProxyBasicAuthHandler(BasicAuthHandler, ProxyAuthHandler):
1083
"""Custom proxy basic authentication handler"""
1086
class HTTPDigestAuthHandler(DigestAuthHandler, HTTPAuthHandler):
1087
"""Custom http basic authentication handler"""
1090
class ProxyDigestAuthHandler(DigestAuthHandler, ProxyAuthHandler):
1091
"""Custom proxy basic authentication handler"""
962
1094
class HTTPErrorProcessor(urllib2.HTTPErrorProcessor):
963
1095
"""Process HTTP error responses.