~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/http/_urllib2_wrappers.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-05-11 07:36:32 UTC
  • mfrom: (4349.1.1 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20090511073632-ti658145fo07a4vw
Correctly handle http servers proposing multiple authentication
        schemes

Show diffs side-by-side

added added

removed removed

Lines of Context:
978
978
      successful and the request authentication parameters have been updated.
979
979
    """
980
980
 
 
981
    scheme = None
 
982
    """The scheme as it appears in the server header (lower cased)"""
 
983
 
981
984
    _max_retry = 3
982
985
    """We don't want to retry authenticating endlessly"""
983
986
 
1035
1038
                # Let's be ready for next round
1036
1039
                self._retry_count = None
1037
1040
                return None
1038
 
        server_header = headers.get(self.auth_required_header, None)
1039
 
        if server_header is None:
 
1041
        server_headers = headers.getheaders(self.auth_required_header)
 
1042
        if not server_headers:
1040
1043
            # The http error MUST have the associated
1041
1044
            # header. This must never happen in production code.
1042
1045
            raise KeyError('%s not found' % self.auth_required_header)
1043
1046
 
1044
1047
        auth = self.get_auth(request)
1045
1048
        auth['modified'] = False
1046
 
        if self.auth_match(server_header, auth):
1047
 
            # auth_match may have modified auth (by adding the
1048
 
            # password or changing the realm, for example)
1049
 
            if (request.get_header(self.auth_header, None) is not None
1050
 
                and not auth['modified']):
1051
 
                # We already tried that, give up
1052
 
                return None
1053
 
 
1054
 
            if self.requires_username and auth.get('user', None) is None:
1055
 
                # Without a known user, we can't authenticate
1056
 
                return None
1057
 
 
1058
 
            # Housekeeping
1059
 
            request.connection.cleanup_pipe()
1060
 
            response = self.parent.open(request)
1061
 
            if response:
1062
 
                self.auth_successful(request, response)
1063
 
            return 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
 
1054
            # one in turn
 
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
 
1062
                    return None
 
1063
 
 
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
 
1068
                # failed.
 
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:
 
1076
                    continue
 
1077
 
 
1078
                if self.requires_username and auth.get('user', None) is None:
 
1079
                    # Without a known user, we can't authenticate
 
1080
                    return None
 
1081
 
 
1082
                # Housekeeping
 
1083
                request.connection.cleanup_pipe()
 
1084
                # Retry the request with an authentication header added
 
1085
                response = self.parent.open(request)
 
1086
                if response:
 
1087
                    self.auth_successful(request, response)
 
1088
                return response
1064
1089
        # We are not qualified to handle the authentication.
1065
1090
        # Note: the authentication error handling will try all
1066
1091
        # available handlers. If one of them authenticates
1192
1217
    NTLM support may also be added.
1193
1218
    """
1194
1219
 
 
1220
    scheme = 'negotiate'
1195
1221
    handler_order = 480
1196
 
 
1197
1222
    requires_username = False
1198
1223
 
1199
1224
    def auth_match(self, header, auth):
1200
1225
        scheme, raw_auth = self._parse_auth_header(header)
1201
 
        if scheme != 'negotiate':
 
1226
        if scheme != self.scheme:
1202
1227
            return False
1203
1228
        self.update_auth(auth, 'scheme', scheme)
1204
1229
        resp = self._auth_match_kerberos(auth)
1237
1262
class BasicAuthHandler(AbstractAuthHandler):
1238
1263
    """A custom basic authentication handler."""
1239
1264
 
 
1265
    scheme = 'basic'
1240
1266
    handler_order = 500
1241
 
 
1242
1267
    auth_regexp = re.compile('realm="([^"]*)"', re.I)
1243
1268
 
1244
1269
    def build_auth_header(self, auth, request):
1255
1280
 
1256
1281
    def auth_match(self, header, auth):
1257
1282
        scheme, raw_auth = self._parse_auth_header(header)
1258
 
        if scheme != 'basic':
 
1283
        if scheme != self.scheme:
1259
1284
            return False
1260
1285
 
1261
1286
        match, realm = self.extract_realm(raw_auth)
1262
1287
        if match:
1263
 
            if scheme != 'basic':
1264
 
                return False
1265
 
 
1266
1288
            # Put useful info into auth
1267
1289
            self.update_auth(auth, 'scheme', scheme)
1268
1290
            self.update_auth(auth, 'realm', realm)
1300
1322
class DigestAuthHandler(AbstractAuthHandler):
1301
1323
    """A custom digest authentication handler."""
1302
1324
 
 
1325
    scheme = 'digest'
1303
1326
    # Before basic as digest is a bit more secure and should be preferred
1304
1327
    handler_order = 490
1305
1328
 
1311
1334
 
1312
1335
    def auth_match(self, header, auth):
1313
1336
        scheme, raw_auth = self._parse_auth_header(header)
1314
 
        if scheme != 'digest':
 
1337
        if scheme != self.scheme:
1315
1338
            return False
1316
1339
 
1317
1340
        # Put the requested authentication info into a dict