Add support for client certs to the Python API.

This adds support for passing a client cert (in the format expected by the
`requests` library) to the `Client` constructor, as well as for specifying
one on the command line or in .zuliprc (through new `client_cert` and
`client_cert_key` options).
This commit is contained in:
Alex Dehnert 2016-06-14 21:00:59 -04:00 committed by Tim Abbott
parent 6034ae7b9a
commit ad94b57e2f

View file

@ -134,6 +134,17 @@ def generate_option_group(parser, prefix=''):
CA certificates. This will be used to CA certificates. This will be used to
verify the server's identity. All verify the server's identity. All
certificates should be PEM encoded.''') certificates should be PEM encoded.''')
group.add_option('--client-cert',
action='store',
dest='client_cert',
help='''Specify a file containing a client
certificate (not needed for most deployments).''')
group.add_option('--client-cert-key',
action='store',
dest='client_cert_key',
help='''Specify a file containing the client
certificate's key (if it is in a separate
file).''')
return group return group
def init_from_options(options, client=None): def init_from_options(options, client=None):
@ -144,7 +155,9 @@ def init_from_options(options, client=None):
return Client(email=options.zulip_email, api_key=options.zulip_api_key, return Client(email=options.zulip_email, api_key=options.zulip_api_key,
config_file=options.zulip_config_file, verbose=options.verbose, config_file=options.zulip_config_file, verbose=options.verbose,
site=options.zulip_site, client=client, site=options.zulip_site, client=client,
cert_bundle=options.cert_bundle, insecure=options.insecure) cert_bundle=options.cert_bundle, insecure=options.insecure,
client_cert=options.client_cert,
client_cert_key=options.client_cert_key)
def get_default_config_filename(): def get_default_config_filename():
config_file = os.path.join(os.environ["HOME"], ".zuliprc") config_file = os.path.join(os.environ["HOME"], ".zuliprc")
@ -158,7 +171,8 @@ class Client(object):
def __init__(self, email=None, api_key=None, config_file=None, def __init__(self, email=None, api_key=None, config_file=None,
verbose=False, retry_on_errors=True, verbose=False, retry_on_errors=True,
site=None, client=None, site=None, client=None,
cert_bundle=None, insecure=None): cert_bundle=None, insecure=None,
client_cert=None, client_cert_key=None):
if client is None: if client is None:
client = _default_client() client = _default_client()
@ -174,6 +188,10 @@ class Client(object):
email = config.get("api", "email") email = config.get("api", "email")
if site is None and config.has_option("api", "site"): if site is None and config.has_option("api", "site"):
site = config.get("api", "site") site = config.get("api", "site")
if client_cert is None and config.has_option("api", "client_cert"):
client_cert = config.get("api", "client_cert")
if client_cert_key is None and config.has_option("api", "client_cert_key"):
client_cert_key = config.get("api", "client_cert_key")
if cert_bundle is None and config.has_option("api", "cert_bundle"): if cert_bundle is None and config.has_option("api", "cert_bundle"):
cert_bundle = config.get("api", "cert_bundle") cert_bundle = config.get("api", "cert_bundle")
if insecure is None and config.has_option("api", "insecure"): if insecure is None and config.has_option("api", "insecure"):
@ -220,6 +238,21 @@ class Client(object):
# Default behavior: verify against system CA certificates # Default behavior: verify against system CA certificates
self.tls_verification=True self.tls_verification=True
if client_cert is None:
if client_cert_key is not None:
raise RuntimeError("client cert key '%s' specified, but no client cert public part provided"
%(client_cert_key,))
else: # we have a client cert
if not os.path.isfile(client_cert):
raise RuntimeError("client cert '%s' does not exist"
%(client_cert,))
if client_cert_key is not None:
if not os.path.isfile(client_cert_key):
raise RuntimeError("client cert key '%s' does not exist"
%(client_cert_key,))
self.client_cert = client_cert
self.client_cert_key = client_cert_key
def get_user_agent(self): def get_user_agent(self):
vendor = '' vendor = ''
vendor_version = '' vendor_version = ''
@ -289,12 +322,21 @@ class Client(object):
else: else:
kwarg = "data" kwarg = "data"
kwargs = {kwarg: query_state["request"]} kwargs = {kwarg: query_state["request"]}
# Build a client cert object for requests
if self.client_cert_key is not None:
client_cert = (self.client_cert, self.client_cert_key)
else:
client_cert = self.client_cert
res = requests.request( res = requests.request(
method, method,
urllib.parse.urljoin(self.base_url, url), urllib.parse.urljoin(self.base_url, url),
auth=requests.auth.HTTPBasicAuth(self.email, auth=requests.auth.HTTPBasicAuth(self.email,
self.api_key), self.api_key),
verify=self.tls_verification, timeout=90, verify=self.tls_verification,
cert=client_cert,
timeout=90,
headers={"User-agent": self.get_user_agent()}, headers={"User-agent": self.get_user_agent()},
**kwargs) **kwargs)