# -*- coding: utf-8 -*- # # Copyright 2013 Google LLC. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """A module to get an unauthenticated http object.""" from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import enum import google_auth_httplib2 from googlecloudsdk.core import context_aware from googlecloudsdk.core import http_proxy from googlecloudsdk.core import log from googlecloudsdk.core import properties from googlecloudsdk.core import transport from googlecloudsdk.core.util import encoding import httplib2 import six def Http(timeout='unset', response_encoding=None, ca_certs=None): """Get an httplib2.Http client that is properly configured for use by gcloud. This method does not add credentials to the client. For an Http client that has been authenticated, use core.credentials.http.Http(). Args: timeout: double, The timeout in seconds to pass to httplib2. This is the socket level timeout. If timeout is None, timeout is infinite. If default argument 'unset' is given, a sensible default is selected using transport.GetDefaultTimeout(). response_encoding: str, the encoding to use to decode the response. ca_certs: str, absolute filename of a ca_certs file that overrides the default. The gcloud config property for ca_certs, in turn, overrides this argument. Returns: An httplib2.Http client object configured with all the required settings for gcloud. """ http_client = _CreateRawHttpClient(timeout, ca_certs) http_client = RequestWrapper().WrapWithDefaults(http_client, response_encoding) return http_client def HttpClient( timeout=None, proxy_info=httplib2.proxy_info_from_environment, ca_certs=httplib2.CA_CERTS, disable_ssl_certificate_validation=False): """Returns a httplib2.Http subclass. Args: timeout: float, Request timeout, in seconds. proxy_info: httplib2.ProxyInfo object or callable ca_certs: str, absolute filename of a ca_certs file disable_ssl_certificate_validation: bool, If true, disable ssl certificate validation. Returns: A httplib2.Http subclass """ if properties.VALUES.proxy.use_urllib3_via_shim.GetBool(): import httplib2shim # pylint:disable=g-import-not-at-top http_class = httplib2shim.Http else: http_class = httplib2.Http result = http_class( timeout=timeout, proxy_info=proxy_info, ca_certs=ca_certs, disable_ssl_certificate_validation=disable_ssl_certificate_validation) ca_config = context_aware.Config() if ca_config and ca_config.config_type == context_aware.ConfigType.ON_DISK_CERTIFICATE: log.debug('Using client certificate %s', ca_config.encrypted_client_cert_path) result.add_certificate(ca_config.encrypted_client_cert_path, ca_config.encrypted_client_cert_path, '', password=ca_config.encrypted_client_cert_password) return result def _CreateRawHttpClient(timeout='unset', ca_certs=None): """Create an HTTP client matching the appropriate gcloud properties.""" # Compared with setting the default timeout in the function signature (i.e. # timeout=300), this lets you test with short default timeouts by mocking # GetDefaultTimeout. if timeout != 'unset': effective_timeout = timeout else: effective_timeout = transport.GetDefaultTimeout() no_validate = properties.VALUES.auth.disable_ssl_validation.GetBool() or False ca_certs_property = properties.VALUES.core.custom_ca_certs_file.Get() # Believe an explicitly-set ca_certs property over anything we added. if ca_certs_property: ca_certs = ca_certs_property if no_validate: ca_certs = None return HttpClient(timeout=effective_timeout, proxy_info=http_proxy.GetHttpProxyInfo(), ca_certs=ca_certs, disable_ssl_certificate_validation=no_validate) class Request(transport.Request): """Encapsulates parameters for making a general HTTP request. This implementation does additional manipulation to ensure that the request parameters are specified in the same way as they were specified by the caller. That is, if the user calls: request('URI', 'GET', None, {'header': '1'}) After modifying the request, we will call request using positional parameters, instead of transforming the request into: request('URI', method='GET', body=None, headers={'header': '1'}) """ @classmethod def FromRequestArgs(cls, *args, **kwargs): return cls(*args, **kwargs) def __init__(self, *args, **kwargs): self._args = args self._kwargs = kwargs uri = RequestParam.URI.Get(args, kwargs) if not six.PY2: # httplib2 needs text under Python 3. uri = encoding.Decode(uri) method = RequestParam.METHOD.Get(args, kwargs) headers = RequestParam.HEADERS.Get(args, kwargs) or {} body = RequestParam.BODY.Get(args, kwargs) super(Request, self).__init__(uri, method, headers, body) def ToRequestArgs(self): args, kwargs = list(self._args), dict(self._kwargs) RequestParam.URI.Set(args, kwargs, self.uri) if self.method: RequestParam.METHOD.Set(args, kwargs, self.method) if self.headers: RequestParam.HEADERS.Set(args, kwargs, self.headers) if self.body: RequestParam.BODY.Set(args, kwargs, self.body) return args, kwargs class Response(transport.Response): """Encapsulates responses from making a general HTTP request.""" @classmethod def FromResponse(cls, response): resp, content = response headers = {h: v for h, v in six.iteritems(resp) if h != 'status'} return cls(resp.get('status'), headers, content) class RequestWrapper(transport.RequestWrapper): """Class for wrapping httplib.Httplib2 requests.""" request_class = Request response_class = Response def DecodeResponse(self, response, response_encoding): response, content = response content = content.decode(response_encoding) return response, content class RequestParam(enum.Enum): """Encapsulates parameters to a request() call and how to extract them. http.request has the following signature: request(self, uri, method="GET", body=None, headers=None, ...) """ URI = ('uri', 0) METHOD = ('method', 1) BODY = ('body', 2) HEADERS = ('headers', 3) def __init__(self, arg_name, index): self.arg_name = arg_name self.index = index def Get(self, args, kwargs): if len(args) > self.index: return args[self.index] if self.arg_name in kwargs: return kwargs[self.arg_name] return None def Set(self, args, kwargs, value): if len(args) > self.index: args[self.index] = value else: kwargs[self.arg_name] = value def GoogleAuthRequest(): """A Request object for google-auth library. Returns: A http request which implements google.auth.transport.Request and uses gcloud's http object in the core. """ return google_auth_httplib2.Request(Http())