[Distutils] HTTP authentication support

David Smith davidsmith at acm.org
Thu Dec 28 05:25:34 CET 2006


Index: setuptools/package_index.py
===================================================================
--- setuptools/package_index.py	(????? 53164)
+++ setuptools/package_index.py	(?????)
@@ -20,9 +20,27 @@
 
 __all__ = [
     'PackageIndex', 'distros_for_url', 'parse_bdist_wininst',
-    'interpret_distro_name',
+    'interpret_distro_name', 'detect_auth_scheme',
 ]
 
+def detect_auth_scheme(url):
+    """Return (scheme, realm) or (None, None) for a URL"""
+    scheme, realm = None, None
+    try:
+        f = urllib2.urlopen(url)
+        f.close()
+    except urllib2.HTTPError, e:
+        if e.code == 401:
+            import re
+            realm = re.findall('realm="([^"]*)"', e.headers['WWW-Authenticate'])
+            if realm:
+                realm = realm[0]
+            else:
+                realm = None
+            scheme = e.headers['WWW-Authenticate'].split()[0].lower()
+        else:
+            raise e
+    return (scheme, realm)
 
 def parse_bdist_wininst(name):
     """Return (base,pyversion) or (None,None) for possible .exe name"""
@@ -166,7 +184,7 @@
     """A distribution index that scans web pages for download URLs"""
 
     def __init__(self, index_url="http://www.python.org/pypi", hosts=('*',),
-        sf_mirrors=None, *args, **kw
+        sf_mirrors=None, username=None, password=None, *args, **kw
     ):
         Environment.__init__(self,*args,**kw)
         self.index_url = index_url + "/"[:not index_url.endswith('/')]
@@ -182,6 +200,8 @@
                 self.sf_mirrors = map(str.strip, sf_mirrors)
         else:
             self.sf_mirrors = ()
+        self.username = username
+        self.password = password
 
 
     def _get_mirrors(self):
@@ -654,13 +674,42 @@
 
 
 
-    def open_url(self, url):
+    def open_url(self, url, username=None, password=None):
+        """username and/or password can be a string or a callable.
+        If a callable, they are called with the host, scheme, and realm."""
+
+        username = username or self.username
+        password = password or self.password
+
         if url.startswith('file:'):
             return local_open(url)
         try:
+            scheme, realm = detect_auth_scheme(url)
+            host = ""
+            if isinstance(url, urllib2.Request):
+                host = url.get_host()
+            else:
+                host = urlparse.urlparse(url)[1]
+            if not scheme:
+                return urllib2.urlopen(url)
+            else:
+                password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
+                if scheme.lower() == 'digest':
+                    auth = urllib2.HTTPDigestAuthHandler(password_mgr)
+                elif scheme.lower() == 'basic':
+                    auth = urllib2.HTTPBasicAuthHandler(password_mgr)
+                else:
+                    auth = None
+                if auth:
+                    if callable(username):
+                        username = username(host, scheme, realm)
+                    if callable(password):
+                        password = password(host, scheme, realm)
+                    password_mgr.add_password(realm, host, username, password)
+                opener = urllib2.build_opener(auth)
             request = urllib2.Request(url)
             request.add_header('User-Agent', user_agent)
-            return urllib2.urlopen(request)
+            return opener.open(request)
         except urllib2.HTTPError, v:
             return v
         except urllib2.URLError, v:
Index: setuptools/command/easy_install.py
===================================================================
--- setuptools/command/easy_install.py	(????? 53164)
+++ setuptools/command/easy_install.py	(?????)
@@ -9,7 +9,8 @@
 
 __ http://peak.telecommunity.com/DevCenter/EasyInstall
 """
-import sys, os.path, zipimport, shutil, tempfile, zipfile, re, stat, random
+import sys, os.path, zipimport, shutil, tempfile, zipfile, re, stat, \
+    random, getpass
 from glob import glob
 from setuptools import Command
 from setuptools.sandbox import run_setup
@@ -71,6 +72,8 @@
         ('no-deps', 'N', "don't install dependencies"),
         ('allow-hosts=', 'H', "pattern(s) that hostnames must match"),
         ('sf-mirrors=', None, "Sourceforge mirror(s) to use"),
+        ('username', 'u', "username for HTTP authentication"),
+        ('password', 'p', "password for HTTP authentication"),
     ]
     boolean_options = [
         'zip-ok', 'multi-version', 'exclude-scripts', 'upgrade', 'always-copy',
@@ -110,7 +113,17 @@
         self.distribution._set_command_options(
             self, self.distribution.get_option_dict('easy_install')
         )
+        self.username = self.prompt_username
+        self.password = self.prompt_password
 
+    def prompt_username(self, host, scheme, realm):
+        self.announce("Host %s requires %s authentication." % (host, scheme),
+                      level=9)
+        return raw_input("Username for %s@%s: " % (realm, host))
+
+    def prompt_password(self, host, scheme, realm):
+        return getpass.getpass("Password for %s@%s: " % (realm, host))
+
     def delete_blockers(self, blockers):
         for filename in blockers:
             if os.path.exists(filename) or os.path.islink(filename):
@@ -169,7 +182,8 @@
         if self.package_index is None:
             self.package_index = self.create_index(
                 self.index_url, search_path = self.shadow_path, hosts=hosts,
-                sf_mirrors = self.sf_mirrors
+                sf_mirrors = self.sf_mirrors, username=self.username,
+                password=self.password
             )
         self.local_index = Environment(self.shadow_path+sys.path)
 


More information about the Distutils-SIG mailing list