~bzr-pqm/bzr/bzr.dev

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