diff --git a/ndg/httpsclient/get.py b/ndg/httpsclient/get.py index bd4959f..2b4a790 100644 --- a/ndg/httpsclient/get.py +++ b/ndg/httpsclient/get.py @@ -6,12 +6,10 @@ import urllib2 import urlparse -from OpenSSL import SSL +from ndg.httpsclient.urllib2_build_opener import build_opener +from ndg.httpsclient.https import HTTPSContextHandler +from ndg.httpsclient import ssl_context_util -from urllib2pyopenssl.urllib2_build_opener import urllib2_build_opener -from urllib2pyopenssl.https import HTTPSContextHandler -import urllib2pyopenssl.ssl_context_util as ssl_context_util -from urllib2pyopenssl.ssl_peer_verification import ServerSSLCertVerification def fetch_from_url(url, config): """Returns data retrieved from a URL. @@ -19,7 +17,7 @@ def fetch_from_url(url, config): @param config - configuration @return data retrieved from URL or None """ - (return_code, return_message, response) = open_url(url, config) + return_code, return_message, response = open_url(url, config) if return_code and return_code == httplib.OK: return_data = response.read() response.close() @@ -38,14 +36,15 @@ def fetch_from_url_to_file(url, config, output_file): boolean indicating whether access was successful ) """ - (return_code, return_message, response) = open_url(url, config) + return_code, return_message, response = open_url(url, config) if return_code == httplib.OK: return_data = response.read() response.close() outfile = open(output_file, "w") outfile.write(return_data) outfile.close() - return (return_code, return_message, (return_code == httplib.OK)) + return return_code, return_message, return_code == httplib.OK + def open_url(url, config): """Attempts to open a connection to a specified URL. @@ -67,18 +66,20 @@ def open_url(url, config): if config.debug: http_handler = urllib2.HTTPHandler(debuglevel=debuglevel) - https_handler = HTTPSContextHandler(config.ssl_context, debuglevel=debuglevel) + https_handler = HTTPSContextHandler(config.ssl_context, + debuglevel=debuglevel) handlers.extend([http_handler, https_handler]) - # Explicitly remove proxy handling if the host is one listed in the value of the no_proxy - # environment variable because urllib2 does use proxy settings set via http_proxy and - # https_proxy, but does not take the no_proxy value into account. + # Explicitly remove proxy handling if the host is one listed in the value of + # the no_proxy environment variable because urllib2 does use proxy settings + # set via http_proxy and https_proxy, but does not take the no_proxy value + # into account. if not _should_use_proxy(url): handlers.append(urllib2.ProxyHandler({})) if config.debug: print "Not using proxy" - opener = urllib2_build_opener(config.ssl_context, *handlers) + opener = build_opener(config.ssl_context, *handlers) # Open the URL and check the response. return_code = 0 @@ -105,6 +106,7 @@ def open_url(url, config): print exc.__class__, exc.__str__() return (return_code, return_message, response) + def _should_use_proxy(url): """Determines whether a proxy should be used to open a connection to the specified URL, based on the value of the no_proxy environment variable. @@ -119,6 +121,7 @@ def _should_use_proxy(url): return True + class Configuration(object): """Checker configuration. """ @@ -131,6 +134,7 @@ def __init__(self, ssl_context, debug): self.ssl_context = ssl_context self.debug = debug + def main(): '''Utility to fetch data using HTTP or HTTPS GET from a specified URL. ''' @@ -141,36 +145,58 @@ def main(): parser.add_option("-c", "--certificate", dest="cert_file", metavar="FILE", default=os.path.expanduser("~/credentials.pem"), help="Certificate file.") - parser.add_option("-t", "--ca-certificate-dir", dest="ca_dir", metavar="PATH", + parser.add_option("-t", "--ca-certificate-dir", dest="ca_dir", + metavar="PATH", default=None, help="Trusted CA certificate file directory.") - parser.add_option("-d", "--debug", action="store_true", dest="debug", default=False, + parser.add_option("-d", "--debug", action="store_true", dest="debug", + default=False, help="Print debug information.") parser.add_option("-f", "--fetch", dest="output_file", metavar="FILE", default=None, help="Output file.") - parser.add_option("-v", "--verify-peer", action="store_true", dest="verify_peer", default=False, + parser.add_option("-v", "--verify-peer", action="store_true", + dest="verify_peer", default=False, help="Verify peer certificate.") (options, args) = parser.parse_args() if len(args) != 1: parser.error("Incorrect number of arguments") url = args[0] - # If a private key file is not specified, the key is assumed to be stored in the certificate file. + + if options.key_file and os.path.exists(options.key_file): + key_file = options.key_file + else: + key_file = None + + if options.cert_file and os.path.exists(options.cert_file): + cert_file = options.cert_file + else: + cert_file = None + + if options.ca_dir and os.path.exists(options.ca_dir): + ca_dir = options.ca_dir + else: + ca_dir = None + + # If a private key file is not specified, the key is assumed to be stored in + # the certificate file. ssl_context = ssl_context_util.make_ssl_context( - options.key_file if options.key_file and os.path.exists(options.key_file) else None, - options.cert_file if options.cert_file and os.path.exists(options.cert_file) else None, + key_file, + cert_file, None, - options.ca_dir if options.ca_dir and os.path.exists(options.ca_dir) else None, + ca_dir, options.verify_peer, url) config = Configuration(ssl_context, options.debug) if options.output_file: - (return_code, return_message, success) = fetch_from_url_to_file(url, config, - options.output_file) - print return_code, return_message + return_code, return_message, success = fetch_from_url_to_file(url, + config, + options.output_file) + print(return_code, return_message) else: data = fetch_from_url(url, config) - print data + print(data) + if __name__=='__main__': logging.basicConfig() diff --git a/ndg/httpsclient/https.py b/ndg/httpsclient/https.py index a9d71dc..f1eaf8d 100644 --- a/ndg/httpsclient/https.py +++ b/ndg/httpsclient/https.py @@ -17,7 +17,7 @@ from OpenSSL import SSL -from urllib2pyopenssl.ssl_socket import SSLSocket +from ndg.httpsclient.ssl_socket import SSLSocket log = logging.getLogger(__name__) diff --git a/ndg/httpsclient/ssl_context_util.py b/ndg/httpsclient/ssl_context_util.py index c2d7246..f9bac12 100644 --- a/ndg/httpsclient/ssl_context_util.py +++ b/ndg/httpsclient/ssl_context_util.py @@ -2,7 +2,7 @@ from OpenSSL import SSL -from urllib2pyopenssl.ssl_peer_verification import ServerSSLCertVerification +from ndg.httpsclient.ssl_peer_verification import ServerSSLCertVerification class SSlContextConfig(object): """ diff --git a/ndg/httpsclient/ssl_peer_verification.py b/ndg/httpsclient/ssl_peer_verification.py index a0f0b07..8d7b069 100644 --- a/ndg/httpsclient/ssl_peer_verification.py +++ b/ndg/httpsclient/ssl_peer_verification.py @@ -30,9 +30,9 @@ class ServerSSLCertVerification(object): PARSER_RE_STR = '/(%s)=' % '|'.join(DN_LUT.keys() + DN_LUT.values()) PARSER_RE = re.compile(PARSER_RE_STR) - __slots__ = ('__hostname', '__certDN', '__serverCNPrefixes') + __slots__ = ('__hostname', '__certDN') - def __init__(self, certDN=None, hostname=None, serverCNPrefixes=None): + def __init__(self, certDN=None, hostname=None): """Override parent class __init__ to enable setting of certDN setting @@ -43,7 +43,6 @@ def __init__(self, certDN=None, hostname=None, serverCNPrefixes=None): """ self.__certDN = None self.__hostname = None - self.__serverCNPrefixes = None if certDN is not None: self.certDN = certDN @@ -51,11 +50,6 @@ def __init__(self, certDN=None, hostname=None, serverCNPrefixes=None): if hostname is not None: self.hostname = hostname - if serverCNPrefixes is not None: - self.serverCNPrefixes = serverCNPrefixes - else: - self.serverCNPrefixes = [''] - def __call__(self, connection, peerCert, errorStatus, errorDepth, preverifyOK): """Verify server certificate @@ -167,16 +161,3 @@ def _setHostname(self, val): hostname = property(fget=_getHostname, fset=_setHostname, doc="hostname of server") - - def _getServerCNPrefixes(self): - return self.__serverCNPrefixes - - def _setServerCNPrefixes(self, val): - if not isinstance(val, list): - raise TypeError("Expecting string type for ServerCNPrefixes " - "attribute") - self.__serverCNPrefixes = val - - serverCNPrefixes = property(fget=_getServerCNPrefixes, - fset=_setServerCNPrefixes, - doc="Server CN Prefixes") diff --git a/ndg/httpsclient/test/__init__.py b/ndg/httpsclient/test/__init__.py index a05fd7f..f97756b 100644 --- a/ndg/httpsclient/test/__init__.py +++ b/ndg/httpsclient/test/__init__.py @@ -8,4 +8,8 @@ __copyright__ = "(C) 2012 Science and Technology Facilities Council" __license__ = "BSD - see LICENSE file in top-level directory" __contact__ = "Philip.Kershaw@stfc.ac.uk" -__revision__ = '$Id$' \ No newline at end of file +__revision__ = '$Id$' +class Constants(object): + PORT = 4443 + HOSTNAME = 'localhost' + TEST_URI = 'https://%s:%d' % (HOSTNAME, PORT) \ No newline at end of file diff --git a/ndg/httpsclient/test/test.py b/ndg/httpsclient/test/test.py index c8b77f3..28c6dd2 100644 --- a/ndg/httpsclient/test/test.py +++ b/ndg/httpsclient/test/test.py @@ -4,8 +4,8 @@ @author: philipkershaw ''' import unittest -from urllib2pyopenssl.urllib2_build_opener import urllib2_build_opener -from urllib2pyopenssl.https import HTTPSConnection +from ndg.httpsclient.urllib2_build_opener import urllib2_build_opener +from ndg.httpsclient.https import HTTPSConnection class Urllib2PyOpenSslTestCase(unittest.TestCase): diff --git a/ndg/httpsclient/test/test_https.py b/ndg/httpsclient/test/test_https.py new file mode 100644 index 0000000..aa63fa9 --- /dev/null +++ b/ndg/httpsclient/test/test_https.py @@ -0,0 +1,27 @@ +''' +Created on Jan 6, 2012 + +@author: philipkershaw +''' +import logging +logging.basicConfig(level=logging.DEBUG) +import unittest + +from ndg.httpsclient.test import Constants +from ndg.httpsclient.https import HTTPSConnection + + +class TestHTTPSConnection(unittest.TestCase): + + + def test01(self): + conn = HTTPSConnection(Constants.HOSTNAME, port=Constants.PORT) + conn.connect() + conn.request('GET', '/') + resp = conn.getresponse() + print('Response = %s' % resp.read()) + conn.close() + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/ndg/httpsclient/test_get.py b/ndg/httpsclient/test_get.py new file mode 100644 index 0000000..e7b2136 --- /dev/null +++ b/ndg/httpsclient/test_get.py @@ -0,0 +1,17 @@ +''' +Created on Jan 6, 2012 + +@author: philipkershaw +''' +import unittest + + +class TestGetModule(unittest.TestCase): + + + def test01(self): + pass + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/ndg/httpsclient/urllib2_build_opener.py b/ndg/httpsclient/urllib2_build_opener.py index 79b5c2a..93a6e91 100644 --- a/ndg/httpsclient/urllib2_build_opener.py +++ b/ndg/httpsclient/urllib2_build_opener.py @@ -13,12 +13,12 @@ HTTPDefaultErrorHandler, HTTPRedirectHandler, FTPHandler, FileHandler, HTTPErrorProcessor) -from urllib2pyopenssl.https import HTTPSContextHandler +from ndg.httpsclient.https import HTTPSContextHandler log = logging.getLogger(__name__) # Copied from urllib2 with modifications for ssl -def urllib2_build_opener(ssl_context=None, *handlers): +def build_opener(ssl_context=None, *handlers): """Create an opener object from a list of handlers. The opener will use several default handlers, including support diff --git a/setup.py b/setup.py index a2eabdc..17a38aa 100644 --- a/setup.py +++ b/setup.py @@ -6,17 +6,37 @@ from setuptools import setup, find_packages setup( - name='urllib2pyopenssl', + name='ndg_httpsclient', version="0.1.0", - description='Provides HTTPS with urllib2 using PyOpenSSL', + description='Provides HTTPS for httplib and urllib2 using PyOpenSSL', author='Richard Wilkinson', long_description=open('README').read(), license='BSD - See LICENCE file for details', namespace_packages=['ndg'], packages=find_packages(), + classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Environment :: Web Environment', + 'Intended Audience :: End Users/Desktop', + 'Intended Audience :: Developers', + 'Intended Audience :: System Administrators', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: GNU Library or Lesser General Public License (BSD)', + 'Natural Language :: English', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python', + 'Topic :: Security', + 'Topic :: Internet', + 'Topic :: Scientific/Engineering', + 'Topic :: System :: Distributed Computing', + 'Topic :: System :: Systems Administration :: Authentication/Directory', + 'Topic :: Software Development :: Libraries :: Python Modules' + ], + zip_safe = False, entry_points = { - 'console_scripts': [ -# 'urllib2pyopenssl_get = urllib2pyopenssl.get:main' - ] + 'console_scripts': ['ndg_httpclient = myproxy.script:main', + ], } )