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