309
297
self.old_server = self.get_secondary_server()
300
class AuthRequestHandler(TestingHTTPRequestHandler):
301
"""Requires an authentication to process requests.
303
This is intended to be used with a server that always and
304
only use one authentication scheme (implemented by daughter
308
# The following attributes should be defined in the server
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
314
if self.authorized():
315
return TestingHTTPRequestHandler.do_GET(self)
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()
327
TestingHTTPRequestHandler.do_GET(self)
330
class BasicAuthRequestHandler(AuthRequestHandler):
331
"""Implements the basic authentication of a request"""
333
def authorized(self):
334
tcs = self.server.test_case_server
335
if tcs.auth_scheme != 'basic':
338
auth_header = self.headers.get(tcs.auth_header_recv, None)
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)
347
def send_header_auth_reqed(self):
348
tcs = self.server.test_case_server
349
self.send_header(tcs.auth_header_sent,
350
'Basic realm="%s"' % tcs.auth_realm)
353
# FIXME: We could send an Authentication-Info header too when
354
# the authentication is succesful
356
class DigestAuthRequestHandler(AuthRequestHandler):
357
"""Implements the digest authentication of a request.
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.
364
def authorized(self):
365
tcs = self.server.test_case_server
366
if tcs.auth_scheme != 'digest':
369
auth_header = self.headers.get(tcs.auth_header_recv, None)
370
if auth_header is None:
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))
376
return tcs.digest_authorized(auth_dict, self.command)
380
def send_header_auth_reqed(self):
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)
387
class AuthServer(HttpServer):
388
"""Extends HttpServer with a dictionary of passwords.
390
This is used as a base class for various schemes which should
391
all use or redefined the associated AuthRequestHandler.
393
Note that no users are defined by default, so add_user should
394
be called before issuing the first request.
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
402
auth_realm = "Thou should not pass"
404
def __init__(self, request_handler, auth_scheme):
405
HttpServer.__init__(self, request_handler)
406
self.auth_scheme = auth_scheme
407
self.password_of = {}
408
self.auth_required_errors = 0
410
def add_user(self, user, password):
411
"""Declare a user with an associated password.
413
password can be empty, use an empty string ('') in that
416
self.password_of[user] = password
418
def authorized(self, user, password):
419
"""Check that the given user provided the right password"""
420
expected_password = self.password_of.get(user, None)
421
return expected_password is not None and password == expected_password
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
429
class DigestAuthServer(AuthServer):
430
"""A digest authentication server"""
434
def __init__(self, request_handler, auth_scheme):
435
AuthServer.__init__(self, request_handler, auth_scheme)
437
def digest_authorized(self, auth, command):
438
nonce = auth['nonce']
439
if nonce != self.auth_nonce:
441
realm = auth['realm']
442
if realm != self.auth_realm:
444
user = auth['username']
445
if not self.password_of.has_key(user):
447
algorithm= auth['algorithm']
448
if algorithm != 'MD5':
454
password = self.password_of[user]
456
# Recalculate the response_digest to compare with the one
458
A1 = '%s:%s:%s' % (user, realm, password)
459
A2 = '%s:%s' % (command, auth['uri'])
461
H = lambda x: md5.new(x).hexdigest()
462
KD = lambda secret, data: H("%s:%s" % (secret, data))
464
nonce_count = int(auth['nc'], 16)
466
ncvalue = '%08x' % nonce_count
468
cnonce = auth['cnonce']
469
noncebit = '%s:%s:%s:%s:%s' % (nonce, ncvalue, cnonce, qop, H(A2))
470
response_digest = KD(H(A1), noncebit)
472
return response_digest == auth['response']
474
class HTTPAuthServer(AuthServer):
475
"""An HTTP server requiring authentication"""
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
483
class ProxyAuthServer(AuthServer):
484
"""A proxy server requiring authentication"""
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
493
class HTTPBasicAuthServer(HTTPAuthServer):
494
"""An HTTP server requiring basic authentication"""
497
HTTPAuthServer.__init__(self, BasicAuthRequestHandler, 'basic')
498
self.init_http_auth()
501
class HTTPDigestAuthServer(DigestAuthServer, HTTPAuthServer):
502
"""An HTTP server requiring digest authentication"""
505
DigestAuthServer.__init__(self, DigestAuthRequestHandler, 'digest')
506
self.init_http_auth()
509
class ProxyBasicAuthServer(ProxyAuthServer):
510
"""A proxy server requiring basic authentication"""
513
ProxyAuthServer.__init__(self, BasicAuthRequestHandler, 'basic')
514
self.init_proxy_auth()
517
class ProxyDigestAuthServer(DigestAuthServer, ProxyAuthServer):
518
"""A proxy server requiring basic authentication"""
521
ProxyAuthServer.__init__(self, DigestAuthRequestHandler, 'digest')
522
self.init_proxy_auth()