~bzr-pqm/bzr/bzr.dev

2052.3.2 by John Arbash Meinel
Change Copyright .. by Canonical to Copyright ... Canonical
1
# Copyright (C) 2005 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
    """
109
    def setUp(self):
1530.1.14 by Robert Collins
Remove duplicate web server from HTTPTestUtil.
110
        super(TestCaseWithWebserver, self).setUp()
3111.1.16 by Vincent Ladeuil
Fix more imports.
111
        self.transport_readonly_server = http_server.HttpServer
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
112
113
114
class TestCaseWithTwoWebservers(TestCaseWithWebserver):
2164.2.13 by v.ladeuil+lp at free
Add tests for redirection. Preserve transport decorations.
115
    """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.
116
2164.2.25 by Vincent Ladeuil
Fix typos noticed by Aaron.
117
    We set up two webservers to allows various tests involving
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
118
    proxies or redirections from one server to the other.
119
    """
120
    def setUp(self):
121
        super(TestCaseWithTwoWebservers, self).setUp()
3111.1.16 by Vincent Ladeuil
Fix more imports.
122
        self.transport_secondary_server = http_server.HttpServer
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
123
        self.__secondary_server = None
124
125
    def create_transport_secondary_server(self):
126
        """Create a transport server from class defined at init.
127
128
        This is mostly a hook for daughter classes.
129
        """
130
        return self.transport_secondary_server()
131
132
    def get_secondary_server(self):
133
        """Get the server instance for the secondary transport."""
134
        if self.__secondary_server is None:
135
            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,
136
            self.start_server(self.__secondary_server)
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
137
        return self.__secondary_server
138
139
3111.1.16 by Vincent Ladeuil
Fix more imports.
140
class ProxyServer(http_server.HttpServer):
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
141
    """A proxy test server for http transports."""
142
143
    proxy_requests = True
2213.1.1 by v.ladeuil+lp at free
Workaround SimpleHTTPRequestHandler.translate_path limitation in
144
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
145
3111.1.16 by Vincent Ladeuil
Fix more imports.
146
class RedirectRequestHandler(http_server.TestingHTTPRequestHandler):
2164.2.13 by v.ladeuil+lp at free
Add tests for redirection. Preserve transport decorations.
147
    """Redirect all request to the specified server"""
148
149
    def parse_request(self):
150
        """Redirect a single HTTP request to another host"""
3111.1.16 by Vincent Ladeuil
Fix more imports.
151
        valid = http_server.TestingHTTPRequestHandler.parse_request(self)
2164.2.13 by v.ladeuil+lp at free
Add tests for redirection. Preserve transport decorations.
152
        if valid:
2164.2.29 by Vincent Ladeuil
Test the http redirection at the request level even if it's not
153
            tcs = self.server.test_case_server
154
            code, target = tcs.is_redirected(self.path)
155
            if code is not None and target is not None:
156
                # Redirect as instructed
157
                self.send_response(code)
2164.2.16 by Vincent Ladeuil
Add tests.
158
                self.send_header('Location', target)
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
159
                # We do not send a body
160
                self.send_header('Content-Length', '0')
2164.2.16 by Vincent Ladeuil
Add tests.
161
                self.end_headers()
162
                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
163
            else:
164
                # We leave the parent class serve the request
165
                pass
2164.2.13 by v.ladeuil+lp at free
Add tests for redirection. Preserve transport decorations.
166
        return valid
167
168
3111.1.16 by Vincent Ladeuil
Fix more imports.
169
class HTTPServerRedirecting(http_server.HttpServer):
2164.2.13 by v.ladeuil+lp at free
Add tests for redirection. Preserve transport decorations.
170
    """An HttpServer redirecting to another server """
171
3111.1.18 by Vincent Ladeuil
Test parametrization for protocol versions achieved. Tests are failing :)
172
    def __init__(self, request_handler=RedirectRequestHandler,
173
                 protocol_version=None):
174
        http_server.HttpServer.__init__(self, request_handler,
175
                                        protocol_version=protocol_version)
2164.2.29 by Vincent Ladeuil
Test the http redirection at the request level even if it's not
176
        # redirections is a list of tuples (source, target, code)
177
        # - source is a regexp for the paths requested
178
        # - target is a replacement for re.sub describing where
179
        #   the request will be redirected
180
        # - code is the http error code associated to the
181
        #   redirection (301 permanent, 302 temporarry, etc
182
        self.redirections = []
183
184
    def redirect_to(self, host, port):
185
        """Redirect all requests to a specific host:port"""
186
        self.redirections = [('(.*)',
187
                              r'http://%s:%s\1' % (host, port) ,
188
                              301)]
189
190
    def is_redirected(self, path):
191
        """Is the path redirected by this server.
192
193
        :param path: the requested relative path
194
195
        :returns: a tuple (code, target) if a matching
196
             redirection is found, (None, None) otherwise.
197
        """
198
        code = None
199
        target = None
200
        for (rsource, rtarget, rcode) in self.redirections:
201
            target, match = re.subn(rsource, rtarget, path)
202
            if match:
203
                code = rcode
204
                break # The first match wins
205
            else:
206
                target = None
207
        return code, target
2164.2.13 by v.ladeuil+lp at free
Add tests for redirection. Preserve transport decorations.
208
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
209
2164.2.22 by Vincent Ladeuil
Take Aaron's review comments into account.
210
class TestCaseWithRedirectedWebserver(TestCaseWithTwoWebservers):
211
   """A support class providing redirections from one server to another.
212
2164.2.25 by Vincent Ladeuil
Fix typos noticed by Aaron.
213
   We set up two webservers to allows various tests involving
2164.2.22 by Vincent Ladeuil
Take Aaron's review comments into account.
214
   redirections.
215
   The 'old' server is redirected to the 'new' server.
216
   """
217
218
   def create_transport_secondary_server(self):
219
       """Create the secondary server redirecting to the primary server"""
220
       new = self.get_readonly_server()
221
       redirecting = HTTPServerRedirecting()
222
       redirecting.redirect_to(new.host, new.port)
223
       return redirecting
224
225
   def setUp(self):
226
       super(TestCaseWithRedirectedWebserver, self).setUp()
227
       # The redirections will point to the new server
228
       self.new_server = self.get_readonly_server()
229
       # The requests to the old server will be redirected
230
       self.old_server = self.get_secondary_server()
231
232
3111.1.16 by Vincent Ladeuil
Fix more imports.
233
class AuthRequestHandler(http_server.TestingHTTPRequestHandler):
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
234
    """Requires an authentication to process requests.
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
235
236
    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.
237
    only use one authentication scheme (implemented by daughter
238
    classes).
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
239
    """
2363.4.8 by Vincent Ladeuil
Implement a basic auth HTTP server, rewrite tests accordingly.
240
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
241
    # The following attributes should be defined in the server
2420.1.10 by Vincent Ladeuil
Doc fixes.
242
    # - auth_header_sent: the header name sent to require auth
243
    # - auth_header_recv: the header received containing auth
244
    # - 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.
245
2363.4.8 by Vincent Ladeuil
Implement a basic auth HTTP server, rewrite tests accordingly.
246
    def do_GET(self):
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
247
        if self.authorized():
3111.1.16 by Vincent Ladeuil
Fix more imports.
248
            return http_server.TestingHTTPRequestHandler.do_GET(self)
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
249
        else:
250
            # Note that we must update test_case_server *before*
251
            # sending the error or the client may try to read it
252
            # before we have sent the whole error back.
253
            tcs = self.server.test_case_server
254
            tcs.auth_required_errors += 1
255
            self.send_response(tcs.auth_error_code)
256
            self.send_header_auth_reqed()
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
257
            # We do not send a body
258
            self.send_header('Content-Length', '0')
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
259
            self.end_headers()
260
            return
2363.4.8 by Vincent Ladeuil
Implement a basic auth HTTP server, rewrite tests accordingly.
261
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
262
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
263
class BasicAuthRequestHandler(AuthRequestHandler):
264
    """Implements the basic authentication of a request"""
265
266
    def authorized(self):
267
        tcs = self.server.test_case_server
268
        if tcs.auth_scheme != 'basic':
269
            return False
270
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
271
        auth_header = self.headers.get(tcs.auth_header_recv, None)
272
        if auth_header:
273
            scheme, raw_auth = auth_header.split(' ', 1)
274
            if scheme.lower() == tcs.auth_scheme:
275
                user, password = raw_auth.decode('base64').split(':')
276
                return tcs.authorized(user, password)
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
277
278
        return False
279
280
    def send_header_auth_reqed(self):
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
281
        tcs = self.server.test_case_server
282
        self.send_header(tcs.auth_header_sent,
283
                         'Basic realm="%s"' % tcs.auth_realm)
284
285
2420.1.19 by Vincent Ladeuil
Cosmetic changes.
286
# FIXME: We could send an Authentication-Info header too when
287
# the authentication is succesful
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
288
289
class DigestAuthRequestHandler(AuthRequestHandler):
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
290
    """Implements the digest authentication of a request.
291
292
    We need persistence for some attributes and that can't be
293
    achieved here since we get instantiated for each request. We
294
    rely on the DigestAuthServer to take care of them.
295
    """
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
296
297
    def authorized(self):
298
        tcs = self.server.test_case_server
299
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
300
        auth_header = self.headers.get(tcs.auth_header_recv, None)
301
        if auth_header is None:
302
            return False
303
        scheme, auth = auth_header.split(None, 1)
304
        if scheme.lower() == tcs.auth_scheme:
305
            auth_dict = urllib2.parse_keqv_list(urllib2.parse_http_list(auth))
306
307
            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.
308
309
        return False
310
311
    def send_header_auth_reqed(self):
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
312
        tcs = self.server.test_case_server
313
        header = 'Digest realm="%s", ' % tcs.auth_realm
2545.2.1 by Vincent Ladeuil
Fix 121889 by working around urllib2 bug.
314
        header += 'nonce="%s", algorithm="%s", qop="auth"' % (tcs.auth_nonce,
315
                                                              'MD5')
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
316
        self.send_header(tcs.auth_header_sent,header)
317
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
318
4307.4.2 by Vincent Ladeuil
Handle servers proposing several authentication schemes.
319
class DigestAndBasicAuthRequestHandler(DigestAuthRequestHandler):
320
    """Implements a digest and basic authentication of a request.
321
322
    I.e. the server proposes both schemes and the client should choose the best
323
    one it can handle, which, in that case, should be digest, the only scheme
324
    accepted here.
325
    """
326
327
    def send_header_auth_reqed(self):
328
        tcs = self.server.test_case_server
329
        self.send_header(tcs.auth_header_sent,
330
                         'Basic realm="%s"' % tcs.auth_realm)
331
        header = 'Digest realm="%s", ' % tcs.auth_realm
332
        header += 'nonce="%s", algorithm="%s", qop="auth"' % (tcs.auth_nonce,
333
                                                              'MD5')
334
        self.send_header(tcs.auth_header_sent,header)
335
336
3111.1.16 by Vincent Ladeuil
Fix more imports.
337
class AuthServer(http_server.HttpServer):
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
338
    """Extends HttpServer with a dictionary of passwords.
339
340
    This is used as a base class for various schemes which should
341
    all use or redefined the associated AuthRequestHandler.
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
342
343
    Note that no users are defined by default, so add_user should
344
    be called before issuing the first request.
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
345
    """
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
346
347
    # The following attributes should be set dy daughter classes
348
    # and are used by AuthRequestHandler.
349
    auth_header_sent = None
350
    auth_header_recv = None
351
    auth_error_code = None
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
352
    auth_realm = "Thou should not pass"
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
353
3111.1.18 by Vincent Ladeuil
Test parametrization for protocol versions achieved. Tests are failing :)
354
    def __init__(self, request_handler, auth_scheme,
355
                 protocol_version=None):
356
        http_server.HttpServer.__init__(self, request_handler,
357
                                        protocol_version=protocol_version)
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
358
        self.auth_scheme = auth_scheme
2363.4.8 by Vincent Ladeuil
Implement a basic auth HTTP server, rewrite tests accordingly.
359
        self.password_of = {}
2420.1.4 by Vincent Ladeuil
Add test checking the number of roundtrips due to 401 or 407 errors.
360
        self.auth_required_errors = 0
2363.4.8 by Vincent Ladeuil
Implement a basic auth HTTP server, rewrite tests accordingly.
361
362
    def add_user(self, user, password):
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
363
        """Declare a user with an associated password.
364
365
        password can be empty, use an empty string ('') in that
366
        case, not None.
367
        """
2363.4.8 by Vincent Ladeuil
Implement a basic auth HTTP server, rewrite tests accordingly.
368
        self.password_of[user] = password
369
370
    def authorized(self, user, password):
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
371
        """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
372
        expected_password = self.password_of.get(user, None)
373
        return expected_password is not None and password == expected_password
374
375
2420.1.19 by Vincent Ladeuil
Cosmetic changes.
376
# FIXME: There is some code duplication with
2900.2.5 by Vincent Ladeuil
ake ftp aware of authentication config.
377
# _urllib2_wrappers.py.DigestAuthHandler. If that duplication
2420.1.19 by Vincent Ladeuil
Cosmetic changes.
378
# grows, it may require a refactoring. Also, we don't implement
379
# SHA algorithm nor MD5-sess here, but that does not seem worth
380
# it.
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
381
class DigestAuthServer(AuthServer):
382
    """A digest authentication server"""
383
2420.1.16 by Vincent Ladeuil
Handle nonce changes. Fix a nasty bug breaking the auth parameters sharing.
384
    auth_nonce = 'now!'
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
385
3111.1.18 by Vincent Ladeuil
Test parametrization for protocol versions achieved. Tests are failing :)
386
    def __init__(self, request_handler, auth_scheme,
387
                 protocol_version=None):
388
        AuthServer.__init__(self, request_handler, auth_scheme,
389
                            protocol_version=protocol_version)
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
390
391
    def digest_authorized(self, auth, command):
2420.1.16 by Vincent Ladeuil
Handle nonce changes. Fix a nasty bug breaking the auth parameters sharing.
392
        nonce = auth['nonce']
393
        if nonce != self.auth_nonce:
394
            return False
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
395
        realm = auth['realm']
396
        if realm != self.auth_realm:
397
            return False
398
        user = auth['username']
399
        if not self.password_of.has_key(user):
400
            return False
401
        algorithm= auth['algorithm']
402
        if algorithm != 'MD5':
403
            return False
404
        qop = auth['qop']
405
        if qop != 'auth':
406
            return False
407
408
        password = self.password_of[user]
409
410
        # Recalculate the response_digest to compare with the one
411
        # sent by the client
412
        A1 = '%s:%s:%s' % (user, realm, password)
413
        A2 = '%s:%s' % (command, auth['uri'])
414
3734.2.4 by Vincent Ladeuil
Fix python2.6 deprecation warnings related to hashlib.
415
        H = lambda x: osutils.md5(x).hexdigest()
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
416
        KD = lambda secret, data: H("%s:%s" % (secret, data))
417
418
        nonce_count = int(auth['nc'], 16)
419
420
        ncvalue = '%08x' % nonce_count
421
422
        cnonce = auth['cnonce']
423
        noncebit = '%s:%s:%s:%s:%s' % (nonce, ncvalue, cnonce, qop, H(A2))
424
        response_digest = KD(H(A1), noncebit)
425
426
        return response_digest == auth['response']
427
4307.4.2 by Vincent Ladeuil
Handle servers proposing several authentication schemes.
428
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
429
class HTTPAuthServer(AuthServer):
430
    """An HTTP server requiring authentication"""
431
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
432
    def init_http_auth(self):
433
        self.auth_header_sent = 'WWW-Authenticate'
434
        self.auth_header_recv = 'Authorization'
435
        self.auth_error_code = 401
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
436
437
438
class ProxyAuthServer(AuthServer):
439
    """A proxy server requiring authentication"""
440
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
441
    def init_proxy_auth(self):
442
        self.proxy_requests = True
443
        self.auth_header_sent = 'Proxy-Authenticate'
444
        self.auth_header_recv = 'Proxy-Authorization'
445
        self.auth_error_code = 407
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
446
447
448
class HTTPBasicAuthServer(HTTPAuthServer):
449
    """An HTTP server requiring basic authentication"""
450
3111.1.18 by Vincent Ladeuil
Test parametrization for protocol versions achieved. Tests are failing :)
451
    def __init__(self, protocol_version=None):
452
        HTTPAuthServer.__init__(self, BasicAuthRequestHandler, 'basic',
453
                                protocol_version=protocol_version)
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
454
        self.init_http_auth()
455
456
457
class HTTPDigestAuthServer(DigestAuthServer, HTTPAuthServer):
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
458
    """An HTTP server requiring digest authentication"""
459
3111.1.18 by Vincent Ladeuil
Test parametrization for protocol versions achieved. Tests are failing :)
460
    def __init__(self, protocol_version=None):
461
        DigestAuthServer.__init__(self, DigestAuthRequestHandler, 'digest',
462
                                  protocol_version=protocol_version)
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
463
        self.init_http_auth()
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
464
465
4307.4.2 by Vincent Ladeuil
Handle servers proposing several authentication schemes.
466
class HTTPBasicAndDigestAuthServer(DigestAuthServer, HTTPAuthServer):
467
    """An HTTP server requiring basic or digest authentication"""
468
469
    def __init__(self, protocol_version=None):
470
        DigestAuthServer.__init__(self, DigestAndBasicAuthRequestHandler,
471
                                  'basicdigest',
472
                                  protocol_version=protocol_version)
473
        self.init_http_auth()
474
        # We really accept Digest only
475
        self.auth_scheme = 'digest'
476
477
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
478
class ProxyBasicAuthServer(ProxyAuthServer):
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
479
    """A proxy server requiring basic authentication"""
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
480
3111.1.18 by Vincent Ladeuil
Test parametrization for protocol versions achieved. Tests are failing :)
481
    def __init__(self, protocol_version=None):
482
        ProxyAuthServer.__init__(self, BasicAuthRequestHandler, 'basic',
483
                                 protocol_version=protocol_version)
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
484
        self.init_proxy_auth()
485
486
487
class ProxyDigestAuthServer(DigestAuthServer, ProxyAuthServer):
488
    """A proxy server requiring basic authentication"""
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
489
3111.1.18 by Vincent Ladeuil
Test parametrization for protocol versions achieved. Tests are failing :)
490
    def __init__(self, protocol_version=None):
491
        ProxyAuthServer.__init__(self, DigestAuthRequestHandler, 'digest',
492
                                 protocol_version=protocol_version)
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
493
        self.init_proxy_auth()
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
494
495
4307.4.2 by Vincent Ladeuil
Handle servers proposing several authentication schemes.
496
class ProxyBasicAndDigestAuthServer(DigestAuthServer, ProxyAuthServer):
497
    """An proxy server requiring basic or digest authentication"""
498
499
    def __init__(self, protocol_version=None):
500
        DigestAuthServer.__init__(self, DigestAndBasicAuthRequestHandler,
501
                                  'basicdigest',
502
                                  protocol_version=protocol_version)
503
        self.init_proxy_auth()
504
        # We really accept Digest only
505
        self.auth_scheme = 'digest'
506
507