~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/http_utils.py

  • Committer: Neil Martinsen-Burrell
  • Date: 2008-06-19 06:57:22 UTC
  • mto: (3505.1.1 ianc-integration)
  • mto: This revision was merged to the branch mainline in revision 3506.
  • Revision ID: nmb@wartburg.edu-20080619065722-dboa3ko7ap8dcf2p
Fix bug 228058: user names with @ signs should work

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
from cStringIO import StringIO
 
18
import errno
 
19
import md5
18
20
import re
 
21
import sha
 
22
import socket
 
23
import threading
 
24
import time
19
25
import urllib2
20
 
 
 
26
import urlparse
21
27
 
22
28
from bzrlib import (
23
29
    errors,
24
 
    osutils,
25
30
    tests,
26
31
    transport,
27
32
    )
28
 
from bzrlib.smart import (
29
 
    medium,
30
 
    )
 
33
from bzrlib.smart import medium, protocol
31
34
from bzrlib.tests import http_server
32
 
from bzrlib.transport import chroot
33
35
 
34
36
 
35
37
class HTTPServerWithSmarts(http_server.HttpServer):
44
46
 
45
47
 
46
48
class SmartRequestHandler(http_server.TestingHTTPRequestHandler):
47
 
    """Extend TestingHTTPRequestHandler to support smart client POSTs.
48
 
 
49
 
    XXX: This duplicates a fair bit of the logic in bzrlib.transport.http.wsgi.
50
 
    """
 
49
    """Extend TestingHTTPRequestHandler to support smart client POSTs."""
51
50
 
52
51
    def do_POST(self):
53
52
        """Hand the request off to a smart server instance."""
54
 
        backing = transport.get_transport_from_path(
55
 
            self.server.test_case_server._home_dir)
56
 
        chroot_server = chroot.ChrootServer(backing)
57
 
        chroot_server.start_server()
58
 
        try:
59
 
            t = transport.get_transport_from_url(chroot_server.get_url())
60
 
            self.do_POST_inner(t)
61
 
        finally:
62
 
            chroot_server.stop_server()
63
 
 
64
 
    def do_POST_inner(self, chrooted_transport):
65
53
        self.send_response(200)
66
54
        self.send_header("Content-type", "application/octet-stream")
67
 
        if not self.path.endswith('.bzr/smart'):
68
 
            raise AssertionError(
69
 
                'POST to path not ending in .bzr/smart: %r' % (self.path,))
70
 
        t = chrooted_transport.clone(self.path[:-len('.bzr/smart')])
 
55
        t = transport.get_transport(self.server.test_case_server._home_dir)
71
56
        # if this fails, we should return 400 bad request, but failure is
72
57
        # failure for now - RBC 20060919
73
58
        data_length = int(self.headers['Content-Length'])
102
87
    one. This will currently fail if the primary transport is not
103
88
    backed by regular disk files.
104
89
    """
105
 
 
106
 
    # These attributes can be overriden or parametrized by daughter clasess if
107
 
    # needed, but must exist so that the create_transport_readonly_server()
108
 
    # method (or any method creating an http(s) server) can propagate it.
109
 
    _protocol_version = None
110
 
    _url_protocol = 'http'
111
 
 
112
90
    def setUp(self):
113
91
        super(TestCaseWithWebserver, self).setUp()
114
92
        self.transport_readonly_server = http_server.HttpServer
115
93
 
116
 
    def create_transport_readonly_server(self):
117
 
        server = self.transport_readonly_server(
118
 
            protocol_version=self._protocol_version)
119
 
        server._url_protocol = self._url_protocol
120
 
        return server
121
 
 
122
94
 
123
95
class TestCaseWithTwoWebservers(TestCaseWithWebserver):
124
96
    """A support class providing readonly urls on two servers that are http://.
136
108
 
137
109
        This is mostly a hook for daughter classes.
138
110
        """
139
 
        server = self.transport_secondary_server(
140
 
            protocol_version=self._protocol_version)
141
 
        server._url_protocol = self._url_protocol
142
 
        return server
 
111
        return self.transport_secondary_server()
143
112
 
144
113
    def get_secondary_server(self):
145
114
        """Get the server instance for the secondary transport."""
146
115
        if self.__secondary_server is None:
147
116
            self.__secondary_server = self.create_transport_secondary_server()
148
 
            self.start_server(self.__secondary_server)
 
117
            self.__secondary_server.setUp()
 
118
            self.addCleanup(self.__secondary_server.tearDown)
149
119
        return self.__secondary_server
150
120
 
151
 
    def get_secondary_url(self, relpath=None):
152
 
        base = self.get_secondary_server().get_url()
153
 
        return self._adjust_url(base, relpath)
154
 
 
155
 
    def get_secondary_transport(self, relpath=None):
156
 
        t = transport.get_transport_from_url(self.get_secondary_url(relpath))
157
 
        self.assertTrue(t.is_readonly())
158
 
        return t
159
 
 
160
121
 
161
122
class ProxyServer(http_server.HttpServer):
162
123
    """A proxy test server for http transports."""
236
197
   The 'old' server is redirected to the 'new' server.
237
198
   """
238
199
 
 
200
   def create_transport_secondary_server(self):
 
201
       """Create the secondary server redirecting to the primary server"""
 
202
       new = self.get_readonly_server()
 
203
       redirecting = HTTPServerRedirecting()
 
204
       redirecting.redirect_to(new.host, new.port)
 
205
       return redirecting
 
206
 
239
207
   def setUp(self):
240
208
       super(TestCaseWithRedirectedWebserver, self).setUp()
241
209
       # The redirections will point to the new server
242
210
       self.new_server = self.get_readonly_server()
243
 
       # The requests to the old server will be redirected to the new server
 
211
       # The requests to the old server will be redirected
244
212
       self.old_server = self.get_secondary_server()
245
213
 
246
 
   def create_transport_secondary_server(self):
247
 
       """Create the secondary server redirecting to the primary server"""
248
 
       new = self.get_readonly_server()
249
 
       redirecting = HTTPServerRedirecting(
250
 
           protocol_version=self._protocol_version)
251
 
       redirecting.redirect_to(new.host, new.port)
252
 
       redirecting._url_protocol = self._url_protocol
253
 
       return redirecting
254
 
 
255
 
   def get_old_url(self, relpath=None):
256
 
        base = self.old_server.get_url()
257
 
        return self._adjust_url(base, relpath)
258
 
 
259
 
   def get_old_transport(self, relpath=None):
260
 
        t = transport.get_transport_from_url(self.get_old_url(relpath))
261
 
        self.assertTrue(t.is_readonly())
262
 
        return t
263
 
 
264
 
   def get_new_url(self, relpath=None):
265
 
        base = self.new_server.get_url()
266
 
        return self._adjust_url(base, relpath)
267
 
 
268
 
   def get_new_transport(self, relpath=None):
269
 
        t = transport.get_transport_from_url(self.get_new_url(relpath))
270
 
        self.assertTrue(t.is_readonly())
271
 
        return t
272
 
 
273
214
 
274
215
class AuthRequestHandler(http_server.TestingHTTPRequestHandler):
275
216
    """Requires an authentication to process requests.
284
225
    # - auth_header_recv: the header received containing auth
285
226
    # - auth_error_code: the error code to indicate auth required
286
227
 
287
 
    def _require_authentication(self):
288
 
        # Note that we must update test_case_server *before*
289
 
        # sending the error or the client may try to read it
290
 
        # before we have sent the whole error back.
291
 
        tcs = self.server.test_case_server
292
 
        tcs.auth_required_errors += 1
293
 
        self.send_response(tcs.auth_error_code)
294
 
        self.send_header_auth_reqed()
295
 
        # We do not send a body
296
 
        self.send_header('Content-Length', '0')
297
 
        self.end_headers()
298
 
        return
299
 
 
300
228
    def do_GET(self):
301
229
        if self.authorized():
302
230
            return http_server.TestingHTTPRequestHandler.do_GET(self)
303
231
        else:
304
 
            return self._require_authentication()
305
 
 
306
 
    def do_HEAD(self):
307
 
        if self.authorized():
308
 
            return http_server.TestingHTTPRequestHandler.do_HEAD(self)
309
 
        else:
310
 
            return self._require_authentication()
 
232
            # Note that we must update test_case_server *before*
 
233
            # sending the error or the client may try to read it
 
234
            # before we have sent the whole error back.
 
235
            tcs = self.server.test_case_server
 
236
            tcs.auth_required_errors += 1
 
237
            self.send_response(tcs.auth_error_code)
 
238
            self.send_header_auth_reqed()
 
239
            # We do not send a body
 
240
            self.send_header('Content-Length', '0')
 
241
            self.end_headers()
 
242
            return
311
243
 
312
244
 
313
245
class BasicAuthRequestHandler(AuthRequestHandler):
346
278
 
347
279
    def authorized(self):
348
280
        tcs = self.server.test_case_server
 
281
        if tcs.auth_scheme != 'digest':
 
282
            return False
349
283
 
350
284
        auth_header = self.headers.get(tcs.auth_header_recv, None)
351
285
        if auth_header is None:
366
300
        self.send_header(tcs.auth_header_sent,header)
367
301
 
368
302
 
369
 
class DigestAndBasicAuthRequestHandler(DigestAuthRequestHandler):
370
 
    """Implements a digest and basic authentication of a request.
371
 
 
372
 
    I.e. the server proposes both schemes and the client should choose the best
373
 
    one it can handle, which, in that case, should be digest, the only scheme
374
 
    accepted here.
375
 
    """
376
 
 
377
 
    def send_header_auth_reqed(self):
378
 
        tcs = self.server.test_case_server
379
 
        self.send_header(tcs.auth_header_sent,
380
 
                         'Basic realm="%s"' % tcs.auth_realm)
381
 
        header = 'Digest realm="%s", ' % tcs.auth_realm
382
 
        header += 'nonce="%s", algorithm="%s", qop="auth"' % (tcs.auth_nonce,
383
 
                                                              'MD5')
384
 
        self.send_header(tcs.auth_header_sent,header)
385
 
 
386
 
 
387
303
class AuthServer(http_server.HttpServer):
388
304
    """Extends HttpServer with a dictionary of passwords.
389
305
 
462
378
        A1 = '%s:%s:%s' % (user, realm, password)
463
379
        A2 = '%s:%s' % (command, auth['uri'])
464
380
 
465
 
        H = lambda x: osutils.md5(x).hexdigest()
 
381
        H = lambda x: md5.new(x).hexdigest()
466
382
        KD = lambda secret, data: H("%s:%s" % (secret, data))
467
383
 
468
384
        nonce_count = int(auth['nc'], 16)
475
391
 
476
392
        return response_digest == auth['response']
477
393
 
478
 
 
479
394
class HTTPAuthServer(AuthServer):
480
395
    """An HTTP server requiring authentication"""
481
396
 
513
428
        self.init_http_auth()
514
429
 
515
430
 
516
 
class HTTPBasicAndDigestAuthServer(DigestAuthServer, HTTPAuthServer):
517
 
    """An HTTP server requiring basic or digest authentication"""
518
 
 
519
 
    def __init__(self, protocol_version=None):
520
 
        DigestAuthServer.__init__(self, DigestAndBasicAuthRequestHandler,
521
 
                                  'basicdigest',
522
 
                                  protocol_version=protocol_version)
523
 
        self.init_http_auth()
524
 
        # We really accept Digest only
525
 
        self.auth_scheme = 'digest'
526
 
 
527
 
 
528
431
class ProxyBasicAuthServer(ProxyAuthServer):
529
432
    """A proxy server requiring basic authentication"""
530
433
 
543
446
        self.init_proxy_auth()
544
447
 
545
448
 
546
 
class ProxyBasicAndDigestAuthServer(DigestAuthServer, ProxyAuthServer):
547
 
    """An proxy server requiring basic or digest authentication"""
548
 
 
549
 
    def __init__(self, protocol_version=None):
550
 
        DigestAuthServer.__init__(self, DigestAndBasicAuthRequestHandler,
551
 
                                  'basicdigest',
552
 
                                  protocol_version=protocol_version)
553
 
        self.init_proxy_auth()
554
 
        # We really accept Digest only
555
 
        self.auth_scheme = 'digest'
556
 
 
557