~bzr-pqm/bzr/bzr.dev

4763.2.4 by John Arbash Meinel
merge bzr.2.1 in preparation for NEWS entry.
1
# Copyright (C) 2005-2010 Canonical Ltd
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
2
#
1185.1.18 by Robert Collins
Lalo Martins remotebranch patch
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
7
#
1185.1.18 by Robert Collins
Lalo Martins remotebranch patch
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
12
#
1185.1.18 by Robert Collins
Lalo Martins remotebranch patch
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1185.1.18 by Robert Collins
Lalo Martins remotebranch patch
16
2004.1.28 by v.ladeuil+lp at free
Merge bzr.dev. Including http modifications by "smart" related code
17
from cStringIO import StringIO
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
18
import errno
2164.2.29 by Vincent Ladeuil
Test the http redirection at the request level even if it's not
19
import re
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
20
import socket
3111.1.7 by Vincent Ladeuil
Further refactoring.
21
import threading
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
22
import time
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
23
import urllib2
2213.1.1 by v.ladeuil+lp at free
Workaround SimpleHTTPRequestHandler.translate_path limitation in
24
import urlparse
1530.1.14 by Robert Collins
Remove duplicate web server from HTTPTestUtil.
25
3734.2.4 by Vincent Ladeuil
Fix python2.6 deprecation warnings related to hashlib.
26
3111.1.16 by Vincent Ladeuil
Fix more imports.
27
from bzrlib import (
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
28
    errors,
3734.2.4 by Vincent Ladeuil
Fix python2.6 deprecation warnings related to hashlib.
29
    osutils,
3111.1.16 by Vincent Ladeuil
Fix more imports.
30
    tests,
5247.3.31 by Vincent Ladeuil
Cleanup some imports in http_utils.py.
31
    transport,
32
    )
33
from bzrlib.smart import (
34
    medium,
35
    protocol,
36
    )
3111.1.16 by Vincent Ladeuil
Fix more imports.
37
from bzrlib.tests import http_server
5247.3.31 by Vincent Ladeuil
Cleanup some imports in http_utils.py.
38
from bzrlib.transport import chroot
3111.1.16 by Vincent Ladeuil
Fix more imports.
39
40
41
class HTTPServerWithSmarts(http_server.HttpServer):
2004.1.28 by v.ladeuil+lp at free
Merge bzr.dev. Including http modifications by "smart" related code
42
    """HTTPServerWithSmarts extends the HttpServer with POST methods that will
43
    trigger a smart server to execute with a transport rooted at the rootdir of
44
    the HTTP server.
45
    """
46
3111.1.25 by Vincent Ladeuil
Fix the smart server failing test and use it against protocol combinations.
47
    def __init__(self, protocol_version=None):
48
        http_server.HttpServer.__init__(self, SmartRequestHandler,
49
                                        protocol_version=protocol_version)
3111.1.16 by Vincent Ladeuil
Fix more imports.
50
51
52
class SmartRequestHandler(http_server.TestingHTTPRequestHandler):
3606.4.1 by Andrew Bennetts
Fix NotImplementedError when probing for smart protocol via HTTP.
53
    """Extend TestingHTTPRequestHandler to support smart client POSTs.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
54
3606.4.1 by Andrew Bennetts
Fix NotImplementedError when probing for smart protocol via HTTP.
55
    XXX: This duplicates a fair bit of the logic in bzrlib.transport.http.wsgi.
56
    """
2004.1.28 by v.ladeuil+lp at free
Merge bzr.dev. Including http modifications by "smart" related code
57
58
    def do_POST(self):
59
        """Hand the request off to a smart server instance."""
5247.3.31 by Vincent Ladeuil
Cleanup some imports in http_utils.py.
60
        backing = transport.get_transport(
61
            self.server.test_case_server._home_dir)
3606.4.1 by Andrew Bennetts
Fix NotImplementedError when probing for smart protocol via HTTP.
62
        chroot_server = chroot.ChrootServer(backing)
4934.3.3 by Martin Pool
Rename Server.setUp to Server.start_server
63
        chroot_server.start_server()
3606.4.1 by Andrew Bennetts
Fix NotImplementedError when probing for smart protocol via HTTP.
64
        try:
5247.3.31 by Vincent Ladeuil
Cleanup some imports in http_utils.py.
65
            t = transport.get_transport(chroot_server.get_url())
3606.4.1 by Andrew Bennetts
Fix NotImplementedError when probing for smart protocol via HTTP.
66
            self.do_POST_inner(t)
67
        finally:
4934.3.1 by Martin Pool
Rename Server.tearDown to .stop_server
68
            chroot_server.stop_server()
3606.4.1 by Andrew Bennetts
Fix NotImplementedError when probing for smart protocol via HTTP.
69
70
    def do_POST_inner(self, chrooted_transport):
2004.1.28 by v.ladeuil+lp at free
Merge bzr.dev. Including http modifications by "smart" related code
71
        self.send_response(200)
72
        self.send_header("Content-type", "application/octet-stream")
3606.4.1 by Andrew Bennetts
Fix NotImplementedError when probing for smart protocol via HTTP.
73
        if not self.path.endswith('.bzr/smart'):
74
            raise AssertionError(
75
                'POST to path not ending in .bzr/smart: %r' % (self.path,))
76
        t = chrooted_transport.clone(self.path[:-len('.bzr/smart')])
3245.4.24 by Andrew Bennetts
Consistently raise errors from the server as ErrorFromSmartServer exceptions.
77
        # if this fails, we should return 400 bad request, but failure is
78
        # failure for now - RBC 20060919
79
        data_length = int(self.headers['Content-Length'])
2004.1.28 by v.ladeuil+lp at free
Merge bzr.dev. Including http modifications by "smart" related code
80
        # TODO: We might like to support streaming responses.  1.0 allows no
81
        # Content-length in this case, so for integrity we should perform our
82
        # own chunking within the stream.
83
        # 1.1 allows chunked responses, and in this case we could chunk using
84
        # the HTTP chunking as this will allow HTTP persistence safely, even if
85
        # we have to stop early due to error, but we would also have to use the
86
        # HTTP trailer facility which may not be widely available.
3245.4.24 by Andrew Bennetts
Consistently raise errors from the server as ErrorFromSmartServer exceptions.
87
        request_bytes = self.rfile.read(data_length)
88
        protocol_factory, unused_bytes = medium._get_protocol_factory_for_bytes(
89
            request_bytes)
2004.1.28 by v.ladeuil+lp at free
Merge bzr.dev. Including http modifications by "smart" related code
90
        out_buffer = StringIO()
3245.4.24 by Andrew Bennetts
Consistently raise errors from the server as ErrorFromSmartServer exceptions.
91
        smart_protocol_request = protocol_factory(t, out_buffer.write, '/')
2004.1.28 by v.ladeuil+lp at free
Merge bzr.dev. Including http modifications by "smart" related code
92
        # Perhaps there should be a SmartServerHTTPMedium that takes care of
93
        # feeding the bytes in the http request to the smart_protocol_request,
94
        # but for now it's simpler to just feed the bytes directly.
3245.4.24 by Andrew Bennetts
Consistently raise errors from the server as ErrorFromSmartServer exceptions.
95
        smart_protocol_request.accept_bytes(unused_bytes)
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
96
        if not (smart_protocol_request.next_read_size() == 0):
97
            raise errors.SmartProtocolError(
98
                "not finished reading, but all data sent to protocol.")
2004.1.28 by v.ladeuil+lp at free
Merge bzr.dev. Including http modifications by "smart" related code
99
        self.send_header("Content-Length", str(len(out_buffer.getvalue())))
100
        self.end_headers()
101
        self.wfile.write(out_buffer.getvalue())
102
103
3111.1.16 by Vincent Ladeuil
Fix more imports.
104
class TestCaseWithWebserver(tests.TestCaseWithTransport):
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
105
    """A support class that provides readonly urls that are http://.
106
2004.3.3 by vila
Better (but still incomplete) design for bogus servers.
107
    This is done by forcing the readonly server to be an http
108
    one. This will currently fail if the primary transport is not
109
    backed by regular disk files.
1185.1.18 by Robert Collins
Lalo Martins remotebranch patch
110
    """
5273.1.4 by Vincent Ladeuil
The default http protocol version wasn't properly defined and as such not respected by some parametrized tests.
111
5247.3.29 by Vincent Ladeuil
Fix a bunch of tests that were building transport objects explicitely instead of relying on self.get_transport() leading to many wrong http implementation objects being tested.
112
    # These attributes can be overriden or parametrized by daughter clasess if
113
    # needed, but must exist so that the create_transport_readonly_server()
5247.6.6 by Vincent Ladeuil
Closing the connection is what pycurl was waiting for.
114
    # method (or any method creating an http(s) server) can propagate it.
5273.1.4 by Vincent Ladeuil
The default http protocol version wasn't properly defined and as such not respected by some parametrized tests.
115
    _protocol_version = None
5247.3.29 by Vincent Ladeuil
Fix a bunch of tests that were building transport objects explicitely instead of relying on self.get_transport() leading to many wrong http implementation objects being tested.
116
    _url_protocol = 'http'
5273.1.4 by Vincent Ladeuil
The default http protocol version wasn't properly defined and as such not respected by some parametrized tests.
117
1185.1.18 by Robert Collins
Lalo Martins remotebranch patch
118
    def setUp(self):
1530.1.14 by Robert Collins
Remove duplicate web server from HTTPTestUtil.
119
        super(TestCaseWithWebserver, self).setUp()
3111.1.16 by Vincent Ladeuil
Fix more imports.
120
        self.transport_readonly_server = http_server.HttpServer
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
121
5273.1.4 by Vincent Ladeuil
The default http protocol version wasn't properly defined and as such not respected by some parametrized tests.
122
    def create_transport_readonly_server(self):
5247.3.29 by Vincent Ladeuil
Fix a bunch of tests that were building transport objects explicitely instead of relying on self.get_transport() leading to many wrong http implementation objects being tested.
123
        server = self.transport_readonly_server(
5273.1.4 by Vincent Ladeuil
The default http protocol version wasn't properly defined and as such not respected by some parametrized tests.
124
            protocol_version=self._protocol_version)
5247.3.29 by Vincent Ladeuil
Fix a bunch of tests that were building transport objects explicitely instead of relying on self.get_transport() leading to many wrong http implementation objects being tested.
125
        server._url_protocol = self._url_protocol
126
        return server
5273.1.4 by Vincent Ladeuil
The default http protocol version wasn't properly defined and as such not respected by some parametrized tests.
127
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
128
129
class TestCaseWithTwoWebservers(TestCaseWithWebserver):
2164.2.13 by v.ladeuil+lp at free
Add tests for redirection. Preserve transport decorations.
130
    """A support class providing readonly urls on two servers that are http://.
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
131
2164.2.25 by Vincent Ladeuil
Fix typos noticed by Aaron.
132
    We set up two webservers to allows various tests involving
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
133
    proxies or redirections from one server to the other.
134
    """
135
    def setUp(self):
136
        super(TestCaseWithTwoWebservers, self).setUp()
3111.1.16 by Vincent Ladeuil
Fix more imports.
137
        self.transport_secondary_server = http_server.HttpServer
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
138
        self.__secondary_server = None
139
140
    def create_transport_secondary_server(self):
141
        """Create a transport server from class defined at init.
142
143
        This is mostly a hook for daughter classes.
144
        """
5247.3.29 by Vincent Ladeuil
Fix a bunch of tests that were building transport objects explicitely instead of relying on self.get_transport() leading to many wrong http implementation objects being tested.
145
        server = self.transport_secondary_server(
5273.1.4 by Vincent Ladeuil
The default http protocol version wasn't properly defined and as such not respected by some parametrized tests.
146
            protocol_version=self._protocol_version)
5247.3.29 by Vincent Ladeuil
Fix a bunch of tests that were building transport objects explicitely instead of relying on self.get_transport() leading to many wrong http implementation objects being tested.
147
        server._url_protocol = self._url_protocol
148
        return server
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
149
150
    def get_secondary_server(self):
151
        """Get the server instance for the secondary transport."""
152
        if self.__secondary_server is None:
153
            self.__secondary_server = self.create_transport_secondary_server()
4659.1.2 by Robert Collins
Refactor creation and shutdown of test servers to use a common helper,
154
            self.start_server(self.__secondary_server)
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
155
        return self.__secondary_server
156
5247.3.29 by Vincent Ladeuil
Fix a bunch of tests that were building transport objects explicitely instead of relying on self.get_transport() leading to many wrong http implementation objects being tested.
157
    def get_secondary_url(self, relpath=None):
158
        base = self.get_secondary_server().get_url()
159
        return self._adjust_url(base, relpath)
160
161
    def get_secondary_transport(self, relpath=None):
5247.3.31 by Vincent Ladeuil
Cleanup some imports in http_utils.py.
162
        t = transport.get_transport(self.get_secondary_url(relpath))
5247.3.29 by Vincent Ladeuil
Fix a bunch of tests that were building transport objects explicitely instead of relying on self.get_transport() leading to many wrong http implementation objects being tested.
163
        self.assertTrue(t.is_readonly())
164
        return t
165
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
166
3111.1.16 by Vincent Ladeuil
Fix more imports.
167
class ProxyServer(http_server.HttpServer):
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
168
    """A proxy test server for http transports."""
169
170
    proxy_requests = True
2213.1.1 by v.ladeuil+lp at free
Workaround SimpleHTTPRequestHandler.translate_path limitation in
171
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
172
3111.1.16 by Vincent Ladeuil
Fix more imports.
173
class RedirectRequestHandler(http_server.TestingHTTPRequestHandler):
2164.2.13 by v.ladeuil+lp at free
Add tests for redirection. Preserve transport decorations.
174
    """Redirect all request to the specified server"""
175
176
    def parse_request(self):
177
        """Redirect a single HTTP request to another host"""
3111.1.16 by Vincent Ladeuil
Fix more imports.
178
        valid = http_server.TestingHTTPRequestHandler.parse_request(self)
2164.2.13 by v.ladeuil+lp at free
Add tests for redirection. Preserve transport decorations.
179
        if valid:
2164.2.29 by Vincent Ladeuil
Test the http redirection at the request level even if it's not
180
            tcs = self.server.test_case_server
181
            code, target = tcs.is_redirected(self.path)
182
            if code is not None and target is not None:
183
                # Redirect as instructed
184
                self.send_response(code)
2164.2.16 by Vincent Ladeuil
Add tests.
185
                self.send_header('Location', target)
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
186
                # We do not send a body
187
                self.send_header('Content-Length', '0')
2164.2.16 by Vincent Ladeuil
Add tests.
188
                self.end_headers()
189
                return False # The job is done
2164.2.29 by Vincent Ladeuil
Test the http redirection at the request level even if it's not
190
            else:
191
                # We leave the parent class serve the request
192
                pass
2164.2.13 by v.ladeuil+lp at free
Add tests for redirection. Preserve transport decorations.
193
        return valid
194
195
3111.1.16 by Vincent Ladeuil
Fix more imports.
196
class HTTPServerRedirecting(http_server.HttpServer):
2164.2.13 by v.ladeuil+lp at free
Add tests for redirection. Preserve transport decorations.
197
    """An HttpServer redirecting to another server """
198
3111.1.18 by Vincent Ladeuil
Test parametrization for protocol versions achieved. Tests are failing :)
199
    def __init__(self, request_handler=RedirectRequestHandler,
200
                 protocol_version=None):
201
        http_server.HttpServer.__init__(self, request_handler,
202
                                        protocol_version=protocol_version)
2164.2.29 by Vincent Ladeuil
Test the http redirection at the request level even if it's not
203
        # redirections is a list of tuples (source, target, code)
204
        # - source is a regexp for the paths requested
205
        # - target is a replacement for re.sub describing where
206
        #   the request will be redirected
207
        # - code is the http error code associated to the
208
        #   redirection (301 permanent, 302 temporarry, etc
209
        self.redirections = []
210
211
    def redirect_to(self, host, port):
212
        """Redirect all requests to a specific host:port"""
213
        self.redirections = [('(.*)',
214
                              r'http://%s:%s\1' % (host, port) ,
215
                              301)]
216
217
    def is_redirected(self, path):
218
        """Is the path redirected by this server.
219
220
        :param path: the requested relative path
221
222
        :returns: a tuple (code, target) if a matching
223
             redirection is found, (None, None) otherwise.
224
        """
225
        code = None
226
        target = None
227
        for (rsource, rtarget, rcode) in self.redirections:
228
            target, match = re.subn(rsource, rtarget, path)
229
            if match:
230
                code = rcode
231
                break # The first match wins
232
            else:
233
                target = None
234
        return code, target
2164.2.13 by v.ladeuil+lp at free
Add tests for redirection. Preserve transport decorations.
235
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
236
2164.2.22 by Vincent Ladeuil
Take Aaron's review comments into account.
237
class TestCaseWithRedirectedWebserver(TestCaseWithTwoWebservers):
238
   """A support class providing redirections from one server to another.
239
2164.2.25 by Vincent Ladeuil
Fix typos noticed by Aaron.
240
   We set up two webservers to allows various tests involving
2164.2.22 by Vincent Ladeuil
Take Aaron's review comments into account.
241
   redirections.
242
   The 'old' server is redirected to the 'new' server.
243
   """
244
5247.3.29 by Vincent Ladeuil
Fix a bunch of tests that were building transport objects explicitely instead of relying on self.get_transport() leading to many wrong http implementation objects being tested.
245
   def setUp(self):
246
       super(TestCaseWithRedirectedWebserver, self).setUp()
247
       # The redirections will point to the new server
248
       self.new_server = self.get_readonly_server()
249
       # The requests to the old server will be redirected to the new server
250
       self.old_server = self.get_secondary_server()
251
2164.2.22 by Vincent Ladeuil
Take Aaron's review comments into account.
252
   def create_transport_secondary_server(self):
253
       """Create the secondary server redirecting to the primary server"""
254
       new = self.get_readonly_server()
5273.1.4 by Vincent Ladeuil
The default http protocol version wasn't properly defined and as such not respected by some parametrized tests.
255
       redirecting = HTTPServerRedirecting(
256
           protocol_version=self._protocol_version)
2164.2.22 by Vincent Ladeuil
Take Aaron's review comments into account.
257
       redirecting.redirect_to(new.host, new.port)
5247.3.29 by Vincent Ladeuil
Fix a bunch of tests that were building transport objects explicitely instead of relying on self.get_transport() leading to many wrong http implementation objects being tested.
258
       redirecting._url_protocol = self._url_protocol
2164.2.22 by Vincent Ladeuil
Take Aaron's review comments into account.
259
       return redirecting
260
5247.3.29 by Vincent Ladeuil
Fix a bunch of tests that were building transport objects explicitely instead of relying on self.get_transport() leading to many wrong http implementation objects being tested.
261
   def get_old_url(self, relpath=None):
262
        base = self.old_server.get_url()
263
        return self._adjust_url(base, relpath)
264
265
   def get_old_transport(self, relpath=None):
5247.3.31 by Vincent Ladeuil
Cleanup some imports in http_utils.py.
266
        t = transport.get_transport(self.get_old_url(relpath))
5247.3.29 by Vincent Ladeuil
Fix a bunch of tests that were building transport objects explicitely instead of relying on self.get_transport() leading to many wrong http implementation objects being tested.
267
        self.assertTrue(t.is_readonly())
268
        return t
269
270
   def get_new_url(self, relpath=None):
271
        base = self.new_server.get_url()
272
        return self._adjust_url(base, relpath)
273
274
   def get_new_transport(self, relpath=None):
5247.3.31 by Vincent Ladeuil
Cleanup some imports in http_utils.py.
275
        t = transport.get_transport(self.get_new_url(relpath))
5247.3.29 by Vincent Ladeuil
Fix a bunch of tests that were building transport objects explicitely instead of relying on self.get_transport() leading to many wrong http implementation objects being tested.
276
        self.assertTrue(t.is_readonly())
277
        return t
2164.2.22 by Vincent Ladeuil
Take Aaron's review comments into account.
278
279
3111.1.16 by Vincent Ladeuil
Fix more imports.
280
class AuthRequestHandler(http_server.TestingHTTPRequestHandler):
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
281
    """Requires an authentication to process requests.
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
282
283
    This is intended to be used with a server that always and
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
284
    only use one authentication scheme (implemented by daughter
285
    classes).
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
286
    """
2363.4.8 by Vincent Ladeuil
Implement a basic auth HTTP server, rewrite tests accordingly.
287
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
288
    # The following attributes should be defined in the server
2420.1.10 by Vincent Ladeuil
Doc fixes.
289
    # - auth_header_sent: the header name sent to require auth
290
    # - auth_header_recv: the header received containing auth
291
    # - auth_error_code: the error code to indicate auth required
2420.1.2 by Vincent Ladeuil
Define tests for http proxy basic authentication. They fail.
292
2363.4.8 by Vincent Ladeuil
Implement a basic auth HTTP server, rewrite tests accordingly.
293
    def do_GET(self):
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
294
        if self.authorized():
3111.1.16 by Vincent Ladeuil
Fix more imports.
295
            return http_server.TestingHTTPRequestHandler.do_GET(self)
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
296
        else:
297
            # Note that we must update test_case_server *before*
298
            # sending the error or the client may try to read it
299
            # before we have sent the whole error back.
300
            tcs = self.server.test_case_server
301
            tcs.auth_required_errors += 1
302
            self.send_response(tcs.auth_error_code)
303
            self.send_header_auth_reqed()
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
304
            # We do not send a body
305
            self.send_header('Content-Length', '0')
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
306
            self.end_headers()
307
            return
2363.4.8 by Vincent Ladeuil
Implement a basic auth HTTP server, rewrite tests accordingly.
308
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
309
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
310
class BasicAuthRequestHandler(AuthRequestHandler):
311
    """Implements the basic authentication of a request"""
312
313
    def authorized(self):
314
        tcs = self.server.test_case_server
315
        if tcs.auth_scheme != 'basic':
316
            return False
317
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
318
        auth_header = self.headers.get(tcs.auth_header_recv, None)
319
        if auth_header:
320
            scheme, raw_auth = auth_header.split(' ', 1)
321
            if scheme.lower() == tcs.auth_scheme:
322
                user, password = raw_auth.decode('base64').split(':')
323
                return tcs.authorized(user, password)
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
324
325
        return False
326
327
    def send_header_auth_reqed(self):
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
328
        tcs = self.server.test_case_server
329
        self.send_header(tcs.auth_header_sent,
330
                         'Basic realm="%s"' % tcs.auth_realm)
331
332
2420.1.19 by Vincent Ladeuil
Cosmetic changes.
333
# FIXME: We could send an Authentication-Info header too when
334
# the authentication is succesful
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
335
336
class DigestAuthRequestHandler(AuthRequestHandler):
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
337
    """Implements the digest authentication of a request.
338
339
    We need persistence for some attributes and that can't be
340
    achieved here since we get instantiated for each request. We
341
    rely on the DigestAuthServer to take care of them.
342
    """
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
343
344
    def authorized(self):
345
        tcs = self.server.test_case_server
346
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
347
        auth_header = self.headers.get(tcs.auth_header_recv, None)
348
        if auth_header is None:
349
            return False
350
        scheme, auth = auth_header.split(None, 1)
351
        if scheme.lower() == tcs.auth_scheme:
352
            auth_dict = urllib2.parse_keqv_list(urllib2.parse_http_list(auth))
353
354
            return tcs.digest_authorized(auth_dict, self.command)
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
355
356
        return False
357
358
    def send_header_auth_reqed(self):
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
359
        tcs = self.server.test_case_server
360
        header = 'Digest realm="%s", ' % tcs.auth_realm
2545.2.1 by Vincent Ladeuil
Fix 121889 by working around urllib2 bug.
361
        header += 'nonce="%s", algorithm="%s", qop="auth"' % (tcs.auth_nonce,
362
                                                              'MD5')
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
363
        self.send_header(tcs.auth_header_sent,header)
364
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
365
4307.4.2 by Vincent Ladeuil
Handle servers proposing several authentication schemes.
366
class DigestAndBasicAuthRequestHandler(DigestAuthRequestHandler):
367
    """Implements a digest and basic authentication of a request.
368
369
    I.e. the server proposes both schemes and the client should choose the best
370
    one it can handle, which, in that case, should be digest, the only scheme
371
    accepted here.
372
    """
373
374
    def send_header_auth_reqed(self):
375
        tcs = self.server.test_case_server
376
        self.send_header(tcs.auth_header_sent,
377
                         'Basic realm="%s"' % tcs.auth_realm)
378
        header = 'Digest realm="%s", ' % tcs.auth_realm
379
        header += 'nonce="%s", algorithm="%s", qop="auth"' % (tcs.auth_nonce,
380
                                                              'MD5')
381
        self.send_header(tcs.auth_header_sent,header)
382
383
3111.1.16 by Vincent Ladeuil
Fix more imports.
384
class AuthServer(http_server.HttpServer):
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
385
    """Extends HttpServer with a dictionary of passwords.
386
387
    This is used as a base class for various schemes which should
388
    all use or redefined the associated AuthRequestHandler.
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
389
390
    Note that no users are defined by default, so add_user should
391
    be called before issuing the first request.
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
392
    """
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
393
394
    # The following attributes should be set dy daughter classes
395
    # and are used by AuthRequestHandler.
396
    auth_header_sent = None
397
    auth_header_recv = None
398
    auth_error_code = None
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
399
    auth_realm = "Thou should not pass"
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
400
3111.1.18 by Vincent Ladeuil
Test parametrization for protocol versions achieved. Tests are failing :)
401
    def __init__(self, request_handler, auth_scheme,
402
                 protocol_version=None):
403
        http_server.HttpServer.__init__(self, request_handler,
404
                                        protocol_version=protocol_version)
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
405
        self.auth_scheme = auth_scheme
2363.4.8 by Vincent Ladeuil
Implement a basic auth HTTP server, rewrite tests accordingly.
406
        self.password_of = {}
2420.1.4 by Vincent Ladeuil
Add test checking the number of roundtrips due to 401 or 407 errors.
407
        self.auth_required_errors = 0
2363.4.8 by Vincent Ladeuil
Implement a basic auth HTTP server, rewrite tests accordingly.
408
409
    def add_user(self, user, password):
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
410
        """Declare a user with an associated password.
411
412
        password can be empty, use an empty string ('') in that
413
        case, not None.
414
        """
2363.4.8 by Vincent Ladeuil
Implement a basic auth HTTP server, rewrite tests accordingly.
415
        self.password_of[user] = password
416
417
    def authorized(self, user, password):
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
418
        """Check that the given user provided the right password"""
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
419
        expected_password = self.password_of.get(user, None)
420
        return expected_password is not None and password == expected_password
421
422
2420.1.19 by Vincent Ladeuil
Cosmetic changes.
423
# FIXME: There is some code duplication with
2900.2.5 by Vincent Ladeuil
ake ftp aware of authentication config.
424
# _urllib2_wrappers.py.DigestAuthHandler. If that duplication
2420.1.19 by Vincent Ladeuil
Cosmetic changes.
425
# grows, it may require a refactoring. Also, we don't implement
426
# SHA algorithm nor MD5-sess here, but that does not seem worth
427
# it.
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
428
class DigestAuthServer(AuthServer):
429
    """A digest authentication server"""
430
2420.1.16 by Vincent Ladeuil
Handle nonce changes. Fix a nasty bug breaking the auth parameters sharing.
431
    auth_nonce = 'now!'
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
432
3111.1.18 by Vincent Ladeuil
Test parametrization for protocol versions achieved. Tests are failing :)
433
    def __init__(self, request_handler, auth_scheme,
434
                 protocol_version=None):
435
        AuthServer.__init__(self, request_handler, auth_scheme,
436
                            protocol_version=protocol_version)
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
437
438
    def digest_authorized(self, auth, command):
2420.1.16 by Vincent Ladeuil
Handle nonce changes. Fix a nasty bug breaking the auth parameters sharing.
439
        nonce = auth['nonce']
440
        if nonce != self.auth_nonce:
441
            return False
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
442
        realm = auth['realm']
443
        if realm != self.auth_realm:
444
            return False
445
        user = auth['username']
446
        if not self.password_of.has_key(user):
447
            return False
448
        algorithm= auth['algorithm']
449
        if algorithm != 'MD5':
450
            return False
451
        qop = auth['qop']
452
        if qop != 'auth':
453
            return False
454
455
        password = self.password_of[user]
456
457
        # Recalculate the response_digest to compare with the one
458
        # sent by the client
459
        A1 = '%s:%s:%s' % (user, realm, password)
460
        A2 = '%s:%s' % (command, auth['uri'])
461
3734.2.4 by Vincent Ladeuil
Fix python2.6 deprecation warnings related to hashlib.
462
        H = lambda x: osutils.md5(x).hexdigest()
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
463
        KD = lambda secret, data: H("%s:%s" % (secret, data))
464
465
        nonce_count = int(auth['nc'], 16)
466
467
        ncvalue = '%08x' % nonce_count
468
469
        cnonce = auth['cnonce']
470
        noncebit = '%s:%s:%s:%s:%s' % (nonce, ncvalue, cnonce, qop, H(A2))
471
        response_digest = KD(H(A1), noncebit)
472
473
        return response_digest == auth['response']
474
4307.4.2 by Vincent Ladeuil
Handle servers proposing several authentication schemes.
475
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
476
class HTTPAuthServer(AuthServer):
477
    """An HTTP server requiring authentication"""
478
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
479
    def init_http_auth(self):
480
        self.auth_header_sent = 'WWW-Authenticate'
481
        self.auth_header_recv = 'Authorization'
482
        self.auth_error_code = 401
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
483
484
485
class ProxyAuthServer(AuthServer):
486
    """A proxy server requiring authentication"""
487
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
488
    def init_proxy_auth(self):
489
        self.proxy_requests = True
490
        self.auth_header_sent = 'Proxy-Authenticate'
491
        self.auth_header_recv = 'Proxy-Authorization'
492
        self.auth_error_code = 407
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
493
494
495
class HTTPBasicAuthServer(HTTPAuthServer):
496
    """An HTTP server requiring basic authentication"""
497
3111.1.18 by Vincent Ladeuil
Test parametrization for protocol versions achieved. Tests are failing :)
498
    def __init__(self, protocol_version=None):
499
        HTTPAuthServer.__init__(self, BasicAuthRequestHandler, 'basic',
500
                                protocol_version=protocol_version)
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
501
        self.init_http_auth()
502
503
504
class HTTPDigestAuthServer(DigestAuthServer, HTTPAuthServer):
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
505
    """An HTTP server requiring digest authentication"""
506
3111.1.18 by Vincent Ladeuil
Test parametrization for protocol versions achieved. Tests are failing :)
507
    def __init__(self, protocol_version=None):
508
        DigestAuthServer.__init__(self, DigestAuthRequestHandler, 'digest',
509
                                  protocol_version=protocol_version)
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
510
        self.init_http_auth()
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
511
512
4307.4.2 by Vincent Ladeuil
Handle servers proposing several authentication schemes.
513
class HTTPBasicAndDigestAuthServer(DigestAuthServer, HTTPAuthServer):
514
    """An HTTP server requiring basic or digest authentication"""
515
516
    def __init__(self, protocol_version=None):
517
        DigestAuthServer.__init__(self, DigestAndBasicAuthRequestHandler,
518
                                  'basicdigest',
519
                                  protocol_version=protocol_version)
520
        self.init_http_auth()
521
        # We really accept Digest only
522
        self.auth_scheme = 'digest'
523
524
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
525
class ProxyBasicAuthServer(ProxyAuthServer):
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
526
    """A proxy server requiring basic authentication"""
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
527
3111.1.18 by Vincent Ladeuil
Test parametrization for protocol versions achieved. Tests are failing :)
528
    def __init__(self, protocol_version=None):
529
        ProxyAuthServer.__init__(self, BasicAuthRequestHandler, 'basic',
530
                                 protocol_version=protocol_version)
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
531
        self.init_proxy_auth()
532
533
534
class ProxyDigestAuthServer(DigestAuthServer, ProxyAuthServer):
535
    """A proxy server requiring basic authentication"""
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
536
3111.1.18 by Vincent Ladeuil
Test parametrization for protocol versions achieved. Tests are failing :)
537
    def __init__(self, protocol_version=None):
538
        ProxyAuthServer.__init__(self, DigestAuthRequestHandler, 'digest',
539
                                 protocol_version=protocol_version)
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
540
        self.init_proxy_auth()
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
541
542
4307.4.2 by Vincent Ladeuil
Handle servers proposing several authentication schemes.
543
class ProxyBasicAndDigestAuthServer(DigestAuthServer, ProxyAuthServer):
544
    """An proxy server requiring basic or digest authentication"""
545
546
    def __init__(self, protocol_version=None):
547
        DigestAuthServer.__init__(self, DigestAndBasicAuthRequestHandler,
548
                                  'basicdigest',
549
                                  protocol_version=protocol_version)
550
        self.init_proxy_auth()
551
        # We really accept Digest only
552
        self.auth_scheme = 'digest'
553
554