Skip to content

Commit

Permalink
Fixes #2 Add HTTP CONNECT proxy support to duo_client_python
Browse files Browse the repository at this point in the history
Summary:
httplib exposed built-in CONNECT support in Python 2.7. An equivalent
private API is available in 2.6.3+. CertValidatingHTTPSConnection
overrides the standard HTTPConnection and breaks CONNECT tunneling, so
this adds the necessary integration there, too.

Test Plan:
Manually submit requests with ca_certs=filename,
ca_certs='DISABLE', and ca_certs='HTTP' through proxy.py from
https://github.com/senko/tornado-proxy.git
  • Loading branch information
ben-duo committed Mar 15, 2013
1 parent 95e4f5a commit ffbb164
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 5 deletions.
57 changes: 53 additions & 4 deletions duo_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,22 @@ def __init__(self, ikey, skey, host,
if ca_certs is None:
ca_certs = DEFAULT_CA_CERTS
self.ca_certs = ca_certs
self.set_proxy(host=None, proxy_type=None)

def set_proxy(self, host, port=None, headers=None,
proxy_type='CONNECT'):
"""
Configure proxy for API calls. Supported proxy_type values:
'CONNECT' - HTTP proxy with CONNECT.
None - Disable proxy.
"""
if proxy_type not in ('CONNECT', None):
raise NotImplementedError('proxy_type=%s' % (proxy_type,))
self.proxy_headers = headers
self.proxy_host = host
self.proxy_port = port
self.proxy_type = proxy_type

def api_call(self, method, path, params):
"""
Expand Down Expand Up @@ -136,14 +152,47 @@ def api_call(self, method, path, params):
body = None
uri = path + '?' + urllib.urlencode(params, doseq=True)

# Host and port for the HTTP(S) connection to the API server.
if self.ca_certs == 'HTTP':
api_port = 80
api_proto = 'http'
else:
api_port = 443
api_proto = 'https'

# Host and port for outer HTTP(S) connection if proxied.
if self.proxy_type is None:
host = self.host
port = api_port
elif self.proxy_type == 'CONNECT':
host = self.proxy_host
port = self.proxy_port
else:
raise NotImplementedError('proxy_type=%s' % (proxy_type,))

# Create outer HTTP(S) connection.
if self.ca_certs == 'HTTP':
conn = httplib.HTTPConnection(self.host)
conn = httplib.HTTPConnection(host, port)
elif self.ca_certs == 'DISABLE':
conn = httplib.HTTPSConnection(self.host, 443)
conn = httplib.HTTPSConnection(host, port)
else:
conn = CertValidatingHTTPSConnection(self.host,
443,
conn = CertValidatingHTTPSConnection(host,
port,
ca_certs=self.ca_certs)

# Configure CONNECT proxy tunnel, if any.
if self.proxy_type == 'CONNECT':
# Ensure the request has the correct Host.
uri = ''.join((api_proto, '://', self.host, uri))
if hasattr(conn, 'set_tunnel'): # 2.7+
conn.set_tunnel(self.host,
api_port,
self.proxy_headers)
elif hasattr(conn, '_set_tunnel'): # 2.6.3+
conn._set_tunnel(self.host,
api_port,
self.proxy_headers)

conn.request(method, uri, body, headers)
response = conn.getresponse()
data = response.read()
Expand Down
5 changes: 4 additions & 1 deletion duo_client/https_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,10 @@ def connect(self):
"Connect to a host on a given (SSL) port."
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((self.host, self.port))
self.sock = ssl.wrap_socket(sock, keyfile=self.key_file,
self.sock = sock
if self._tunnel_host:
self._tunnel()
self.sock = ssl.wrap_socket(self.sock, keyfile=self.key_file,
certfile=self.cert_file,
cert_reqs=self.cert_reqs,
ca_certs=self.ca_certs)
Expand Down

0 comments on commit ffbb164

Please sign in to comment.