Skip to content
This repository has been archived by the owner on Nov 5, 2019. It is now read-only.

Commit

Permalink
Begin transport module for httplib2 specific pieces.
Browse files Browse the repository at this point in the history
Towards #554.
  • Loading branch information
dhermes committed Jul 21, 2016
1 parent 3e9b857 commit 213050d
Show file tree
Hide file tree
Showing 8 changed files with 353 additions and 145 deletions.
1 change: 1 addition & 0 deletions docs/source/oauth2client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Submodules
oauth2client.file
oauth2client.service_account
oauth2client.tools
oauth2client.transport
oauth2client.util

Module contents
Expand Down
7 changes: 7 additions & 0 deletions docs/source/oauth2client.transport.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
oauth2client.transport module
=============================

.. automodule:: oauth2client.transport
:members:
:undoc-members:
:show-inheritance:
140 changes: 12 additions & 128 deletions oauth2client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import sys
import tempfile

import httplib2
import six
from six.moves import http_client
from six.moves import urllib
Expand All @@ -39,9 +38,9 @@
from oauth2client import GOOGLE_REVOKE_URI
from oauth2client import GOOGLE_TOKEN_INFO_URI
from oauth2client import GOOGLE_TOKEN_URI
from oauth2client import transport
from oauth2client import util
from oauth2client._helpers import _from_bytes
from oauth2client._helpers import _to_bytes
from oauth2client._helpers import _urlsafe_b64decode


Expand Down Expand Up @@ -71,9 +70,6 @@
# Constant to use for the out of band OAuth 2.0 flow.
OOB_CALLBACK_URN = 'urn:ietf:wg:oauth:2.0:oob'

# Google Data client libraries may need to set this to [401, 403].
REFRESH_STATUS_CODES = (http_client.UNAUTHORIZED,)

# The value representing user credentials.
AUTHORIZED_USER = 'authorized_user'

Expand Down Expand Up @@ -120,6 +116,12 @@
# easier testing (by replacing with a stub).
_UTCNOW = datetime.datetime.utcnow

# NOTE: These names were previously defined in this module but have been
# moved into `oauth2client.transport`,
clean_headers = transport.clean_headers
MemoryCache = transport.MemoryCache
REFRESH_STATUS_CODES = transport.REFRESH_STATUS_CODES


class SETTINGS(object):
"""Settings namespace for globally defined values."""
Expand Down Expand Up @@ -177,22 +179,6 @@ class CryptoUnavailableError(Error, NotImplementedError):
"""Raised when a crypto library is required, but none is available."""


class MemoryCache(object):
"""httplib2 Cache implementation which only caches locally."""

def __init__(self):
self.cache = {}

def get(self, key):
return self.cache.get(key)

def set(self, key, value):
self.cache[key] = value

def delete(self, key):
self.cache.pop(key, None)


def _parse_expiry(expiry):
if expiry and isinstance(expiry, datetime.datetime):
return expiry.strftime(EXPIRY_FORMAT)
Expand Down Expand Up @@ -451,32 +437,6 @@ def delete(self):
self.release_lock()


def clean_headers(headers):
"""Forces header keys and values to be strings, i.e not unicode.
The httplib module just concats the header keys and values in a way that
may make the message header a unicode string, which, if it then tries to
contatenate to a binary request body may result in a unicode decode error.
Args:
headers: dict, A dictionary of headers.
Returns:
The same dictionary but with all the keys converted to strings.
"""
clean = {}
try:
for k, v in six.iteritems(headers):
if not isinstance(k, six.binary_type):
k = str(k)
if not isinstance(v, six.binary_type):
v = str(v)
clean[_to_bytes(k)] = _to_bytes(v)
except UnicodeEncodeError:
raise NonAsciiHeaderError(k, ': ', v)
return clean


def _update_query_params(uri, params):
"""Updates a URI with new query parameters.
Expand All @@ -494,26 +454,6 @@ def _update_query_params(uri, params):
return urllib.parse.urlunparse(new_parts)


def _initialize_headers(headers):
"""Creates a copy of the headers."""
if headers is None:
headers = {}
else:
headers = dict(headers)
return headers


def _apply_user_agent(headers, user_agent):
"""Adds a user-agent to the headers."""
if user_agent is not None:
if 'user-agent' in headers:
headers['user-agent'] = (user_agent + ' ' + headers['user-agent'])
else:
headers['user-agent'] = user_agent

return headers


class OAuth2Credentials(Credentials):
"""Credentials object for OAuth 2.0.
Expand Down Expand Up @@ -604,58 +544,7 @@ def authorize(self, http):
that adds in the Authorization header and then calls the original
version of 'request()'.
"""
request_orig = http.request

# The closure that will replace 'httplib2.Http.request'.
def new_request(uri, method='GET', body=None, headers=None,
redirections=httplib2.DEFAULT_MAX_REDIRECTS,
connection_type=None):
if not self.access_token:
logger.info('Attempting refresh to obtain '
'initial access_token')
self._refresh(request_orig)

# Clone and modify the request headers to add the appropriate
# Authorization header.
headers = _initialize_headers(headers)
self.apply(headers)
_apply_user_agent(headers, self.user_agent)

body_stream_position = None
if all(getattr(body, stream_prop, None) for stream_prop in
('read', 'seek', 'tell')):
body_stream_position = body.tell()

resp, content = request_orig(uri, method, body,
clean_headers(headers),
redirections, connection_type)

# A stored token may expire between the time it is retrieved and
# the time the request is made, so we may need to try twice.
max_refresh_attempts = 2
for refresh_attempt in range(max_refresh_attempts):
if resp.status not in REFRESH_STATUS_CODES:
break
logger.info('Refreshing due to a %s (attempt %s/%s)',
resp.status, refresh_attempt + 1,
max_refresh_attempts)
self._refresh(request_orig)
self.apply(headers)
if body_stream_position is not None:
body.seek(body_stream_position)

resp, content = request_orig(uri, method, body,
clean_headers(headers),
redirections, connection_type)

return (resp, content)

# Replace the request method with our own closure.
http.request = new_request

# Set credentials as a property of the request method.
setattr(http.request, 'credentials', self)

transport.wrap_http_for_auth(self, http)
return http

def refresh(self, http):
Expand Down Expand Up @@ -781,7 +670,7 @@ def get_access_token(self, http=None):
"""
if not self.access_token or self.access_token_expired:
if not http:
http = httplib2.Http()
http = transport.get_http_object()
self.refresh(http)
return AccessTokenInfo(access_token=self.access_token,
expires_in=self._expires_in())
Expand Down Expand Up @@ -1654,11 +1543,6 @@ def _require_crypto_or_die():
raise CryptoUnavailableError('No crypto library available')


# Only used in verify_id_token(), which is always calling to the same URI
# for the certs.
_cached_http = httplib2.Http(MemoryCache())


@util.positional(2)
def verify_id_token(id_token, audience, http=None,
cert_uri=ID_TOKEN_VERIFICATION_CERTS):
Expand All @@ -1684,7 +1568,7 @@ def verify_id_token(id_token, audience, http=None,
"""
_require_crypto_or_die()
if http is None:
http = _cached_http
http = transport.get_cached_http()

resp, content = http.request(cert_uri)
if resp.status == http_client.OK:
Expand Down Expand Up @@ -2027,7 +1911,7 @@ def step1_get_device_and_user_codes(self, http=None):
headers['user-agent'] = self.user_agent

if http is None:
http = httplib2.Http()
http = transport.get_http_object()

resp, content = http.request(self.device_uri, method='POST', body=body,
headers=headers)
Expand Down Expand Up @@ -2110,7 +1994,7 @@ def step2_exchange(self, code=None, http=None, device_flow_info=None):
headers['user-agent'] = self.user_agent

if http is None:
http = httplib2.Http()
http = transport.get_http_object()

resp, content = http.request(self.token_uri, method='POST', body=body,
headers=headers)
Expand Down
6 changes: 3 additions & 3 deletions oauth2client/service_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@
from oauth2client import GOOGLE_TOKEN_URI
from oauth2client import util
from oauth2client._helpers import _from_bytes
from oauth2client.client import _apply_user_agent
from oauth2client.client import _initialize_headers
from oauth2client.client import _UTCNOW
from oauth2client.client import AccessTokenInfo
from oauth2client.client import AssertionCredentials
from oauth2client.client import clean_headers
from oauth2client.client import EXPIRY_FORMAT
from oauth2client.client import SERVICE_ACCOUNT
from oauth2client.transport import _apply_user_agent
from oauth2client.transport import _initialize_headers
from oauth2client.transport import clean_headers


_PASSWORD_DEFAULT = 'notasecret'
Expand Down
Loading

0 comments on commit 213050d

Please sign in to comment.