~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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
19
import md5
2004.1.29 by v.ladeuil+lp at free
New tests for http range requests handling.
20
from SimpleHTTPServer import SimpleHTTPRequestHandler
2164.2.29 by Vincent Ladeuil
Test the http redirection at the request level even if it's not
21
import re
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
22
import sha
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
23
import socket
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
24
import time
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
25
import urllib2
2213.1.1 by v.ladeuil+lp at free
Workaround SimpleHTTPRequestHandler.translate_path limitation in
26
import urlparse
1530.1.14 by Robert Collins
Remove duplicate web server from HTTPTestUtil.
27
2018.5.150 by Andrew Bennetts
Tidy imports in HTTPTestUtil.py
28
from bzrlib.smart import protocol
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.
29
from bzrlib.tests import TestCaseWithTransport
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
30
from bzrlib.tests.HttpServer import (
31
    HttpServer,
32
    TestingHTTPRequestHandler,
33
    )
2004.1.28 by v.ladeuil+lp at free
Merge bzr.dev. Including http modifications by "smart" related code
34
from bzrlib.transport import (
35
    get_transport,
36
    )
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
37
38
39
class WallRequestHandler(TestingHTTPRequestHandler):
40
    """Whatever request comes in, close the connection"""
41
42
    def handle_one_request(self):
43
        """Handle a single HTTP request, by abruptly closing the connection"""
44
        self.close_connection = 1
45
46
47
class BadStatusRequestHandler(TestingHTTPRequestHandler):
48
    """Whatever request comes in, returns a bad status"""
49
50
    def parse_request(self):
51
        """Fakes handling a single HTTP request, returns a bad status"""
52
        ignored = TestingHTTPRequestHandler.parse_request(self)
53
        try:
54
            self.send_response(0, "Bad status")
55
            self.end_headers()
56
        except socket.error, e:
2158.2.1 by v.ladeuil+lp at free
Windows tests cleanup.
57
            # We don't want to pollute the test results with
58
            # spurious server errors while test succeed. In our
2188.1.1 by Aaron Bentley
Windows tests cleanup. (Vincent Ladeuil)
59
            # case, it may occur that the test has already read
2158.2.1 by v.ladeuil+lp at free
Windows tests cleanup.
60
            # the 'Bad Status' and closed the socket while we are
61
            # still trying to send some headers... So the test is
2188.1.1 by Aaron Bentley
Windows tests cleanup. (Vincent Ladeuil)
62
            # ok, but if we raise the exception, the output is
2158.2.1 by v.ladeuil+lp at free
Windows tests cleanup.
63
            # dirty. So we don't raise, but we close the
64
            # connection, just to be safe :)
65
            spurious = [errno.EPIPE,
66
                        errno.ECONNRESET,
67
                        errno.ECONNABORTED,
68
                        ]
69
            if (len(e.args) > 0) and (e.args[0] in spurious):
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
70
                self.close_connection = 1
71
                pass
72
            else:
73
                raise
74
        return False
75
76
77
class InvalidStatusRequestHandler(TestingHTTPRequestHandler):
78
    """Whatever request comes in, returns am invalid status"""
79
80
    def parse_request(self):
81
        """Fakes handling a single HTTP request, returns a bad status"""
82
        ignored = TestingHTTPRequestHandler.parse_request(self)
83
        self.wfile.write("Invalid status line\r\n")
84
        return False
85
86
87
class BadProtocolRequestHandler(TestingHTTPRequestHandler):
88
    """Whatever request comes in, returns a bad protocol version"""
89
90
    def parse_request(self):
91
        """Fakes handling a single HTTP request, returns a bad status"""
92
        ignored = TestingHTTPRequestHandler.parse_request(self)
93
        # Returns an invalid protocol version, but curl just
94
        # ignores it and those cannot be tested.
95
        self.wfile.write("%s %d %s\r\n" % ('HTTP/0.0',
96
                                           404,
97
                                           'Look at my protocol version'))
98
        return False
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
100
2004.1.27 by v.ladeuil+lp at free
Fix bug #57644 by issuing an explicit error message.
101
class ForbiddenRequestHandler(TestingHTTPRequestHandler):
102
    """Whatever request comes in, returns a 403 code"""
103
104
    def parse_request(self):
105
        """Handle a single HTTP request, by replying we cannot handle it"""
106
        ignored = TestingHTTPRequestHandler.parse_request(self)
107
        self.send_error(403)
108
        return False
109
110
2004.1.28 by v.ladeuil+lp at free
Merge bzr.dev. Including http modifications by "smart" related code
111
class HTTPServerWithSmarts(HttpServer):
112
    """HTTPServerWithSmarts extends the HttpServer with POST methods that will
113
    trigger a smart server to execute with a transport rooted at the rootdir of
114
    the HTTP server.
115
    """
116
117
    def __init__(self):
118
        HttpServer.__init__(self, SmartRequestHandler)
119
120
121
class SmartRequestHandler(TestingHTTPRequestHandler):
122
    """Extend TestingHTTPRequestHandler to support smart client POSTs."""
123
124
    def do_POST(self):
125
        """Hand the request off to a smart server instance."""
126
        self.send_response(200)
127
        self.send_header("Content-type", "application/octet-stream")
2164.2.28 by Vincent Ladeuil
TestingHTTPServer.test_case_server renamed from test_case to avoid confusions.
128
        transport = get_transport(self.server.test_case_server._home_dir)
2004.1.28 by v.ladeuil+lp at free
Merge bzr.dev. Including http modifications by "smart" related code
129
        # TODO: We might like to support streaming responses.  1.0 allows no
130
        # Content-length in this case, so for integrity we should perform our
131
        # own chunking within the stream.
132
        # 1.1 allows chunked responses, and in this case we could chunk using
133
        # the HTTP chunking as this will allow HTTP persistence safely, even if
134
        # we have to stop early due to error, but we would also have to use the
135
        # HTTP trailer facility which may not be widely available.
136
        out_buffer = StringIO()
2018.5.150 by Andrew Bennetts
Tidy imports in HTTPTestUtil.py
137
        smart_protocol_request = protocol.SmartServerRequestProtocolOne(
2004.1.28 by v.ladeuil+lp at free
Merge bzr.dev. Including http modifications by "smart" related code
138
                transport, out_buffer.write)
139
        # if this fails, we should return 400 bad request, but failure is
140
        # failure for now - RBC 20060919
141
        data_length = int(self.headers['Content-Length'])
142
        # Perhaps there should be a SmartServerHTTPMedium that takes care of
143
        # feeding the bytes in the http request to the smart_protocol_request,
144
        # but for now it's simpler to just feed the bytes directly.
145
        smart_protocol_request.accept_bytes(self.rfile.read(data_length))
146
        assert smart_protocol_request.next_read_size() == 0, (
147
            "not finished reading, but all data sent to protocol.")
148
        self.send_header("Content-Length", str(len(out_buffer.getvalue())))
149
        self.end_headers()
150
        self.wfile.write(out_buffer.getvalue())
151
152
2004.1.29 by v.ladeuil+lp at free
New tests for http range requests handling.
153
class SingleRangeRequestHandler(TestingHTTPRequestHandler):
154
    """Always reply to range request as if they were single.
155
156
    Don't be explicit about it, just to annoy the clients.
157
    """
158
159
    def get_multiple_ranges(self, file, file_size, ranges):
160
        """Answer as if it was a single range request and ignores the rest"""
161
        (start, end) = ranges[0]
162
        return self.get_single_range(file, file_size, start, end)
163
164
165
class NoRangeRequestHandler(TestingHTTPRequestHandler):
166
    """Ignore range requests without notice"""
167
168
    # Just bypass the range handling done by TestingHTTPRequestHandler
169
    do_GET = SimpleHTTPRequestHandler.do_GET
170
171
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.
172
class TestCaseWithWebserver(TestCaseWithTransport):
173
    """A support class that provides readonly urls that are http://.
174
2004.3.3 by vila
Better (but still incomplete) design for bogus servers.
175
    This is done by forcing the readonly server to be an http
176
    one. This will currently fail if the primary transport is not
177
    backed by regular disk files.
1185.1.18 by Robert Collins
Lalo Martins remotebranch patch
178
    """
179
    def setUp(self):
1530.1.14 by Robert Collins
Remove duplicate web server from HTTPTestUtil.
180
        super(TestCaseWithWebserver, self).setUp()
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
181
        self.transport_readonly_server = HttpServer
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
182
183
184
class TestCaseWithTwoWebservers(TestCaseWithWebserver):
2164.2.13 by v.ladeuil+lp at free
Add tests for redirection. Preserve transport decorations.
185
    """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.
186
2164.2.25 by Vincent Ladeuil
Fix typos noticed by Aaron.
187
    We set up two webservers to allows various tests involving
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
188
    proxies or redirections from one server to the other.
189
    """
190
    def setUp(self):
191
        super(TestCaseWithTwoWebservers, self).setUp()
192
        self.transport_secondary_server = HttpServer
193
        self.__secondary_server = None
194
195
    def create_transport_secondary_server(self):
196
        """Create a transport server from class defined at init.
197
198
        This is mostly a hook for daughter classes.
199
        """
200
        return self.transport_secondary_server()
201
202
    def get_secondary_server(self):
203
        """Get the server instance for the secondary transport."""
204
        if self.__secondary_server is None:
205
            self.__secondary_server = self.create_transport_secondary_server()
206
            self.__secondary_server.setUp()
207
            self.addCleanup(self.__secondary_server.tearDown)
208
        return self.__secondary_server
209
210
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
211
class ProxyServer(HttpServer):
212
    """A proxy test server for http transports."""
213
214
    proxy_requests = True
2213.1.1 by v.ladeuil+lp at free
Workaround SimpleHTTPRequestHandler.translate_path limitation in
215
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
216
2164.2.13 by v.ladeuil+lp at free
Add tests for redirection. Preserve transport decorations.
217
class RedirectRequestHandler(TestingHTTPRequestHandler):
218
    """Redirect all request to the specified server"""
219
220
    def parse_request(self):
221
        """Redirect a single HTTP request to another host"""
222
        valid = TestingHTTPRequestHandler.parse_request(self)
223
        if valid:
2164.2.29 by Vincent Ladeuil
Test the http redirection at the request level even if it's not
224
            tcs = self.server.test_case_server
225
            code, target = tcs.is_redirected(self.path)
226
            if code is not None and target is not None:
227
                # Redirect as instructed
228
                self.send_response(code)
2164.2.16 by Vincent Ladeuil
Add tests.
229
                self.send_header('Location', target)
230
                self.end_headers()
231
                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
232
            else:
233
                # We leave the parent class serve the request
234
                pass
2164.2.13 by v.ladeuil+lp at free
Add tests for redirection. Preserve transport decorations.
235
        return valid
236
237
238
class HTTPServerRedirecting(HttpServer):
239
    """An HttpServer redirecting to another server """
240
2164.2.16 by Vincent Ladeuil
Add tests.
241
    def __init__(self, request_handler=RedirectRequestHandler):
2164.2.13 by v.ladeuil+lp at free
Add tests for redirection. Preserve transport decorations.
242
        HttpServer.__init__(self, request_handler)
2164.2.29 by Vincent Ladeuil
Test the http redirection at the request level even if it's not
243
        # redirections is a list of tuples (source, target, code)
244
        # - source is a regexp for the paths requested
245
        # - target is a replacement for re.sub describing where
246
        #   the request will be redirected
247
        # - code is the http error code associated to the
248
        #   redirection (301 permanent, 302 temporarry, etc
249
        self.redirections = []
250
251
    def redirect_to(self, host, port):
252
        """Redirect all requests to a specific host:port"""
253
        self.redirections = [('(.*)',
254
                              r'http://%s:%s\1' % (host, port) ,
255
                              301)]
256
257
    def is_redirected(self, path):
258
        """Is the path redirected by this server.
259
260
        :param path: the requested relative path
261
262
        :returns: a tuple (code, target) if a matching
263
             redirection is found, (None, None) otherwise.
264
        """
265
        code = None
266
        target = None
267
        for (rsource, rtarget, rcode) in self.redirections:
268
            target, match = re.subn(rsource, rtarget, path)
269
            if match:
270
                code = rcode
271
                break # The first match wins
272
            else:
273
                target = None
274
        return code, target
2164.2.13 by v.ladeuil+lp at free
Add tests for redirection. Preserve transport decorations.
275
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
276
2164.2.22 by Vincent Ladeuil
Take Aaron's review comments into account.
277
class TestCaseWithRedirectedWebserver(TestCaseWithTwoWebservers):
278
   """A support class providing redirections from one server to another.
279
2164.2.25 by Vincent Ladeuil
Fix typos noticed by Aaron.
280
   We set up two webservers to allows various tests involving
2164.2.22 by Vincent Ladeuil
Take Aaron's review comments into account.
281
   redirections.
282
   The 'old' server is redirected to the 'new' server.
283
   """
284
285
   def create_transport_secondary_server(self):
286
       """Create the secondary server redirecting to the primary server"""
287
       new = self.get_readonly_server()
288
       redirecting = HTTPServerRedirecting()
289
       redirecting.redirect_to(new.host, new.port)
290
       return redirecting
291
292
   def setUp(self):
293
       super(TestCaseWithRedirectedWebserver, self).setUp()
294
       # The redirections will point to the new server
295
       self.new_server = self.get_readonly_server()
296
       # The requests to the old server will be redirected
297
       self.old_server = self.get_secondary_server()
298
299
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
300
class AuthRequestHandler(TestingHTTPRequestHandler):
301
    """Requires an authentication to process requests.
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
302
303
    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.
304
    only use one authentication scheme (implemented by daughter
305
    classes).
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
306
    """
2363.4.8 by Vincent Ladeuil
Implement a basic auth HTTP server, rewrite tests accordingly.
307
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
308
    # The following attributes should be defined in the server
2420.1.10 by Vincent Ladeuil
Doc fixes.
309
    # - auth_header_sent: the header name sent to require auth
310
    # - auth_header_recv: the header received containing auth
311
    # - 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.
312
2363.4.8 by Vincent Ladeuil
Implement a basic auth HTTP server, rewrite tests accordingly.
313
    def do_GET(self):
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
314
        if self.authorized():
315
            return TestingHTTPRequestHandler.do_GET(self)
316
        else:
317
            # Note that we must update test_case_server *before*
318
            # sending the error or the client may try to read it
319
            # before we have sent the whole error back.
320
            tcs = self.server.test_case_server
321
            tcs.auth_required_errors += 1
322
            self.send_response(tcs.auth_error_code)
323
            self.send_header_auth_reqed()
324
            self.end_headers()
325
            return
2363.4.8 by Vincent Ladeuil
Implement a basic auth HTTP server, rewrite tests accordingly.
326
327
        TestingHTTPRequestHandler.do_GET(self)
328
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
329
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
330
class BasicAuthRequestHandler(AuthRequestHandler):
331
    """Implements the basic authentication of a request"""
332
333
    def authorized(self):
334
        tcs = self.server.test_case_server
335
        if tcs.auth_scheme != 'basic':
336
            return False
337
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
338
        auth_header = self.headers.get(tcs.auth_header_recv, None)
339
        if auth_header:
340
            scheme, raw_auth = auth_header.split(' ', 1)
341
            if scheme.lower() == tcs.auth_scheme:
342
                user, password = raw_auth.decode('base64').split(':')
343
                return tcs.authorized(user, password)
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
344
345
        return False
346
347
    def send_header_auth_reqed(self):
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
348
        tcs = self.server.test_case_server
349
        self.send_header(tcs.auth_header_sent,
350
                         'Basic realm="%s"' % tcs.auth_realm)
351
352
2420.1.19 by Vincent Ladeuil
Cosmetic changes.
353
# FIXME: We could send an Authentication-Info header too when
354
# the authentication is succesful
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
355
356
class DigestAuthRequestHandler(AuthRequestHandler):
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
357
    """Implements the digest authentication of a request.
358
359
    We need persistence for some attributes and that can't be
360
    achieved here since we get instantiated for each request. We
361
    rely on the DigestAuthServer to take care of them.
362
    """
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
363
364
    def authorized(self):
365
        tcs = self.server.test_case_server
366
        if tcs.auth_scheme != 'digest':
367
            return False
368
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
369
        auth_header = self.headers.get(tcs.auth_header_recv, None)
370
        if auth_header is None:
371
            return False
372
        scheme, auth = auth_header.split(None, 1)
373
        if scheme.lower() == tcs.auth_scheme:
374
            auth_dict = urllib2.parse_keqv_list(urllib2.parse_http_list(auth))
375
376
            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.
377
378
        return False
379
380
    def send_header_auth_reqed(self):
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
381
        tcs = self.server.test_case_server
382
        header = 'Digest realm="%s", ' % tcs.auth_realm
383
        header += 'nonce="%s", algorithm=%s, qop=auth' % (tcs.auth_nonce, 'MD5')
384
        self.send_header(tcs.auth_header_sent,header)
385
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
386
387
class AuthServer(HttpServer):
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
404
    def __init__(self, request_handler, auth_scheme):
2363.4.8 by Vincent Ladeuil
Implement a basic auth HTTP server, rewrite tests accordingly.
405
        HttpServer.__init__(self, request_handler)
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
406
        self.auth_scheme = auth_scheme
2363.4.8 by Vincent Ladeuil
Implement a basic auth HTTP server, rewrite tests accordingly.
407
        self.password_of = {}
2420.1.4 by Vincent Ladeuil
Add test checking the number of roundtrips due to 401 or 407 errors.
408
        self.auth_required_errors = 0
2363.4.8 by Vincent Ladeuil
Implement a basic auth HTTP server, rewrite tests accordingly.
409
410
    def add_user(self, user, password):
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
411
        """Declare a user with an associated password.
412
413
        password can be empty, use an empty string ('') in that
414
        case, not None.
415
        """
2363.4.8 by Vincent Ladeuil
Implement a basic auth HTTP server, rewrite tests accordingly.
416
        self.password_of[user] = password
417
418
    def authorized(self, user, password):
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
419
        """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
420
        expected_password = self.password_of.get(user, None)
421
        return expected_password is not None and password == expected_password
422
423
2420.1.19 by Vincent Ladeuil
Cosmetic changes.
424
# FIXME: There is some code duplication with
425
# _urllib2_wrappers.py.DigestAuthHandler. If that duplciation
426
# grows, it may require a refactoring. Also, we don't implement
427
# SHA algorithm nor MD5-sess here, but that does not seem worth
428
# it.
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
429
class DigestAuthServer(AuthServer):
430
    """A digest authentication server"""
431
2420.1.16 by Vincent Ladeuil
Handle nonce changes. Fix a nasty bug breaking the auth parameters sharing.
432
    auth_nonce = 'now!'
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
433
434
    def __init__(self, request_handler, auth_scheme):
435
        AuthServer.__init__(self, request_handler, auth_scheme)
436
437
    def digest_authorized(self, auth, command):
2420.1.16 by Vincent Ladeuil
Handle nonce changes. Fix a nasty bug breaking the auth parameters sharing.
438
        nonce = auth['nonce']
439
        if nonce != self.auth_nonce:
440
            return False
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
441
        realm = auth['realm']
442
        if realm != self.auth_realm:
443
            return False
444
        user = auth['username']
445
        if not self.password_of.has_key(user):
446
            return False
447
        algorithm= auth['algorithm']
448
        if algorithm != 'MD5':
449
            return False
450
        qop = auth['qop']
451
        if qop != 'auth':
452
            return False
453
454
        password = self.password_of[user]
455
456
        # Recalculate the response_digest to compare with the one
457
        # sent by the client
458
        A1 = '%s:%s:%s' % (user, realm, password)
459
        A2 = '%s:%s' % (command, auth['uri'])
460
461
        H = lambda x: md5.new(x).hexdigest()
462
        KD = lambda secret, data: H("%s:%s" % (secret, data))
463
464
        nonce_count = int(auth['nc'], 16)
465
466
        ncvalue = '%08x' % nonce_count
467
468
        cnonce = auth['cnonce']
469
        noncebit = '%s:%s:%s:%s:%s' % (nonce, ncvalue, cnonce, qop, H(A2))
470
        response_digest = KD(H(A1), noncebit)
471
472
        return response_digest == auth['response']
473
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
474
class HTTPAuthServer(AuthServer):
475
    """An HTTP server requiring authentication"""
476
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
477
    def init_http_auth(self):
478
        self.auth_header_sent = 'WWW-Authenticate'
479
        self.auth_header_recv = 'Authorization'
480
        self.auth_error_code = 401
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
481
482
483
class ProxyAuthServer(AuthServer):
484
    """A proxy server requiring authentication"""
485
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
486
    def init_proxy_auth(self):
487
        self.proxy_requests = True
488
        self.auth_header_sent = 'Proxy-Authenticate'
489
        self.auth_header_recv = 'Proxy-Authorization'
490
        self.auth_error_code = 407
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
491
492
493
class HTTPBasicAuthServer(HTTPAuthServer):
494
    """An HTTP server requiring basic authentication"""
495
496
    def __init__(self):
497
        HTTPAuthServer.__init__(self, BasicAuthRequestHandler, 'basic')
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
498
        self.init_http_auth()
499
500
501
class HTTPDigestAuthServer(DigestAuthServer, HTTPAuthServer):
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
502
    """An HTTP server requiring digest authentication"""
503
504
    def __init__(self):
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
505
        DigestAuthServer.__init__(self, DigestAuthRequestHandler, 'digest')
506
        self.init_http_auth()
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
507
508
509
class ProxyBasicAuthServer(ProxyAuthServer):
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
510
    """A proxy server requiring basic authentication"""
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
511
512
    def __init__(self):
513
        ProxyAuthServer.__init__(self, BasicAuthRequestHandler, 'basic')
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
514
        self.init_proxy_auth()
515
516
517
class ProxyDigestAuthServer(DigestAuthServer, ProxyAuthServer):
518
    """A proxy server requiring basic authentication"""
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
519
520
    def __init__(self):
521
        ProxyAuthServer.__init__(self, DigestAuthRequestHandler, 'digest')
2420.1.11 by Vincent Ladeuil
Implement digest authentication. Test suite passes. Tested against apache-2.x.
522
        self.init_proxy_auth()
2420.1.9 by Vincent Ladeuil
Refactor proxy and auth test classes. Tests failing for digest auth.
523
524