aboutsummaryrefslogtreecommitdiff
path: root/oauth2client/client.py
diff options
context:
space:
mode:
Diffstat (limited to 'oauth2client/client.py')
-rw-r--r--oauth2client/client.py277
1 files changed, 120 insertions, 157 deletions
diff --git a/oauth2client/client.py b/oauth2client/client.py
index 7618960..8956443 100644
--- a/oauth2client/client.py
+++ b/oauth2client/client.py
@@ -34,11 +34,13 @@ from six.moves import urllib
import oauth2client
from oauth2client import _helpers
-from oauth2client import _pkce
from oauth2client import clientsecrets
from oauth2client import transport
+from oauth2client import util
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
HAS_OPENSSL = False
HAS_CRYPTO = False
try:
@@ -98,20 +100,20 @@ AccessTokenInfo = collections.namedtuple(
DEFAULT_ENV_NAME = 'UNKNOWN'
# If set to True _get_environment avoid GCE check (_detect_gce_environment)
-NO_GCE_CHECK = os.getenv('NO_GCE_CHECK', 'False')
+NO_GCE_CHECK = os.environ.setdefault('NO_GCE_CHECK', 'False')
# Timeout in seconds to wait for the GCE metadata server when detecting the
# GCE environment.
try:
- GCE_METADATA_TIMEOUT = int(os.getenv('GCE_METADATA_TIMEOUT', 3))
+ GCE_METADATA_TIMEOUT = int(
+ os.environ.setdefault('GCE_METADATA_TIMEOUT', '3'))
except ValueError: # pragma: NO COVER
GCE_METADATA_TIMEOUT = 3
_SERVER_SOFTWARE = 'SERVER_SOFTWARE'
-_GCE_METADATA_URI = 'http://' + os.getenv('GCE_METADATA_IP', '169.254.169.254')
-_METADATA_FLAVOR_HEADER = 'metadata-flavor' # lowercase header
+_GCE_METADATA_HOST = '169.254.169.254'
+_METADATA_FLAVOR_HEADER = 'Metadata-Flavor'
_DESIRED_METADATA_FLAVOR = 'Google'
-_GCE_HEADERS = {_METADATA_FLAVOR_HEADER: _DESIRED_METADATA_FLAVOR}
# Expose utcnow() at module level to allow for
# easier testing (by replacing with a stub).
@@ -438,6 +440,23 @@ class Storage(object):
self.release_lock()
+def _update_query_params(uri, params):
+ """Updates a URI with new query parameters.
+
+ Args:
+ uri: string, A valid URI, with potential existing query parameters.
+ params: dict, A dictionary of query parameters.
+
+ Returns:
+ The same URI but with the new query parameters added.
+ """
+ parts = urllib.parse.urlparse(uri)
+ query_params = dict(urllib.parse.parse_qsl(parts.query))
+ query_params.update(params)
+ new_parts = parts._replace(query=urllib.parse.urlencode(query_params))
+ return urllib.parse.urlunparse(new_parts)
+
+
class OAuth2Credentials(Credentials):
"""Credentials object for OAuth 2.0.
@@ -447,11 +466,11 @@ class OAuth2Credentials(Credentials):
OAuth2Credentials objects may be safely pickled and unpickled.
"""
- @_helpers.positional(8)
+ @util.positional(8)
def __init__(self, access_token, client_id, client_secret, refresh_token,
token_expiry, token_uri, user_agent, revoke_uri=None,
id_token=None, token_response=None, scopes=None,
- token_info_uri=None, id_token_jwt=None):
+ token_info_uri=None):
"""Create an instance of OAuth2Credentials.
This constructor is not usually called by the user, instead
@@ -474,11 +493,8 @@ class OAuth2Credentials(Credentials):
because some providers (e.g. wordpress.com) include
extra fields that clients may want.
scopes: list, authorized scopes for these credentials.
- token_info_uri: string, the URI for the token info endpoint.
- Defaults to None; scopes can not be refreshed if
- this is None.
- id_token_jwt: string, the encoded and signed identity JWT. The
- decoded version of this is stored in id_token.
+ token_info_uri: string, the URI for the token info endpoint. Defaults
+ to None; scopes can not be refreshed if this is None.
Notes:
store: callable, A callable that when passed a Credential
@@ -496,9 +512,8 @@ class OAuth2Credentials(Credentials):
self.user_agent = user_agent
self.revoke_uri = revoke_uri
self.id_token = id_token
- self.id_token_jwt = id_token_jwt
self.token_response = token_response
- self.scopes = set(_helpers.string_to_scopes(scopes or []))
+ self.scopes = set(util.string_to_scopes(scopes or []))
self.token_info_uri = token_info_uri
# True if the credentials have been revoked or expired and can't be
@@ -542,7 +557,7 @@ class OAuth2Credentials(Credentials):
http: httplib2.Http, an http object to be used to make the refresh
request.
"""
- self._refresh(http)
+ self._refresh(http.request)
def revoke(self, http):
"""Revokes a refresh_token and makes the credentials void.
@@ -551,7 +566,7 @@ class OAuth2Credentials(Credentials):
http: httplib2.Http, an http object to be used to make the revoke
request.
"""
- self._revoke(http)
+ self._revoke(http.request)
def apply(self, headers):
"""Add the authorization to the headers.
@@ -577,7 +592,7 @@ class OAuth2Credentials(Credentials):
not have scopes. In both cases, you can use refresh_scopes() to
obtain the canonical set of scopes.
"""
- scopes = _helpers.string_to_scopes(scopes)
+ scopes = util.string_to_scopes(scopes)
return set(scopes).issubset(self.scopes)
def retrieve_scopes(self, http):
@@ -592,7 +607,7 @@ class OAuth2Credentials(Credentials):
Returns:
A set of strings containing the canonical list of scopes.
"""
- self._retrieve_scopes(http)
+ self._retrieve_scopes(http.request)
return self.scopes
@classmethod
@@ -625,7 +640,6 @@ class OAuth2Credentials(Credentials):
data['user_agent'],
revoke_uri=data.get('revoke_uri', None),
id_token=data.get('id_token', None),
- id_token_jwt=data.get('id_token_jwt', None),
token_response=data.get('token_response', None),
scopes=data.get('scopes', None),
token_info_uri=data.get('token_info_uri', None))
@@ -732,7 +746,7 @@ class OAuth2Credentials(Credentials):
return headers
- def _refresh(self, http):
+ def _refresh(self, http_request):
"""Refreshes the access_token.
This method first checks by reading the Storage object if available.
@@ -740,13 +754,15 @@ class OAuth2Credentials(Credentials):
refresh is completed.
Args:
- http: an object to be used to make HTTP requests.
+ http_request: callable, a callable that matches the method
+ signature of httplib2.Http.request, used to make the
+ refresh request.
Raises:
HttpAccessTokenRefreshError: When the refresh fails.
"""
if not self.store:
- self._do_refresh_request(http)
+ self._do_refresh_request(http_request)
else:
self.store.acquire_lock()
try:
@@ -758,15 +774,17 @@ class OAuth2Credentials(Credentials):
logger.info('Updated access_token read from Storage')
self._updateFromCredential(new_cred)
else:
- self._do_refresh_request(http)
+ self._do_refresh_request(http_request)
finally:
self.store.release_lock()
- def _do_refresh_request(self, http):
+ def _do_refresh_request(self, http_request):
"""Refresh the access_token using the refresh_token.
Args:
- http: an object to be used to make HTTP requests.
+ http_request: callable, a callable that matches the method
+ signature of httplib2.Http.request, used to make the
+ refresh request.
Raises:
HttpAccessTokenRefreshError: When the refresh fails.
@@ -775,9 +793,8 @@ class OAuth2Credentials(Credentials):
headers = self._generate_refresh_request_headers()
logger.info('Refreshing access_token')
- resp, content = transport.request(
- http, self.token_uri, method='POST',
- body=body, headers=headers)
+ resp, content = http_request(
+ self.token_uri, method='POST', body=body, headers=headers)
content = _helpers._from_bytes(content)
if resp.status == http_client.OK:
d = json.loads(content)
@@ -791,10 +808,8 @@ class OAuth2Credentials(Credentials):
self.token_expiry = None
if 'id_token' in d:
self.id_token = _extract_id_token(d['id_token'])
- self.id_token_jwt = d['id_token']
else:
self.id_token = None
- self.id_token_jwt = None
# On temporary refresh errors, the user does not actually have to
# re-authorize, so we unflag here.
self.invalid = False
@@ -804,7 +819,7 @@ class OAuth2Credentials(Credentials):
# An {'error':...} response body means the token is expired or
# revoked, so we flag the credentials as such.
logger.info('Failed to retrieve access token: %s', content)
- error_msg = 'Invalid response {0}.'.format(resp.status)
+ error_msg = 'Invalid response {0}.'.format(resp['status'])
try:
d = json.loads(content)
if 'error' in d:
@@ -818,19 +833,23 @@ class OAuth2Credentials(Credentials):
pass
raise HttpAccessTokenRefreshError(error_msg, status=resp.status)
- def _revoke(self, http):
+ def _revoke(self, http_request):
"""Revokes this credential and deletes the stored copy (if it exists).
Args:
- http: an object to be used to make HTTP requests.
+ http_request: callable, a callable that matches the method
+ signature of httplib2.Http.request, used to make the
+ revoke request.
"""
- self._do_revoke(http, self.refresh_token or self.access_token)
+ self._do_revoke(http_request, self.refresh_token or self.access_token)
- def _do_revoke(self, http, token):
+ def _do_revoke(self, http_request, token):
"""Revokes this credential and deletes the stored copy (if it exists).
Args:
- http: an object to be used to make HTTP requests.
+ http_request: callable, a callable that matches the method
+ 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
access_token or refresh_token.
@@ -840,13 +859,8 @@ class OAuth2Credentials(Credentials):
"""
logger.info('Revoking token')
query_params = {'token': token}
- token_revoke_uri = _helpers.update_query_params(
- self.revoke_uri, query_params)
- resp, content = transport.request(http, token_revoke_uri)
- if resp.status == http_client.METHOD_NOT_ALLOWED:
- body = urllib.parse.urlencode(query_params)
- resp, content = transport.request(http, token_revoke_uri,
- method='POST', body=body)
+ token_revoke_uri = _update_query_params(self.revoke_uri, query_params)
+ resp, content = http_request(token_revoke_uri)
if resp.status == http_client.OK:
self.invalid = True
else:
@@ -862,19 +876,23 @@ class OAuth2Credentials(Credentials):
if self.store:
self.store.delete()
- def _retrieve_scopes(self, http):
+ def _retrieve_scopes(self, http_request):
"""Retrieves the list of authorized scopes from the OAuth2 provider.
Args:
- http: an object to be used to make HTTP requests.
+ http_request: callable, a callable that matches the method
+ signature of httplib2.Http.request, used to make the
+ revoke request.
"""
- self._do_retrieve_scopes(http, self.access_token)
+ self._do_retrieve_scopes(http_request, self.access_token)
- def _do_retrieve_scopes(self, http, token):
+ def _do_retrieve_scopes(self, http_request, token):
"""Retrieves the list of authorized scopes from the OAuth2 provider.
Args:
- http: an object to be used to make HTTP requests.
+ http_request: callable, a callable that matches the method
+ signature of httplib2.Http.request, used to make the
+ refresh request.
token: A string used as the token to identify the credentials to
the provider.
@@ -884,13 +902,13 @@ class OAuth2Credentials(Credentials):
"""
logger.info('Refreshing scopes')
query_params = {'access_token': token, 'fields': 'scope'}
- token_info_uri = _helpers.update_query_params(
- self.token_info_uri, query_params)
- resp, content = transport.request(http, token_info_uri)
+ token_info_uri = _update_query_params(self.token_info_uri,
+ query_params)
+ resp, content = http_request(token_info_uri)
content = _helpers._from_bytes(content)
if resp.status == http_client.OK:
d = json.loads(content)
- self.scopes = set(_helpers.string_to_scopes(d.get('scope', '')))
+ self.scopes = set(util.string_to_scopes(d.get('scope', '')))
else:
error_msg = 'Invalid response {0}.'.format(resp.status)
try:
@@ -959,25 +977,19 @@ class AccessTokenCredentials(OAuth2Credentials):
data['user_agent'])
return retval
- def _refresh(self, http):
- """Refreshes the access token.
-
- Args:
- http: unused HTTP object.
-
- Raises:
- AccessTokenCredentialsError: always
- """
+ def _refresh(self, http_request):
raise AccessTokenCredentialsError(
'The access_token is expired or invalid and can\'t be refreshed.')
- def _revoke(self, http):
+ def _revoke(self, http_request):
"""Revokes the access_token and deletes the store if available.
Args:
- http: an object to be used to make HTTP requests.
+ http_request: callable, a callable that matches the method
+ signature of httplib2.Http.request, used to make the
+ revoke request.
"""
- self._do_revoke(http, self.access_token)
+ self._do_revoke(http_request, self.access_token)
def _detect_gce_environment():
@@ -993,16 +1005,21 @@ def _detect_gce_environment():
# could lead to false negatives in the event that we are on GCE, but
# the metadata resolution was particularly slow. The latter case is
# "unlikely".
- http = transport.get_http_object(timeout=GCE_METADATA_TIMEOUT)
+ connection = six.moves.http_client.HTTPConnection(
+ _GCE_METADATA_HOST, timeout=GCE_METADATA_TIMEOUT)
+
try:
- response, _ = transport.request(
- http, _GCE_METADATA_URI, headers=_GCE_HEADERS)
- return (
- response.status == http_client.OK and
- response.get(_METADATA_FLAVOR_HEADER) == _DESIRED_METADATA_FLAVOR)
+ headers = {_METADATA_FLAVOR_HEADER: _DESIRED_METADATA_FLAVOR}
+ connection.request('GET', '/', headers=headers)
+ response = connection.getresponse()
+ if response.status == http_client.OK:
+ return (response.getheader(_METADATA_FLAVOR_HEADER) ==
+ _DESIRED_METADATA_FLAVOR)
except socket.error: # socket.timeout or socket.error(64, 'Host is down')
logger.info('Timeout attempting to reach GCE metadata service.')
return False
+ finally:
+ connection.close()
def _in_gae_environment():
@@ -1452,7 +1469,7 @@ class AssertionCredentials(GoogleCredentials):
AssertionCredentials objects may be safely pickled and unpickled.
"""
- @_helpers.positional(2)
+ @util.positional(2)
def __init__(self, assertion_type, user_agent=None,
token_uri=oauth2client.GOOGLE_TOKEN_URI,
revoke_uri=oauth2client.GOOGLE_REVOKE_URI,
@@ -1494,13 +1511,15 @@ class AssertionCredentials(GoogleCredentials):
"""Generate assertion string to be used in the access token request."""
raise NotImplementedError
- def _revoke(self, http):
+ def _revoke(self, http_request):
"""Revokes the access_token and deletes the store if available.
Args:
- http: an object to be used to make HTTP requests.
+ http_request: callable, a callable that matches the method
+ signature of httplib2.Http.request, used to make the
+ revoke request.
"""
- self._do_revoke(http, self.access_token)
+ self._do_revoke(http_request, self.access_token)
def sign_blob(self, blob):
"""Cryptographically sign a blob (of bytes).
@@ -1526,7 +1545,7 @@ def _require_crypto_or_die():
raise CryptoUnavailableError('No crypto library available')
-@_helpers.positional(2)
+@util.positional(2)
def verify_id_token(id_token, audience, http=None,
cert_uri=ID_TOKEN_VERIFICATION_CERTS):
"""Verifies a signed JWT id_token.
@@ -1553,7 +1572,7 @@ def verify_id_token(id_token, audience, http=None,
if http is None:
http = transport.get_cached_http()
- resp, content = transport.request(http, cert_uri)
+ resp, content = http.request(cert_uri)
if resp.status == http_client.OK:
certs = json.loads(_helpers._from_bytes(content))
return crypt.verify_signed_jwt_with_certs(id_token, certs, audience)
@@ -1605,7 +1624,7 @@ def _parse_exchange_token_response(content):
except Exception:
# different JSON libs raise different exceptions,
# so we just do a catch-all here
- resp = _helpers.parse_unique_urlencoded(content)
+ resp = dict(urllib.parse.parse_qsl(content))
# some providers respond with 'expires', others with 'expires_in'
if resp and 'expires' in resp:
@@ -1614,7 +1633,7 @@ def _parse_exchange_token_response(content):
return resp
-@_helpers.positional(4)
+@util.positional(4)
def credentials_from_code(client_id, client_secret, scope, code,
redirect_uri='postmessage', http=None,
user_agent=None,
@@ -1622,9 +1641,7 @@ def credentials_from_code(client_id, client_secret, scope, code,
auth_uri=oauth2client.GOOGLE_AUTH_URI,
revoke_uri=oauth2client.GOOGLE_REVOKE_URI,
device_uri=oauth2client.GOOGLE_DEVICE_URI,
- token_info_uri=oauth2client.GOOGLE_TOKEN_INFO_URI,
- pkce=False,
- code_verifier=None):
+ token_info_uri=oauth2client.GOOGLE_TOKEN_INFO_URI):
"""Exchanges an authorization code for an OAuth2Credentials object.
Args:
@@ -1648,15 +1665,6 @@ def credentials_from_code(client_id, client_secret, scope, code,
device_uri: string, URI for device authorization endpoint. For
convenience defaults to Google's endpoints but any OAuth
2.0 provider can be used.
- pkce: boolean, default: False, Generate and include a "Proof Key
- for Code Exchange" (PKCE) with your authorization and token
- requests. This adds security for installed applications that
- cannot protect a client_secret. See RFC 7636 for details.
- code_verifier: bytestring or None, default: None, parameter passed
- as part of the code exchange when pkce=True. If
- None, a code_verifier will automatically be
- generated as part of step1_get_authorize_url(). See
- RFC 7636 for details.
Returns:
An OAuth2Credentials object.
@@ -1667,20 +1675,16 @@ def credentials_from_code(client_id, client_secret, scope, code,
"""
flow = OAuth2WebServerFlow(client_id, client_secret, scope,
redirect_uri=redirect_uri,
- user_agent=user_agent,
- auth_uri=auth_uri,
- token_uri=token_uri,
- revoke_uri=revoke_uri,
+ user_agent=user_agent, auth_uri=auth_uri,
+ token_uri=token_uri, revoke_uri=revoke_uri,
device_uri=device_uri,
- token_info_uri=token_info_uri,
- pkce=pkce,
- code_verifier=code_verifier)
+ token_info_uri=token_info_uri)
credentials = flow.step2_exchange(code, http=http)
return credentials
-@_helpers.positional(3)
+@util.positional(3)
def credentials_from_clientsecrets_and_code(filename, scope, code,
message=None,
redirect_uri='postmessage',
@@ -1709,15 +1713,6 @@ def credentials_from_clientsecrets_and_code(filename, scope, code,
cache: An optional cache service client that implements get() and set()
methods. See clientsecrets.loadfile() for details.
device_uri: string, OAuth 2.0 device authorization endpoint
- pkce: boolean, default: False, Generate and include a "Proof Key
- for Code Exchange" (PKCE) with your authorization and token
- requests. This adds security for installed applications that
- cannot protect a client_secret. See RFC 7636 for details.
- code_verifier: bytestring or None, default: None, parameter passed
- as part of the code exchange when pkce=True. If
- None, a code_verifier will automatically be
- generated as part of step1_get_authorize_url(). See
- RFC 7636 for details.
Returns:
An OAuth2Credentials object.
@@ -1808,7 +1803,7 @@ class OAuth2WebServerFlow(Flow):
OAuth2WebServerFlow objects may be safely pickled and unpickled.
"""
- @_helpers.positional(4)
+ @util.positional(4)
def __init__(self, client_id,
client_secret=None,
scope=None,
@@ -1821,8 +1816,6 @@ class OAuth2WebServerFlow(Flow):
device_uri=oauth2client.GOOGLE_DEVICE_URI,
token_info_uri=oauth2client.GOOGLE_TOKEN_INFO_URI,
authorization_header=None,
- pkce=False,
- code_verifier=None,
**kwargs):
"""Constructor for OAuth2WebServerFlow.
@@ -1860,15 +1853,6 @@ class OAuth2WebServerFlow(Flow):
require a client to authenticate using a
header value instead of passing client_secret
in the POST body.
- pkce: boolean, default: False, Generate and include a "Proof Key
- for Code Exchange" (PKCE) with your authorization and token
- requests. This adds security for installed applications that
- cannot protect a client_secret. See RFC 7636 for details.
- code_verifier: bytestring or None, default: None, parameter passed
- as part of the code exchange when pkce=True. If
- None, a code_verifier will automatically be
- generated as part of step1_get_authorize_url(). See
- RFC 7636 for details.
**kwargs: dict, The keyword arguments are all optional and required
parameters for the OAuth calls.
"""
@@ -1878,7 +1862,7 @@ class OAuth2WebServerFlow(Flow):
raise TypeError("The value of scope must not be None")
self.client_id = client_id
self.client_secret = client_secret
- self.scope = _helpers.scopes_to_string(scope)
+ self.scope = util.scopes_to_string(scope)
self.redirect_uri = redirect_uri
self.login_hint = login_hint
self.user_agent = user_agent
@@ -1888,11 +1872,9 @@ class OAuth2WebServerFlow(Flow):
self.device_uri = device_uri
self.token_info_uri = token_info_uri
self.authorization_header = authorization_header
- self._pkce = pkce
- self.code_verifier = code_verifier
self.params = _oauth2_web_server_flow_params(kwargs)
- @_helpers.positional(1)
+ @util.positional(1)
def step1_get_authorize_url(self, redirect_uri=None, state=None):
"""Returns a URI to redirect to the provider.
@@ -1930,17 +1912,10 @@ class OAuth2WebServerFlow(Flow):
query_params['state'] = state
if self.login_hint is not None:
query_params['login_hint'] = self.login_hint
- if self._pkce:
- if not self.code_verifier:
- self.code_verifier = _pkce.code_verifier()
- challenge = _pkce.code_challenge(self.code_verifier)
- query_params['code_challenge'] = challenge
- query_params['code_challenge_method'] = 'S256'
-
query_params.update(self.params)
- return _helpers.update_query_params(self.auth_uri, query_params)
+ return _update_query_params(self.auth_uri, query_params)
- @_helpers.positional(1)
+ @util.positional(1)
def step1_get_device_and_user_codes(self, http=None):
"""Returns a user code and the verification URL where to enter it
@@ -1965,8 +1940,8 @@ class OAuth2WebServerFlow(Flow):
if http is None:
http = transport.get_http_object()
- resp, content = transport.request(
- http, self.device_uri, method='POST', body=body, headers=headers)
+ resp, content = http.request(self.device_uri, method='POST', body=body,
+ headers=headers)
content = _helpers._from_bytes(content)
if resp.status == http_client.OK:
try:
@@ -1988,7 +1963,7 @@ class OAuth2WebServerFlow(Flow):
pass
raise OAuth2DeviceCodeError(error_msg)
- @_helpers.positional(2)
+ @util.positional(2)
def step2_exchange(self, code=None, http=None, device_flow_info=None):
"""Exchanges a code for OAuth2Credentials.
@@ -2031,8 +2006,6 @@ class OAuth2WebServerFlow(Flow):
}
if self.client_secret is not None:
post_data['client_secret'] = self.client_secret
- if self._pkce:
- post_data['code_verifier'] = self.code_verifier
if device_flow_info is not None:
post_data['grant_type'] = 'http://oauth.net/grant_type/device/1.0'
else:
@@ -2050,8 +2023,8 @@ class OAuth2WebServerFlow(Flow):
if http is None:
http = transport.get_http_object()
- resp, content = transport.request(
- http, self.token_uri, method='POST', body=body, headers=headers)
+ resp, content = http.request(self.token_uri, method='POST', body=body,
+ headers=headers)
d = _parse_exchange_token_response(content)
if resp.status == http_client.OK and 'access_token' in d:
access_token = d['access_token']
@@ -2066,17 +2039,15 @@ class OAuth2WebServerFlow(Flow):
token_expiry = delta + _UTCNOW()
extracted_id_token = None
- id_token_jwt = None
if 'id_token' in d:
extracted_id_token = _extract_id_token(d['id_token'])
- id_token_jwt = d['id_token']
logger.info('Successfully retrieved access token')
return OAuth2Credentials(
access_token, self.client_id, self.client_secret,
refresh_token, token_expiry, self.token_uri, self.user_agent,
revoke_uri=self.revoke_uri, id_token=extracted_id_token,
- id_token_jwt=id_token_jwt, token_response=d, scopes=self.scope,
+ token_response=d, scopes=self.scope,
token_info_uri=self.token_info_uri)
else:
logger.info('Failed to retrieve access token: %s', content)
@@ -2089,11 +2060,10 @@ class OAuth2WebServerFlow(Flow):
raise FlowExchangeError(error_msg)
-@_helpers.positional(2)
+@util.positional(2)
def flow_from_clientsecrets(filename, scope, redirect_uri=None,
message=None, cache=None, login_hint=None,
- device_uri=None, pkce=None, code_verifier=None,
- prompt=None):
+ device_uri=None):
"""Create a Flow from a clientsecrets file.
Will create the right kind of Flow based on the contents of the
@@ -2142,17 +2112,10 @@ def flow_from_clientsecrets(filename, scope, redirect_uri=None,
'login_hint': login_hint,
}
revoke_uri = client_info.get('revoke_uri')
- optional = (
- 'revoke_uri',
- 'device_uri',
- 'pkce',
- 'code_verifier',
- 'prompt'
- )
- for param in optional:
- if locals()[param] is not None:
- constructor_kwargs[param] = locals()[param]
-
+ if revoke_uri is not None:
+ constructor_kwargs['revoke_uri'] = revoke_uri
+ if device_uri is not None:
+ constructor_kwargs['device_uri'] = device_uri
return OAuth2WebServerFlow(
client_info['client_id'], client_info['client_secret'],
scope, **constructor_kwargs)