/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

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

  • Committer: Robert Collins
  • Date: 2009-05-23 20:57:12 UTC
  • mfrom: (4371 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4441.
  • Revision ID: robertc@robertcollins.net-20090523205712-lcwbfqk6vwavinuv
MergeĀ .dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
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
16
16
 
17
17
"""Implementaion of urllib2 tailored to bzr needs
18
18
 
917
917
 
918
918
        (scheme, user, password,
919
919
         host, port, path) = transport.ConnectedTransport._split_url(proxy)
 
920
        if not host:
 
921
            raise errors.InvalidURL(proxy, 'No host component')
920
922
 
921
923
        if request.proxy_auth == {}:
922
924
            # No proxy auth parameter are available, we are handling the first
976
978
      successful and the request authentication parameters have been updated.
977
979
    """
978
980
 
 
981
    scheme = None
 
982
    """The scheme as it appears in the server header (lower cased)"""
 
983
 
979
984
    _max_retry = 3
980
985
    """We don't want to retry authenticating endlessly"""
981
986
 
1033
1038
                # Let's be ready for next round
1034
1039
                self._retry_count = None
1035
1040
                return 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)
1041
1046
 
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
1050
 
                return None
1051
 
 
1052
 
            if self.requires_username and auth.get('user', None) is None:
1053
 
                # Without a known user, we can't authenticate
1054
 
                return None
1055
 
 
1056
 
            # Housekeeping
1057
 
            request.connection.cleanup_pipe()
1058
 
            response = self.parent.open(request)
1059
 
            if response:
1060
 
                self.auth_successful(request, response)
1061
 
            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
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'],
1130
 
                                      realm=realm)
 
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'
1155
1183
        return prompt
1156
1184
 
 
1185
    def _build_username_prompt(self, auth):
 
1186
        """Build a prompt taking the protocol used into account.
 
1187
 
 
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.
 
1191
 
 
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.
 
1195
        """
 
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'
 
1201
        return prompt
 
1202
 
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.
1172
1218
    """
1173
1219
 
 
1220
    scheme = 'negotiate'
1174
1221
    handler_order = 480
1175
 
 
1176
1222
    requires_username = False
1177
1223
 
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:
1181
1227
            return False
1182
1228
        self.update_auth(auth, 'scheme', scheme)
1183
1229
        resp = self._auth_match_kerberos(auth)
1216
1262
class BasicAuthHandler(AbstractAuthHandler):
1217
1263
    """A custom basic authentication handler."""
1218
1264
 
 
1265
    scheme = 'basic'
1219
1266
    handler_order = 500
1220
 
 
1221
1267
    auth_regexp = re.compile('realm="([^"]*)"', re.I)
1222
1268
 
1223
1269
    def build_auth_header(self, auth, request):
1225
1271
        auth_header = 'Basic ' + raw.encode('base64').strip()
1226
1272
        return auth_header
1227
1273
 
 
1274
    def extract_realm(self, header_value):
 
1275
        match = self.auth_regexp.search(header_value)
 
1276
        realm = None
 
1277
        if match:
 
1278
            realm = match.group(1)
 
1279
        return match, realm
 
1280
 
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:
1231
1284
            return False
1232
1285
 
1233
 
        match = self.auth_regexp.search(raw_auth)
 
1286
        match, realm = self.extract_realm(raw_auth)
1234
1287
        if match:
1235
 
            realm = match.groups()
1236
 
            if scheme != 'basic':
1237
 
                return False
1238
 
 
1239
1288
            # Put useful info into auth
1240
1289
            self.update_auth(auth, 'scheme', scheme)
1241
1290
            self.update_auth(auth, 'realm', realm)
1273
1322
class DigestAuthHandler(AbstractAuthHandler):
1274
1323
    """A custom digest authentication handler."""
1275
1324
 
 
1325
    scheme = 'digest'
1276
1326
    # Before basic as digest is a bit more secure and should be preferred
1277
1327
    handler_order = 490
1278
1328
 
1284
1334
 
1285
1335
    def auth_match(self, header, auth):
1286
1336
        scheme, raw_auth = self._parse_auth_header(header)
1287
 
        if scheme != 'digest':
 
1337
        if scheme != self.scheme:
1288
1338
            return False
1289
1339
 
1290
1340
        # Put the requested authentication info into a dict
1385
1435
    def build_password_prompt(self, auth):
1386
1436
        return self._build_password_prompt(auth)
1387
1437
 
 
1438
    def build_username_prompt(self, auth):
 
1439
        return self._build_username_prompt(auth)
 
1440
 
1388
1441
    def http_error_401(self, req, fp, code, msg, headers):
1389
1442
        return self.auth_required(req, headers)
1390
1443
 
1416
1469
        prompt = 'Proxy ' + prompt
1417
1470
        return prompt
1418
1471
 
 
1472
    def build_username_prompt(self, auth):
 
1473
        prompt = self._build_username_prompt(auth)
 
1474
        prompt = 'Proxy ' + prompt
 
1475
        return prompt
 
1476
 
1419
1477
    def http_error_407(self, req, fp, code, msg, headers):
1420
1478
        return self.auth_required(req, headers)
1421
1479