mirror of
https://github.com/GAM-team/GAM.git
synced 2026-07-03 20:31:35 +00:00
oauth2client 4.0
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
"""Client library for using OAuth2, especially with Google APIs."""
|
"""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_AUTH_URI = 'https://accounts.google.com/o/oauth2/v2/auth'
|
||||||
GOOGLE_DEVICE_URI = 'https://accounts.google.com/o/oauth2/device/code'
|
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.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
"""Helper functions for commonly used utilities."""
|
"""Helper functions for commonly used utilities."""
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
import functools
|
||||||
|
import inspect
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import warnings
|
||||||
|
|
||||||
import six
|
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):
|
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
|
import oauth2client
|
||||||
from oauth2client import _helpers
|
from oauth2client import _helpers
|
||||||
|
from oauth2client import _pkce
|
||||||
from oauth2client import clientsecrets
|
from oauth2client import clientsecrets
|
||||||
from oauth2client import transport
|
from oauth2client import transport
|
||||||
from oauth2client import util
|
|
||||||
|
|
||||||
|
|
||||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
|
||||||
|
|
||||||
HAS_OPENSSL = False
|
HAS_OPENSSL = False
|
||||||
HAS_CRYPTO = False
|
HAS_CRYPTO = False
|
||||||
try:
|
try:
|
||||||
@@ -100,20 +98,20 @@ AccessTokenInfo = collections.namedtuple(
|
|||||||
DEFAULT_ENV_NAME = 'UNKNOWN'
|
DEFAULT_ENV_NAME = 'UNKNOWN'
|
||||||
|
|
||||||
# If set to True _get_environment avoid GCE check (_detect_gce_environment)
|
# 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
|
# Timeout in seconds to wait for the GCE metadata server when detecting the
|
||||||
# GCE environment.
|
# GCE environment.
|
||||||
try:
|
try:
|
||||||
GCE_METADATA_TIMEOUT = int(
|
GCE_METADATA_TIMEOUT = int(os.getenv('GCE_METADATA_TIMEOUT', 3))
|
||||||
os.environ.setdefault('GCE_METADATA_TIMEOUT', '3'))
|
|
||||||
except ValueError: # pragma: NO COVER
|
except ValueError: # pragma: NO COVER
|
||||||
GCE_METADATA_TIMEOUT = 3
|
GCE_METADATA_TIMEOUT = 3
|
||||||
|
|
||||||
_SERVER_SOFTWARE = 'SERVER_SOFTWARE'
|
_SERVER_SOFTWARE = 'SERVER_SOFTWARE'
|
||||||
_GCE_METADATA_HOST = '169.254.169.254'
|
_GCE_METADATA_URI = 'http://169.254.169.254'
|
||||||
_METADATA_FLAVOR_HEADER = 'Metadata-Flavor'
|
_METADATA_FLAVOR_HEADER = 'metadata-flavor' # lowercase header
|
||||||
_DESIRED_METADATA_FLAVOR = 'Google'
|
_DESIRED_METADATA_FLAVOR = 'Google'
|
||||||
|
_GCE_HEADERS = {_METADATA_FLAVOR_HEADER: _DESIRED_METADATA_FLAVOR}
|
||||||
|
|
||||||
# Expose utcnow() at module level to allow for
|
# Expose utcnow() at module level to allow for
|
||||||
# easier testing (by replacing with a stub).
|
# easier testing (by replacing with a stub).
|
||||||
@@ -273,7 +271,7 @@ class Credentials(object):
|
|||||||
to_serialize[key] = val.decode('utf-8')
|
to_serialize[key] = val.decode('utf-8')
|
||||||
if isinstance(val, set):
|
if isinstance(val, set):
|
||||||
to_serialize[key] = list(val)
|
to_serialize[key] = list(val)
|
||||||
return json.dumps(to_serialize, indent=4, sort_keys=True)
|
return json.dumps(to_serialize)
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
"""Creating a JSON representation of an instance of Credentials.
|
"""Creating a JSON representation of an instance of Credentials.
|
||||||
@@ -440,23 +438,6 @@ class Storage(object):
|
|||||||
self.release_lock()
|
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):
|
class OAuth2Credentials(Credentials):
|
||||||
"""Credentials object for OAuth 2.0.
|
"""Credentials object for OAuth 2.0.
|
||||||
|
|
||||||
@@ -466,7 +447,7 @@ class OAuth2Credentials(Credentials):
|
|||||||
OAuth2Credentials objects may be safely pickled and unpickled.
|
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,
|
def __init__(self, access_token, client_id, client_secret, refresh_token,
|
||||||
token_expiry, token_uri, user_agent, revoke_uri=None,
|
token_expiry, token_uri, user_agent, revoke_uri=None,
|
||||||
id_token=None, token_response=None, scopes=None,
|
id_token=None, token_response=None, scopes=None,
|
||||||
@@ -513,7 +494,7 @@ class OAuth2Credentials(Credentials):
|
|||||||
self.revoke_uri = revoke_uri
|
self.revoke_uri = revoke_uri
|
||||||
self.id_token = id_token
|
self.id_token = id_token
|
||||||
self.token_response = token_response
|
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
|
self.token_info_uri = token_info_uri
|
||||||
|
|
||||||
# True if the credentials have been revoked or expired and can't be
|
# 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
|
http: httplib2.Http, an http object to be used to make the refresh
|
||||||
request.
|
request.
|
||||||
"""
|
"""
|
||||||
self._refresh(http.request)
|
self._refresh(http)
|
||||||
|
|
||||||
def revoke(self, http):
|
def revoke(self, http):
|
||||||
"""Revokes a refresh_token and makes the credentials void.
|
"""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
|
http: httplib2.Http, an http object to be used to make the revoke
|
||||||
request.
|
request.
|
||||||
"""
|
"""
|
||||||
self._revoke(http.request)
|
self._revoke(http)
|
||||||
|
|
||||||
def apply(self, headers):
|
def apply(self, headers):
|
||||||
"""Add the authorization to the 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
|
not have scopes. In both cases, you can use refresh_scopes() to
|
||||||
obtain the canonical set of scopes.
|
obtain the canonical set of scopes.
|
||||||
"""
|
"""
|
||||||
scopes = util.string_to_scopes(scopes)
|
scopes = _helpers.string_to_scopes(scopes)
|
||||||
return set(scopes).issubset(self.scopes)
|
return set(scopes).issubset(self.scopes)
|
||||||
|
|
||||||
def retrieve_scopes(self, http):
|
def retrieve_scopes(self, http):
|
||||||
@@ -607,7 +588,7 @@ class OAuth2Credentials(Credentials):
|
|||||||
Returns:
|
Returns:
|
||||||
A set of strings containing the canonical list of scopes.
|
A set of strings containing the canonical list of scopes.
|
||||||
"""
|
"""
|
||||||
self._retrieve_scopes(http.request)
|
self._retrieve_scopes(http)
|
||||||
return self.scopes
|
return self.scopes
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -746,7 +727,7 @@ class OAuth2Credentials(Credentials):
|
|||||||
|
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
def _refresh(self, http_request):
|
def _refresh(self, http):
|
||||||
"""Refreshes the access_token.
|
"""Refreshes the access_token.
|
||||||
|
|
||||||
This method first checks by reading the Storage object if available.
|
This method first checks by reading the Storage object if available.
|
||||||
@@ -754,15 +735,13 @@ class OAuth2Credentials(Credentials):
|
|||||||
refresh is completed.
|
refresh is completed.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
http_request: callable, a callable that matches the method
|
http: an object to be used to make HTTP requests.
|
||||||
signature of httplib2.Http.request, used to make the
|
|
||||||
refresh request.
|
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
HttpAccessTokenRefreshError: When the refresh fails.
|
HttpAccessTokenRefreshError: When the refresh fails.
|
||||||
"""
|
"""
|
||||||
if not self.store:
|
if not self.store:
|
||||||
self._do_refresh_request(http_request)
|
self._do_refresh_request(http)
|
||||||
else:
|
else:
|
||||||
self.store.acquire_lock()
|
self.store.acquire_lock()
|
||||||
try:
|
try:
|
||||||
@@ -774,17 +753,15 @@ class OAuth2Credentials(Credentials):
|
|||||||
logger.info('Updated access_token read from Storage')
|
logger.info('Updated access_token read from Storage')
|
||||||
self._updateFromCredential(new_cred)
|
self._updateFromCredential(new_cred)
|
||||||
else:
|
else:
|
||||||
self._do_refresh_request(http_request)
|
self._do_refresh_request(http)
|
||||||
finally:
|
finally:
|
||||||
self.store.release_lock()
|
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.
|
"""Refresh the access_token using the refresh_token.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
http_request: callable, a callable that matches the method
|
http: an object to be used to make HTTP requests.
|
||||||
signature of httplib2.Http.request, used to make the
|
|
||||||
refresh request.
|
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
HttpAccessTokenRefreshError: When the refresh fails.
|
HttpAccessTokenRefreshError: When the refresh fails.
|
||||||
@@ -793,8 +770,9 @@ class OAuth2Credentials(Credentials):
|
|||||||
headers = self._generate_refresh_request_headers()
|
headers = self._generate_refresh_request_headers()
|
||||||
|
|
||||||
logger.info('Refreshing access_token')
|
logger.info('Refreshing access_token')
|
||||||
resp, content = http_request(
|
resp, content = transport.request(
|
||||||
self.token_uri, method='POST', body=body, headers=headers)
|
http, self.token_uri, method='POST',
|
||||||
|
body=body, headers=headers)
|
||||||
content = _helpers._from_bytes(content)
|
content = _helpers._from_bytes(content)
|
||||||
if resp.status == http_client.OK:
|
if resp.status == http_client.OK:
|
||||||
d = json.loads(content)
|
d = json.loads(content)
|
||||||
@@ -819,7 +797,7 @@ class OAuth2Credentials(Credentials):
|
|||||||
# An {'error':...} response body means the token is expired or
|
# An {'error':...} response body means the token is expired or
|
||||||
# revoked, so we flag the credentials as such.
|
# revoked, so we flag the credentials as such.
|
||||||
logger.info('Failed to retrieve access token: %s', content)
|
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:
|
try:
|
||||||
d = json.loads(content)
|
d = json.loads(content)
|
||||||
if 'error' in d:
|
if 'error' in d:
|
||||||
@@ -833,23 +811,19 @@ class OAuth2Credentials(Credentials):
|
|||||||
pass
|
pass
|
||||||
raise HttpAccessTokenRefreshError(error_msg, status=resp.status)
|
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).
|
"""Revokes this credential and deletes the stored copy (if it exists).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
http_request: callable, a callable that matches the method
|
http: an object to be used to make HTTP requests.
|
||||||
signature of httplib2.Http.request, used to make the
|
|
||||||
revoke request.
|
|
||||||
"""
|
"""
|
||||||
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).
|
"""Revokes this credential and deletes the stored copy (if it exists).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
http_request: callable, a callable that matches the method
|
http: an object to be used to make HTTP requests.
|
||||||
signature of httplib2.Http.request, used to make the
|
|
||||||
refresh request.
|
|
||||||
token: A string used as the token to be revoked. Can be either an
|
token: A string used as the token to be revoked. Can be either an
|
||||||
access_token or refresh_token.
|
access_token or refresh_token.
|
||||||
|
|
||||||
@@ -859,8 +833,13 @@ class OAuth2Credentials(Credentials):
|
|||||||
"""
|
"""
|
||||||
logger.info('Revoking token')
|
logger.info('Revoking token')
|
||||||
query_params = {'token': token}
|
query_params = {'token': token}
|
||||||
token_revoke_uri = _update_query_params(self.revoke_uri, query_params)
|
token_revoke_uri = _helpers.update_query_params(
|
||||||
resp, content = http_request(token_revoke_uri)
|
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:
|
if resp.status == http_client.OK:
|
||||||
self.invalid = True
|
self.invalid = True
|
||||||
else:
|
else:
|
||||||
@@ -876,23 +855,19 @@ class OAuth2Credentials(Credentials):
|
|||||||
if self.store:
|
if self.store:
|
||||||
self.store.delete()
|
self.store.delete()
|
||||||
|
|
||||||
def _retrieve_scopes(self, http_request):
|
def _retrieve_scopes(self, http):
|
||||||
"""Retrieves the list of authorized scopes from the OAuth2 provider.
|
"""Retrieves the list of authorized scopes from the OAuth2 provider.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
http_request: callable, a callable that matches the method
|
http: an object to be used to make HTTP requests.
|
||||||
signature of httplib2.Http.request, used to make the
|
|
||||||
revoke request.
|
|
||||||
"""
|
"""
|
||||||
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.
|
"""Retrieves the list of authorized scopes from the OAuth2 provider.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
http_request: callable, a callable that matches the method
|
http: an object to be used to make HTTP requests.
|
||||||
signature of httplib2.Http.request, used to make the
|
|
||||||
refresh request.
|
|
||||||
token: A string used as the token to identify the credentials to
|
token: A string used as the token to identify the credentials to
|
||||||
the provider.
|
the provider.
|
||||||
|
|
||||||
@@ -902,13 +877,13 @@ class OAuth2Credentials(Credentials):
|
|||||||
"""
|
"""
|
||||||
logger.info('Refreshing scopes')
|
logger.info('Refreshing scopes')
|
||||||
query_params = {'access_token': token, 'fields': 'scope'}
|
query_params = {'access_token': token, 'fields': 'scope'}
|
||||||
token_info_uri = _update_query_params(self.token_info_uri,
|
token_info_uri = _helpers.update_query_params(
|
||||||
query_params)
|
self.token_info_uri, query_params)
|
||||||
resp, content = http_request(token_info_uri)
|
resp, content = transport.request(http, token_info_uri)
|
||||||
content = _helpers._from_bytes(content)
|
content = _helpers._from_bytes(content)
|
||||||
if resp.status == http_client.OK:
|
if resp.status == http_client.OK:
|
||||||
d = json.loads(content)
|
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:
|
else:
|
||||||
error_msg = 'Invalid response {0}.'.format(resp.status)
|
error_msg = 'Invalid response {0}.'.format(resp.status)
|
||||||
try:
|
try:
|
||||||
@@ -977,19 +952,25 @@ class AccessTokenCredentials(OAuth2Credentials):
|
|||||||
data['user_agent'])
|
data['user_agent'])
|
||||||
return retval
|
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(
|
raise AccessTokenCredentialsError(
|
||||||
'The access_token is expired or invalid and can\'t be refreshed.')
|
'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.
|
"""Revokes the access_token and deletes the store if available.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
http_request: callable, a callable that matches the method
|
http: an object to be used to make HTTP requests.
|
||||||
signature of httplib2.Http.request, used to make the
|
|
||||||
revoke request.
|
|
||||||
"""
|
"""
|
||||||
self._do_revoke(http_request, self.access_token)
|
self._do_revoke(http, self.access_token)
|
||||||
|
|
||||||
|
|
||||||
def _detect_gce_environment():
|
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
|
# could lead to false negatives in the event that we are on GCE, but
|
||||||
# the metadata resolution was particularly slow. The latter case is
|
# the metadata resolution was particularly slow. The latter case is
|
||||||
# "unlikely".
|
# "unlikely".
|
||||||
connection = six.moves.http_client.HTTPConnection(
|
http = transport.get_http_object(timeout=GCE_METADATA_TIMEOUT)
|
||||||
_GCE_METADATA_HOST, timeout=GCE_METADATA_TIMEOUT)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
headers = {_METADATA_FLAVOR_HEADER: _DESIRED_METADATA_FLAVOR}
|
response, _ = transport.request(
|
||||||
connection.request('GET', '/', headers=headers)
|
http, _GCE_METADATA_URI, headers=_GCE_HEADERS)
|
||||||
response = connection.getresponse()
|
return (
|
||||||
if response.status == http_client.OK:
|
response.status == http_client.OK and
|
||||||
return (response.getheader(_METADATA_FLAVOR_HEADER) ==
|
response.get(_METADATA_FLAVOR_HEADER) == _DESIRED_METADATA_FLAVOR)
|
||||||
_DESIRED_METADATA_FLAVOR)
|
|
||||||
except socket.error: # socket.timeout or socket.error(64, 'Host is down')
|
except socket.error: # socket.timeout or socket.error(64, 'Host is down')
|
||||||
logger.info('Timeout attempting to reach GCE metadata service.')
|
logger.info('Timeout attempting to reach GCE metadata service.')
|
||||||
return False
|
return False
|
||||||
finally:
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
|
|
||||||
def _in_gae_environment():
|
def _in_gae_environment():
|
||||||
@@ -1469,7 +1445,7 @@ class AssertionCredentials(GoogleCredentials):
|
|||||||
AssertionCredentials objects may be safely pickled and unpickled.
|
AssertionCredentials objects may be safely pickled and unpickled.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@util.positional(2)
|
@_helpers.positional(2)
|
||||||
def __init__(self, assertion_type, user_agent=None,
|
def __init__(self, assertion_type, user_agent=None,
|
||||||
token_uri=oauth2client.GOOGLE_TOKEN_URI,
|
token_uri=oauth2client.GOOGLE_TOKEN_URI,
|
||||||
revoke_uri=oauth2client.GOOGLE_REVOKE_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."""
|
"""Generate assertion string to be used in the access token request."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def _revoke(self, http_request):
|
def _revoke(self, http):
|
||||||
"""Revokes the access_token and deletes the store if available.
|
"""Revokes the access_token and deletes the store if available.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
http_request: callable, a callable that matches the method
|
http: an object to be used to make HTTP requests.
|
||||||
signature of httplib2.Http.request, used to make the
|
|
||||||
revoke request.
|
|
||||||
"""
|
"""
|
||||||
self._do_revoke(http_request, self.access_token)
|
self._do_revoke(http, self.access_token)
|
||||||
|
|
||||||
def sign_blob(self, blob):
|
def sign_blob(self, blob):
|
||||||
"""Cryptographically sign a blob (of bytes).
|
"""Cryptographically sign a blob (of bytes).
|
||||||
@@ -1545,7 +1519,7 @@ def _require_crypto_or_die():
|
|||||||
raise CryptoUnavailableError('No crypto library available')
|
raise CryptoUnavailableError('No crypto library available')
|
||||||
|
|
||||||
|
|
||||||
@util.positional(2)
|
@_helpers.positional(2)
|
||||||
def verify_id_token(id_token, audience, http=None,
|
def verify_id_token(id_token, audience, http=None,
|
||||||
cert_uri=ID_TOKEN_VERIFICATION_CERTS):
|
cert_uri=ID_TOKEN_VERIFICATION_CERTS):
|
||||||
"""Verifies a signed JWT id_token.
|
"""Verifies a signed JWT id_token.
|
||||||
@@ -1572,7 +1546,7 @@ def verify_id_token(id_token, audience, http=None,
|
|||||||
if http is None:
|
if http is None:
|
||||||
http = transport.get_cached_http()
|
http = transport.get_cached_http()
|
||||||
|
|
||||||
resp, content = http.request(cert_uri)
|
resp, content = transport.request(http, cert_uri)
|
||||||
if resp.status == http_client.OK:
|
if resp.status == http_client.OK:
|
||||||
certs = json.loads(_helpers._from_bytes(content))
|
certs = json.loads(_helpers._from_bytes(content))
|
||||||
return crypt.verify_signed_jwt_with_certs(id_token, certs, audience)
|
return crypt.verify_signed_jwt_with_certs(id_token, certs, audience)
|
||||||
@@ -1624,7 +1598,7 @@ def _parse_exchange_token_response(content):
|
|||||||
except Exception:
|
except Exception:
|
||||||
# different JSON libs raise different exceptions,
|
# different JSON libs raise different exceptions,
|
||||||
# so we just do a catch-all here
|
# 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'
|
# some providers respond with 'expires', others with 'expires_in'
|
||||||
if resp and 'expires' in resp:
|
if resp and 'expires' in resp:
|
||||||
@@ -1633,7 +1607,7 @@ def _parse_exchange_token_response(content):
|
|||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
@util.positional(4)
|
@_helpers.positional(4)
|
||||||
def credentials_from_code(client_id, client_secret, scope, code,
|
def credentials_from_code(client_id, client_secret, scope, code,
|
||||||
redirect_uri='postmessage', http=None,
|
redirect_uri='postmessage', http=None,
|
||||||
user_agent=None,
|
user_agent=None,
|
||||||
@@ -1641,7 +1615,9 @@ def credentials_from_code(client_id, client_secret, scope, code,
|
|||||||
auth_uri=oauth2client.GOOGLE_AUTH_URI,
|
auth_uri=oauth2client.GOOGLE_AUTH_URI,
|
||||||
revoke_uri=oauth2client.GOOGLE_REVOKE_URI,
|
revoke_uri=oauth2client.GOOGLE_REVOKE_URI,
|
||||||
device_uri=oauth2client.GOOGLE_DEVICE_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.
|
"""Exchanges an authorization code for an OAuth2Credentials object.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -1665,6 +1641,15 @@ def credentials_from_code(client_id, client_secret, scope, code,
|
|||||||
device_uri: string, URI for device authorization endpoint. For
|
device_uri: string, URI for device authorization endpoint. For
|
||||||
convenience defaults to Google's endpoints but any OAuth
|
convenience defaults to Google's endpoints but any OAuth
|
||||||
2.0 provider can be used.
|
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:
|
Returns:
|
||||||
An OAuth2Credentials object.
|
An OAuth2Credentials object.
|
||||||
@@ -1675,16 +1660,20 @@ def credentials_from_code(client_id, client_secret, scope, code,
|
|||||||
"""
|
"""
|
||||||
flow = OAuth2WebServerFlow(client_id, client_secret, scope,
|
flow = OAuth2WebServerFlow(client_id, client_secret, scope,
|
||||||
redirect_uri=redirect_uri,
|
redirect_uri=redirect_uri,
|
||||||
user_agent=user_agent, auth_uri=auth_uri,
|
user_agent=user_agent,
|
||||||
token_uri=token_uri, revoke_uri=revoke_uri,
|
auth_uri=auth_uri,
|
||||||
|
token_uri=token_uri,
|
||||||
|
revoke_uri=revoke_uri,
|
||||||
device_uri=device_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)
|
credentials = flow.step2_exchange(code, http=http)
|
||||||
return credentials
|
return credentials
|
||||||
|
|
||||||
|
|
||||||
@util.positional(3)
|
@_helpers.positional(3)
|
||||||
def credentials_from_clientsecrets_and_code(filename, scope, code,
|
def credentials_from_clientsecrets_and_code(filename, scope, code,
|
||||||
message=None,
|
message=None,
|
||||||
redirect_uri='postmessage',
|
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()
|
cache: An optional cache service client that implements get() and set()
|
||||||
methods. See clientsecrets.loadfile() for details.
|
methods. See clientsecrets.loadfile() for details.
|
||||||
device_uri: string, OAuth 2.0 device authorization endpoint
|
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:
|
Returns:
|
||||||
An OAuth2Credentials object.
|
An OAuth2Credentials object.
|
||||||
@@ -1803,7 +1801,7 @@ class OAuth2WebServerFlow(Flow):
|
|||||||
OAuth2WebServerFlow objects may be safely pickled and unpickled.
|
OAuth2WebServerFlow objects may be safely pickled and unpickled.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@util.positional(4)
|
@_helpers.positional(4)
|
||||||
def __init__(self, client_id,
|
def __init__(self, client_id,
|
||||||
client_secret=None,
|
client_secret=None,
|
||||||
scope=None,
|
scope=None,
|
||||||
@@ -1816,6 +1814,8 @@ class OAuth2WebServerFlow(Flow):
|
|||||||
device_uri=oauth2client.GOOGLE_DEVICE_URI,
|
device_uri=oauth2client.GOOGLE_DEVICE_URI,
|
||||||
token_info_uri=oauth2client.GOOGLE_TOKEN_INFO_URI,
|
token_info_uri=oauth2client.GOOGLE_TOKEN_INFO_URI,
|
||||||
authorization_header=None,
|
authorization_header=None,
|
||||||
|
pkce=False,
|
||||||
|
code_verifier=None,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
"""Constructor for OAuth2WebServerFlow.
|
"""Constructor for OAuth2WebServerFlow.
|
||||||
|
|
||||||
@@ -1853,6 +1853,15 @@ class OAuth2WebServerFlow(Flow):
|
|||||||
require a client to authenticate using a
|
require a client to authenticate using a
|
||||||
header value instead of passing client_secret
|
header value instead of passing client_secret
|
||||||
in the POST body.
|
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
|
**kwargs: dict, The keyword arguments are all optional and required
|
||||||
parameters for the OAuth calls.
|
parameters for the OAuth calls.
|
||||||
"""
|
"""
|
||||||
@@ -1862,7 +1871,7 @@ class OAuth2WebServerFlow(Flow):
|
|||||||
raise TypeError("The value of scope must not be None")
|
raise TypeError("The value of scope must not be None")
|
||||||
self.client_id = client_id
|
self.client_id = client_id
|
||||||
self.client_secret = client_secret
|
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.redirect_uri = redirect_uri
|
||||||
self.login_hint = login_hint
|
self.login_hint = login_hint
|
||||||
self.user_agent = user_agent
|
self.user_agent = user_agent
|
||||||
@@ -1872,9 +1881,11 @@ class OAuth2WebServerFlow(Flow):
|
|||||||
self.device_uri = device_uri
|
self.device_uri = device_uri
|
||||||
self.token_info_uri = token_info_uri
|
self.token_info_uri = token_info_uri
|
||||||
self.authorization_header = authorization_header
|
self.authorization_header = authorization_header
|
||||||
|
self._pkce = pkce
|
||||||
|
self.code_verifier = code_verifier
|
||||||
self.params = _oauth2_web_server_flow_params(kwargs)
|
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):
|
def step1_get_authorize_url(self, redirect_uri=None, state=None):
|
||||||
"""Returns a URI to redirect to the provider.
|
"""Returns a URI to redirect to the provider.
|
||||||
|
|
||||||
@@ -1912,10 +1923,17 @@ class OAuth2WebServerFlow(Flow):
|
|||||||
query_params['state'] = state
|
query_params['state'] = state
|
||||||
if self.login_hint is not None:
|
if self.login_hint is not None:
|
||||||
query_params['login_hint'] = self.login_hint
|
query_params['login_hint'] = self.login_hint
|
||||||
query_params.update(self.params)
|
if self._pkce:
|
||||||
return _update_query_params(self.auth_uri, query_params)
|
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):
|
def step1_get_device_and_user_codes(self, http=None):
|
||||||
"""Returns a user code and the verification URL where to enter it
|
"""Returns a user code and the verification URL where to enter it
|
||||||
|
|
||||||
@@ -1940,8 +1958,8 @@ class OAuth2WebServerFlow(Flow):
|
|||||||
if http is None:
|
if http is None:
|
||||||
http = transport.get_http_object()
|
http = transport.get_http_object()
|
||||||
|
|
||||||
resp, content = http.request(self.device_uri, method='POST', body=body,
|
resp, content = transport.request(
|
||||||
headers=headers)
|
http, self.device_uri, method='POST', body=body, headers=headers)
|
||||||
content = _helpers._from_bytes(content)
|
content = _helpers._from_bytes(content)
|
||||||
if resp.status == http_client.OK:
|
if resp.status == http_client.OK:
|
||||||
try:
|
try:
|
||||||
@@ -1963,7 +1981,7 @@ class OAuth2WebServerFlow(Flow):
|
|||||||
pass
|
pass
|
||||||
raise OAuth2DeviceCodeError(error_msg)
|
raise OAuth2DeviceCodeError(error_msg)
|
||||||
|
|
||||||
@util.positional(2)
|
@_helpers.positional(2)
|
||||||
def step2_exchange(self, code=None, http=None, device_flow_info=None):
|
def step2_exchange(self, code=None, http=None, device_flow_info=None):
|
||||||
"""Exchanges a code for OAuth2Credentials.
|
"""Exchanges a code for OAuth2Credentials.
|
||||||
|
|
||||||
@@ -2006,6 +2024,8 @@ class OAuth2WebServerFlow(Flow):
|
|||||||
}
|
}
|
||||||
if self.client_secret is not None:
|
if self.client_secret is not None:
|
||||||
post_data['client_secret'] = self.client_secret
|
post_data['client_secret'] = self.client_secret
|
||||||
|
if self._pkce:
|
||||||
|
post_data['code_verifier'] = self.code_verifier
|
||||||
if device_flow_info is not None:
|
if device_flow_info is not None:
|
||||||
post_data['grant_type'] = 'http://oauth.net/grant_type/device/1.0'
|
post_data['grant_type'] = 'http://oauth.net/grant_type/device/1.0'
|
||||||
else:
|
else:
|
||||||
@@ -2023,8 +2043,8 @@ class OAuth2WebServerFlow(Flow):
|
|||||||
if http is None:
|
if http is None:
|
||||||
http = transport.get_http_object()
|
http = transport.get_http_object()
|
||||||
|
|
||||||
resp, content = http.request(self.token_uri, method='POST', body=body,
|
resp, content = transport.request(
|
||||||
headers=headers)
|
http, self.token_uri, method='POST', body=body, headers=headers)
|
||||||
d = _parse_exchange_token_response(content)
|
d = _parse_exchange_token_response(content)
|
||||||
if resp.status == http_client.OK and 'access_token' in d:
|
if resp.status == http_client.OK and 'access_token' in d:
|
||||||
access_token = d['access_token']
|
access_token = d['access_token']
|
||||||
@@ -2060,10 +2080,10 @@ class OAuth2WebServerFlow(Flow):
|
|||||||
raise FlowExchangeError(error_msg)
|
raise FlowExchangeError(error_msg)
|
||||||
|
|
||||||
|
|
||||||
@util.positional(2)
|
@_helpers.positional(2)
|
||||||
def flow_from_clientsecrets(filename, scope, redirect_uri=None,
|
def flow_from_clientsecrets(filename, scope, redirect_uri=None,
|
||||||
message=None, cache=None, login_hint=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.
|
"""Create a Flow from a clientsecrets file.
|
||||||
|
|
||||||
Will create the right kind of Flow based on the contents of the
|
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,
|
'login_hint': login_hint,
|
||||||
}
|
}
|
||||||
revoke_uri = client_info.get('revoke_uri')
|
revoke_uri = client_info.get('revoke_uri')
|
||||||
if revoke_uri is not None:
|
optional = ('revoke_uri', 'device_uri', 'pkce', 'code_verifier')
|
||||||
constructor_kwargs['revoke_uri'] = revoke_uri
|
for param in optional:
|
||||||
if device_uri is not None:
|
if locals()[param] is not None:
|
||||||
constructor_kwargs['device_uri'] = device_uri
|
constructor_kwargs[param] = locals()[param]
|
||||||
|
|
||||||
return OAuth2WebServerFlow(
|
return OAuth2WebServerFlow(
|
||||||
client_info['client_id'], client_info['client_secret'],
|
client_info['client_id'], client_info['client_secret'],
|
||||||
scope, **constructor_kwargs)
|
scope, **constructor_kwargs)
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import json
|
|||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
|
||||||
|
|
||||||
# Properties that make a client_secrets.json file valid.
|
# Properties that make a client_secrets.json file valid.
|
||||||
TYPE_WEB = 'web'
|
TYPE_WEB = 'web'
|
||||||
|
|||||||
@@ -20,28 +20,25 @@ See https://cloud.google.com/compute/docs/metadata
|
|||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import httplib2
|
|
||||||
from six.moves import http_client
|
from six.moves import http_client
|
||||||
from six.moves.urllib import parse as urlparse
|
from six.moves.urllib import parse as urlparse
|
||||||
|
|
||||||
from oauth2client import _helpers
|
from oauth2client import _helpers
|
||||||
from oauth2client import client
|
from oauth2client import client
|
||||||
from oauth2client import util
|
from oauth2client import transport
|
||||||
|
|
||||||
|
|
||||||
METADATA_ROOT = 'http://metadata.google.internal/computeMetadata/v1/'
|
METADATA_ROOT = 'http://metadata.google.internal/computeMetadata/v1/'
|
||||||
METADATA_HEADERS = {'Metadata-Flavor': 'Google'}
|
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.
|
"""Fetch a resource from the metadata server.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
http: an object to be used to make HTTP requests.
|
||||||
path: A string indicating the resource to retrieve. For example,
|
path: A string indicating the resource to retrieve. For example,
|
||||||
'instance/service-accounts/defualt'
|
'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.
|
root: A string indicating the full path to the metadata server root.
|
||||||
recursive: A boolean indicating whether to do a recursive query of
|
recursive: A boolean indicating whether to do a recursive query of
|
||||||
metadata. See
|
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.
|
A dictionary if the metadata server returns JSON, otherwise a string.
|
||||||
|
|
||||||
Raises:
|
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 = urlparse.urljoin(root, path)
|
||||||
url = util._add_query_parameter(url, 'recursive', recursive)
|
url = _helpers._add_query_parameter(url, 'recursive', recursive)
|
||||||
|
|
||||||
response, content = http_request(
|
response, content = transport.request(
|
||||||
url,
|
http, url, headers=METADATA_HEADERS)
|
||||||
headers=METADATA_HEADERS
|
|
||||||
)
|
|
||||||
|
|
||||||
if response.status == http_client.OK:
|
if response.status == http_client.OK:
|
||||||
decoded = _helpers._from_bytes(content)
|
decoded = _helpers._from_bytes(content)
|
||||||
@@ -68,21 +64,20 @@ def get(http_request, path, root=METADATA_ROOT, recursive=None):
|
|||||||
else:
|
else:
|
||||||
return decoded
|
return decoded
|
||||||
else:
|
else:
|
||||||
raise httplib2.HttpLib2Error(
|
raise http_client.HTTPException(
|
||||||
'Failed to retrieve {0} from the Google Compute Engine'
|
'Failed to retrieve {0} from the Google Compute Engine'
|
||||||
'metadata service. Response:\n{1}'.format(url, response))
|
'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.
|
"""Get information about a service account from the metadata server.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
http: an object to be used to make HTTP requests.
|
||||||
service_account: An email specifying the service account for which to
|
service_account: An email specifying the service account for which to
|
||||||
look up information. Default will be information for the "default"
|
look up information. Default will be information for the "default"
|
||||||
service account of the current compute engine instance.
|
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:
|
Returns:
|
||||||
A dictionary with information about the specified service account,
|
A dictionary with information about the specified service account,
|
||||||
for example:
|
for example:
|
||||||
@@ -94,21 +89,19 @@ def get_service_account_info(http_request, service_account='default'):
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
return get(
|
return get(
|
||||||
http_request,
|
http,
|
||||||
'instance/service-accounts/{0}/'.format(service_account),
|
'instance/service-accounts/{0}/'.format(service_account),
|
||||||
recursive=True)
|
recursive=True)
|
||||||
|
|
||||||
|
|
||||||
def get_token(http_request, service_account='default'):
|
def get_token(http, service_account='default'):
|
||||||
"""Fetch an oauth token for the
|
"""Fetch an oauth token for the
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
http: an object to be used to make HTTP requests.
|
||||||
service_account: An email specifying the service account this token
|
service_account: An email specifying the service account this token
|
||||||
should represent. Default will be a token for the "default" service
|
should represent. Default will be a token for the "default" service
|
||||||
account of the current compute engine instance.
|
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:
|
Returns:
|
||||||
A tuple of (access token, token expiration), where access token is the
|
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.
|
that indicates when the access token will expire.
|
||||||
"""
|
"""
|
||||||
token_json = get(
|
token_json = get(
|
||||||
http_request,
|
http,
|
||||||
'instance/service-accounts/{0}/token'.format(service_account))
|
'instance/service-accounts/{0}/token'.format(service_account))
|
||||||
token_expiry = client._UTCNOW() + datetime.timedelta(
|
token_expiry = client._UTCNOW() + datetime.timedelta(
|
||||||
seconds=token_json['expires_in'])
|
seconds=token_json['expires_in'])
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ from google.appengine.api import memcache
|
|||||||
from google.appengine.api import users
|
from google.appengine.api import users
|
||||||
from google.appengine.ext import db
|
from google.appengine.ext import db
|
||||||
from google.appengine.ext.webapp.util import login_required
|
from google.appengine.ext.webapp.util import login_required
|
||||||
import httplib2
|
|
||||||
import webapp2 as webapp
|
import webapp2 as webapp
|
||||||
|
|
||||||
import oauth2client
|
import oauth2client
|
||||||
|
from oauth2client import _helpers
|
||||||
from oauth2client import client
|
from oauth2client import client
|
||||||
from oauth2client import clientsecrets
|
from oauth2client import clientsecrets
|
||||||
from oauth2client import util
|
from oauth2client import transport
|
||||||
from oauth2client.contrib import xsrfutil
|
from oauth2client.contrib import xsrfutil
|
||||||
|
|
||||||
# This is a temporary fix for a Google internal issue.
|
# This is a temporary fix for a Google internal issue.
|
||||||
@@ -45,8 +45,6 @@ except ImportError: # pragma: NO COVER
|
|||||||
_appengine_ndb = None
|
_appengine_ndb = None
|
||||||
|
|
||||||
|
|
||||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
OAUTH2CLIENT_NAMESPACE = 'oauth2client#ns'
|
OAUTH2CLIENT_NAMESPACE = 'oauth2client#ns'
|
||||||
@@ -131,7 +129,7 @@ class AppAssertionCredentials(client.AssertionCredentials):
|
|||||||
information to generate and refresh its own access tokens.
|
information to generate and refresh its own access tokens.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@util.positional(2)
|
@_helpers.positional(2)
|
||||||
def __init__(self, scope, **kwargs):
|
def __init__(self, scope, **kwargs):
|
||||||
"""Constructor for AppAssertionCredentials
|
"""Constructor for AppAssertionCredentials
|
||||||
|
|
||||||
@@ -143,7 +141,7 @@ class AppAssertionCredentials(client.AssertionCredentials):
|
|||||||
or unspecified, the default service account for
|
or unspecified, the default service account for
|
||||||
the app is used.
|
the app is used.
|
||||||
"""
|
"""
|
||||||
self.scope = util.scopes_to_string(scope)
|
self.scope = _helpers.scopes_to_string(scope)
|
||||||
self._kwargs = kwargs
|
self._kwargs = kwargs
|
||||||
self.service_account_id = kwargs.get('service_account_id', None)
|
self.service_account_id = kwargs.get('service_account_id', None)
|
||||||
self._service_account_email = None
|
self._service_account_email = None
|
||||||
@@ -157,17 +155,15 @@ class AppAssertionCredentials(client.AssertionCredentials):
|
|||||||
data = json.loads(json_data)
|
data = json.loads(json_data)
|
||||||
return AppAssertionCredentials(data['scope'])
|
return AppAssertionCredentials(data['scope'])
|
||||||
|
|
||||||
def _refresh(self, http_request):
|
def _refresh(self, http):
|
||||||
"""Refreshes the access_token.
|
"""Refreshes the access token.
|
||||||
|
|
||||||
Since the underlying App Engine app_identity implementation does its
|
Since the underlying App Engine app_identity implementation does its
|
||||||
own caching we can skip all the storage hoops and just to a refresh
|
own caching we can skip all the storage hoops and just to a refresh
|
||||||
using the API.
|
using the API.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
http_request: callable, a callable that matches the method
|
http: unused HTTP object
|
||||||
signature of httplib2.Http.request, used to make the
|
|
||||||
refresh request.
|
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
AccessTokenRefreshError: When the refresh fails.
|
AccessTokenRefreshError: When the refresh fails.
|
||||||
@@ -305,7 +301,7 @@ class StorageByKeyName(client.Storage):
|
|||||||
and that entities are stored by key_name.
|
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):
|
def __init__(self, model, key_name, property_name, cache=None, user=None):
|
||||||
"""Constructor for Storage.
|
"""Constructor for Storage.
|
||||||
|
|
||||||
@@ -523,7 +519,7 @@ class OAuth2Decorator(object):
|
|||||||
|
|
||||||
flow = property(get_flow, set_flow)
|
flow = property(get_flow, set_flow)
|
||||||
|
|
||||||
@util.positional(4)
|
@_helpers.positional(4)
|
||||||
def __init__(self, client_id, client_secret, scope,
|
def __init__(self, client_id, client_secret, scope,
|
||||||
auth_uri=oauth2client.GOOGLE_AUTH_URI,
|
auth_uri=oauth2client.GOOGLE_AUTH_URI,
|
||||||
token_uri=oauth2client.GOOGLE_TOKEN_URI,
|
token_uri=oauth2client.GOOGLE_TOKEN_URI,
|
||||||
@@ -590,7 +586,7 @@ class OAuth2Decorator(object):
|
|||||||
self.credentials = None
|
self.credentials = None
|
||||||
self._client_id = client_id
|
self._client_id = client_id
|
||||||
self._client_secret = client_secret
|
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._auth_uri = auth_uri
|
||||||
self._token_uri = token_uri
|
self._token_uri = token_uri
|
||||||
self._revoke_uri = revoke_uri
|
self._revoke_uri = revoke_uri
|
||||||
@@ -742,7 +738,8 @@ class OAuth2Decorator(object):
|
|||||||
*args: Positional arguments passed to httplib2.Http constructor.
|
*args: Positional arguments passed to httplib2.Http constructor.
|
||||||
**kwargs: 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
|
@property
|
||||||
def callback_path(self):
|
def callback_path(self):
|
||||||
@@ -804,7 +801,7 @@ class OAuth2Decorator(object):
|
|||||||
if (decorator._token_response_param and
|
if (decorator._token_response_param and
|
||||||
credentials.token_response):
|
credentials.token_response):
|
||||||
resp_json = json.dumps(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,
|
redirect_uri, decorator._token_response_param,
|
||||||
resp_json)
|
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):
|
def __init__(self, filename, scope, message=None, cache=None, **kwargs):
|
||||||
"""Constructor
|
"""Constructor
|
||||||
|
|
||||||
@@ -891,7 +888,7 @@ class OAuth2DecoratorFromClientSecrets(OAuth2Decorator):
|
|||||||
self._message = 'Please configure your application for OAuth 2.0.'
|
self._message = 'Please configure your application for OAuth 2.0.'
|
||||||
|
|
||||||
|
|
||||||
@util.positional(2)
|
@_helpers.positional(2)
|
||||||
def oauth2decorator_from_clientsecrets(filename, scope,
|
def oauth2decorator_from_clientsecrets(filename, scope,
|
||||||
message=None, cache=None):
|
message=None, cache=None):
|
||||||
"""Creates an OAuth2Decorator populated from a clientsecrets file.
|
"""Creates an OAuth2Decorator populated from a clientsecrets file.
|
||||||
|
|||||||
@@ -117,7 +117,12 @@ class DevshellCredentials(client.GoogleCredentials):
|
|||||||
user_agent)
|
user_agent)
|
||||||
self._refresh(None)
|
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.devshell_response = _SendRecv()
|
||||||
self.access_token = self.devshell_response.access_token
|
self.access_token = self.devshell_response.access_token
|
||||||
expires_in = self.devshell_response.expires_in
|
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
|
This helper also requires the Django Session Middleware, so
|
||||||
``django.contrib.sessions.middleware`` should be in INSTALLED_APPS as well.
|
``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
|
Add the client secrets created earlier to the settings. You can either
|
||||||
specify the path to the credentials file in JSON format
|
specify the path to the credentials file in JSON format
|
||||||
@@ -228,10 +231,10 @@ import importlib
|
|||||||
import django.conf
|
import django.conf
|
||||||
from django.core import exceptions
|
from django.core import exceptions
|
||||||
from django.core import urlresolvers
|
from django.core import urlresolvers
|
||||||
import httplib2
|
|
||||||
from six.moves.urllib import parse
|
from six.moves.urllib import parse
|
||||||
|
|
||||||
from oauth2client import clientsecrets
|
from oauth2client import clientsecrets
|
||||||
|
from oauth2client import transport
|
||||||
from oauth2client.contrib import dictionary_storage
|
from oauth2client.contrib import dictionary_storage
|
||||||
from oauth2client.contrib.django_util import storage
|
from oauth2client.contrib.django_util import storage
|
||||||
|
|
||||||
@@ -335,15 +338,25 @@ class OAuth2Settings(object):
|
|||||||
self.request_prefix = getattr(settings_instance,
|
self.request_prefix = getattr(settings_instance,
|
||||||
'GOOGLE_OAUTH2_REQUEST_ATTRIBUTE',
|
'GOOGLE_OAUTH2_REQUEST_ATTRIBUTE',
|
||||||
GOOGLE_OAUTH2_REQUEST_ATTRIBUTE)
|
GOOGLE_OAUTH2_REQUEST_ATTRIBUTE)
|
||||||
self.client_id, self.client_secret = \
|
info = _get_oauth2_client_id_and_secret(settings_instance)
|
||||||
_get_oauth2_client_id_and_secret(settings_instance)
|
self.client_id, self.client_secret = info
|
||||||
|
|
||||||
if ('django.contrib.sessions.middleware.SessionMiddleware'
|
# Django 1.10 deprecated MIDDLEWARE_CLASSES in favor of MIDDLEWARE
|
||||||
not in settings_instance.MIDDLEWARE_CLASSES):
|
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(
|
||||||
|
'Django settings has neither MIDDLEWARE nor MIDDLEWARE_CLASSES'
|
||||||
|
'configured')
|
||||||
|
|
||||||
|
if ('django.contrib.sessions.middleware.SessionMiddleware' not in
|
||||||
|
middleware_settings):
|
||||||
raise exceptions.ImproperlyConfigured(
|
raise exceptions.ImproperlyConfigured(
|
||||||
'The Google OAuth2 Helper requires session middleware to '
|
'The Google OAuth2 Helper requires session middleware to '
|
||||||
'be installed. Edit your MIDDLEWARE_CLASSES setting'
|
'be installed. Edit your MIDDLEWARE_CLASSES or MIDDLEWARE '
|
||||||
' to include \'django.contrib.sessions.middleware.'
|
'setting to include \'django.contrib.sessions.middleware.'
|
||||||
'SessionMiddleware\'.')
|
'SessionMiddleware\'.')
|
||||||
(self.storage_model, self.storage_model_user_property,
|
(self.storage_model, self.storage_model_user_property,
|
||||||
self.storage_model_credentials_property) = _get_storage_model()
|
self.storage_model_credentials_property) = _get_storage_model()
|
||||||
@@ -470,8 +483,7 @@ class UserOAuth2(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def http(self):
|
def http(self):
|
||||||
"""Helper method to create an HTTP client authorized with OAuth2
|
"""Helper: create HTTP client authorized with OAuth2 credentials."""
|
||||||
credentials."""
|
|
||||||
if self.has_credentials():
|
if self.has_credentials():
|
||||||
return self.credentials.authorize(httplib2.Http())
|
return self.credentials.authorize(transport.get_http_object())
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ in the configured storage."""
|
|||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import pickle
|
|
||||||
|
|
||||||
from django import http
|
from django import http
|
||||||
from django import shortcuts
|
from django import shortcuts
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import urlresolvers
|
from django.core import urlresolvers
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
|
import jsonpickle
|
||||||
from six.moves.urllib import parse
|
from six.moves.urllib import parse
|
||||||
|
|
||||||
from oauth2client import client
|
from oauth2client import client
|
||||||
@@ -71,7 +71,7 @@ def _make_flow(request, scopes, return_url=None):
|
|||||||
urlresolvers.reverse("google_oauth:callback")))
|
urlresolvers.reverse("google_oauth:callback")))
|
||||||
|
|
||||||
flow_key = _FLOW_KEY.format(csrf_token)
|
flow_key = _FLOW_KEY.format(csrf_token)
|
||||||
request.session[flow_key] = pickle.dumps(flow)
|
request.session[flow_key] = jsonpickle.encode(flow)
|
||||||
return flow
|
return flow
|
||||||
|
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ def _get_flow_for_token(csrf_token, request):
|
|||||||
CSRF token.
|
CSRF token.
|
||||||
"""
|
"""
|
||||||
flow_pickle = request.session.get(_FLOW_KEY.format(csrf_token), None)
|
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):
|
def oauth2_callback(request):
|
||||||
@@ -170,7 +170,10 @@ def oauth2_authorize(request):
|
|||||||
A redirect to Google OAuth2 Authorization.
|
A redirect to Google OAuth2 Authorization.
|
||||||
"""
|
"""
|
||||||
return_url = request.GET.get('return_url', None)
|
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
|
# Model storage (but not session storage) requires a logged in user
|
||||||
if django_util.oauth2_settings.storage_model:
|
if django_util.oauth2_settings.storage_model:
|
||||||
if not request.user.is_authenticated():
|
if not request.user.is_authenticated():
|
||||||
@@ -178,13 +181,11 @@ def oauth2_authorize(request):
|
|||||||
settings.LOGIN_URL, parse.quote(request.get_full_path())))
|
settings.LOGIN_URL, parse.quote(request.get_full_path())))
|
||||||
# This checks for the case where we ended up here because of a logged
|
# 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
|
# out user but we had credentials for it in the first place
|
||||||
elif get_storage(request).get() is not None:
|
else:
|
||||||
|
user_oauth = django_util.UserOAuth2(request, scopes, return_url)
|
||||||
|
if user_oauth.has_credentials():
|
||||||
return redirect(return_url)
|
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)
|
flow = _make_flow(request=request, scopes=scopes, return_url=return_url)
|
||||||
auth_url = flow.step1_get_authorize_url()
|
auth_url = flow.step1_get_authorize_url()
|
||||||
return shortcuts.redirect(auth_url)
|
return shortcuts.redirect(auth_url)
|
||||||
|
|||||||
@@ -179,16 +179,14 @@ try:
|
|||||||
except ImportError: # pragma: NO COVER
|
except ImportError: # pragma: NO COVER
|
||||||
raise ImportError('The flask utilities require flask 0.9 or newer.')
|
raise ImportError('The flask utilities require flask 0.9 or newer.')
|
||||||
|
|
||||||
import httplib2
|
|
||||||
import six.moves.http_client as httplib
|
import six.moves.http_client as httplib
|
||||||
|
|
||||||
from oauth2client import client
|
from oauth2client import client
|
||||||
from oauth2client import clientsecrets
|
from oauth2client import clientsecrets
|
||||||
|
from oauth2client import transport
|
||||||
from oauth2client.contrib import dictionary_storage
|
from oauth2client.contrib import dictionary_storage
|
||||||
|
|
||||||
|
|
||||||
__author__ = 'jonwayne@google.com (Jon Wayne Parrott)'
|
|
||||||
|
|
||||||
_DEFAULT_SCOPES = ('email',)
|
_DEFAULT_SCOPES = ('email',)
|
||||||
_CREDENTIALS_KEY = 'google_oauth2_credentials'
|
_CREDENTIALS_KEY = 'google_oauth2_credentials'
|
||||||
_FLOW_KEY = 'google_oauth2_flow_{0}'
|
_FLOW_KEY = 'google_oauth2_flow_{0}'
|
||||||
@@ -553,4 +551,5 @@ class UserOAuth2(object):
|
|||||||
"""
|
"""
|
||||||
if not self.credentials:
|
if not self.credentials:
|
||||||
raise ValueError('No credentials available.')
|
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 logging
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import httplib2
|
from six.moves import http_client
|
||||||
|
|
||||||
from oauth2client import client
|
from oauth2client import client
|
||||||
from oauth2client.contrib import _metadata
|
from oauth2client.contrib import _metadata
|
||||||
|
|
||||||
|
|
||||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
_SCOPES_WARNING = """\
|
_SCOPES_WARNING = """\
|
||||||
@@ -98,44 +96,40 @@ class AppAssertionCredentials(client.AssertionCredentials):
|
|||||||
Returns:
|
Returns:
|
||||||
A set of strings containing the canonical list of scopes.
|
A set of strings containing the canonical list of scopes.
|
||||||
"""
|
"""
|
||||||
self._retrieve_info(http.request)
|
self._retrieve_info(http)
|
||||||
return self.scopes
|
return self.scopes
|
||||||
|
|
||||||
def _retrieve_info(self, http_request):
|
def _retrieve_info(self, http):
|
||||||
"""Validates invalid service accounts by retrieving service account info.
|
"""Retrieves service account info for invalid credentials.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
http_request: callable, a callable that matches the method
|
http: an object to be used to make HTTP requests.
|
||||||
signature of httplib2.Http.request, used to make the
|
|
||||||
request to the metadata server
|
|
||||||
"""
|
"""
|
||||||
if self.invalid:
|
if self.invalid:
|
||||||
info = _metadata.get_service_account_info(
|
info = _metadata.get_service_account_info(
|
||||||
http_request,
|
http,
|
||||||
service_account=self.service_account_email or 'default')
|
service_account=self.service_account_email or 'default')
|
||||||
self.invalid = False
|
self.invalid = False
|
||||||
self.service_account_email = info['email']
|
self.service_account_email = info['email']
|
||||||
self.scopes = info['scopes']
|
self.scopes = info['scopes']
|
||||||
|
|
||||||
def _refresh(self, http_request):
|
def _refresh(self, http):
|
||||||
"""Refreshes the access_token.
|
"""Refreshes the access token.
|
||||||
|
|
||||||
Skip all the storage hoops and just refresh using the API.
|
Skip all the storage hoops and just refresh using the API.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
http_request: callable, a callable that matches the method
|
http: an object to be used to make HTTP requests.
|
||||||
signature of httplib2.Http.request, used to make
|
|
||||||
the refresh request.
|
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
HttpAccessTokenRefreshError: When the refresh fails.
|
HttpAccessTokenRefreshError: When the refresh fails.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self._retrieve_info(http_request)
|
self._retrieve_info(http)
|
||||||
self.access_token, self.token_expiry = _metadata.get_token(
|
self.access_token, self.token_expiry = _metadata.get_token(
|
||||||
http_request, service_account=self.service_account_email)
|
http, service_account=self.service_account_email)
|
||||||
except httplib2.HttpLib2Error as e:
|
except http_client.HTTPException as err:
|
||||||
raise client.HttpAccessTokenRefreshError(str(e))
|
raise client.HttpAccessTokenRefreshError(str(err))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serialization_data(self):
|
def serialization_data(self):
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ import keyring
|
|||||||
from oauth2client import client
|
from oauth2client import client
|
||||||
|
|
||||||
|
|
||||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
|
||||||
|
|
||||||
|
|
||||||
class Storage(client.Storage):
|
class Storage(client.Storage):
|
||||||
"""Store and retrieve a single credential to and from the keyring.
|
"""Store and retrieve a single credential to and from the keyring.
|
||||||
|
|
||||||
|
|||||||
@@ -20,12 +20,7 @@ import hmac
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from oauth2client import _helpers
|
from oauth2client import _helpers
|
||||||
from oauth2client import util
|
|
||||||
|
|
||||||
__authors__ = [
|
|
||||||
'"Doug Coker" <dcoker@google.com>',
|
|
||||||
'"Joe Gregorio" <jcgregorio@google.com>',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Delimiter character
|
# Delimiter character
|
||||||
DELIMITER = b':'
|
DELIMITER = b':'
|
||||||
@@ -34,7 +29,7 @@ DELIMITER = b':'
|
|||||||
DEFAULT_TIMEOUT_SECS = 60 * 60
|
DEFAULT_TIMEOUT_SECS = 60 * 60
|
||||||
|
|
||||||
|
|
||||||
@util.positional(2)
|
@_helpers.positional(2)
|
||||||
def generate_token(key, user_id, action_id='', when=None):
|
def generate_token(key, user_id, action_id='', when=None):
|
||||||
"""Generates a URL-safe token for the given user, action, time tuple.
|
"""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
|
return token
|
||||||
|
|
||||||
|
|
||||||
@util.positional(3)
|
@_helpers.positional(3)
|
||||||
def validate_token(key, token, user_id, action_id="", current_time=None):
|
def validate_token(key, token, user_id, action_id="", current_time=None):
|
||||||
"""Validates that the given token authorizes the user for the action.
|
"""Validates that the given token authorizes the user for the action.
|
||||||
|
|
||||||
|
|||||||
@@ -21,16 +21,10 @@ credentials.
|
|||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
from oauth2client import _helpers
|
||||||
from oauth2client import client
|
from oauth2client import client
|
||||||
|
|
||||||
|
|
||||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
|
||||||
|
|
||||||
|
|
||||||
class CredentialsFileSymbolicLinkError(Exception):
|
|
||||||
"""Credentials files must not be symbolic links."""
|
|
||||||
|
|
||||||
|
|
||||||
class Storage(client.Storage):
|
class Storage(client.Storage):
|
||||||
"""Store and retrieve a single credential to and from a file."""
|
"""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())
|
super(Storage, self).__init__(lock=threading.Lock())
|
||||||
self._filename = filename
|
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):
|
def locked_get(self):
|
||||||
"""Retrieve Credential from file.
|
"""Retrieve Credential from file.
|
||||||
|
|
||||||
@@ -50,10 +39,10 @@ class Storage(client.Storage):
|
|||||||
oauth2client.client.Credentials
|
oauth2client.client.Credentials
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
CredentialsFileSymbolicLinkError if the file is a symbolic link.
|
IOError if the file is a symbolic link.
|
||||||
"""
|
"""
|
||||||
credentials = None
|
credentials = None
|
||||||
self._validate_file()
|
_helpers.validate_file(self._filename)
|
||||||
try:
|
try:
|
||||||
f = open(self._filename, 'rb')
|
f = open(self._filename, 'rb')
|
||||||
content = f.read()
|
content = f.read()
|
||||||
@@ -89,10 +78,10 @@ class Storage(client.Storage):
|
|||||||
credentials: Credentials, the credentials to store.
|
credentials: Credentials, the credentials to store.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
CredentialsFileSymbolicLinkError if the file is a symbolic link.
|
IOError if the file is a symbolic link.
|
||||||
"""
|
"""
|
||||||
self._create_file_if_needed()
|
self._create_file_if_needed()
|
||||||
self._validate_file()
|
_helpers.validate_file(self._filename)
|
||||||
f = open(self._filename, 'w')
|
f = open(self._filename, 'w')
|
||||||
f.write(credentials.to_json())
|
f.write(credentials.to_json())
|
||||||
f.close()
|
f.close()
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ from oauth2client import _helpers
|
|||||||
from oauth2client import client
|
from oauth2client import client
|
||||||
from oauth2client import crypt
|
from oauth2client import crypt
|
||||||
from oauth2client import transport
|
from oauth2client import transport
|
||||||
from oauth2client import util
|
|
||||||
|
|
||||||
|
|
||||||
_PASSWORD_DEFAULT = 'notasecret'
|
_PASSWORD_DEFAULT = 'notasecret'
|
||||||
@@ -110,7 +109,7 @@ class ServiceAccountCredentials(client.AssertionCredentials):
|
|||||||
|
|
||||||
self._service_account_email = service_account_email
|
self._service_account_email = service_account_email
|
||||||
self._signer = signer
|
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._private_key_id = private_key_id
|
||||||
self.client_id = client_id
|
self.client_id = client_id
|
||||||
self._user_agent = user_agent
|
self._user_agent = user_agent
|
||||||
@@ -650,9 +649,22 @@ class _JWTAccessCredentials(ServiceAccountCredentials):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def refresh(self, http):
|
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)
|
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()
|
self.access_token, self.token_expiry = self._create_token()
|
||||||
|
|
||||||
def _create_token(self, additional_claims=None):
|
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 input
|
||||||
from six.moves import urllib
|
from six.moves import urllib
|
||||||
|
|
||||||
|
from oauth2client import _helpers
|
||||||
from oauth2client import client
|
from oauth2client import client
|
||||||
from oauth2client import util
|
|
||||||
|
|
||||||
|
|
||||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
|
||||||
__all__ = ['argparser', 'run_flow', 'message_if_missing']
|
__all__ = ['argparser', 'run_flow', 'message_if_missing']
|
||||||
|
|
||||||
_CLIENT_SECRETS_MESSAGE = """WARNING: Please configure OAuth 2.0
|
_CLIENT_SECRETS_MESSAGE = """WARNING: Please configure OAuth 2.0
|
||||||
@@ -123,22 +122,22 @@ class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
|||||||
if an error occurred.
|
if an error occurred.
|
||||||
"""
|
"""
|
||||||
self.send_response(http_client.OK)
|
self.send_response(http_client.OK)
|
||||||
self.send_header("Content-type", "text/html")
|
self.send_header('Content-type', 'text/html')
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
query = self.path.split('?', 1)[-1]
|
parts = urllib.parse.urlparse(self.path)
|
||||||
query = dict(urllib.parse.parse_qsl(query))
|
query = _helpers.parse_unique_urlencoded(parts.query)
|
||||||
self.server.query_params = query
|
self.server.query_params = query
|
||||||
self.wfile.write(
|
self.wfile.write(
|
||||||
b"<html><head><title>Authentication Status</title></head>")
|
b'<html><head><title>Authentication Status</title></head>')
|
||||||
self.wfile.write(
|
self.wfile.write(
|
||||||
b"<body><p>The authentication flow has completed.</p>")
|
b'<body><p>The authentication flow has completed.</p>')
|
||||||
self.wfile.write(b"</body></html>")
|
self.wfile.write(b'</body></html>')
|
||||||
|
|
||||||
def log_message(self, format, *args):
|
def log_message(self, format, *args):
|
||||||
"""Do not log messages to stdout while running as cmd. line program."""
|
"""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):
|
def run_flow(flow, storage, flags=None, http=None):
|
||||||
"""Core code for a command-line application.
|
"""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
|
flow.redirect_uri = oauth_callback
|
||||||
authorize_url = flow.step1_get_authorize_url()
|
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:
|
if not flags.noauth_local_webserver:
|
||||||
import webbrowser
|
import webbrowser
|
||||||
webbrowser.open(authorize_url, new=1, autoraise=True)
|
webbrowser.open(authorize_url, new=1, autoraise=True)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import httplib2
|
|||||||
import six
|
import six
|
||||||
from six.moves import http_client
|
from six.moves import http_client
|
||||||
|
|
||||||
from oauth2client._helpers import _to_bytes
|
from oauth2client import _helpers
|
||||||
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -58,13 +58,19 @@ def get_cached_http():
|
|||||||
return _CACHED_HTTP
|
return _CACHED_HTTP
|
||||||
|
|
||||||
|
|
||||||
def get_http_object():
|
def get_http_object(*args, **kwargs):
|
||||||
"""Return a new HTTP object.
|
"""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:
|
Returns:
|
||||||
httplib2.Http, an HTTP object.
|
httplib2.Http, an HTTP object.
|
||||||
"""
|
"""
|
||||||
return httplib2.Http()
|
return httplib2.Http(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def _initialize_headers(headers):
|
def _initialize_headers(headers):
|
||||||
@@ -121,7 +127,7 @@ def clean_headers(headers):
|
|||||||
k = str(k)
|
k = str(k)
|
||||||
if not isinstance(v, six.binary_type):
|
if not isinstance(v, six.binary_type):
|
||||||
v = str(v)
|
v = str(v)
|
||||||
clean[_to_bytes(k)] = _to_bytes(v)
|
clean[_helpers._to_bytes(k)] = _helpers._to_bytes(v)
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
from oauth2client.client import NonAsciiHeaderError
|
from oauth2client.client import NonAsciiHeaderError
|
||||||
raise NonAsciiHeaderError(k, ': ', v)
|
raise NonAsciiHeaderError(k, ': ', v)
|
||||||
@@ -164,7 +170,7 @@ def wrap_http_for_auth(credentials, http):
|
|||||||
_STREAM_PROPERTIES):
|
_STREAM_PROPERTIES):
|
||||||
body_stream_position = body.tell()
|
body_stream_position = body.tell()
|
||||||
|
|
||||||
resp, content = orig_request_method(uri, method, body,
|
resp, content = request(orig_request_method, uri, method, body,
|
||||||
clean_headers(headers),
|
clean_headers(headers),
|
||||||
redirections, connection_type)
|
redirections, connection_type)
|
||||||
|
|
||||||
@@ -182,7 +188,7 @@ def wrap_http_for_auth(credentials, http):
|
|||||||
if body_stream_position is not None:
|
if body_stream_position is not None:
|
||||||
body.seek(body_stream_position)
|
body.seek(body_stream_position)
|
||||||
|
|
||||||
resp, content = orig_request_method(uri, method, body,
|
resp, content = request(orig_request_method, uri, method, body,
|
||||||
clean_headers(headers),
|
clean_headers(headers),
|
||||||
redirections, connection_type)
|
redirections, connection_type)
|
||||||
|
|
||||||
@@ -192,7 +198,7 @@ def wrap_http_for_auth(credentials, http):
|
|||||||
http.request = new_request
|
http.request = new_request
|
||||||
|
|
||||||
# Set credentials as a property of the request method.
|
# 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):
|
def wrap_http_for_jwt_access(credentials, http):
|
||||||
@@ -222,8 +228,8 @@ def wrap_http_for_jwt_access(credentials, http):
|
|||||||
if (credentials.access_token is None or
|
if (credentials.access_token is None or
|
||||||
credentials.access_token_expired):
|
credentials.access_token_expired):
|
||||||
credentials.refresh(None)
|
credentials.refresh(None)
|
||||||
return authenticated_request_method(uri, method, body,
|
return request(authenticated_request_method, uri,
|
||||||
headers, redirections,
|
method, body, headers, redirections,
|
||||||
connection_type)
|
connection_type)
|
||||||
else:
|
else:
|
||||||
# If we don't have an 'aud' (audience) claim,
|
# If we don't have an 'aud' (audience) claim,
|
||||||
@@ -234,12 +240,46 @@ def wrap_http_for_jwt_access(credentials, http):
|
|||||||
token, unused_expiry = credentials._create_token({'aud': uri_root})
|
token, unused_expiry = credentials._create_token({'aud': uri_root})
|
||||||
|
|
||||||
headers['Authorization'] = 'Bearer ' + token
|
headers['Authorization'] = 'Bearer ' + token
|
||||||
return orig_request_method(uri, method, body,
|
return request(orig_request_method, uri, method, body,
|
||||||
clean_headers(headers),
|
clean_headers(headers),
|
||||||
redirections, connection_type)
|
redirections, connection_type)
|
||||||
|
|
||||||
# Replace the request method with our own closure.
|
# Replace the request method with our own closure.
|
||||||
http.request = new_request
|
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())
|
_CACHED_HTTP = httplib2.Http(MemoryCache())
|
||||||
|
|||||||
Reference in New Issue
Block a user