13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
"""Implementaion of urllib2 tailored to bzr needs
1033
1038
# Let's be ready for next round
1034
1039
self._retry_count = None
1036
server_header = headers.get(self.auth_required_header, None)
1037
if server_header is None:
1041
server_headers = headers.getheaders(self.auth_required_header)
1042
if not server_headers:
1038
1043
# The http error MUST have the associated
1039
1044
# header. This must never happen in production code.
1040
1045
raise KeyError('%s not found' % self.auth_required_header)
1042
1047
auth = self.get_auth(request)
1043
1048
auth['modified'] = False
1044
if self.auth_match(server_header, auth):
1045
# auth_match may have modified auth (by adding the
1046
# password or changing the realm, for example)
1047
if (request.get_header(self.auth_header, None) is not None
1048
and not auth['modified']):
1049
# We already tried that, give up
1052
if self.requires_username and auth.get('user', None) is None:
1053
# Without a known user, we can't authenticate
1057
request.connection.cleanup_pipe()
1058
response = self.parent.open(request)
1060
self.auth_successful(request, response)
1049
# FIXME: the auth handler should be selected at a single place instead
1050
# of letting all handlers try to match all headers, but the current
1051
# design doesn't allow a simple implementation.
1052
for server_header in server_headers:
1053
# Several schemes can be proposed by the server, try to match each
1055
matching_handler = self.auth_match(server_header, auth)
1056
if matching_handler:
1057
# auth_match may have modified auth (by adding the
1058
# password or changing the realm, for example)
1059
if (request.get_header(self.auth_header, None) is not None
1060
and not auth['modified']):
1061
# We already tried that, give up
1064
# Only the most secure scheme proposed by the server should be
1065
# used, since the handlers use 'handler_order' to describe that
1066
# property, the first handler tried takes precedence, the
1067
# others should not attempt to authenticate if the best one
1069
best_scheme = auth.get('best_scheme', None)
1070
if best_scheme is None:
1071
# At that point, if current handler should doesn't succeed
1072
# the credentials are wrong (or incomplete), but we know
1073
# that the associated scheme should be used.
1074
best_scheme = auth['best_scheme'] = self.scheme
1075
if best_scheme != self.scheme:
1078
if self.requires_username and auth.get('user', None) is None:
1079
# Without a known user, we can't authenticate
1083
request.connection.cleanup_pipe()
1084
# Retry the request with an authentication header added
1085
response = self.parent.open(request)
1087
self.auth_successful(request, response)
1062
1089
# We are not qualified to handle the authentication.
1063
1090
# Note: the authentication error handling will try all
1064
1091
# available handlers. If one of them authenticates
1127
1154
if user is None:
1128
1155
user = auth_conf.get_user(auth['protocol'], auth['host'],
1129
1156
port=auth['port'], path=auth['path'],
1157
realm=realm, ask=True,
1158
prompt=self.build_username_prompt(auth))
1131
1159
if user is not None and password is None:
1132
1160
password = auth_conf.get_password(
1133
1161
auth['protocol'], auth['host'], user, port=auth['port'],
1154
1182
prompt += ' password'
1185
def _build_username_prompt(self, auth):
1186
"""Build a prompt taking the protocol used into account.
1188
The AuthHandler is used by http and https, we want that information in
1189
the prompt, so we build the prompt from the authentication dict which
1190
contains all the needed parts.
1192
Also, http and proxy AuthHandlers present different prompts to the
1193
user. The daughter classes should implements a public
1194
build_username_prompt using this method.
1196
prompt = '%s' % auth['protocol'].upper() + ' %(host)s'
1197
realm = auth['realm']
1198
if realm is not None:
1199
prompt += ", Realm: '%s'" % realm
1200
prompt += ' username'
1157
1203
def http_request(self, request):
1158
1204
"""Insert an authentication header if information is available"""
1159
1205
auth = self.get_auth(request)
1171
1217
NTLM support may also be added.
1220
scheme = 'negotiate'
1174
1221
handler_order = 480
1176
1222
requires_username = False
1178
1224
def auth_match(self, header, auth):
1179
1225
scheme, raw_auth = self._parse_auth_header(header)
1180
if scheme != 'negotiate':
1226
if scheme != self.scheme:
1182
1228
self.update_auth(auth, 'scheme', scheme)
1183
1229
resp = self._auth_match_kerberos(auth)
1225
1271
auth_header = 'Basic ' + raw.encode('base64').strip()
1226
1272
return auth_header
1274
def extract_realm(self, header_value):
1275
match = self.auth_regexp.search(header_value)
1278
realm = match.group(1)
1228
1281
def auth_match(self, header, auth):
1229
1282
scheme, raw_auth = self._parse_auth_header(header)
1230
if scheme != 'basic':
1283
if scheme != self.scheme:
1233
match = self.auth_regexp.search(raw_auth)
1286
match, realm = self.extract_realm(raw_auth)
1235
realm = match.groups()
1236
if scheme != 'basic':
1239
1288
# Put useful info into auth
1240
1289
self.update_auth(auth, 'scheme', scheme)
1241
1290
self.update_auth(auth, 'realm', realm)
1385
1435
def build_password_prompt(self, auth):
1386
1436
return self._build_password_prompt(auth)
1438
def build_username_prompt(self, auth):
1439
return self._build_username_prompt(auth)
1388
1441
def http_error_401(self, req, fp, code, msg, headers):
1389
1442
return self.auth_required(req, headers)
1416
1469
prompt = 'Proxy ' + prompt
1472
def build_username_prompt(self, auth):
1473
prompt = self._build_username_prompt(auth)
1474
prompt = 'Proxy ' + prompt
1419
1477
def http_error_407(self, req, fp, code, msg, headers):
1420
1478
return self.auth_required(req, headers)