Skip to content

Commit

Permalink
* More work on unit tests
Browse files Browse the repository at this point in the history
 * fixed imports for the new namespace
 * some simplifying of peer verification and get modules
 

git-svn-id: http://proj.badc.rl.ac.uk/svn/ndg-security/trunk/ndg_httpsclient@7973 051b1e3e-aa0c-0410-b6c2-bfbade6052be
  • Loading branch information
pjkersha committed Jan 6, 2012
1 parent 8ad766f commit f9c6bde
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 57 deletions.
74 changes: 50 additions & 24 deletions ndg/httpsclient/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,18 @@
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.
@param url - URL to attempt to open
@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()
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -119,6 +121,7 @@ def _should_use_proxy(url):

return True


class Configuration(object):
"""Checker configuration.
"""
Expand All @@ -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.
'''
Expand All @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion ndg/httpsclient/https.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down
2 changes: 1 addition & 1 deletion ndg/httpsclient/ssl_context_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down
23 changes: 2 additions & 21 deletions ndg/httpsclient/ssl_peer_verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -43,19 +43,13 @@ 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

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
Expand Down Expand Up @@ -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")
6 changes: 5 additions & 1 deletion ndg/httpsclient/test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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$'
__revision__ = '$Id$'
class Constants(object):
PORT = 4443
HOSTNAME = 'localhost'
TEST_URI = 'https://%s:%d' % (HOSTNAME, PORT)
4 changes: 2 additions & 2 deletions ndg/httpsclient/test/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
27 changes: 27 additions & 0 deletions ndg/httpsclient/test/test_https.py
Original file line number Diff line number Diff line change
@@ -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()
17 changes: 17 additions & 0 deletions ndg/httpsclient/test_get.py
Original file line number Diff line number Diff line change
@@ -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()
4 changes: 2 additions & 2 deletions ndg/httpsclient/urllib2_build_opener.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 25 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
],
}
)

0 comments on commit f9c6bde

Please sign in to comment.