mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-28 18:01:36 +00:00
oauth2client 4.0
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
|
||||
"""Client library for using OAuth2, especially with Google APIs."""
|
||||
|
||||
__version__ = '3.0.0'
|
||||
__version__ = '4.0.0'
|
||||
|
||||
GOOGLE_AUTH_URI = 'https://accounts.google.com/o/oauth2/v2/auth'
|
||||
GOOGLE_DEVICE_URI = 'https://accounts.google.com/o/oauth2/device/code'
|
||||
|
||||
@@ -11,12 +11,248 @@
|
||||
# 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.
|
||||
|
||||
"""Helper functions for commonly used utilities."""
|
||||
|
||||
import base64
|
||||
import functools
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import warnings
|
||||
|
||||
import six
|
||||
from six.moves import urllib
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
POSITIONAL_WARNING = 'WARNING'
|
||||
POSITIONAL_EXCEPTION = 'EXCEPTION'
|
||||
POSITIONAL_IGNORE = 'IGNORE'
|
||||
POSITIONAL_SET = frozenset([POSITIONAL_WARNING, POSITIONAL_EXCEPTION,
|
||||
POSITIONAL_IGNORE])
|
||||
|
||||
positional_parameters_enforcement = POSITIONAL_WARNING
|
||||
|
||||
_SYM_LINK_MESSAGE = 'File: {0}: Is a symbolic link.'
|
||||
_IS_DIR_MESSAGE = '{0}: Is a directory'
|
||||
_MISSING_FILE_MESSAGE = 'Cannot access {0}: No such file or directory'
|
||||
|
||||
|
||||
def positional(max_positional_args):
|
||||
"""A decorator to declare that only the first N arguments my be positional.
|
||||
|
||||
This decorator makes it easy to support Python 3 style keyword-only
|
||||
parameters. For example, in Python 3 it is possible to write::
|
||||
|
||||
def fn(pos1, *, kwonly1=None, kwonly1=None):
|
||||
...
|
||||
|
||||
All named parameters after ``*`` must be a keyword::
|
||||
|
||||
fn(10, 'kw1', 'kw2') # Raises exception.
|
||||
fn(10, kwonly1='kw1') # Ok.
|
||||
|
||||
Example
|
||||
^^^^^^^
|
||||
|
||||
To define a function like above, do::
|
||||
|
||||
@positional(1)
|
||||
def fn(pos1, kwonly1=None, kwonly2=None):
|
||||
...
|
||||
|
||||
If no default value is provided to a keyword argument, it becomes a
|
||||
required keyword argument::
|
||||
|
||||
@positional(0)
|
||||
def fn(required_kw):
|
||||
...
|
||||
|
||||
This must be called with the keyword parameter::
|
||||
|
||||
fn() # Raises exception.
|
||||
fn(10) # Raises exception.
|
||||
fn(required_kw=10) # Ok.
|
||||
|
||||
When defining instance or class methods always remember to account for
|
||||
``self`` and ``cls``::
|
||||
|
||||
class MyClass(object):
|
||||
|
||||
@positional(2)
|
||||
def my_method(self, pos1, kwonly1=None):
|
||||
...
|
||||
|
||||
@classmethod
|
||||
@positional(2)
|
||||
def my_method(cls, pos1, kwonly1=None):
|
||||
...
|
||||
|
||||
The positional decorator behavior is controlled by
|
||||
``_helpers.positional_parameters_enforcement``, which may be set to
|
||||
``POSITIONAL_EXCEPTION``, ``POSITIONAL_WARNING`` or
|
||||
``POSITIONAL_IGNORE`` to raise an exception, log a warning, or do
|
||||
nothing, respectively, if a declaration is violated.
|
||||
|
||||
Args:
|
||||
max_positional_arguments: Maximum number of positional arguments. All
|
||||
parameters after the this index must be
|
||||
keyword only.
|
||||
|
||||
Returns:
|
||||
A decorator that prevents using arguments after max_positional_args
|
||||
from being used as positional parameters.
|
||||
|
||||
Raises:
|
||||
TypeError: if a key-word only argument is provided as a positional
|
||||
parameter, but only if
|
||||
_helpers.positional_parameters_enforcement is set to
|
||||
POSITIONAL_EXCEPTION.
|
||||
"""
|
||||
|
||||
def positional_decorator(wrapped):
|
||||
@functools.wraps(wrapped)
|
||||
def positional_wrapper(*args, **kwargs):
|
||||
if len(args) > max_positional_args:
|
||||
plural_s = ''
|
||||
if max_positional_args != 1:
|
||||
plural_s = 's'
|
||||
message = ('{function}() takes at most {args_max} positional '
|
||||
'argument{plural} ({args_given} given)'.format(
|
||||
function=wrapped.__name__,
|
||||
args_max=max_positional_args,
|
||||
args_given=len(args),
|
||||
plural=plural_s))
|
||||
if positional_parameters_enforcement == POSITIONAL_EXCEPTION:
|
||||
raise TypeError(message)
|
||||
elif positional_parameters_enforcement == POSITIONAL_WARNING:
|
||||
logger.warning(message)
|
||||
return wrapped(*args, **kwargs)
|
||||
return positional_wrapper
|
||||
|
||||
if isinstance(max_positional_args, six.integer_types):
|
||||
return positional_decorator
|
||||
else:
|
||||
args, _, _, defaults = inspect.getargspec(max_positional_args)
|
||||
return positional(len(args) - len(defaults))(max_positional_args)
|
||||
|
||||
|
||||
def scopes_to_string(scopes):
|
||||
"""Converts scope value to a string.
|
||||
|
||||
If scopes is a string then it is simply passed through. If scopes is an
|
||||
iterable then a string is returned that is all the individual scopes
|
||||
concatenated with spaces.
|
||||
|
||||
Args:
|
||||
scopes: string or iterable of strings, the scopes.
|
||||
|
||||
Returns:
|
||||
The scopes formatted as a single string.
|
||||
"""
|
||||
if isinstance(scopes, six.string_types):
|
||||
return scopes
|
||||
else:
|
||||
return ' '.join(scopes)
|
||||
|
||||
|
||||
def string_to_scopes(scopes):
|
||||
"""Converts stringifed scope value to a list.
|
||||
|
||||
If scopes is a list then it is simply passed through. If scopes is an
|
||||
string then a list of each individual scope is returned.
|
||||
|
||||
Args:
|
||||
scopes: a string or iterable of strings, the scopes.
|
||||
|
||||
Returns:
|
||||
The scopes in a list.
|
||||
"""
|
||||
if not scopes:
|
||||
return []
|
||||
elif isinstance(scopes, six.string_types):
|
||||
return scopes.split(' ')
|
||||
else:
|
||||
return scopes
|
||||
|
||||
|
||||
def parse_unique_urlencoded(content):
|
||||
"""Parses unique key-value parameters from urlencoded content.
|
||||
|
||||
Args:
|
||||
content: string, URL-encoded key-value pairs.
|
||||
|
||||
Returns:
|
||||
dict, The key-value pairs from ``content``.
|
||||
|
||||
Raises:
|
||||
ValueError: if one of the keys is repeated.
|
||||
"""
|
||||
urlencoded_params = urllib.parse.parse_qs(content)
|
||||
params = {}
|
||||
for key, value in six.iteritems(urlencoded_params):
|
||||
if len(value) != 1:
|
||||
msg = ('URL-encoded content contains a repeated value:'
|
||||
'%s -> %s' % (key, ', '.join(value)))
|
||||
raise ValueError(msg)
|
||||
params[key] = value[0]
|
||||
return params
|
||||
|
||||
|
||||
def update_query_params(uri, params):
|
||||
"""Updates a URI with new query parameters.
|
||||
|
||||
If a given key from ``params`` is repeated in the ``uri``, then
|
||||
the URI will be considered invalid and an error will occur.
|
||||
|
||||
If the URI is valid, then each value from ``params`` will
|
||||
replace the corresponding value in the query parameters (if
|
||||
it exists).
|
||||
|
||||
Args:
|
||||
uri: string, A valid URI, with potential existing query parameters.
|
||||
params: dict, A dictionary of query parameters.
|
||||
|
||||
Returns:
|
||||
The same URI but with the new query parameters added.
|
||||
"""
|
||||
parts = urllib.parse.urlparse(uri)
|
||||
query_params = parse_unique_urlencoded(parts.query)
|
||||
query_params.update(params)
|
||||
new_query = urllib.parse.urlencode(query_params)
|
||||
new_parts = parts._replace(query=new_query)
|
||||
return urllib.parse.urlunparse(new_parts)
|
||||
|
||||
|
||||
def _add_query_parameter(url, name, value):
|
||||
"""Adds a query parameter to a url.
|
||||
|
||||
Replaces the current value if it already exists in the URL.
|
||||
|
||||
Args:
|
||||
url: string, url to add the query parameter to.
|
||||
name: string, query parameter name.
|
||||
value: string, query parameter value.
|
||||
|
||||
Returns:
|
||||
Updated query parameter. Does not update the url if value is None.
|
||||
"""
|
||||
if value is None:
|
||||
return url
|
||||
else:
|
||||
return update_query_params(url, {name: value})
|
||||
|
||||
|
||||
def validate_file(filename):
|
||||
if os.path.islink(filename):
|
||||
raise IOError(_SYM_LINK_MESSAGE.format(filename))
|
||||
elif os.path.isdir(filename):
|
||||
raise IOError(_IS_DIR_MESSAGE.format(filename))
|
||||
elif not os.path.isfile(filename):
|
||||
warnings.warn(_MISSING_FILE_MESSAGE.format(filename))
|
||||
|
||||
|
||||
def _parse_pem_key(raw_key_input):
|
||||
|
||||
65
src/oauth2client/_pkce.py
Normal file
65
src/oauth2client/_pkce.py
Normal file
@@ -0,0 +1,65 @@
|
||||
# Copyright 2016 Google Inc. 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.
|
||||
|
||||
"""
|
||||
Utility functions for implementing Proof Key for Code Exchange (PKCE) by OAuth
|
||||
Public Clients
|
||||
|
||||
See RFC7636.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
|
||||
def code_verifier(n_bytes=64):
|
||||
"""
|
||||
Generates a 'code_verifier' as described in section 4.1 of RFC 7636.
|
||||
|
||||
This is a 'high-entropy cryptographic random string' that will be
|
||||
impractical for an attacker to guess.
|
||||
|
||||
Args:
|
||||
n_bytes: integer between 31 and 96, inclusive. default: 64
|
||||
number of bytes of entropy to include in verifier.
|
||||
|
||||
Returns:
|
||||
Bytestring, representing urlsafe base64-encoded random data.
|
||||
"""
|
||||
verifier = base64.urlsafe_b64encode(os.urandom(n_bytes))
|
||||
# https://tools.ietf.org/html/rfc7636#section-4.1
|
||||
# minimum length of 43 characters and a maximum length of 128 characters.
|
||||
if len(verifier) < 43:
|
||||
raise ValueError("Verifier too short. n_bytes must be > 30.")
|
||||
elif len(verifier) > 128:
|
||||
raise ValueError("Verifier too long. n_bytes must be < 97.")
|
||||
else:
|
||||
return verifier
|
||||
|
||||
|
||||
def code_challenge(verifier):
|
||||
"""
|
||||
Creates a 'code_challenge' as described in section 4.2 of RFC 7636
|
||||
by taking the sha256 hash of the verifier and then urlsafe
|
||||
base64-encoding it.
|
||||
|
||||
Args:
|
||||
verifier: bytestring, representing a code_verifier as generated by
|
||||
code_verifier().
|
||||
|
||||
Returns:
|
||||
Bytestring, representing a urlsafe base64-encoded sha256 hash digest.
|
||||
"""
|
||||
return base64.urlsafe_b64encode(hashlib.sha256(verifier).digest())
|
||||
@@ -34,13 +34,11 @@ from six.moves import urllib
|
||||
|
||||
import oauth2client
|
||||
from oauth2client import _helpers
|
||||
from oauth2client import _pkce
|
||||
from oauth2client import clientsecrets
|
||||
from oauth2client import transport
|
||||
from oauth2client import util
|
||||
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
HAS_OPENSSL = False
|
||||
HAS_CRYPTO = False
|
||||
try:
|
||||
@@ -100,20 +98,20 @@ AccessTokenInfo = collections.namedtuple(
|
||||
DEFAULT_ENV_NAME = 'UNKNOWN'
|
||||
|
||||
# If set to True _get_environment avoid GCE check (_detect_gce_environment)
|
||||
NO_GCE_CHECK = os.environ.setdefault('NO_GCE_CHECK', 'False')
|
||||
NO_GCE_CHECK = os.getenv('NO_GCE_CHECK', 'False')
|
||||
|
||||
# Timeout in seconds to wait for the GCE metadata server when detecting the
|
||||
# GCE environment.
|
||||
try:
|
||||
GCE_METADATA_TIMEOUT = int(
|
||||
os.environ.setdefault('GCE_METADATA_TIMEOUT', '3'))
|
||||
GCE_METADATA_TIMEOUT = int(os.getenv('GCE_METADATA_TIMEOUT', 3))
|
||||
except ValueError: # pragma: NO COVER
|
||||
GCE_METADATA_TIMEOUT = 3
|
||||
|
||||
_SERVER_SOFTWARE = 'SERVER_SOFTWARE'
|
||||
_GCE_METADATA_HOST = '169.254.169.254'
|
||||
_METADATA_FLAVOR_HEADER = 'Metadata-Flavor'
|
||||
_GCE_METADATA_URI = 'http://169.254.169.254'
|
||||
_METADATA_FLAVOR_HEADER = 'metadata-flavor' # lowercase header
|
||||
_DESIRED_METADATA_FLAVOR = 'Google'
|
||||
_GCE_HEADERS = {_METADATA_FLAVOR_HEADER: _DESIRED_METADATA_FLAVOR}
|
||||
|
||||
# Expose utcnow() at module level to allow for
|
||||
# easier testing (by replacing with a stub).
|
||||
@@ -273,7 +271,7 @@ class Credentials(object):
|
||||
to_serialize[key] = val.decode('utf-8')
|
||||
if isinstance(val, set):
|
||||
to_serialize[key] = list(val)
|
||||
return json.dumps(to_serialize, indent=4, sort_keys=True)
|
||||
return json.dumps(to_serialize)
|
||||
|
||||
def to_json(self):
|
||||
"""Creating a JSON representation of an instance of Credentials.
|
||||
@@ -440,23 +438,6 @@ class Storage(object):
|
||||
self.release_lock()
|
||||
|
||||
|
||||
def _update_query_params(uri, params):
|
||||
"""Updates a URI with new query parameters.
|
||||
|
||||
Args:
|
||||
uri: string, A valid URI, with potential existing query parameters.
|
||||
params: dict, A dictionary of query parameters.
|
||||
|
||||
Returns:
|
||||
The same URI but with the new query parameters added.
|
||||
"""
|
||||
parts = urllib.parse.urlparse(uri)
|
||||
query_params = dict(urllib.parse.parse_qsl(parts.query))
|
||||
query_params.update(params)
|
||||
new_parts = parts._replace(query=urllib.parse.urlencode(query_params))
|
||||
return urllib.parse.urlunparse(new_parts)
|
||||
|
||||
|
||||
class OAuth2Credentials(Credentials):
|
||||
"""Credentials object for OAuth 2.0.
|
||||
|
||||
@@ -466,7 +447,7 @@ class OAuth2Credentials(Credentials):
|
||||
OAuth2Credentials objects may be safely pickled and unpickled.
|
||||
"""
|
||||
|
||||
@util.positional(8)
|
||||
@_helpers.positional(8)
|
||||
def __init__(self, access_token, client_id, client_secret, refresh_token,
|
||||
token_expiry, token_uri, user_agent, revoke_uri=None,
|
||||
id_token=None, token_response=None, scopes=None,
|
||||
@@ -513,7 +494,7 @@ class OAuth2Credentials(Credentials):
|
||||
self.revoke_uri = revoke_uri
|
||||
self.id_token = id_token
|
||||
self.token_response = token_response
|
||||
self.scopes = set(util.string_to_scopes(scopes or []))
|
||||
self.scopes = set(_helpers.string_to_scopes(scopes or []))
|
||||
self.token_info_uri = token_info_uri
|
||||
|
||||
# True if the credentials have been revoked or expired and can't be
|
||||
@@ -557,7 +538,7 @@ class OAuth2Credentials(Credentials):
|
||||
http: httplib2.Http, an http object to be used to make the refresh
|
||||
request.
|
||||
"""
|
||||
self._refresh(http.request)
|
||||
self._refresh(http)
|
||||
|
||||
def revoke(self, http):
|
||||
"""Revokes a refresh_token and makes the credentials void.
|
||||
@@ -566,7 +547,7 @@ class OAuth2Credentials(Credentials):
|
||||
http: httplib2.Http, an http object to be used to make the revoke
|
||||
request.
|
||||
"""
|
||||
self._revoke(http.request)
|
||||
self._revoke(http)
|
||||
|
||||
def apply(self, headers):
|
||||
"""Add the authorization to the headers.
|
||||
@@ -592,7 +573,7 @@ class OAuth2Credentials(Credentials):
|
||||
not have scopes. In both cases, you can use refresh_scopes() to
|
||||
obtain the canonical set of scopes.
|
||||
"""
|
||||
scopes = util.string_to_scopes(scopes)
|
||||
scopes = _helpers.string_to_scopes(scopes)
|
||||
return set(scopes).issubset(self.scopes)
|
||||
|
||||
def retrieve_scopes(self, http):
|
||||
@@ -607,7 +588,7 @@ class OAuth2Credentials(Credentials):
|
||||
Returns:
|
||||
A set of strings containing the canonical list of scopes.
|
||||
"""
|
||||
self._retrieve_scopes(http.request)
|
||||
self._retrieve_scopes(http)
|
||||
return self.scopes
|
||||
|
||||
@classmethod
|
||||
@@ -746,7 +727,7 @@ class OAuth2Credentials(Credentials):
|
||||
|
||||
return headers
|
||||
|
||||
def _refresh(self, http_request):
|
||||
def _refresh(self, http):
|
||||
"""Refreshes the access_token.
|
||||
|
||||
This method first checks by reading the Storage object if available.
|
||||
@@ -754,15 +735,13 @@ class OAuth2Credentials(Credentials):
|
||||
refresh is completed.
|
||||
|
||||
Args:
|
||||
http_request: callable, a callable that matches the method
|
||||
signature of httplib2.Http.request, used to make the
|
||||
refresh request.
|
||||
http: an object to be used to make HTTP requests.
|
||||
|
||||
Raises:
|
||||
HttpAccessTokenRefreshError: When the refresh fails.
|
||||
"""
|
||||
if not self.store:
|
||||
self._do_refresh_request(http_request)
|
||||
self._do_refresh_request(http)
|
||||
else:
|
||||
self.store.acquire_lock()
|
||||
try:
|
||||
@@ -774,17 +753,15 @@ class OAuth2Credentials(Credentials):
|
||||
logger.info('Updated access_token read from Storage')
|
||||
self._updateFromCredential(new_cred)
|
||||
else:
|
||||
self._do_refresh_request(http_request)
|
||||
self._do_refresh_request(http)
|
||||
finally:
|
||||
self.store.release_lock()
|
||||
|
||||
def _do_refresh_request(self, http_request):
|
||||
def _do_refresh_request(self, http):
|
||||
"""Refresh the access_token using the refresh_token.
|
||||
|
||||
Args:
|
||||
http_request: callable, a callable that matches the method
|
||||
signature of httplib2.Http.request, used to make the
|
||||
refresh request.
|
||||
http: an object to be used to make HTTP requests.
|
||||
|
||||
Raises:
|
||||
HttpAccessTokenRefreshError: When the refresh fails.
|
||||
@@ -793,8 +770,9 @@ class OAuth2Credentials(Credentials):
|
||||
headers = self._generate_refresh_request_headers()
|
||||
|
||||
logger.info('Refreshing access_token')
|
||||
resp, content = http_request(
|
||||
self.token_uri, method='POST', body=body, headers=headers)
|
||||
resp, content = transport.request(
|
||||
http, self.token_uri, method='POST',
|
||||
body=body, headers=headers)
|
||||
content = _helpers._from_bytes(content)
|
||||
if resp.status == http_client.OK:
|
||||
d = json.loads(content)
|
||||
@@ -819,7 +797,7 @@ class OAuth2Credentials(Credentials):
|
||||
# An {'error':...} response body means the token is expired or
|
||||
# revoked, so we flag the credentials as such.
|
||||
logger.info('Failed to retrieve access token: %s', content)
|
||||
error_msg = 'Invalid response {0}.'.format(resp['status'])
|
||||
error_msg = 'Invalid response {0}.'.format(resp.status)
|
||||
try:
|
||||
d = json.loads(content)
|
||||
if 'error' in d:
|
||||
@@ -833,23 +811,19 @@ class OAuth2Credentials(Credentials):
|
||||
pass
|
||||
raise HttpAccessTokenRefreshError(error_msg, status=resp.status)
|
||||
|
||||
def _revoke(self, http_request):
|
||||
def _revoke(self, http):
|
||||
"""Revokes this credential and deletes the stored copy (if it exists).
|
||||
|
||||
Args:
|
||||
http_request: callable, a callable that matches the method
|
||||
signature of httplib2.Http.request, used to make the
|
||||
revoke request.
|
||||
http: an object to be used to make HTTP requests.
|
||||
"""
|
||||
self._do_revoke(http_request, self.refresh_token or self.access_token)
|
||||
self._do_revoke(http, self.refresh_token or self.access_token)
|
||||
|
||||
def _do_revoke(self, http_request, token):
|
||||
def _do_revoke(self, http, token):
|
||||
"""Revokes this credential and deletes the stored copy (if it exists).
|
||||
|
||||
Args:
|
||||
http_request: callable, a callable that matches the method
|
||||
signature of httplib2.Http.request, used to make the
|
||||
refresh request.
|
||||
http: an object to be used to make HTTP requests.
|
||||
token: A string used as the token to be revoked. Can be either an
|
||||
access_token or refresh_token.
|
||||
|
||||
@@ -859,8 +833,13 @@ class OAuth2Credentials(Credentials):
|
||||
"""
|
||||
logger.info('Revoking token')
|
||||
query_params = {'token': token}
|
||||
token_revoke_uri = _update_query_params(self.revoke_uri, query_params)
|
||||
resp, content = http_request(token_revoke_uri)
|
||||
token_revoke_uri = _helpers.update_query_params(
|
||||
self.revoke_uri, query_params)
|
||||
resp, content = transport.request(http, token_revoke_uri)
|
||||
if resp.status == http_client.METHOD_NOT_ALLOWED:
|
||||
body = urllib.parse.urlencode(query_params)
|
||||
resp, content = transport.request(http, token_revoke_uri,
|
||||
method='POST', body=body)
|
||||
if resp.status == http_client.OK:
|
||||
self.invalid = True
|
||||
else:
|
||||
@@ -876,23 +855,19 @@ class OAuth2Credentials(Credentials):
|
||||
if self.store:
|
||||
self.store.delete()
|
||||
|
||||
def _retrieve_scopes(self, http_request):
|
||||
def _retrieve_scopes(self, http):
|
||||
"""Retrieves the list of authorized scopes from the OAuth2 provider.
|
||||
|
||||
Args:
|
||||
http_request: callable, a callable that matches the method
|
||||
signature of httplib2.Http.request, used to make the
|
||||
revoke request.
|
||||
http: an object to be used to make HTTP requests.
|
||||
"""
|
||||
self._do_retrieve_scopes(http_request, self.access_token)
|
||||
self._do_retrieve_scopes(http, self.access_token)
|
||||
|
||||
def _do_retrieve_scopes(self, http_request, token):
|
||||
def _do_retrieve_scopes(self, http, token):
|
||||
"""Retrieves the list of authorized scopes from the OAuth2 provider.
|
||||
|
||||
Args:
|
||||
http_request: callable, a callable that matches the method
|
||||
signature of httplib2.Http.request, used to make the
|
||||
refresh request.
|
||||
http: an object to be used to make HTTP requests.
|
||||
token: A string used as the token to identify the credentials to
|
||||
the provider.
|
||||
|
||||
@@ -902,13 +877,13 @@ class OAuth2Credentials(Credentials):
|
||||
"""
|
||||
logger.info('Refreshing scopes')
|
||||
query_params = {'access_token': token, 'fields': 'scope'}
|
||||
token_info_uri = _update_query_params(self.token_info_uri,
|
||||
query_params)
|
||||
resp, content = http_request(token_info_uri)
|
||||
token_info_uri = _helpers.update_query_params(
|
||||
self.token_info_uri, query_params)
|
||||
resp, content = transport.request(http, token_info_uri)
|
||||
content = _helpers._from_bytes(content)
|
||||
if resp.status == http_client.OK:
|
||||
d = json.loads(content)
|
||||
self.scopes = set(util.string_to_scopes(d.get('scope', '')))
|
||||
self.scopes = set(_helpers.string_to_scopes(d.get('scope', '')))
|
||||
else:
|
||||
error_msg = 'Invalid response {0}.'.format(resp.status)
|
||||
try:
|
||||
@@ -977,19 +952,25 @@ class AccessTokenCredentials(OAuth2Credentials):
|
||||
data['user_agent'])
|
||||
return retval
|
||||
|
||||
def _refresh(self, http_request):
|
||||
def _refresh(self, http):
|
||||
"""Refreshes the access token.
|
||||
|
||||
Args:
|
||||
http: unused HTTP object.
|
||||
|
||||
Raises:
|
||||
AccessTokenCredentialsError: always
|
||||
"""
|
||||
raise AccessTokenCredentialsError(
|
||||
'The access_token is expired or invalid and can\'t be refreshed.')
|
||||
|
||||
def _revoke(self, http_request):
|
||||
def _revoke(self, http):
|
||||
"""Revokes the access_token and deletes the store if available.
|
||||
|
||||
Args:
|
||||
http_request: callable, a callable that matches the method
|
||||
signature of httplib2.Http.request, used to make the
|
||||
revoke request.
|
||||
http: an object to be used to make HTTP requests.
|
||||
"""
|
||||
self._do_revoke(http_request, self.access_token)
|
||||
self._do_revoke(http, self.access_token)
|
||||
|
||||
|
||||
def _detect_gce_environment():
|
||||
@@ -1005,21 +986,16 @@ def _detect_gce_environment():
|
||||
# could lead to false negatives in the event that we are on GCE, but
|
||||
# the metadata resolution was particularly slow. The latter case is
|
||||
# "unlikely".
|
||||
connection = six.moves.http_client.HTTPConnection(
|
||||
_GCE_METADATA_HOST, timeout=GCE_METADATA_TIMEOUT)
|
||||
|
||||
http = transport.get_http_object(timeout=GCE_METADATA_TIMEOUT)
|
||||
try:
|
||||
headers = {_METADATA_FLAVOR_HEADER: _DESIRED_METADATA_FLAVOR}
|
||||
connection.request('GET', '/', headers=headers)
|
||||
response = connection.getresponse()
|
||||
if response.status == http_client.OK:
|
||||
return (response.getheader(_METADATA_FLAVOR_HEADER) ==
|
||||
_DESIRED_METADATA_FLAVOR)
|
||||
response, _ = transport.request(
|
||||
http, _GCE_METADATA_URI, headers=_GCE_HEADERS)
|
||||
return (
|
||||
response.status == http_client.OK and
|
||||
response.get(_METADATA_FLAVOR_HEADER) == _DESIRED_METADATA_FLAVOR)
|
||||
except socket.error: # socket.timeout or socket.error(64, 'Host is down')
|
||||
logger.info('Timeout attempting to reach GCE metadata service.')
|
||||
return False
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
|
||||
def _in_gae_environment():
|
||||
@@ -1469,7 +1445,7 @@ class AssertionCredentials(GoogleCredentials):
|
||||
AssertionCredentials objects may be safely pickled and unpickled.
|
||||
"""
|
||||
|
||||
@util.positional(2)
|
||||
@_helpers.positional(2)
|
||||
def __init__(self, assertion_type, user_agent=None,
|
||||
token_uri=oauth2client.GOOGLE_TOKEN_URI,
|
||||
revoke_uri=oauth2client.GOOGLE_REVOKE_URI,
|
||||
@@ -1511,15 +1487,13 @@ class AssertionCredentials(GoogleCredentials):
|
||||
"""Generate assertion string to be used in the access token request."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _revoke(self, http_request):
|
||||
def _revoke(self, http):
|
||||
"""Revokes the access_token and deletes the store if available.
|
||||
|
||||
Args:
|
||||
http_request: callable, a callable that matches the method
|
||||
signature of httplib2.Http.request, used to make the
|
||||
revoke request.
|
||||
http: an object to be used to make HTTP requests.
|
||||
"""
|
||||
self._do_revoke(http_request, self.access_token)
|
||||
self._do_revoke(http, self.access_token)
|
||||
|
||||
def sign_blob(self, blob):
|
||||
"""Cryptographically sign a blob (of bytes).
|
||||
@@ -1545,7 +1519,7 @@ def _require_crypto_or_die():
|
||||
raise CryptoUnavailableError('No crypto library available')
|
||||
|
||||
|
||||
@util.positional(2)
|
||||
@_helpers.positional(2)
|
||||
def verify_id_token(id_token, audience, http=None,
|
||||
cert_uri=ID_TOKEN_VERIFICATION_CERTS):
|
||||
"""Verifies a signed JWT id_token.
|
||||
@@ -1572,7 +1546,7 @@ def verify_id_token(id_token, audience, http=None,
|
||||
if http is None:
|
||||
http = transport.get_cached_http()
|
||||
|
||||
resp, content = http.request(cert_uri)
|
||||
resp, content = transport.request(http, cert_uri)
|
||||
if resp.status == http_client.OK:
|
||||
certs = json.loads(_helpers._from_bytes(content))
|
||||
return crypt.verify_signed_jwt_with_certs(id_token, certs, audience)
|
||||
@@ -1624,7 +1598,7 @@ def _parse_exchange_token_response(content):
|
||||
except Exception:
|
||||
# different JSON libs raise different exceptions,
|
||||
# so we just do a catch-all here
|
||||
resp = dict(urllib.parse.parse_qsl(content))
|
||||
resp = _helpers.parse_unique_urlencoded(content)
|
||||
|
||||
# some providers respond with 'expires', others with 'expires_in'
|
||||
if resp and 'expires' in resp:
|
||||
@@ -1633,7 +1607,7 @@ def _parse_exchange_token_response(content):
|
||||
return resp
|
||||
|
||||
|
||||
@util.positional(4)
|
||||
@_helpers.positional(4)
|
||||
def credentials_from_code(client_id, client_secret, scope, code,
|
||||
redirect_uri='postmessage', http=None,
|
||||
user_agent=None,
|
||||
@@ -1641,7 +1615,9 @@ def credentials_from_code(client_id, client_secret, scope, code,
|
||||
auth_uri=oauth2client.GOOGLE_AUTH_URI,
|
||||
revoke_uri=oauth2client.GOOGLE_REVOKE_URI,
|
||||
device_uri=oauth2client.GOOGLE_DEVICE_URI,
|
||||
token_info_uri=oauth2client.GOOGLE_TOKEN_INFO_URI):
|
||||
token_info_uri=oauth2client.GOOGLE_TOKEN_INFO_URI,
|
||||
pkce=False,
|
||||
code_verifier=None):
|
||||
"""Exchanges an authorization code for an OAuth2Credentials object.
|
||||
|
||||
Args:
|
||||
@@ -1665,6 +1641,15 @@ def credentials_from_code(client_id, client_secret, scope, code,
|
||||
device_uri: string, URI for device authorization endpoint. For
|
||||
convenience defaults to Google's endpoints but any OAuth
|
||||
2.0 provider can be used.
|
||||
pkce: boolean, default: False, Generate and include a "Proof Key
|
||||
for Code Exchange" (PKCE) with your authorization and token
|
||||
requests. This adds security for installed applications that
|
||||
cannot protect a client_secret. See RFC 7636 for details.
|
||||
code_verifier: bytestring or None, default: None, parameter passed
|
||||
as part of the code exchange when pkce=True. If
|
||||
None, a code_verifier will automatically be
|
||||
generated as part of step1_get_authorize_url(). See
|
||||
RFC 7636 for details.
|
||||
|
||||
Returns:
|
||||
An OAuth2Credentials object.
|
||||
@@ -1675,16 +1660,20 @@ def credentials_from_code(client_id, client_secret, scope, code,
|
||||
"""
|
||||
flow = OAuth2WebServerFlow(client_id, client_secret, scope,
|
||||
redirect_uri=redirect_uri,
|
||||
user_agent=user_agent, auth_uri=auth_uri,
|
||||
token_uri=token_uri, revoke_uri=revoke_uri,
|
||||
user_agent=user_agent,
|
||||
auth_uri=auth_uri,
|
||||
token_uri=token_uri,
|
||||
revoke_uri=revoke_uri,
|
||||
device_uri=device_uri,
|
||||
token_info_uri=token_info_uri)
|
||||
token_info_uri=token_info_uri,
|
||||
pkce=pkce,
|
||||
code_verifier=code_verifier)
|
||||
|
||||
credentials = flow.step2_exchange(code, http=http)
|
||||
return credentials
|
||||
|
||||
|
||||
@util.positional(3)
|
||||
@_helpers.positional(3)
|
||||
def credentials_from_clientsecrets_and_code(filename, scope, code,
|
||||
message=None,
|
||||
redirect_uri='postmessage',
|
||||
@@ -1713,6 +1702,15 @@ def credentials_from_clientsecrets_and_code(filename, scope, code,
|
||||
cache: An optional cache service client that implements get() and set()
|
||||
methods. See clientsecrets.loadfile() for details.
|
||||
device_uri: string, OAuth 2.0 device authorization endpoint
|
||||
pkce: boolean, default: False, Generate and include a "Proof Key
|
||||
for Code Exchange" (PKCE) with your authorization and token
|
||||
requests. This adds security for installed applications that
|
||||
cannot protect a client_secret. See RFC 7636 for details.
|
||||
code_verifier: bytestring or None, default: None, parameter passed
|
||||
as part of the code exchange when pkce=True. If
|
||||
None, a code_verifier will automatically be
|
||||
generated as part of step1_get_authorize_url(). See
|
||||
RFC 7636 for details.
|
||||
|
||||
Returns:
|
||||
An OAuth2Credentials object.
|
||||
@@ -1803,7 +1801,7 @@ class OAuth2WebServerFlow(Flow):
|
||||
OAuth2WebServerFlow objects may be safely pickled and unpickled.
|
||||
"""
|
||||
|
||||
@util.positional(4)
|
||||
@_helpers.positional(4)
|
||||
def __init__(self, client_id,
|
||||
client_secret=None,
|
||||
scope=None,
|
||||
@@ -1816,6 +1814,8 @@ class OAuth2WebServerFlow(Flow):
|
||||
device_uri=oauth2client.GOOGLE_DEVICE_URI,
|
||||
token_info_uri=oauth2client.GOOGLE_TOKEN_INFO_URI,
|
||||
authorization_header=None,
|
||||
pkce=False,
|
||||
code_verifier=None,
|
||||
**kwargs):
|
||||
"""Constructor for OAuth2WebServerFlow.
|
||||
|
||||
@@ -1853,6 +1853,15 @@ class OAuth2WebServerFlow(Flow):
|
||||
require a client to authenticate using a
|
||||
header value instead of passing client_secret
|
||||
in the POST body.
|
||||
pkce: boolean, default: False, Generate and include a "Proof Key
|
||||
for Code Exchange" (PKCE) with your authorization and token
|
||||
requests. This adds security for installed applications that
|
||||
cannot protect a client_secret. See RFC 7636 for details.
|
||||
code_verifier: bytestring or None, default: None, parameter passed
|
||||
as part of the code exchange when pkce=True. If
|
||||
None, a code_verifier will automatically be
|
||||
generated as part of step1_get_authorize_url(). See
|
||||
RFC 7636 for details.
|
||||
**kwargs: dict, The keyword arguments are all optional and required
|
||||
parameters for the OAuth calls.
|
||||
"""
|
||||
@@ -1862,7 +1871,7 @@ class OAuth2WebServerFlow(Flow):
|
||||
raise TypeError("The value of scope must not be None")
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.scope = util.scopes_to_string(scope)
|
||||
self.scope = _helpers.scopes_to_string(scope)
|
||||
self.redirect_uri = redirect_uri
|
||||
self.login_hint = login_hint
|
||||
self.user_agent = user_agent
|
||||
@@ -1872,9 +1881,11 @@ class OAuth2WebServerFlow(Flow):
|
||||
self.device_uri = device_uri
|
||||
self.token_info_uri = token_info_uri
|
||||
self.authorization_header = authorization_header
|
||||
self._pkce = pkce
|
||||
self.code_verifier = code_verifier
|
||||
self.params = _oauth2_web_server_flow_params(kwargs)
|
||||
|
||||
@util.positional(1)
|
||||
@_helpers.positional(1)
|
||||
def step1_get_authorize_url(self, redirect_uri=None, state=None):
|
||||
"""Returns a URI to redirect to the provider.
|
||||
|
||||
@@ -1912,10 +1923,17 @@ class OAuth2WebServerFlow(Flow):
|
||||
query_params['state'] = state
|
||||
if self.login_hint is not None:
|
||||
query_params['login_hint'] = self.login_hint
|
||||
query_params.update(self.params)
|
||||
return _update_query_params(self.auth_uri, query_params)
|
||||
if self._pkce:
|
||||
if not self.code_verifier:
|
||||
self.code_verifier = _pkce.code_verifier()
|
||||
challenge = _pkce.code_challenge(self.code_verifier)
|
||||
query_params['code_challenge'] = challenge
|
||||
query_params['code_challenge_method'] = 'S256'
|
||||
|
||||
@util.positional(1)
|
||||
query_params.update(self.params)
|
||||
return _helpers.update_query_params(self.auth_uri, query_params)
|
||||
|
||||
@_helpers.positional(1)
|
||||
def step1_get_device_and_user_codes(self, http=None):
|
||||
"""Returns a user code and the verification URL where to enter it
|
||||
|
||||
@@ -1940,8 +1958,8 @@ class OAuth2WebServerFlow(Flow):
|
||||
if http is None:
|
||||
http = transport.get_http_object()
|
||||
|
||||
resp, content = http.request(self.device_uri, method='POST', body=body,
|
||||
headers=headers)
|
||||
resp, content = transport.request(
|
||||
http, self.device_uri, method='POST', body=body, headers=headers)
|
||||
content = _helpers._from_bytes(content)
|
||||
if resp.status == http_client.OK:
|
||||
try:
|
||||
@@ -1963,7 +1981,7 @@ class OAuth2WebServerFlow(Flow):
|
||||
pass
|
||||
raise OAuth2DeviceCodeError(error_msg)
|
||||
|
||||
@util.positional(2)
|
||||
@_helpers.positional(2)
|
||||
def step2_exchange(self, code=None, http=None, device_flow_info=None):
|
||||
"""Exchanges a code for OAuth2Credentials.
|
||||
|
||||
@@ -2006,6 +2024,8 @@ class OAuth2WebServerFlow(Flow):
|
||||
}
|
||||
if self.client_secret is not None:
|
||||
post_data['client_secret'] = self.client_secret
|
||||
if self._pkce:
|
||||
post_data['code_verifier'] = self.code_verifier
|
||||
if device_flow_info is not None:
|
||||
post_data['grant_type'] = 'http://oauth.net/grant_type/device/1.0'
|
||||
else:
|
||||
@@ -2023,8 +2043,8 @@ class OAuth2WebServerFlow(Flow):
|
||||
if http is None:
|
||||
http = transport.get_http_object()
|
||||
|
||||
resp, content = http.request(self.token_uri, method='POST', body=body,
|
||||
headers=headers)
|
||||
resp, content = transport.request(
|
||||
http, self.token_uri, method='POST', body=body, headers=headers)
|
||||
d = _parse_exchange_token_response(content)
|
||||
if resp.status == http_client.OK and 'access_token' in d:
|
||||
access_token = d['access_token']
|
||||
@@ -2060,10 +2080,10 @@ class OAuth2WebServerFlow(Flow):
|
||||
raise FlowExchangeError(error_msg)
|
||||
|
||||
|
||||
@util.positional(2)
|
||||
@_helpers.positional(2)
|
||||
def flow_from_clientsecrets(filename, scope, redirect_uri=None,
|
||||
message=None, cache=None, login_hint=None,
|
||||
device_uri=None):
|
||||
device_uri=None, pkce=None, code_verifier=None):
|
||||
"""Create a Flow from a clientsecrets file.
|
||||
|
||||
Will create the right kind of Flow based on the contents of the
|
||||
@@ -2112,10 +2132,11 @@ def flow_from_clientsecrets(filename, scope, redirect_uri=None,
|
||||
'login_hint': login_hint,
|
||||
}
|
||||
revoke_uri = client_info.get('revoke_uri')
|
||||
if revoke_uri is not None:
|
||||
constructor_kwargs['revoke_uri'] = revoke_uri
|
||||
if device_uri is not None:
|
||||
constructor_kwargs['device_uri'] = device_uri
|
||||
optional = ('revoke_uri', 'device_uri', 'pkce', 'code_verifier')
|
||||
for param in optional:
|
||||
if locals()[param] is not None:
|
||||
constructor_kwargs[param] = locals()[param]
|
||||
|
||||
return OAuth2WebServerFlow(
|
||||
client_info['client_id'], client_info['client_secret'],
|
||||
scope, **constructor_kwargs)
|
||||
|
||||
@@ -22,7 +22,6 @@ import json
|
||||
|
||||
import six
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
# Properties that make a client_secrets.json file valid.
|
||||
TYPE_WEB = 'web'
|
||||
|
||||
@@ -20,28 +20,25 @@ See https://cloud.google.com/compute/docs/metadata
|
||||
import datetime
|
||||
import json
|
||||
|
||||
import httplib2
|
||||
from six.moves import http_client
|
||||
from six.moves.urllib import parse as urlparse
|
||||
|
||||
from oauth2client import _helpers
|
||||
from oauth2client import client
|
||||
from oauth2client import util
|
||||
from oauth2client import transport
|
||||
|
||||
|
||||
METADATA_ROOT = 'http://metadata.google.internal/computeMetadata/v1/'
|
||||
METADATA_HEADERS = {'Metadata-Flavor': 'Google'}
|
||||
|
||||
|
||||
def get(http_request, path, root=METADATA_ROOT, recursive=None):
|
||||
def get(http, path, root=METADATA_ROOT, recursive=None):
|
||||
"""Fetch a resource from the metadata server.
|
||||
|
||||
Args:
|
||||
http: an object to be used to make HTTP requests.
|
||||
path: A string indicating the resource to retrieve. For example,
|
||||
'instance/service-accounts/defualt'
|
||||
http_request: A callable that matches the method
|
||||
signature of httplib2.Http.request. Used to make the request to the
|
||||
metadataserver.
|
||||
root: A string indicating the full path to the metadata server root.
|
||||
recursive: A boolean indicating whether to do a recursive query of
|
||||
metadata. See
|
||||
@@ -51,15 +48,14 @@ def get(http_request, path, root=METADATA_ROOT, recursive=None):
|
||||
A dictionary if the metadata server returns JSON, otherwise a string.
|
||||
|
||||
Raises:
|
||||
httplib2.Httplib2Error if an error corrured while retrieving metadata.
|
||||
http_client.HTTPException if an error corrured while
|
||||
retrieving metadata.
|
||||
"""
|
||||
url = urlparse.urljoin(root, path)
|
||||
url = util._add_query_parameter(url, 'recursive', recursive)
|
||||
url = _helpers._add_query_parameter(url, 'recursive', recursive)
|
||||
|
||||
response, content = http_request(
|
||||
url,
|
||||
headers=METADATA_HEADERS
|
||||
)
|
||||
response, content = transport.request(
|
||||
http, url, headers=METADATA_HEADERS)
|
||||
|
||||
if response.status == http_client.OK:
|
||||
decoded = _helpers._from_bytes(content)
|
||||
@@ -68,21 +64,20 @@ def get(http_request, path, root=METADATA_ROOT, recursive=None):
|
||||
else:
|
||||
return decoded
|
||||
else:
|
||||
raise httplib2.HttpLib2Error(
|
||||
raise http_client.HTTPException(
|
||||
'Failed to retrieve {0} from the Google Compute Engine'
|
||||
'metadata service. Response:\n{1}'.format(url, response))
|
||||
|
||||
|
||||
def get_service_account_info(http_request, service_account='default'):
|
||||
def get_service_account_info(http, service_account='default'):
|
||||
"""Get information about a service account from the metadata server.
|
||||
|
||||
Args:
|
||||
http: an object to be used to make HTTP requests.
|
||||
service_account: An email specifying the service account for which to
|
||||
look up information. Default will be information for the "default"
|
||||
service account of the current compute engine instance.
|
||||
http_request: A callable that matches the method
|
||||
signature of httplib2.Http.request. Used to make the request to the
|
||||
metadata server.
|
||||
|
||||
Returns:
|
||||
A dictionary with information about the specified service account,
|
||||
for example:
|
||||
@@ -94,21 +89,19 @@ def get_service_account_info(http_request, service_account='default'):
|
||||
}
|
||||
"""
|
||||
return get(
|
||||
http_request,
|
||||
http,
|
||||
'instance/service-accounts/{0}/'.format(service_account),
|
||||
recursive=True)
|
||||
|
||||
|
||||
def get_token(http_request, service_account='default'):
|
||||
def get_token(http, service_account='default'):
|
||||
"""Fetch an oauth token for the
|
||||
|
||||
Args:
|
||||
http: an object to be used to make HTTP requests.
|
||||
service_account: An email specifying the service account this token
|
||||
should represent. Default will be a token for the "default" service
|
||||
account of the current compute engine instance.
|
||||
http_request: A callable that matches the method
|
||||
signature of httplib2.Http.request. Used to make the request to the
|
||||
metadataserver.
|
||||
|
||||
Returns:
|
||||
A tuple of (access token, token expiration), where access token is the
|
||||
@@ -116,7 +109,7 @@ def get_token(http_request, service_account='default'):
|
||||
that indicates when the access token will expire.
|
||||
"""
|
||||
token_json = get(
|
||||
http_request,
|
||||
http,
|
||||
'instance/service-accounts/{0}/token'.format(service_account))
|
||||
token_expiry = client._UTCNOW() + datetime.timedelta(
|
||||
seconds=token_json['expires_in'])
|
||||
|
||||
@@ -29,13 +29,13 @@ from google.appengine.api import memcache
|
||||
from google.appengine.api import users
|
||||
from google.appengine.ext import db
|
||||
from google.appengine.ext.webapp.util import login_required
|
||||
import httplib2
|
||||
import webapp2 as webapp
|
||||
|
||||
import oauth2client
|
||||
from oauth2client import _helpers
|
||||
from oauth2client import client
|
||||
from oauth2client import clientsecrets
|
||||
from oauth2client import util
|
||||
from oauth2client import transport
|
||||
from oauth2client.contrib import xsrfutil
|
||||
|
||||
# This is a temporary fix for a Google internal issue.
|
||||
@@ -45,8 +45,6 @@ except ImportError: # pragma: NO COVER
|
||||
_appengine_ndb = None
|
||||
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
OAUTH2CLIENT_NAMESPACE = 'oauth2client#ns'
|
||||
@@ -131,7 +129,7 @@ class AppAssertionCredentials(client.AssertionCredentials):
|
||||
information to generate and refresh its own access tokens.
|
||||
"""
|
||||
|
||||
@util.positional(2)
|
||||
@_helpers.positional(2)
|
||||
def __init__(self, scope, **kwargs):
|
||||
"""Constructor for AppAssertionCredentials
|
||||
|
||||
@@ -143,7 +141,7 @@ class AppAssertionCredentials(client.AssertionCredentials):
|
||||
or unspecified, the default service account for
|
||||
the app is used.
|
||||
"""
|
||||
self.scope = util.scopes_to_string(scope)
|
||||
self.scope = _helpers.scopes_to_string(scope)
|
||||
self._kwargs = kwargs
|
||||
self.service_account_id = kwargs.get('service_account_id', None)
|
||||
self._service_account_email = None
|
||||
@@ -157,17 +155,15 @@ class AppAssertionCredentials(client.AssertionCredentials):
|
||||
data = json.loads(json_data)
|
||||
return AppAssertionCredentials(data['scope'])
|
||||
|
||||
def _refresh(self, http_request):
|
||||
"""Refreshes the access_token.
|
||||
def _refresh(self, http):
|
||||
"""Refreshes the access token.
|
||||
|
||||
Since the underlying App Engine app_identity implementation does its
|
||||
own caching we can skip all the storage hoops and just to a refresh
|
||||
using the API.
|
||||
|
||||
Args:
|
||||
http_request: callable, a callable that matches the method
|
||||
signature of httplib2.Http.request, used to make the
|
||||
refresh request.
|
||||
http: unused HTTP object
|
||||
|
||||
Raises:
|
||||
AccessTokenRefreshError: When the refresh fails.
|
||||
@@ -305,7 +301,7 @@ class StorageByKeyName(client.Storage):
|
||||
and that entities are stored by key_name.
|
||||
"""
|
||||
|
||||
@util.positional(4)
|
||||
@_helpers.positional(4)
|
||||
def __init__(self, model, key_name, property_name, cache=None, user=None):
|
||||
"""Constructor for Storage.
|
||||
|
||||
@@ -523,7 +519,7 @@ class OAuth2Decorator(object):
|
||||
|
||||
flow = property(get_flow, set_flow)
|
||||
|
||||
@util.positional(4)
|
||||
@_helpers.positional(4)
|
||||
def __init__(self, client_id, client_secret, scope,
|
||||
auth_uri=oauth2client.GOOGLE_AUTH_URI,
|
||||
token_uri=oauth2client.GOOGLE_TOKEN_URI,
|
||||
@@ -590,7 +586,7 @@ class OAuth2Decorator(object):
|
||||
self.credentials = None
|
||||
self._client_id = client_id
|
||||
self._client_secret = client_secret
|
||||
self._scope = util.scopes_to_string(scope)
|
||||
self._scope = _helpers.scopes_to_string(scope)
|
||||
self._auth_uri = auth_uri
|
||||
self._token_uri = token_uri
|
||||
self._revoke_uri = revoke_uri
|
||||
@@ -742,7 +738,8 @@ class OAuth2Decorator(object):
|
||||
*args: Positional arguments passed to httplib2.Http constructor.
|
||||
**kwargs: Positional arguments passed to httplib2.Http constructor.
|
||||
"""
|
||||
return self.credentials.authorize(httplib2.Http(*args, **kwargs))
|
||||
return self.credentials.authorize(
|
||||
transport.get_http_object(*args, **kwargs))
|
||||
|
||||
@property
|
||||
def callback_path(self):
|
||||
@@ -804,7 +801,7 @@ class OAuth2Decorator(object):
|
||||
if (decorator._token_response_param and
|
||||
credentials.token_response):
|
||||
resp_json = json.dumps(credentials.token_response)
|
||||
redirect_uri = util._add_query_parameter(
|
||||
redirect_uri = _helpers._add_query_parameter(
|
||||
redirect_uri, decorator._token_response_param,
|
||||
resp_json)
|
||||
|
||||
@@ -848,7 +845,7 @@ class OAuth2DecoratorFromClientSecrets(OAuth2Decorator):
|
||||
|
||||
"""
|
||||
|
||||
@util.positional(3)
|
||||
@_helpers.positional(3)
|
||||
def __init__(self, filename, scope, message=None, cache=None, **kwargs):
|
||||
"""Constructor
|
||||
|
||||
@@ -891,7 +888,7 @@ class OAuth2DecoratorFromClientSecrets(OAuth2Decorator):
|
||||
self._message = 'Please configure your application for OAuth 2.0.'
|
||||
|
||||
|
||||
@util.positional(2)
|
||||
@_helpers.positional(2)
|
||||
def oauth2decorator_from_clientsecrets(filename, scope,
|
||||
message=None, cache=None):
|
||||
"""Creates an OAuth2Decorator populated from a clientsecrets file.
|
||||
|
||||
@@ -117,7 +117,12 @@ class DevshellCredentials(client.GoogleCredentials):
|
||||
user_agent)
|
||||
self._refresh(None)
|
||||
|
||||
def _refresh(self, http_request):
|
||||
def _refresh(self, http):
|
||||
"""Refreshes the access token.
|
||||
|
||||
Args:
|
||||
http: unused HTTP object
|
||||
"""
|
||||
self.devshell_response = _SendRecv()
|
||||
self.access_token = self.devshell_response.access_token
|
||||
expires_in = self.devshell_response.expires_in
|
||||
|
||||
@@ -52,6 +52,9 @@ Add the helper to your INSTALLED_APPS:
|
||||
|
||||
This helper also requires the Django Session Middleware, so
|
||||
``django.contrib.sessions.middleware`` should be in INSTALLED_APPS as well.
|
||||
MIDDLEWARE or MIDDLEWARE_CLASSES (in Django versions <1.10) should also
|
||||
contain the string 'django.contrib.sessions.middleware.SessionMiddleware'.
|
||||
|
||||
|
||||
Add the client secrets created earlier to the settings. You can either
|
||||
specify the path to the credentials file in JSON format
|
||||
@@ -228,10 +231,10 @@ import importlib
|
||||
import django.conf
|
||||
from django.core import exceptions
|
||||
from django.core import urlresolvers
|
||||
import httplib2
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from oauth2client import clientsecrets
|
||||
from oauth2client import transport
|
||||
from oauth2client.contrib import dictionary_storage
|
||||
from oauth2client.contrib.django_util import storage
|
||||
|
||||
@@ -335,16 +338,26 @@ class OAuth2Settings(object):
|
||||
self.request_prefix = getattr(settings_instance,
|
||||
'GOOGLE_OAUTH2_REQUEST_ATTRIBUTE',
|
||||
GOOGLE_OAUTH2_REQUEST_ATTRIBUTE)
|
||||
self.client_id, self.client_secret = \
|
||||
_get_oauth2_client_id_and_secret(settings_instance)
|
||||
info = _get_oauth2_client_id_and_secret(settings_instance)
|
||||
self.client_id, self.client_secret = info
|
||||
|
||||
if ('django.contrib.sessions.middleware.SessionMiddleware'
|
||||
not in settings_instance.MIDDLEWARE_CLASSES):
|
||||
# Django 1.10 deprecated MIDDLEWARE_CLASSES in favor of MIDDLEWARE
|
||||
middleware_settings = getattr(settings_instance, 'MIDDLEWARE', None)
|
||||
if middleware_settings is None:
|
||||
middleware_settings = getattr(
|
||||
settings_instance, 'MIDDLEWARE_CLASSES', None)
|
||||
if middleware_settings is None:
|
||||
raise exceptions.ImproperlyConfigured(
|
||||
'The Google OAuth2 Helper requires session middleware to '
|
||||
'be installed. Edit your MIDDLEWARE_CLASSES setting'
|
||||
' to include \'django.contrib.sessions.middleware.'
|
||||
'SessionMiddleware\'.')
|
||||
'Django settings has neither MIDDLEWARE nor MIDDLEWARE_CLASSES'
|
||||
'configured')
|
||||
|
||||
if ('django.contrib.sessions.middleware.SessionMiddleware' not in
|
||||
middleware_settings):
|
||||
raise exceptions.ImproperlyConfigured(
|
||||
'The Google OAuth2 Helper requires session middleware to '
|
||||
'be installed. Edit your MIDDLEWARE_CLASSES or MIDDLEWARE '
|
||||
'setting to include \'django.contrib.sessions.middleware.'
|
||||
'SessionMiddleware\'.')
|
||||
(self.storage_model, self.storage_model_user_property,
|
||||
self.storage_model_credentials_property) = _get_storage_model()
|
||||
|
||||
@@ -470,8 +483,7 @@ class UserOAuth2(object):
|
||||
|
||||
@property
|
||||
def http(self):
|
||||
"""Helper method to create an HTTP client authorized with OAuth2
|
||||
credentials."""
|
||||
"""Helper: create HTTP client authorized with OAuth2 credentials."""
|
||||
if self.has_credentials():
|
||||
return self.credentials.authorize(httplib2.Http())
|
||||
return self.credentials.authorize(transport.get_http_object())
|
||||
return None
|
||||
|
||||
@@ -22,13 +22,13 @@ in the configured storage."""
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import pickle
|
||||
|
||||
from django import http
|
||||
from django import shortcuts
|
||||
from django.conf import settings
|
||||
from django.core import urlresolvers
|
||||
from django.shortcuts import redirect
|
||||
import jsonpickle
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from oauth2client import client
|
||||
@@ -71,7 +71,7 @@ def _make_flow(request, scopes, return_url=None):
|
||||
urlresolvers.reverse("google_oauth:callback")))
|
||||
|
||||
flow_key = _FLOW_KEY.format(csrf_token)
|
||||
request.session[flow_key] = pickle.dumps(flow)
|
||||
request.session[flow_key] = jsonpickle.encode(flow)
|
||||
return flow
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ def _get_flow_for_token(csrf_token, request):
|
||||
CSRF token.
|
||||
"""
|
||||
flow_pickle = request.session.get(_FLOW_KEY.format(csrf_token), None)
|
||||
return None if flow_pickle is None else pickle.loads(flow_pickle)
|
||||
return None if flow_pickle is None else jsonpickle.decode(flow_pickle)
|
||||
|
||||
|
||||
def oauth2_callback(request):
|
||||
@@ -170,7 +170,10 @@ def oauth2_authorize(request):
|
||||
A redirect to Google OAuth2 Authorization.
|
||||
"""
|
||||
return_url = request.GET.get('return_url', None)
|
||||
if not return_url:
|
||||
return_url = request.META.get('HTTP_REFERER', '/')
|
||||
|
||||
scopes = request.GET.getlist('scopes', django_util.oauth2_settings.scopes)
|
||||
# Model storage (but not session storage) requires a logged in user
|
||||
if django_util.oauth2_settings.storage_model:
|
||||
if not request.user.is_authenticated():
|
||||
@@ -178,13 +181,11 @@ def oauth2_authorize(request):
|
||||
settings.LOGIN_URL, parse.quote(request.get_full_path())))
|
||||
# This checks for the case where we ended up here because of a logged
|
||||
# out user but we had credentials for it in the first place
|
||||
elif get_storage(request).get() is not None:
|
||||
return redirect(return_url)
|
||||
else:
|
||||
user_oauth = django_util.UserOAuth2(request, scopes, return_url)
|
||||
if user_oauth.has_credentials():
|
||||
return redirect(return_url)
|
||||
|
||||
scopes = request.GET.getlist('scopes', django_util.oauth2_settings.scopes)
|
||||
|
||||
if not return_url:
|
||||
return_url = request.META.get('HTTP_REFERER', '/')
|
||||
flow = _make_flow(request=request, scopes=scopes, return_url=return_url)
|
||||
auth_url = flow.step1_get_authorize_url()
|
||||
return shortcuts.redirect(auth_url)
|
||||
|
||||
@@ -179,16 +179,14 @@ try:
|
||||
except ImportError: # pragma: NO COVER
|
||||
raise ImportError('The flask utilities require flask 0.9 or newer.')
|
||||
|
||||
import httplib2
|
||||
import six.moves.http_client as httplib
|
||||
|
||||
from oauth2client import client
|
||||
from oauth2client import clientsecrets
|
||||
from oauth2client import transport
|
||||
from oauth2client.contrib import dictionary_storage
|
||||
|
||||
|
||||
__author__ = 'jonwayne@google.com (Jon Wayne Parrott)'
|
||||
|
||||
_DEFAULT_SCOPES = ('email',)
|
||||
_CREDENTIALS_KEY = 'google_oauth2_credentials'
|
||||
_FLOW_KEY = 'google_oauth2_flow_{0}'
|
||||
@@ -553,4 +551,5 @@ class UserOAuth2(object):
|
||||
"""
|
||||
if not self.credentials:
|
||||
raise ValueError('No credentials available.')
|
||||
return self.credentials.authorize(httplib2.Http(*args, **kwargs))
|
||||
return self.credentials.authorize(
|
||||
transport.get_http_object(*args, **kwargs))
|
||||
|
||||
@@ -20,14 +20,12 @@ Utilities for making it easier to use OAuth 2.0 on Google Compute Engine.
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
import httplib2
|
||||
from six.moves import http_client
|
||||
|
||||
from oauth2client import client
|
||||
from oauth2client.contrib import _metadata
|
||||
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_SCOPES_WARNING = """\
|
||||
@@ -98,44 +96,40 @@ class AppAssertionCredentials(client.AssertionCredentials):
|
||||
Returns:
|
||||
A set of strings containing the canonical list of scopes.
|
||||
"""
|
||||
self._retrieve_info(http.request)
|
||||
self._retrieve_info(http)
|
||||
return self.scopes
|
||||
|
||||
def _retrieve_info(self, http_request):
|
||||
"""Validates invalid service accounts by retrieving service account info.
|
||||
def _retrieve_info(self, http):
|
||||
"""Retrieves service account info for invalid credentials.
|
||||
|
||||
Args:
|
||||
http_request: callable, a callable that matches the method
|
||||
signature of httplib2.Http.request, used to make the
|
||||
request to the metadata server
|
||||
http: an object to be used to make HTTP requests.
|
||||
"""
|
||||
if self.invalid:
|
||||
info = _metadata.get_service_account_info(
|
||||
http_request,
|
||||
http,
|
||||
service_account=self.service_account_email or 'default')
|
||||
self.invalid = False
|
||||
self.service_account_email = info['email']
|
||||
self.scopes = info['scopes']
|
||||
|
||||
def _refresh(self, http_request):
|
||||
"""Refreshes the access_token.
|
||||
def _refresh(self, http):
|
||||
"""Refreshes the access token.
|
||||
|
||||
Skip all the storage hoops and just refresh using the API.
|
||||
|
||||
Args:
|
||||
http_request: callable, a callable that matches the method
|
||||
signature of httplib2.Http.request, used to make
|
||||
the refresh request.
|
||||
http: an object to be used to make HTTP requests.
|
||||
|
||||
Raises:
|
||||
HttpAccessTokenRefreshError: When the refresh fails.
|
||||
"""
|
||||
try:
|
||||
self._retrieve_info(http_request)
|
||||
self._retrieve_info(http)
|
||||
self.access_token, self.token_expiry = _metadata.get_token(
|
||||
http_request, service_account=self.service_account_email)
|
||||
except httplib2.HttpLib2Error as e:
|
||||
raise client.HttpAccessTokenRefreshError(str(e))
|
||||
http, service_account=self.service_account_email)
|
||||
except http_client.HTTPException as err:
|
||||
raise client.HttpAccessTokenRefreshError(str(err))
|
||||
|
||||
@property
|
||||
def serialization_data(self):
|
||||
|
||||
@@ -24,9 +24,6 @@ import keyring
|
||||
from oauth2client import client
|
||||
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
|
||||
class Storage(client.Storage):
|
||||
"""Store and retrieve a single credential to and from the keyring.
|
||||
|
||||
|
||||
@@ -20,12 +20,7 @@ import hmac
|
||||
import time
|
||||
|
||||
from oauth2client import _helpers
|
||||
from oauth2client import util
|
||||
|
||||
__authors__ = [
|
||||
'"Doug Coker" <dcoker@google.com>',
|
||||
'"Joe Gregorio" <jcgregorio@google.com>',
|
||||
]
|
||||
|
||||
# Delimiter character
|
||||
DELIMITER = b':'
|
||||
@@ -34,7 +29,7 @@ DELIMITER = b':'
|
||||
DEFAULT_TIMEOUT_SECS = 60 * 60
|
||||
|
||||
|
||||
@util.positional(2)
|
||||
@_helpers.positional(2)
|
||||
def generate_token(key, user_id, action_id='', when=None):
|
||||
"""Generates a URL-safe token for the given user, action, time tuple.
|
||||
|
||||
@@ -62,7 +57,7 @@ def generate_token(key, user_id, action_id='', when=None):
|
||||
return token
|
||||
|
||||
|
||||
@util.positional(3)
|
||||
@_helpers.positional(3)
|
||||
def validate_token(key, token, user_id, action_id="", current_time=None):
|
||||
"""Validates that the given token authorizes the user for the action.
|
||||
|
||||
|
||||
@@ -21,16 +21,10 @@ credentials.
|
||||
import os
|
||||
import threading
|
||||
|
||||
from oauth2client import _helpers
|
||||
from oauth2client import client
|
||||
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
|
||||
class CredentialsFileSymbolicLinkError(Exception):
|
||||
"""Credentials files must not be symbolic links."""
|
||||
|
||||
|
||||
class Storage(client.Storage):
|
||||
"""Store and retrieve a single credential to and from a file."""
|
||||
|
||||
@@ -38,11 +32,6 @@ class Storage(client.Storage):
|
||||
super(Storage, self).__init__(lock=threading.Lock())
|
||||
self._filename = filename
|
||||
|
||||
def _validate_file(self):
|
||||
if os.path.islink(self._filename):
|
||||
raise CredentialsFileSymbolicLinkError(
|
||||
'File: {0} is a symbolic link.'.format(self._filename))
|
||||
|
||||
def locked_get(self):
|
||||
"""Retrieve Credential from file.
|
||||
|
||||
@@ -50,10 +39,10 @@ class Storage(client.Storage):
|
||||
oauth2client.client.Credentials
|
||||
|
||||
Raises:
|
||||
CredentialsFileSymbolicLinkError if the file is a symbolic link.
|
||||
IOError if the file is a symbolic link.
|
||||
"""
|
||||
credentials = None
|
||||
self._validate_file()
|
||||
_helpers.validate_file(self._filename)
|
||||
try:
|
||||
f = open(self._filename, 'rb')
|
||||
content = f.read()
|
||||
@@ -89,10 +78,10 @@ class Storage(client.Storage):
|
||||
credentials: Credentials, the credentials to store.
|
||||
|
||||
Raises:
|
||||
CredentialsFileSymbolicLinkError if the file is a symbolic link.
|
||||
IOError if the file is a symbolic link.
|
||||
"""
|
||||
self._create_file_if_needed()
|
||||
self._validate_file()
|
||||
_helpers.validate_file(self._filename)
|
||||
f = open(self._filename, 'w')
|
||||
f.write(credentials.to_json())
|
||||
f.close()
|
||||
|
||||
@@ -25,7 +25,6 @@ from oauth2client import _helpers
|
||||
from oauth2client import client
|
||||
from oauth2client import crypt
|
||||
from oauth2client import transport
|
||||
from oauth2client import util
|
||||
|
||||
|
||||
_PASSWORD_DEFAULT = 'notasecret'
|
||||
@@ -110,7 +109,7 @@ class ServiceAccountCredentials(client.AssertionCredentials):
|
||||
|
||||
self._service_account_email = service_account_email
|
||||
self._signer = signer
|
||||
self._scopes = util.scopes_to_string(scopes)
|
||||
self._scopes = _helpers.scopes_to_string(scopes)
|
||||
self._private_key_id = private_key_id
|
||||
self.client_id = client_id
|
||||
self._user_agent = user_agent
|
||||
@@ -650,9 +649,22 @@ class _JWTAccessCredentials(ServiceAccountCredentials):
|
||||
return result
|
||||
|
||||
def refresh(self, http):
|
||||
"""Refreshes the access_token.
|
||||
|
||||
The HTTP object is unused since no request needs to be made to
|
||||
get a new token, it can just be generated locally.
|
||||
|
||||
Args:
|
||||
http: unused HTTP object
|
||||
"""
|
||||
self._refresh(None)
|
||||
|
||||
def _refresh(self, http_request):
|
||||
def _refresh(self, http):
|
||||
"""Refreshes the access_token.
|
||||
|
||||
Args:
|
||||
http: unused HTTP object
|
||||
"""
|
||||
self.access_token, self.token_expiry = self._create_token()
|
||||
|
||||
def _create_token(self, additional_claims=None):
|
||||
|
||||
@@ -30,11 +30,10 @@ from six.moves import http_client
|
||||
from six.moves import input
|
||||
from six.moves import urllib
|
||||
|
||||
from oauth2client import _helpers
|
||||
from oauth2client import client
|
||||
from oauth2client import util
|
||||
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
__all__ = ['argparser', 'run_flow', 'message_if_missing']
|
||||
|
||||
_CLIENT_SECRETS_MESSAGE = """WARNING: Please configure OAuth 2.0
|
||||
@@ -123,22 +122,22 @@ class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
if an error occurred.
|
||||
"""
|
||||
self.send_response(http_client.OK)
|
||||
self.send_header("Content-type", "text/html")
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
query = self.path.split('?', 1)[-1]
|
||||
query = dict(urllib.parse.parse_qsl(query))
|
||||
parts = urllib.parse.urlparse(self.path)
|
||||
query = _helpers.parse_unique_urlencoded(parts.query)
|
||||
self.server.query_params = query
|
||||
self.wfile.write(
|
||||
b"<html><head><title>Authentication Status</title></head>")
|
||||
b'<html><head><title>Authentication Status</title></head>')
|
||||
self.wfile.write(
|
||||
b"<body><p>The authentication flow has completed.</p>")
|
||||
self.wfile.write(b"</body></html>")
|
||||
b'<body><p>The authentication flow has completed.</p>')
|
||||
self.wfile.write(b'</body></html>')
|
||||
|
||||
def log_message(self, format, *args):
|
||||
"""Do not log messages to stdout while running as cmd. line program."""
|
||||
|
||||
|
||||
@util.positional(3)
|
||||
@_helpers.positional(3)
|
||||
def run_flow(flow, storage, flags=None, http=None):
|
||||
"""Core code for a command-line application.
|
||||
|
||||
@@ -218,16 +217,6 @@ def run_flow(flow, storage, flags=None, http=None):
|
||||
flow.redirect_uri = oauth_callback
|
||||
authorize_url = flow.step1_get_authorize_url()
|
||||
|
||||
if flags.short_url:
|
||||
try:
|
||||
from googleapiclient.discovery import build
|
||||
service = build('urlshortener', 'v1', http=http)
|
||||
url_result = service.url().insert(body={'longUrl': authorize_url},
|
||||
key=u'AIzaSyBlmgbii8QfJSYmC9VTMOfqrAt5Vj5wtzE').execute()
|
||||
authorize_url = url_result['id']
|
||||
except:
|
||||
pass
|
||||
|
||||
if not flags.noauth_local_webserver:
|
||||
import webbrowser
|
||||
webbrowser.open(authorize_url, new=1, autoraise=True)
|
||||
|
||||
@@ -18,7 +18,7 @@ import httplib2
|
||||
import six
|
||||
from six.moves import http_client
|
||||
|
||||
from oauth2client._helpers import _to_bytes
|
||||
from oauth2client import _helpers
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -58,13 +58,19 @@ def get_cached_http():
|
||||
return _CACHED_HTTP
|
||||
|
||||
|
||||
def get_http_object():
|
||||
def get_http_object(*args, **kwargs):
|
||||
"""Return a new HTTP object.
|
||||
|
||||
Args:
|
||||
*args: tuple, The positional arguments to be passed when
|
||||
contructing a new HTTP object.
|
||||
**kwargs: dict, The keyword arguments to be passed when
|
||||
contructing a new HTTP object.
|
||||
|
||||
Returns:
|
||||
httplib2.Http, an HTTP object.
|
||||
"""
|
||||
return httplib2.Http()
|
||||
return httplib2.Http(*args, **kwargs)
|
||||
|
||||
|
||||
def _initialize_headers(headers):
|
||||
@@ -121,7 +127,7 @@ def clean_headers(headers):
|
||||
k = str(k)
|
||||
if not isinstance(v, six.binary_type):
|
||||
v = str(v)
|
||||
clean[_to_bytes(k)] = _to_bytes(v)
|
||||
clean[_helpers._to_bytes(k)] = _helpers._to_bytes(v)
|
||||
except UnicodeEncodeError:
|
||||
from oauth2client.client import NonAsciiHeaderError
|
||||
raise NonAsciiHeaderError(k, ': ', v)
|
||||
@@ -164,9 +170,9 @@ def wrap_http_for_auth(credentials, http):
|
||||
_STREAM_PROPERTIES):
|
||||
body_stream_position = body.tell()
|
||||
|
||||
resp, content = orig_request_method(uri, method, body,
|
||||
clean_headers(headers),
|
||||
redirections, connection_type)
|
||||
resp, content = request(orig_request_method, 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.
|
||||
@@ -182,9 +188,9 @@ def wrap_http_for_auth(credentials, http):
|
||||
if body_stream_position is not None:
|
||||
body.seek(body_stream_position)
|
||||
|
||||
resp, content = orig_request_method(uri, method, body,
|
||||
clean_headers(headers),
|
||||
redirections, connection_type)
|
||||
resp, content = request(orig_request_method, uri, method, body,
|
||||
clean_headers(headers),
|
||||
redirections, connection_type)
|
||||
|
||||
return resp, content
|
||||
|
||||
@@ -192,7 +198,7 @@ def wrap_http_for_auth(credentials, http):
|
||||
http.request = new_request
|
||||
|
||||
# Set credentials as a property of the request method.
|
||||
setattr(http.request, 'credentials', credentials)
|
||||
http.request.credentials = credentials
|
||||
|
||||
|
||||
def wrap_http_for_jwt_access(credentials, http):
|
||||
@@ -222,9 +228,9 @@ def wrap_http_for_jwt_access(credentials, http):
|
||||
if (credentials.access_token is None or
|
||||
credentials.access_token_expired):
|
||||
credentials.refresh(None)
|
||||
return authenticated_request_method(uri, method, body,
|
||||
headers, redirections,
|
||||
connection_type)
|
||||
return request(authenticated_request_method, uri,
|
||||
method, body, headers, redirections,
|
||||
connection_type)
|
||||
else:
|
||||
# If we don't have an 'aud' (audience) claim,
|
||||
# create a 1-time token with the uri root as the audience
|
||||
@@ -234,12 +240,46 @@ def wrap_http_for_jwt_access(credentials, http):
|
||||
token, unused_expiry = credentials._create_token({'aud': uri_root})
|
||||
|
||||
headers['Authorization'] = 'Bearer ' + token
|
||||
return orig_request_method(uri, method, body,
|
||||
clean_headers(headers),
|
||||
redirections, connection_type)
|
||||
return request(orig_request_method, uri, method, body,
|
||||
clean_headers(headers),
|
||||
redirections, connection_type)
|
||||
|
||||
# Replace the request method with our own closure.
|
||||
http.request = new_request
|
||||
|
||||
# Set credentials as a property of the request method.
|
||||
http.request.credentials = credentials
|
||||
|
||||
|
||||
def request(http, uri, method='GET', body=None, headers=None,
|
||||
redirections=httplib2.DEFAULT_MAX_REDIRECTS,
|
||||
connection_type=None):
|
||||
"""Make an HTTP request with an HTTP object and arguments.
|
||||
|
||||
Args:
|
||||
http: httplib2.Http, an http object to be used to make requests.
|
||||
uri: string, The URI to be requested.
|
||||
method: string, The HTTP method to use for the request. Defaults
|
||||
to 'GET'.
|
||||
body: string, The payload / body in HTTP request. By default
|
||||
there is no payload.
|
||||
headers: dict, Key-value pairs of request headers. By default
|
||||
there are no headers.
|
||||
redirections: int, The number of allowed 203 redirects for
|
||||
the request. Defaults to 5.
|
||||
connection_type: httplib.HTTPConnection, a subclass to be used for
|
||||
establishing connection. If not set, the type
|
||||
will be determined from the ``uri``.
|
||||
|
||||
Returns:
|
||||
tuple, a pair of a httplib2.Response with the status code and other
|
||||
headers and the bytes of the content returned.
|
||||
"""
|
||||
# NOTE: Allowing http or http.request is temporary (See Issue 601).
|
||||
http_callable = getattr(http, 'request', http)
|
||||
return http_callable(uri, method=method, body=body, headers=headers,
|
||||
redirections=redirections,
|
||||
connection_type=connection_type)
|
||||
|
||||
|
||||
_CACHED_HTTP = httplib2.Http(MemoryCache())
|
||||
|
||||
Reference in New Issue
Block a user