~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Vincent Ladeuil
  • Date: 2007-04-17 22:07:18 UTC
  • mto: (2420.1.21 bzr.http.auth)
  • mto: This revision was merged to the branch mainline in revision 2463.
  • Revision ID: v.ladeuil+lp@free.fr-20070417220718-kce3mj0wn8hi8m02
Implement http proxy basic authentication.

* bzrlib/transport/http/_urllib2_wrappers.py:
(Request.set_proxy_auth): New method.
(extract_credentials): Moved from
HttpTransport_urllib._extract_auth.
(ProxyHandler.__init__): We need a password_manager for
authentication.
(ProxyHandler.set_proxy): Don't add the auth header, the
ProxyHandlers will do it later.
(ProxyBasicAuthHandler): New class. Handle the http basic
authentication for proxy.
(Opener.__init__): Enable ProxyBasicAuthHandler.

* bzrlib/transport/http/_urllib.py:
(HttpTransport_urllib._extract_auth): Moved to
_urllib2_wrappers.extract_credentials.

* bzrlib/tests/test_http.py:
(TestHttpProxyWhiteBox.test_empty_pass,
TestHttpProxyWhiteBox.test_user_pass): Deleted. Euivalent tests
exists in TestHTTPProxyBasicAuth now.

* bzrlib/tests/HTTPTestUtil.py:
(BasicAuthRequestHandler): Fix inheritance.
(ProxyBasicAuthRequestHandler): Force the use of
FakeProxyRequestHandler.translate_path. This is a bit ugly :-/

Show diffs side-by-side

added added

removed removed

Lines of Context:
157
157
        # Unless told otherwise, redirections are not followed
158
158
        self.follow_redirections = False
159
159
        self.set_auth(None, None, None) # Until the first 401
 
160
        self.set_proxy_auth(None, None, None) # Until the first 407
160
161
 
161
162
    def set_auth(self, auth_scheme, user, password=None):
162
163
        self.auth_scheme = auth_scheme
163
164
        self.user = user
164
165
        self.password = password
165
166
 
 
167
    # FIXME: this is not called, we incur a rountrip for each
 
168
    # request.
 
169
    def set_proxy_auth(self, auth_scheme, user, password=None):
 
170
        self.proxy_auth_scheme = auth_scheme
 
171
        self.proxy_user = user
 
172
        self.proxy_password = password
 
173
 
166
174
    def get_method(self):
167
175
        return self.method
168
176
 
169
177
 
 
178
def extract_credentials(url):
 
179
    """Extracts credentials information from url.
 
180
 
 
181
    Get user and password from url of the form: http://user:pass@host/path
 
182
    :returns: (clean_url, user, password)
 
183
    """
 
184
    scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
 
185
 
 
186
    if '@' in netloc:
 
187
        auth, netloc = netloc.split('@', 1)
 
188
        if ':' in auth:
 
189
            user, password = auth.split(':', 1)
 
190
        else:
 
191
            user, password = auth, None
 
192
        user = urllib.unquote(user)
 
193
        if password is not None:
 
194
            password = urllib.unquote(password)
 
195
    else:
 
196
        user = None
 
197
        password = None
 
198
 
 
199
    # Build the clean url
 
200
    clean_url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
 
201
 
 
202
    return clean_url, user, password
 
203
 
 
204
 
170
205
# The urlib2.xxxAuthHandler handle the authentication of the
171
206
# requests, to do that, they need an urllib2 PasswordManager *at
172
207
# build time*. We also need one to reuse the passwords already
594
629
    handler_order = 100
595
630
    _debuglevel = DEBUG
596
631
 
597
 
    def __init__(self, proxies=None):
 
632
    def __init__(self, password_manager, proxies=None):
598
633
        urllib2.ProxyHandler.__init__(self, proxies)
 
634
        self.password_manager = password_manager
599
635
        # First, let's get rid of urllib2 implementation
600
636
        for type, proxy in self.proxies.items():
601
637
            if self._debuglevel > 0:
675
711
            raise errors.InvalidURL(proxy,
676
712
                                    'Invalid syntax in proxy env variable')
677
713
        elif '@' in host:
678
 
            user_pass, host = host.split('@', 1)
679
 
            if ':' in user_pass:
680
 
                user, password = user_pass.split(':', 1)
681
 
            else:
682
 
                user = user_pass
683
 
                password = ''
684
 
            user_pass = '%s:%s' % (urllib.unquote(user),
685
 
                                   urllib.unquote(password))
686
 
            user_pass = user_pass.encode('base64').strip()
687
 
            request.add_header('Proxy-authorization', 'Basic ' + user_pass)
 
714
            # Extract credentials from the url and store them in
 
715
            # the password manager so that the
 
716
            # ProxyxxxAuthHandler can use them later.
 
717
            clean_url, user, password = extract_credentials(proxy)
 
718
            if user and password is not None: # '' is a valid password
 
719
                pm = self.password_manager
 
720
                pm.add_password(None, self.base, self._user, self._password)
688
721
        host = urllib.unquote(host)
689
722
        request.set_proxy(host, type)
690
723
        if self._debuglevel > 0:
739
772
        return response
740
773
 
741
774
 
 
775
class ProxyBasicAuthHandler(urllib2.ProxyBasicAuthHandler):
 
776
    """Custom proxy basic authentication handler.
 
777
 
 
778
    Send the authentication preventively to avoid the roundtrip
 
779
    associated with the 407 error.
 
780
    """
 
781
 
 
782
    def get_auth(self, user, password):
 
783
        raw = '%s:%s' % (user, password)
 
784
        auth = 'Basic ' + raw.encode('base64').strip()
 
785
        return auth
 
786
 
 
787
    def set_auth(self, request):
 
788
        """Add the authentication header if needed.
 
789
 
 
790
        All required informations should be part of the request.
 
791
        """
 
792
        if request.proxy_password is not None:
 
793
            request.add_header(self.auth_header,
 
794
                               self.get_auth(request.proxy_user,
 
795
                                             request.proxy_password))
 
796
 
 
797
    def http_request(self, request):
 
798
        """Insert an authentication header if information is available"""
 
799
        if request.proxy_auth_scheme == 'basic' \
 
800
                and request.proxy_password is not None:
 
801
            self.set_auth(request)
 
802
        return request
 
803
 
 
804
    https_request = http_request # FIXME: Need test
 
805
 
 
806
    def http_error_407(self, req, fp, code, msg, headers):
 
807
        """Trap the 401 to gather the auth type."""
 
808
        response = urllib2.ProxyBasicAuthHandler.http_error_407(self, req, fp,
 
809
                                                                code, msg,
 
810
                                                                headers)
 
811
        if response is not None:
 
812
            # We capture the auth_scheme to be able to send the
 
813
            # authentication header with the next requests
 
814
            # without waiting for a 407 error.
 
815
            # The urllib2.ProxyBasicAuthHandler will return a
 
816
            # response *only* if the basic authentication
 
817
            # succeeds. If another scheme is used or the
 
818
            # authentication fails, the response will be None.
 
819
            req.proxy_auth_scheme = 'basic'
 
820
 
 
821
        return response
 
822
 
 
823
 
742
824
class HTTPErrorProcessor(urllib2.HTTPErrorProcessor):
743
825
    """Process HTTP error responses.
744
826
 
797
879
        # commented out below
798
880
        self._opener = urllib2.build_opener( \
799
881
            connection, redirect, error,
800
 
            ProxyHandler,
 
882
            ProxyHandler(self.password_manager),
801
883
            HTTPBasicAuthHandler(self.password_manager),
802
884
            #urllib2.HTTPDigestAuthHandler(self.password_manager),
803
 
            #urllib2.ProxyBasicAuthHandler,
 
885
            ProxyBasicAuthHandler(self.password_manager),
804
886
            #urllib2.ProxyDigestAuthHandler,
805
887
            HTTPHandler,
806
888
            HTTPSHandler,