[Python-checkins] python/dist/src/Lib urllib2.py,1.55,1.56
jhylton at users.sourceforge.net
jhylton at users.sourceforge.net
Tue Oct 21 14:07:10 EDT 2003
Update of /cvsroot/python/python/dist/src/Lib
In directory sc8-pr-cvs1:/tmp/cvs-serv15593
Modified Files:
urllib2.py
Log Message:
Apply patch 823328 -- support for rfc 2617 digestion authentication.
The patch was tweaked slightly. It's get a different mechanism for
generating the cnonce which uses /dev/urandom when possible to
generate less-easily-guessed random input.
Also rearrange the imports so that they are alphabetical and
duplicates are eliminated.
Add a few XXX comments about things left undone and things that could
be improved.
Index: urllib2.py
===================================================================
RCS file: /cvsroot/python/python/dist/src/Lib/urllib2.py,v
retrieving revision 1.55
retrieving revision 1.56
diff -C2 -d -r1.55 -r1.56
*** urllib2.py 20 Oct 2003 14:01:50 -0000 1.55
--- urllib2.py 21 Oct 2003 18:07:07 -0000 1.56
***************
*** 88,107 ****
# check digest against correct (i.e. non-apache) implementation
! import socket
import httplib
import inspect
- import re
- import base64
- import urlparse
import md5
import mimetypes
import mimetools
import rfc822
! import ftplib
import sys
import time
! import os
! import gopherlib
! import posixpath
try:
--- 88,109 ----
# check digest against correct (i.e. non-apache) implementation
! import base64
! import ftplib
! import gopherlib
import httplib
import inspect
import md5
import mimetypes
import mimetools
+ import os
+ import posixpath
+ import random
+ import re
import rfc822
! import sha
! import socket
import sys
import time
! import urlparse
try:
***************
*** 110,119 ****
from StringIO import StringIO
- try:
- import sha
- except ImportError:
- # need 1.5.2 final
- sha = None
-
# not sure how many of these need to be gotten rid of
from urllib import unwrap, unquote, splittype, splithost, \
--- 112,115 ----
***************
*** 121,131 ****
splitattr, ftpwrapper, noheaders
! # support for proxies via environment variables
! from urllib import getproxies
!
! # support for FileHandler
! from urllib import localhost, url2pathname
! __version__ = "2.0a1"
_opener = None
--- 117,124 ----
splitattr, ftpwrapper, noheaders
! # support for FileHandler, proxies via environment variables
! from urllib import localhost, url2pathname, getproxies
! __version__ = "2.1"
_opener = None
***************
*** 681,685 ****
--- 674,701 ----
+ def randombytes(n):
+ """Return n random bytes."""
+ # Use /dev/urandom if it is available. Fall back to random module
+ # if not. It might be worthwhile to extend this function to use
+ # other platform-specific mechanisms for getting random bytes.
+ if os.path.exists("/dev/urandom"):
+ f = open("/dev/urandom")
+ s = f.read(n)
+ f.close()
+ return s
+ else:
+ L = [chr(random.randrange(0, 256)) for i in range(n)]
+ return "".join(L)
+
class AbstractDigestAuthHandler:
+ # Digest authentication is specified in RFC 2617.
+
+ # XXX The client does not inspect the Authentication-Info header
+ # in a successful response.
+
+ # XXX It should be possible to test this implementation against
+ # a mock server that just generates a static set of challenges.
+
+ # XXX qop="auth-int" supports is shaky
def __init__(self, passwd=None):
***************
*** 688,698 ****
self.passwd = passwd
self.add_password = self.passwd.add_password
! def http_error_auth_reqed(self, authreq, host, req, headers):
! authreq = headers.get(self.auth_header, None)
if authreq:
! kind = authreq.split()[0]
! if kind == 'Digest':
return self.retry_http_digest_auth(req, authreq)
def retry_http_digest_auth(self, req, auth):
--- 704,732 ----
self.passwd = passwd
self.add_password = self.passwd.add_password
+ self.retried = 0
+ self.nonce_count = 0
! def reset_retry_count(self):
! self.retried = 0
!
! def http_error_auth_reqed(self, auth_header, host, req, headers):
! authreq = headers.get(auth_header, None)
! if self.retried > 5:
! # Don't fail endlessly - if we failed once, we'll probably
! # fail a second time. Hm. Unless the Password Manager is
! # prompting for the information. Crap. This isn't great
! # but it's better than the current 'repeat until recursion
! # depth exceeded' approach <wink>
! raise HTTPError(req.get_full_url(), 401, "digest auth failed",
! headers, None)
! else:
! self.retried += 1
if authreq:
! scheme = authreq.split()[0]
! if scheme.lower() == 'digest':
return self.retry_http_digest_auth(req, authreq)
+ else:
+ raise ValueError("AbstractDigestAuthHandler doesn't know "
+ "about %s"%(scheme))
def retry_http_digest_auth(self, req, auth):
***************
*** 708,715 ****
--- 742,760 ----
return resp
+ def get_cnonce(self, nonce):
+ # The cnonce-value is an opaque
+ # quoted string value provided by the client and used by both client
+ # and server to avoid chosen plaintext attacks, to provide mutual
+ # authentication, and to provide some message integrity protection.
+ # This isn't a fabulous effort, but it's probably Good Enough.
+ dig = sha.new("%s:%s:%s:%s" % (self.nonce_count, nonce, time.ctime(),
+ randombytes(8))).hexdigest()
+ return dig[:16]
+
def get_authorization(self, req, chal):
try:
realm = chal['realm']
nonce = chal['nonce']
+ qop = chal.get('qop')
algorithm = chal.get('algorithm', 'MD5')
# mod_digest doesn't send an opaque, even though it isn't
***************
*** 723,728 ****
return None
! user, pw = self.passwd.find_user_password(realm,
! req.get_full_url())
if user is None:
return None
--- 768,772 ----
return None
! user, pw = self.passwd.find_user_password(realm, req.get_full_url())
if user is None:
return None
***************
*** 738,742 ****
# XXX selector: what about proxies and full urls
req.get_selector())
! respdig = KD(H(A1), "%s:%s" % (nonce, H(A2)))
# XXX should the partial digests be encoded too?
--- 782,797 ----
# XXX selector: what about proxies and full urls
req.get_selector())
! if qop == 'auth':
! self.nonce_count += 1
! ncvalue = '%08x' % self.nonce_count
! cnonce = self.get_cnonce(nonce)
! noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, H(A2))
! respdig = KD(H(A1), noncebit)
! elif qop is None:
! respdig = KD(H(A1), "%s:%s" % (nonce, H(A2)))
! else:
! # XXX handle auth-int.
! pass
!
# XXX should the partial digests be encoded too?
***************
*** 750,753 ****
--- 805,810 ----
if algorithm != 'MD5':
base = base + ', algorithm="%s"' % algorithm
+ if qop:
+ base = base + ', qop=auth, nc=%s, cnonce="%s"' % (ncvalue, cnonce)
return base
***************
*** 755,763 ****
# lambdas assume digest modules are imported at the top level
if algorithm == 'MD5':
! H = lambda x, e=encode_digest:e(md5.new(x).digest())
elif algorithm == 'SHA':
! H = lambda x, e=encode_digest:e(sha.new(x).digest())
# XXX MD5-sess
! KD = lambda s, d, H=H: H("%s:%s" % (s, d))
return H, KD
--- 812,820 ----
# lambdas assume digest modules are imported at the top level
if algorithm == 'MD5':
! H = lambda x: md5.new(x).hexdigest()
elif algorithm == 'SHA':
! H = lambda x: sha.new(x).hexdigest()
# XXX MD5-sess
! KD = lambda s, d: H("%s:%s" % (s, d))
return H, KD
***************
*** 778,782 ****
def http_error_401(self, req, fp, code, msg, headers):
host = urlparse.urlparse(req.get_full_url())[1]
! self.http_error_auth_reqed('www-authenticate', host, req, headers)
--- 835,842 ----
def http_error_401(self, req, fp, code, msg, headers):
host = urlparse.urlparse(req.get_full_url())[1]
! retry = self.http_error_auth_reqed('www-authenticate',
! host, req, headers)
! self.reset_retry_count()
! return retry
***************
*** 787,802 ****
def http_error_407(self, req, fp, code, msg, headers):
host = req.get_host()
! self.http_error_auth_reqed('proxy-authenticate', host, req, headers)
!
!
! def encode_digest(digest):
! hexrep = []
! for c in digest:
! n = (ord(c) >> 4) & 0xf
! hexrep.append(hex(n)[-1])
! n = ord(c) & 0xf
! hexrep.append(hex(n)[-1])
! return ''.join(hexrep)
!
class AbstractHTTPHandler(BaseHandler):
--- 847,854 ----
def http_error_407(self, req, fp, code, msg, headers):
host = req.get_host()
! retry = self.http_error_auth_reqed('proxy-authenticate',
! host, req, headers)
! self.reset_retry_count()
! return retry
class AbstractHTTPHandler(BaseHandler):
More information about the Python-checkins
mailing list