73
81
self.filesock = filesock
74
82
self._report_activity = report_activity
84
def report_activity(self, size, direction):
85
if self._report_activity:
86
self._report_activity(size, direction)
77
88
def read(self, size=1):
78
89
s = self.filesock.read(size)
79
self._report_activity(len(s), 'read')
90
self.report_activity(len(s), 'read')
82
def readline(self, size=-1):
83
s = self.filesock.readline(size)
84
self._report_activity(len(s), 'read')
94
# This should be readline(self, size=-1), but httplib in python 2.4 and
95
# 2.5 defines a SSLFile wrapper whose readline method lacks the size
96
# parameter. So until we drop support for 2.4 and 2.5 and since we
97
# don't *need* the size parameter we'll stay with readline(self)
99
s = self.filesock.readline()
100
self.report_activity(len(s), 'read')
87
103
def __getattr__(self, name):
95
111
self._report_activity = report_activity
97
def send(self, s, *args):
98
self.sock.send(s, *args)
99
self._report_activity(len(s), 'write')
113
def report_activity(self, size, direction):
114
if self._report_activity:
115
self._report_activity(size, direction)
101
117
def sendall(self, s, *args):
102
self.sock.send(s, *args)
103
self._report_activity(len(s), 'write')
118
self.sock.sendall(s, *args)
119
self.report_activity(len(s), 'write')
105
121
def recv(self, *args):
106
122
s = self.sock.recv(*args)
107
self._report_activity(len(s), 'read')
123
self.report_activity(len(s), 'read')
110
126
def makefile(self, mode='r', bufsize=-1):
353
368
def set_proxy(self, proxy, type):
354
369
"""Set the proxy and remember the proxied host."""
355
self.proxied_host = self.get_host()
370
host, port = urllib.splitport(self.get_host())
372
# We need to set the default port ourselves way before it gets set
373
# in the HTTP[S]Connection object at build time.
374
if self.type == 'https':
375
conn_class = HTTPSConnection
377
conn_class = HTTPConnection
378
port = conn_class.default_port
379
self.proxied_host = '%s:%s' % (host, port)
356
380
urllib2.Request.set_proxy(self, proxy, type)
983
1019
# in such a cycle by default.
984
1020
self._retry_count = None
1022
def _parse_auth_header(self, server_header):
1023
"""Parse the authentication header.
1025
:param server_header: The value of the header sent by the server
1026
describing the authenticaion request.
1028
:return: A tuple (scheme, remainder) scheme being the first word in the
1029
given header (lower cased), remainder may be None.
1032
scheme, remainder = server_header.split(None, 1)
1034
scheme = server_header
1036
return (scheme.lower(), remainder)
986
1038
def update_auth(self, auth, key, value):
987
1039
"""Update a value in auth marking the auth as modified if needed"""
988
1040
old_value = auth.get(key, None)
1007
1059
# Let's be ready for next round
1008
1060
self._retry_count = None
1010
server_header = headers.get(self.auth_required_header, None)
1011
if server_header is None:
1062
server_headers = headers.getheaders(self.auth_required_header)
1063
if not server_headers:
1012
1064
# The http error MUST have the associated
1013
1065
# header. This must never happen in production code.
1014
1066
raise KeyError('%s not found' % self.auth_required_header)
1016
1068
auth = self.get_auth(request)
1017
1069
auth['modified'] = False
1018
if self.auth_match(server_header, auth):
1019
# auth_match may have modified auth (by adding the
1020
# password or changing the realm, for example)
1021
if (request.get_header(self.auth_header, None) is not None
1022
and not auth['modified']):
1023
# We already tried that, give up
1026
if auth.get('user', None) is None:
1027
# Without a known user, we can't authenticate
1031
request.connection.cleanup_pipe()
1032
response = self.parent.open(request)
1034
self.auth_successful(request, response)
1070
# Put some common info in auth if the caller didn't
1071
if auth.get('path', None) is None:
1073
host, port, path) = urlutils.parse_url(request.get_full_url())
1074
self.update_auth(auth, 'protocol', protocol)
1075
self.update_auth(auth, 'host', host)
1076
self.update_auth(auth, 'port', port)
1077
self.update_auth(auth, 'path', path)
1078
# FIXME: the auth handler should be selected at a single place instead
1079
# of letting all handlers try to match all headers, but the current
1080
# design doesn't allow a simple implementation.
1081
for server_header in server_headers:
1082
# Several schemes can be proposed by the server, try to match each
1084
matching_handler = self.auth_match(server_header, auth)
1085
if matching_handler:
1086
# auth_match may have modified auth (by adding the
1087
# password or changing the realm, for example)
1088
if (request.get_header(self.auth_header, None) is not None
1089
and not auth['modified']):
1090
# We already tried that, give up
1093
# Only the most secure scheme proposed by the server should be
1094
# used, since the handlers use 'handler_order' to describe that
1095
# property, the first handler tried takes precedence, the
1096
# others should not attempt to authenticate if the best one
1098
best_scheme = auth.get('best_scheme', None)
1099
if best_scheme is None:
1100
# At that point, if current handler should doesn't succeed
1101
# the credentials are wrong (or incomplete), but we know
1102
# that the associated scheme should be used.
1103
best_scheme = auth['best_scheme'] = self.scheme
1104
if best_scheme != self.scheme:
1107
if self.requires_username and auth.get('user', None) is None:
1108
# Without a known user, we can't authenticate
1112
request.connection.cleanup_pipe()
1113
# Retry the request with an authentication header added
1114
response = self.parent.open(request)
1116
self.auth_successful(request, response)
1036
1118
# We are not qualified to handle the authentication.
1037
1119
# Note: the authentication error handling will try all
1038
1120
# available handlers. If one of them authenticates
1094
1176
and then during dialog with the server).
1096
1178
auth_conf = config.AuthenticationConfig()
1098
password = auth['password']
1179
user = auth.get('user', None)
1180
password = auth.get('password', None)
1099
1181
realm = auth['realm']
1101
1183
if user is None:
1102
1184
user = auth_conf.get_user(auth['protocol'], auth['host'],
1103
1185
port=auth['port'], path=auth['path'],
1186
realm=realm, ask=True,
1187
prompt=self.build_username_prompt(auth))
1105
1188
if user is not None and password is None:
1106
1189
password = auth_conf.get_password(
1107
1190
auth['protocol'], auth['host'], user, port=auth['port'],
1128
1211
prompt += ' password'
1214
def _build_username_prompt(self, auth):
1215
"""Build a prompt taking the protocol used into account.
1217
The AuthHandler is used by http and https, we want that information in
1218
the prompt, so we build the prompt from the authentication dict which
1219
contains all the needed parts.
1221
Also, http and proxy AuthHandlers present different prompts to the
1222
user. The daughter classes should implements a public
1223
build_username_prompt using this method.
1225
prompt = '%s' % auth['protocol'].upper() + ' %(host)s'
1226
realm = auth['realm']
1227
if realm is not None:
1228
prompt += ", Realm: '%s'" % realm
1229
prompt += ' username'
1131
1232
def http_request(self, request):
1132
1233
"""Insert an authentication header if information is available"""
1133
1234
auth = self.get_auth(request)
1138
1239
https_request = http_request # FIXME: Need test
1242
class NegotiateAuthHandler(AbstractAuthHandler):
1243
"""A authentication handler that handles WWW-Authenticate: Negotiate.
1245
At the moment this handler supports just Kerberos. In the future,
1246
NTLM support may also be added.
1249
scheme = 'negotiate'
1251
requires_username = False
1253
def auth_match(self, header, auth):
1254
scheme, raw_auth = self._parse_auth_header(header)
1255
if scheme != self.scheme:
1257
self.update_auth(auth, 'scheme', scheme)
1258
resp = self._auth_match_kerberos(auth)
1261
# Optionally should try to authenticate using NTLM here
1262
self.update_auth(auth, 'negotiate_response', resp)
1265
def _auth_match_kerberos(self, auth):
1266
"""Try to create a GSSAPI response for authenticating against a host."""
1267
if not have_kerberos:
1269
ret, vc = kerberos.authGSSClientInit("HTTP@%(host)s" % auth)
1271
trace.warning('Unable to create GSSAPI context for %s: %d',
1274
ret = kerberos.authGSSClientStep(vc, "")
1276
trace.mutter('authGSSClientStep failed: %d', ret)
1278
return kerberos.authGSSClientResponse(vc)
1280
def build_auth_header(self, auth, request):
1281
return "Negotiate %s" % auth['negotiate_response']
1283
def auth_params_reusable(self, auth):
1284
# If the auth scheme is known, it means a previous
1285
# authentication was successful, all information is
1286
# available, no further checks are needed.
1287
return (auth.get('scheme', None) == 'negotiate' and
1288
auth.get('negotiate_response', None) is not None)
1141
1291
class BasicAuthHandler(AbstractAuthHandler):
1142
1292
"""A custom basic authentication handler."""
1144
1295
handler_order = 500
1146
1296
auth_regexp = re.compile('realm="([^"]*)"', re.I)
1148
1298
def build_auth_header(self, auth, request):
1150
1300
auth_header = 'Basic ' + raw.encode('base64').strip()
1151
1301
return auth_header
1303
def extract_realm(self, header_value):
1304
match = self.auth_regexp.search(header_value)
1307
realm = match.group(1)
1153
1310
def auth_match(self, header, auth):
1154
scheme, raw_auth = header.split(None, 1)
1155
scheme = scheme.lower()
1156
if scheme != 'basic':
1311
scheme, raw_auth = self._parse_auth_header(header)
1312
if scheme != self.scheme:
1159
match = self.auth_regexp.search(raw_auth)
1315
match, realm = self.extract_realm(raw_auth)
1161
realm = match.groups()
1162
if scheme != 'basic':
1165
1317
# Put useful info into auth
1166
1318
self.update_auth(auth, 'scheme', scheme)
1167
1319
self.update_auth(auth, 'realm', realm)
1168
if auth['user'] is None or auth['password'] is None:
1320
if (auth.get('user', None) is None
1321
or auth.get('password', None) is None):
1169
1322
user, password = self.get_user_password(auth)
1170
1323
self.update_auth(auth, 'user', user)
1171
1324
self.update_auth(auth, 'password', password)
1230
1383
# Put useful info into auth
1231
1384
self.update_auth(auth, 'scheme', scheme)
1232
1385
self.update_auth(auth, 'realm', realm)
1233
if auth['user'] is None or auth['password'] is None:
1386
if auth.get('user', None) is None or auth.get('password', None) is None:
1234
1387
user, password = self.get_user_password(auth)
1235
1388
self.update_auth(auth, 'user', user)
1236
1389
self.update_auth(auth, 'password', password)