Added delete option to database storage.
This commit is contained in:
parent
308604a33c
commit
963b5bc68b
1868 changed files with 192402 additions and 13278 deletions
15
venv/Lib/site-packages/google/oauth2/__init__.py
Normal file
15
venv/Lib/site-packages/google/oauth2/__init__.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Google OAuth 2.0 Library for Python."""
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
259
venv/Lib/site-packages/google/oauth2/_client.py
Normal file
259
venv/Lib/site-packages/google/oauth2/_client.py
Normal file
|
@ -0,0 +1,259 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""OAuth 2.0 client.
|
||||
|
||||
This is a client for interacting with an OAuth 2.0 authorization server's
|
||||
token endpoint.
|
||||
|
||||
For more information about the token endpoint, see
|
||||
`Section 3.1 of rfc6749`_
|
||||
|
||||
.. _Section 3.1 of rfc6749: https://tools.ietf.org/html/rfc6749#section-3.2
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import json
|
||||
|
||||
import six
|
||||
from six.moves import http_client
|
||||
from six.moves import urllib
|
||||
|
||||
from google.auth import _helpers
|
||||
from google.auth import exceptions
|
||||
from google.auth import jwt
|
||||
|
||||
_URLENCODED_CONTENT_TYPE = "application/x-www-form-urlencoded"
|
||||
_JWT_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
||||
_REFRESH_GRANT_TYPE = "refresh_token"
|
||||
|
||||
|
||||
def _handle_error_response(response_body):
|
||||
""""Translates an error response into an exception.
|
||||
|
||||
Args:
|
||||
response_body (str): The decoded response data.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.RefreshError
|
||||
"""
|
||||
try:
|
||||
error_data = json.loads(response_body)
|
||||
error_details = "{}: {}".format(
|
||||
error_data["error"], error_data.get("error_description")
|
||||
)
|
||||
# If no details could be extracted, use the response data.
|
||||
except (KeyError, ValueError):
|
||||
error_details = response_body
|
||||
|
||||
raise exceptions.RefreshError(error_details, response_body)
|
||||
|
||||
|
||||
def _parse_expiry(response_data):
|
||||
"""Parses the expiry field from a response into a datetime.
|
||||
|
||||
Args:
|
||||
response_data (Mapping): The JSON-parsed response data.
|
||||
|
||||
Returns:
|
||||
Optional[datetime]: The expiration or ``None`` if no expiration was
|
||||
specified.
|
||||
"""
|
||||
expires_in = response_data.get("expires_in", None)
|
||||
|
||||
if expires_in is not None:
|
||||
return _helpers.utcnow() + datetime.timedelta(seconds=expires_in)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def _token_endpoint_request(request, token_uri, body):
|
||||
"""Makes a request to the OAuth 2.0 authorization server's token endpoint.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): A callable used to make
|
||||
HTTP requests.
|
||||
token_uri (str): The OAuth 2.0 authorizations server's token endpoint
|
||||
URI.
|
||||
body (Mapping[str, str]): The parameters to send in the request body.
|
||||
|
||||
Returns:
|
||||
Mapping[str, str]: The JSON-decoded response data.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.RefreshError: If the token endpoint returned
|
||||
an error.
|
||||
"""
|
||||
body = urllib.parse.urlencode(body).encode("utf-8")
|
||||
headers = {"content-type": _URLENCODED_CONTENT_TYPE}
|
||||
|
||||
retry = 0
|
||||
# retry to fetch token for maximum of two times if any internal failure
|
||||
# occurs.
|
||||
while True:
|
||||
response = request(method="POST", url=token_uri, headers=headers, body=body)
|
||||
response_body = (
|
||||
response.data.decode("utf-8")
|
||||
if hasattr(response.data, "decode")
|
||||
else response.data
|
||||
)
|
||||
response_data = json.loads(response_body)
|
||||
|
||||
if response.status == http_client.OK:
|
||||
break
|
||||
else:
|
||||
error_desc = response_data.get("error_description") or ""
|
||||
error_code = response_data.get("error") or ""
|
||||
if (
|
||||
any(e == "internal_failure" for e in (error_code, error_desc))
|
||||
and retry < 1
|
||||
):
|
||||
retry += 1
|
||||
continue
|
||||
_handle_error_response(response_body)
|
||||
|
||||
return response_data
|
||||
|
||||
|
||||
def jwt_grant(request, token_uri, assertion):
|
||||
"""Implements the JWT Profile for OAuth 2.0 Authorization Grants.
|
||||
|
||||
For more details, see `rfc7523 section 4`_.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): A callable used to make
|
||||
HTTP requests.
|
||||
token_uri (str): The OAuth 2.0 authorizations server's token endpoint
|
||||
URI.
|
||||
assertion (str): The OAuth 2.0 assertion.
|
||||
|
||||
Returns:
|
||||
Tuple[str, Optional[datetime], Mapping[str, str]]: The access token,
|
||||
expiration, and additional data returned by the token endpoint.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.RefreshError: If the token endpoint returned
|
||||
an error.
|
||||
|
||||
.. _rfc7523 section 4: https://tools.ietf.org/html/rfc7523#section-4
|
||||
"""
|
||||
body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE}
|
||||
|
||||
response_data = _token_endpoint_request(request, token_uri, body)
|
||||
|
||||
try:
|
||||
access_token = response_data["access_token"]
|
||||
except KeyError as caught_exc:
|
||||
new_exc = exceptions.RefreshError("No access token in response.", response_data)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
expiry = _parse_expiry(response_data)
|
||||
|
||||
return access_token, expiry, response_data
|
||||
|
||||
|
||||
def id_token_jwt_grant(request, token_uri, assertion):
|
||||
"""Implements the JWT Profile for OAuth 2.0 Authorization Grants, but
|
||||
requests an OpenID Connect ID Token instead of an access token.
|
||||
|
||||
This is a variant on the standard JWT Profile that is currently unique
|
||||
to Google. This was added for the benefit of authenticating to services
|
||||
that require ID Tokens instead of access tokens or JWT bearer tokens.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): A callable used to make
|
||||
HTTP requests.
|
||||
token_uri (str): The OAuth 2.0 authorization server's token endpoint
|
||||
URI.
|
||||
assertion (str): JWT token signed by a service account. The token's
|
||||
payload must include a ``target_audience`` claim.
|
||||
|
||||
Returns:
|
||||
Tuple[str, Optional[datetime], Mapping[str, str]]:
|
||||
The (encoded) Open ID Connect ID Token, expiration, and additional
|
||||
data returned by the endpoint.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.RefreshError: If the token endpoint returned
|
||||
an error.
|
||||
"""
|
||||
body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE}
|
||||
|
||||
response_data = _token_endpoint_request(request, token_uri, body)
|
||||
|
||||
try:
|
||||
id_token = response_data["id_token"]
|
||||
except KeyError as caught_exc:
|
||||
new_exc = exceptions.RefreshError("No ID token in response.", response_data)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
payload = jwt.decode(id_token, verify=False)
|
||||
expiry = datetime.datetime.utcfromtimestamp(payload["exp"])
|
||||
|
||||
return id_token, expiry, response_data
|
||||
|
||||
|
||||
def refresh_grant(
|
||||
request, token_uri, refresh_token, client_id, client_secret, scopes=None
|
||||
):
|
||||
"""Implements the OAuth 2.0 refresh token grant.
|
||||
|
||||
For more details, see `rfc678 section 6`_.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): A callable used to make
|
||||
HTTP requests.
|
||||
token_uri (str): The OAuth 2.0 authorizations server's token endpoint
|
||||
URI.
|
||||
refresh_token (str): The refresh token to use to get a new access
|
||||
token.
|
||||
client_id (str): The OAuth 2.0 application's client ID.
|
||||
client_secret (str): The Oauth 2.0 appliaction's client secret.
|
||||
scopes (Optional(Sequence[str])): Scopes to request. If present, all
|
||||
scopes must be authorized for the refresh token. Useful if refresh
|
||||
token has a wild card scope (e.g.
|
||||
'https://www.googleapis.com/auth/any-api').
|
||||
|
||||
Returns:
|
||||
Tuple[str, Optional[str], Optional[datetime], Mapping[str, str]]: The
|
||||
access token, new refresh token, expiration, and additional data
|
||||
returned by the token endpoint.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.RefreshError: If the token endpoint returned
|
||||
an error.
|
||||
|
||||
.. _rfc6748 section 6: https://tools.ietf.org/html/rfc6749#section-6
|
||||
"""
|
||||
body = {
|
||||
"grant_type": _REFRESH_GRANT_TYPE,
|
||||
"client_id": client_id,
|
||||
"client_secret": client_secret,
|
||||
"refresh_token": refresh_token,
|
||||
}
|
||||
if scopes:
|
||||
body["scope"] = " ".join(scopes)
|
||||
|
||||
response_data = _token_endpoint_request(request, token_uri, body)
|
||||
|
||||
try:
|
||||
access_token = response_data["access_token"]
|
||||
except KeyError as caught_exc:
|
||||
new_exc = exceptions.RefreshError("No access token in response.", response_data)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
refresh_token = response_data.get("refresh_token", refresh_token)
|
||||
expiry = _parse_expiry(response_data)
|
||||
|
||||
return access_token, refresh_token, expiry, response_data
|
264
venv/Lib/site-packages/google/oauth2/_client_async.py
Normal file
264
venv/Lib/site-packages/google/oauth2/_client_async.py
Normal file
|
@ -0,0 +1,264 @@
|
|||
# Copyright 2020 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""OAuth 2.0 async client.
|
||||
|
||||
This is a client for interacting with an OAuth 2.0 authorization server's
|
||||
token endpoint.
|
||||
|
||||
For more information about the token endpoint, see
|
||||
`Section 3.1 of rfc6749`_
|
||||
|
||||
.. _Section 3.1 of rfc6749: https://tools.ietf.org/html/rfc6749#section-3.2
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import json
|
||||
|
||||
import six
|
||||
from six.moves import http_client
|
||||
from six.moves import urllib
|
||||
|
||||
from google.auth import _helpers
|
||||
from google.auth import exceptions
|
||||
from google.auth import jwt
|
||||
from google.oauth2 import _client as client
|
||||
|
||||
|
||||
def _handle_error_response(response_body):
|
||||
""""Translates an error response into an exception.
|
||||
|
||||
Args:
|
||||
response_body (str): The decoded response data.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.RefreshError
|
||||
"""
|
||||
try:
|
||||
error_data = json.loads(response_body)
|
||||
error_details = "{}: {}".format(
|
||||
error_data["error"], error_data.get("error_description")
|
||||
)
|
||||
# If no details could be extracted, use the response data.
|
||||
except (KeyError, ValueError):
|
||||
error_details = response_body
|
||||
|
||||
raise exceptions.RefreshError(error_details, response_body)
|
||||
|
||||
|
||||
def _parse_expiry(response_data):
|
||||
"""Parses the expiry field from a response into a datetime.
|
||||
|
||||
Args:
|
||||
response_data (Mapping): The JSON-parsed response data.
|
||||
|
||||
Returns:
|
||||
Optional[datetime]: The expiration or ``None`` if no expiration was
|
||||
specified.
|
||||
"""
|
||||
expires_in = response_data.get("expires_in", None)
|
||||
|
||||
if expires_in is not None:
|
||||
return _helpers.utcnow() + datetime.timedelta(seconds=expires_in)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
async def _token_endpoint_request(request, token_uri, body):
|
||||
"""Makes a request to the OAuth 2.0 authorization server's token endpoint.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): A callable used to make
|
||||
HTTP requests.
|
||||
token_uri (str): The OAuth 2.0 authorizations server's token endpoint
|
||||
URI.
|
||||
body (Mapping[str, str]): The parameters to send in the request body.
|
||||
|
||||
Returns:
|
||||
Mapping[str, str]: The JSON-decoded response data.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.RefreshError: If the token endpoint returned
|
||||
an error.
|
||||
"""
|
||||
body = urllib.parse.urlencode(body).encode("utf-8")
|
||||
headers = {"content-type": client._URLENCODED_CONTENT_TYPE}
|
||||
|
||||
retry = 0
|
||||
# retry to fetch token for maximum of two times if any internal failure
|
||||
# occurs.
|
||||
while True:
|
||||
|
||||
response = await request(
|
||||
method="POST", url=token_uri, headers=headers, body=body
|
||||
)
|
||||
|
||||
# Using data.read() resulted in zlib decompression errors. This may require future investigation.
|
||||
response_body1 = await response.content()
|
||||
|
||||
response_body = (
|
||||
response_body1.decode("utf-8")
|
||||
if hasattr(response_body1, "decode")
|
||||
else response_body1
|
||||
)
|
||||
|
||||
response_data = json.loads(response_body)
|
||||
|
||||
if response.status == http_client.OK:
|
||||
break
|
||||
else:
|
||||
error_desc = response_data.get("error_description") or ""
|
||||
error_code = response_data.get("error") or ""
|
||||
if (
|
||||
any(e == "internal_failure" for e in (error_code, error_desc))
|
||||
and retry < 1
|
||||
):
|
||||
retry += 1
|
||||
continue
|
||||
_handle_error_response(response_body)
|
||||
|
||||
return response_data
|
||||
|
||||
|
||||
async def jwt_grant(request, token_uri, assertion):
|
||||
"""Implements the JWT Profile for OAuth 2.0 Authorization Grants.
|
||||
|
||||
For more details, see `rfc7523 section 4`_.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): A callable used to make
|
||||
HTTP requests.
|
||||
token_uri (str): The OAuth 2.0 authorizations server's token endpoint
|
||||
URI.
|
||||
assertion (str): The OAuth 2.0 assertion.
|
||||
|
||||
Returns:
|
||||
Tuple[str, Optional[datetime], Mapping[str, str]]: The access token,
|
||||
expiration, and additional data returned by the token endpoint.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.RefreshError: If the token endpoint returned
|
||||
an error.
|
||||
|
||||
.. _rfc7523 section 4: https://tools.ietf.org/html/rfc7523#section-4
|
||||
"""
|
||||
body = {"assertion": assertion, "grant_type": client._JWT_GRANT_TYPE}
|
||||
|
||||
response_data = await _token_endpoint_request(request, token_uri, body)
|
||||
|
||||
try:
|
||||
access_token = response_data["access_token"]
|
||||
except KeyError as caught_exc:
|
||||
new_exc = exceptions.RefreshError("No access token in response.", response_data)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
expiry = _parse_expiry(response_data)
|
||||
|
||||
return access_token, expiry, response_data
|
||||
|
||||
|
||||
async def id_token_jwt_grant(request, token_uri, assertion):
|
||||
"""Implements the JWT Profile for OAuth 2.0 Authorization Grants, but
|
||||
requests an OpenID Connect ID Token instead of an access token.
|
||||
|
||||
This is a variant on the standard JWT Profile that is currently unique
|
||||
to Google. This was added for the benefit of authenticating to services
|
||||
that require ID Tokens instead of access tokens or JWT bearer tokens.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): A callable used to make
|
||||
HTTP requests.
|
||||
token_uri (str): The OAuth 2.0 authorization server's token endpoint
|
||||
URI.
|
||||
assertion (str): JWT token signed by a service account. The token's
|
||||
payload must include a ``target_audience`` claim.
|
||||
|
||||
Returns:
|
||||
Tuple[str, Optional[datetime], Mapping[str, str]]:
|
||||
The (encoded) Open ID Connect ID Token, expiration, and additional
|
||||
data returned by the endpoint.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.RefreshError: If the token endpoint returned
|
||||
an error.
|
||||
"""
|
||||
body = {"assertion": assertion, "grant_type": client._JWT_GRANT_TYPE}
|
||||
|
||||
response_data = await _token_endpoint_request(request, token_uri, body)
|
||||
|
||||
try:
|
||||
id_token = response_data["id_token"]
|
||||
except KeyError as caught_exc:
|
||||
new_exc = exceptions.RefreshError("No ID token in response.", response_data)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
payload = jwt.decode(id_token, verify=False)
|
||||
expiry = datetime.datetime.utcfromtimestamp(payload["exp"])
|
||||
|
||||
return id_token, expiry, response_data
|
||||
|
||||
|
||||
async def refresh_grant(
|
||||
request, token_uri, refresh_token, client_id, client_secret, scopes=None
|
||||
):
|
||||
"""Implements the OAuth 2.0 refresh token grant.
|
||||
|
||||
For more details, see `rfc678 section 6`_.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): A callable used to make
|
||||
HTTP requests.
|
||||
token_uri (str): The OAuth 2.0 authorizations server's token endpoint
|
||||
URI.
|
||||
refresh_token (str): The refresh token to use to get a new access
|
||||
token.
|
||||
client_id (str): The OAuth 2.0 application's client ID.
|
||||
client_secret (str): The Oauth 2.0 appliaction's client secret.
|
||||
scopes (Optional(Sequence[str])): Scopes to request. If present, all
|
||||
scopes must be authorized for the refresh token. Useful if refresh
|
||||
token has a wild card scope (e.g.
|
||||
'https://www.googleapis.com/auth/any-api').
|
||||
|
||||
Returns:
|
||||
Tuple[str, Optional[str], Optional[datetime], Mapping[str, str]]: The
|
||||
access token, new refresh token, expiration, and additional data
|
||||
returned by the token endpoint.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.RefreshError: If the token endpoint returned
|
||||
an error.
|
||||
|
||||
.. _rfc6748 section 6: https://tools.ietf.org/html/rfc6749#section-6
|
||||
"""
|
||||
body = {
|
||||
"grant_type": client._REFRESH_GRANT_TYPE,
|
||||
"client_id": client_id,
|
||||
"client_secret": client_secret,
|
||||
"refresh_token": refresh_token,
|
||||
}
|
||||
if scopes:
|
||||
body["scope"] = " ".join(scopes)
|
||||
|
||||
response_data = await _token_endpoint_request(request, token_uri, body)
|
||||
|
||||
try:
|
||||
access_token = response_data["access_token"]
|
||||
except KeyError as caught_exc:
|
||||
new_exc = exceptions.RefreshError("No access token in response.", response_data)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
refresh_token = response_data.get("refresh_token", refresh_token)
|
||||
expiry = _parse_expiry(response_data)
|
||||
|
||||
return access_token, refresh_token, expiry, response_data
|
108
venv/Lib/site-packages/google/oauth2/_credentials_async.py
Normal file
108
venv/Lib/site-packages/google/oauth2/_credentials_async.py
Normal file
|
@ -0,0 +1,108 @@
|
|||
# Copyright 2020 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""OAuth 2.0 Async Credentials.
|
||||
|
||||
This module provides credentials based on OAuth 2.0 access and refresh tokens.
|
||||
These credentials usually access resources on behalf of a user (resource
|
||||
owner).
|
||||
|
||||
Specifically, this is intended to use access tokens acquired using the
|
||||
`Authorization Code grant`_ and can refresh those tokens using a
|
||||
optional `refresh token`_.
|
||||
|
||||
Obtaining the initial access and refresh token is outside of the scope of this
|
||||
module. Consult `rfc6749 section 4.1`_ for complete details on the
|
||||
Authorization Code grant flow.
|
||||
|
||||
.. _Authorization Code grant: https://tools.ietf.org/html/rfc6749#section-1.3.1
|
||||
.. _refresh token: https://tools.ietf.org/html/rfc6749#section-6
|
||||
.. _rfc6749 section 4.1: https://tools.ietf.org/html/rfc6749#section-4.1
|
||||
"""
|
||||
|
||||
from google.auth import _credentials_async as credentials
|
||||
from google.auth import _helpers
|
||||
from google.auth import exceptions
|
||||
from google.oauth2 import _client_async as _client
|
||||
from google.oauth2 import credentials as oauth2_credentials
|
||||
|
||||
|
||||
class Credentials(oauth2_credentials.Credentials):
|
||||
"""Credentials using OAuth 2.0 access and refresh tokens.
|
||||
|
||||
The credentials are considered immutable. If you want to modify the
|
||||
quota project, use :meth:`with_quota_project` or ::
|
||||
|
||||
credentials = credentials.with_quota_project('myproject-123)
|
||||
"""
|
||||
|
||||
@_helpers.copy_docstring(credentials.Credentials)
|
||||
async def refresh(self, request):
|
||||
if (
|
||||
self._refresh_token is None
|
||||
or self._token_uri is None
|
||||
or self._client_id is None
|
||||
or self._client_secret is None
|
||||
):
|
||||
raise exceptions.RefreshError(
|
||||
"The credentials do not contain the necessary fields need to "
|
||||
"refresh the access token. You must specify refresh_token, "
|
||||
"token_uri, client_id, and client_secret."
|
||||
)
|
||||
|
||||
(
|
||||
access_token,
|
||||
refresh_token,
|
||||
expiry,
|
||||
grant_response,
|
||||
) = await _client.refresh_grant(
|
||||
request,
|
||||
self._token_uri,
|
||||
self._refresh_token,
|
||||
self._client_id,
|
||||
self._client_secret,
|
||||
self._scopes,
|
||||
)
|
||||
|
||||
self.token = access_token
|
||||
self.expiry = expiry
|
||||
self._refresh_token = refresh_token
|
||||
self._id_token = grant_response.get("id_token")
|
||||
|
||||
if self._scopes and "scopes" in grant_response:
|
||||
requested_scopes = frozenset(self._scopes)
|
||||
granted_scopes = frozenset(grant_response["scopes"].split())
|
||||
scopes_requested_but_not_granted = requested_scopes - granted_scopes
|
||||
if scopes_requested_but_not_granted:
|
||||
raise exceptions.RefreshError(
|
||||
"Not all requested scopes were granted by the "
|
||||
"authorization server, missing scopes {}.".format(
|
||||
", ".join(scopes_requested_but_not_granted)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class UserAccessTokenCredentials(oauth2_credentials.UserAccessTokenCredentials):
|
||||
"""Access token credentials for user account.
|
||||
|
||||
Obtain the access token for a given user account or the current active
|
||||
user account with the ``gcloud auth print-access-token`` command.
|
||||
|
||||
Args:
|
||||
account (Optional[str]): Account to get the access token for. If not
|
||||
specified, the current active account will be used.
|
||||
quota_project_id (Optional[str]): The project ID used for quota
|
||||
and billing.
|
||||
|
||||
"""
|
267
venv/Lib/site-packages/google/oauth2/_id_token_async.py
Normal file
267
venv/Lib/site-packages/google/oauth2/_id_token_async.py
Normal file
|
@ -0,0 +1,267 @@
|
|||
# Copyright 2020 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Google ID Token helpers.
|
||||
|
||||
Provides support for verifying `OpenID Connect ID Tokens`_, especially ones
|
||||
generated by Google infrastructure.
|
||||
|
||||
To parse and verify an ID Token issued by Google's OAuth 2.0 authorization
|
||||
server use :func:`verify_oauth2_token`. To verify an ID Token issued by
|
||||
Firebase, use :func:`verify_firebase_token`.
|
||||
|
||||
A general purpose ID Token verifier is available as :func:`verify_token`.
|
||||
|
||||
Example::
|
||||
|
||||
from google.oauth2 import _id_token_async
|
||||
from google.auth.transport import aiohttp_requests
|
||||
|
||||
request = aiohttp_requests.Request()
|
||||
|
||||
id_info = await _id_token_async.verify_oauth2_token(
|
||||
token, request, 'my-client-id.example.com')
|
||||
|
||||
if id_info['iss'] != 'https://accounts.google.com':
|
||||
raise ValueError('Wrong issuer.')
|
||||
|
||||
userid = id_info['sub']
|
||||
|
||||
By default, this will re-fetch certificates for each verification. Because
|
||||
Google's public keys are only changed infrequently (on the order of once per
|
||||
day), you may wish to take advantage of caching to reduce latency and the
|
||||
potential for network errors. This can be accomplished using an external
|
||||
library like `CacheControl`_ to create a cache-aware
|
||||
:class:`google.auth.transport.Request`::
|
||||
|
||||
import cachecontrol
|
||||
import google.auth.transport.requests
|
||||
import requests
|
||||
|
||||
session = requests.session()
|
||||
cached_session = cachecontrol.CacheControl(session)
|
||||
request = google.auth.transport.requests.Request(session=cached_session)
|
||||
|
||||
.. _OpenID Connect ID Token:
|
||||
http://openid.net/specs/openid-connect-core-1_0.html#IDToken
|
||||
.. _CacheControl: https://cachecontrol.readthedocs.io
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import six
|
||||
from six.moves import http_client
|
||||
|
||||
from google.auth import environment_vars
|
||||
from google.auth import exceptions
|
||||
from google.auth import jwt
|
||||
from google.auth.transport import requests
|
||||
from google.oauth2 import id_token as sync_id_token
|
||||
|
||||
|
||||
async def _fetch_certs(request, certs_url):
|
||||
"""Fetches certificates.
|
||||
|
||||
Google-style cerificate endpoints return JSON in the format of
|
||||
``{'key id': 'x509 certificate'}``.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): The object used to make
|
||||
HTTP requests. This must be an aiohttp request.
|
||||
certs_url (str): The certificate endpoint URL.
|
||||
|
||||
Returns:
|
||||
Mapping[str, str]: A mapping of public key ID to x.509 certificate
|
||||
data.
|
||||
"""
|
||||
response = await request(certs_url, method="GET")
|
||||
|
||||
if response.status != http_client.OK:
|
||||
raise exceptions.TransportError(
|
||||
"Could not fetch certificates at {}".format(certs_url)
|
||||
)
|
||||
|
||||
data = await response.data.read()
|
||||
|
||||
return json.loads(json.dumps(data))
|
||||
|
||||
|
||||
async def verify_token(
|
||||
id_token, request, audience=None, certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL
|
||||
):
|
||||
"""Verifies an ID token and returns the decoded token.
|
||||
|
||||
Args:
|
||||
id_token (Union[str, bytes]): The encoded token.
|
||||
request (google.auth.transport.Request): The object used to make
|
||||
HTTP requests. This must be an aiohttp request.
|
||||
audience (str): The audience that this token is intended for. If None
|
||||
then the audience is not verified.
|
||||
certs_url (str): The URL that specifies the certificates to use to
|
||||
verify the token. This URL should return JSON in the format of
|
||||
``{'key id': 'x509 certificate'}``.
|
||||
|
||||
Returns:
|
||||
Mapping[str, Any]: The decoded token.
|
||||
"""
|
||||
certs = await _fetch_certs(request, certs_url)
|
||||
|
||||
return jwt.decode(id_token, certs=certs, audience=audience)
|
||||
|
||||
|
||||
async def verify_oauth2_token(id_token, request, audience=None):
|
||||
"""Verifies an ID Token issued by Google's OAuth 2.0 authorization server.
|
||||
|
||||
Args:
|
||||
id_token (Union[str, bytes]): The encoded token.
|
||||
request (google.auth.transport.Request): The object used to make
|
||||
HTTP requests. This must be an aiohttp request.
|
||||
audience (str): The audience that this token is intended for. This is
|
||||
typically your application's OAuth 2.0 client ID. If None then the
|
||||
audience is not verified.
|
||||
|
||||
Returns:
|
||||
Mapping[str, Any]: The decoded token.
|
||||
|
||||
Raises:
|
||||
exceptions.GoogleAuthError: If the issuer is invalid.
|
||||
"""
|
||||
idinfo = await verify_token(
|
||||
id_token,
|
||||
request,
|
||||
audience=audience,
|
||||
certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL,
|
||||
)
|
||||
|
||||
if idinfo["iss"] not in sync_id_token._GOOGLE_ISSUERS:
|
||||
raise exceptions.GoogleAuthError(
|
||||
"Wrong issuer. 'iss' should be one of the following: {}".format(
|
||||
sync_id_token._GOOGLE_ISSUERS
|
||||
)
|
||||
)
|
||||
|
||||
return idinfo
|
||||
|
||||
|
||||
async def verify_firebase_token(id_token, request, audience=None):
|
||||
"""Verifies an ID Token issued by Firebase Authentication.
|
||||
|
||||
Args:
|
||||
id_token (Union[str, bytes]): The encoded token.
|
||||
request (google.auth.transport.Request): The object used to make
|
||||
HTTP requests. This must be an aiohttp request.
|
||||
audience (str): The audience that this token is intended for. This is
|
||||
typically your Firebase application ID. If None then the audience
|
||||
is not verified.
|
||||
|
||||
Returns:
|
||||
Mapping[str, Any]: The decoded token.
|
||||
"""
|
||||
return await verify_token(
|
||||
id_token,
|
||||
request,
|
||||
audience=audience,
|
||||
certs_url=sync_id_token._GOOGLE_APIS_CERTS_URL,
|
||||
)
|
||||
|
||||
|
||||
async def fetch_id_token(request, audience):
|
||||
"""Fetch the ID Token from the current environment.
|
||||
|
||||
This function acquires ID token from the environment in the following order:
|
||||
|
||||
1. If the application is running in Compute Engine, App Engine or Cloud Run,
|
||||
then the ID token are obtained from the metadata server.
|
||||
2. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
|
||||
to the path of a valid service account JSON file, then ID token is
|
||||
acquired using this service account credentials.
|
||||
3. If metadata server doesn't exist and no valid service account credentials
|
||||
are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will
|
||||
be raised.
|
||||
|
||||
Example::
|
||||
|
||||
import google.oauth2._id_token_async
|
||||
import google.auth.transport.aiohttp_requests
|
||||
|
||||
request = google.auth.transport.aiohttp_requests.Request()
|
||||
target_audience = "https://pubsub.googleapis.com"
|
||||
|
||||
id_token = await google.oauth2._id_token_async.fetch_id_token(request, target_audience)
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.aiohttp_requests.Request): A callable used to make
|
||||
HTTP requests.
|
||||
audience (str): The audience that this ID token is intended for.
|
||||
|
||||
Returns:
|
||||
str: The ID token.
|
||||
|
||||
Raises:
|
||||
~google.auth.exceptions.DefaultCredentialsError:
|
||||
If metadata server doesn't exist and no valid service account
|
||||
credentials are found.
|
||||
"""
|
||||
# 1. First try to fetch ID token from metadata server if it exists. The code
|
||||
# works for GAE and Cloud Run metadata server as well.
|
||||
try:
|
||||
from google.auth import compute_engine
|
||||
|
||||
request_new = requests.Request()
|
||||
credentials = compute_engine.IDTokenCredentials(
|
||||
request_new, audience, use_metadata_identity_endpoint=True
|
||||
)
|
||||
credentials.refresh(request_new)
|
||||
|
||||
return credentials.token
|
||||
|
||||
except (ImportError, exceptions.TransportError, exceptions.RefreshError):
|
||||
pass
|
||||
|
||||
# 2. Try to use service account credentials to get ID token.
|
||||
|
||||
# Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
|
||||
# variable.
|
||||
credentials_filename = os.environ.get(environment_vars.CREDENTIALS)
|
||||
if not (
|
||||
credentials_filename
|
||||
and os.path.exists(credentials_filename)
|
||||
and os.path.isfile(credentials_filename)
|
||||
):
|
||||
raise exceptions.DefaultCredentialsError(
|
||||
"Neither metadata server or valid service account credentials are found."
|
||||
)
|
||||
|
||||
try:
|
||||
with open(credentials_filename, "r") as f:
|
||||
info = json.load(f)
|
||||
credentials_content = (
|
||||
(info.get("type") == "service_account") and info or None
|
||||
)
|
||||
|
||||
from google.oauth2 import _service_account_async as service_account
|
||||
|
||||
credentials = service_account.IDTokenCredentials.from_service_account_info(
|
||||
credentials_content, target_audience=audience
|
||||
)
|
||||
except ValueError as caught_exc:
|
||||
new_exc = exceptions.DefaultCredentialsError(
|
||||
"Neither metadata server or valid service account credentials are found.",
|
||||
caught_exc,
|
||||
)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
await credentials.refresh(request)
|
||||
return credentials.token
|
132
venv/Lib/site-packages/google/oauth2/_service_account_async.py
Normal file
132
venv/Lib/site-packages/google/oauth2/_service_account_async.py
Normal file
|
@ -0,0 +1,132 @@
|
|||
# Copyright 2020 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Service Accounts: JSON Web Token (JWT) Profile for OAuth 2.0
|
||||
|
||||
NOTE: This file adds asynchronous refresh methods to both credentials
|
||||
classes, and therefore async/await syntax is required when calling this
|
||||
method when using service account credentials with asynchronous functionality.
|
||||
Otherwise, all other methods are inherited from the regular service account
|
||||
credentials file google.oauth2.service_account
|
||||
|
||||
"""
|
||||
|
||||
from google.auth import _credentials_async as credentials_async
|
||||
from google.auth import _helpers
|
||||
from google.oauth2 import _client_async
|
||||
from google.oauth2 import service_account
|
||||
|
||||
|
||||
class Credentials(
|
||||
service_account.Credentials, credentials_async.Scoped, credentials_async.Credentials
|
||||
):
|
||||
"""Service account credentials
|
||||
|
||||
Usually, you'll create these credentials with one of the helper
|
||||
constructors. To create credentials using a Google service account
|
||||
private key JSON file::
|
||||
|
||||
credentials = _service_account_async.Credentials.from_service_account_file(
|
||||
'service-account.json')
|
||||
|
||||
Or if you already have the service account file loaded::
|
||||
|
||||
service_account_info = json.load(open('service_account.json'))
|
||||
credentials = _service_account_async.Credentials.from_service_account_info(
|
||||
service_account_info)
|
||||
|
||||
Both helper methods pass on arguments to the constructor, so you can
|
||||
specify additional scopes and a subject if necessary::
|
||||
|
||||
credentials = _service_account_async.Credentials.from_service_account_file(
|
||||
'service-account.json',
|
||||
scopes=['email'],
|
||||
subject='user@example.com')
|
||||
|
||||
The credentials are considered immutable. If you want to modify the scopes
|
||||
or the subject used for delegation, use :meth:`with_scopes` or
|
||||
:meth:`with_subject`::
|
||||
|
||||
scoped_credentials = credentials.with_scopes(['email'])
|
||||
delegated_credentials = credentials.with_subject(subject)
|
||||
|
||||
To add a quota project, use :meth:`with_quota_project`::
|
||||
|
||||
credentials = credentials.with_quota_project('myproject-123')
|
||||
"""
|
||||
|
||||
@_helpers.copy_docstring(credentials_async.Credentials)
|
||||
async def refresh(self, request):
|
||||
assertion = self._make_authorization_grant_assertion()
|
||||
access_token, expiry, _ = await _client_async.jwt_grant(
|
||||
request, self._token_uri, assertion
|
||||
)
|
||||
self.token = access_token
|
||||
self.expiry = expiry
|
||||
|
||||
|
||||
class IDTokenCredentials(
|
||||
service_account.IDTokenCredentials,
|
||||
credentials_async.Signing,
|
||||
credentials_async.Credentials,
|
||||
):
|
||||
"""Open ID Connect ID Token-based service account credentials.
|
||||
|
||||
These credentials are largely similar to :class:`.Credentials`, but instead
|
||||
of using an OAuth 2.0 Access Token as the bearer token, they use an Open
|
||||
ID Connect ID Token as the bearer token. These credentials are useful when
|
||||
communicating to services that require ID Tokens and can not accept access
|
||||
tokens.
|
||||
|
||||
Usually, you'll create these credentials with one of the helper
|
||||
constructors. To create credentials using a Google service account
|
||||
private key JSON file::
|
||||
|
||||
credentials = (
|
||||
_service_account_async.IDTokenCredentials.from_service_account_file(
|
||||
'service-account.json'))
|
||||
|
||||
Or if you already have the service account file loaded::
|
||||
|
||||
service_account_info = json.load(open('service_account.json'))
|
||||
credentials = (
|
||||
_service_account_async.IDTokenCredentials.from_service_account_info(
|
||||
service_account_info))
|
||||
|
||||
Both helper methods pass on arguments to the constructor, so you can
|
||||
specify additional scopes and a subject if necessary::
|
||||
|
||||
credentials = (
|
||||
_service_account_async.IDTokenCredentials.from_service_account_file(
|
||||
'service-account.json',
|
||||
scopes=['email'],
|
||||
subject='user@example.com'))
|
||||
`
|
||||
The credentials are considered immutable. If you want to modify the scopes
|
||||
or the subject used for delegation, use :meth:`with_scopes` or
|
||||
:meth:`with_subject`::
|
||||
|
||||
scoped_credentials = credentials.with_scopes(['email'])
|
||||
delegated_credentials = credentials.with_subject(subject)
|
||||
|
||||
"""
|
||||
|
||||
@_helpers.copy_docstring(credentials_async.Credentials)
|
||||
async def refresh(self, request):
|
||||
assertion = self._make_authorization_grant_assertion()
|
||||
access_token, expiry, _ = await _client_async.id_token_jwt_grant(
|
||||
request, self._token_uri, assertion
|
||||
)
|
||||
self.token = access_token
|
||||
self.expiry = expiry
|
383
venv/Lib/site-packages/google/oauth2/credentials.py
Normal file
383
venv/Lib/site-packages/google/oauth2/credentials.py
Normal file
|
@ -0,0 +1,383 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""OAuth 2.0 Credentials.
|
||||
|
||||
This module provides credentials based on OAuth 2.0 access and refresh tokens.
|
||||
These credentials usually access resources on behalf of a user (resource
|
||||
owner).
|
||||
|
||||
Specifically, this is intended to use access tokens acquired using the
|
||||
`Authorization Code grant`_ and can refresh those tokens using a
|
||||
optional `refresh token`_.
|
||||
|
||||
Obtaining the initial access and refresh token is outside of the scope of this
|
||||
module. Consult `rfc6749 section 4.1`_ for complete details on the
|
||||
Authorization Code grant flow.
|
||||
|
||||
.. _Authorization Code grant: https://tools.ietf.org/html/rfc6749#section-1.3.1
|
||||
.. _refresh token: https://tools.ietf.org/html/rfc6749#section-6
|
||||
.. _rfc6749 section 4.1: https://tools.ietf.org/html/rfc6749#section-4.1
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
import io
|
||||
import json
|
||||
|
||||
import six
|
||||
|
||||
from google.auth import _cloud_sdk
|
||||
from google.auth import _helpers
|
||||
from google.auth import credentials
|
||||
from google.auth import exceptions
|
||||
from google.oauth2 import _client
|
||||
|
||||
|
||||
# The Google OAuth 2.0 token endpoint. Used for authorized user credentials.
|
||||
_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token"
|
||||
|
||||
|
||||
class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaProject):
|
||||
"""Credentials using OAuth 2.0 access and refresh tokens.
|
||||
|
||||
The credentials are considered immutable. If you want to modify the
|
||||
quota project, use :meth:`with_quota_project` or ::
|
||||
|
||||
credentials = credentials.with_quota_project('myproject-123)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
token,
|
||||
refresh_token=None,
|
||||
id_token=None,
|
||||
token_uri=None,
|
||||
client_id=None,
|
||||
client_secret=None,
|
||||
scopes=None,
|
||||
quota_project_id=None,
|
||||
expiry=None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
token (Optional(str)): The OAuth 2.0 access token. Can be None
|
||||
if refresh information is provided.
|
||||
refresh_token (str): The OAuth 2.0 refresh token. If specified,
|
||||
credentials can be refreshed.
|
||||
id_token (str): The Open ID Connect ID Token.
|
||||
token_uri (str): The OAuth 2.0 authorization server's token
|
||||
endpoint URI. Must be specified for refresh, can be left as
|
||||
None if the token can not be refreshed.
|
||||
client_id (str): The OAuth 2.0 client ID. Must be specified for
|
||||
refresh, can be left as None if the token can not be refreshed.
|
||||
client_secret(str): The OAuth 2.0 client secret. Must be specified
|
||||
for refresh, can be left as None if the token can not be
|
||||
refreshed.
|
||||
scopes (Sequence[str]): The scopes used to obtain authorization.
|
||||
This parameter is used by :meth:`has_scopes`. OAuth 2.0
|
||||
credentials can not request additional scopes after
|
||||
authorization. The scopes must be derivable from the refresh
|
||||
token if refresh information is provided (e.g. The refresh
|
||||
token scopes are a superset of this or contain a wild card
|
||||
scope like 'https://www.googleapis.com/auth/any-api').
|
||||
quota_project_id (Optional[str]): The project ID used for quota and billing.
|
||||
This project may be different from the project used to
|
||||
create the credentials.
|
||||
"""
|
||||
super(Credentials, self).__init__()
|
||||
self.token = token
|
||||
self.expiry = expiry
|
||||
self._refresh_token = refresh_token
|
||||
self._id_token = id_token
|
||||
self._scopes = scopes
|
||||
self._token_uri = token_uri
|
||||
self._client_id = client_id
|
||||
self._client_secret = client_secret
|
||||
self._quota_project_id = quota_project_id
|
||||
|
||||
def __getstate__(self):
|
||||
"""A __getstate__ method must exist for the __setstate__ to be called
|
||||
This is identical to the default implementation.
|
||||
See https://docs.python.org/3.7/library/pickle.html#object.__setstate__
|
||||
"""
|
||||
return self.__dict__
|
||||
|
||||
def __setstate__(self, d):
|
||||
"""Credentials pickled with older versions of the class do not have
|
||||
all the attributes."""
|
||||
self.token = d.get("token")
|
||||
self.expiry = d.get("expiry")
|
||||
self._refresh_token = d.get("_refresh_token")
|
||||
self._id_token = d.get("_id_token")
|
||||
self._scopes = d.get("_scopes")
|
||||
self._token_uri = d.get("_token_uri")
|
||||
self._client_id = d.get("_client_id")
|
||||
self._client_secret = d.get("_client_secret")
|
||||
self._quota_project_id = d.get("_quota_project_id")
|
||||
|
||||
@property
|
||||
def refresh_token(self):
|
||||
"""Optional[str]: The OAuth 2.0 refresh token."""
|
||||
return self._refresh_token
|
||||
|
||||
@property
|
||||
def scopes(self):
|
||||
"""Optional[str]: The OAuth 2.0 permission scopes."""
|
||||
return self._scopes
|
||||
|
||||
@property
|
||||
def token_uri(self):
|
||||
"""Optional[str]: The OAuth 2.0 authorization server's token endpoint
|
||||
URI."""
|
||||
return self._token_uri
|
||||
|
||||
@property
|
||||
def id_token(self):
|
||||
"""Optional[str]: The Open ID Connect ID Token.
|
||||
|
||||
Depending on the authorization server and the scopes requested, this
|
||||
may be populated when credentials are obtained and updated when
|
||||
:meth:`refresh` is called. This token is a JWT. It can be verified
|
||||
and decoded using :func:`google.oauth2.id_token.verify_oauth2_token`.
|
||||
"""
|
||||
return self._id_token
|
||||
|
||||
@property
|
||||
def client_id(self):
|
||||
"""Optional[str]: The OAuth 2.0 client ID."""
|
||||
return self._client_id
|
||||
|
||||
@property
|
||||
def client_secret(self):
|
||||
"""Optional[str]: The OAuth 2.0 client secret."""
|
||||
return self._client_secret
|
||||
|
||||
@property
|
||||
def requires_scopes(self):
|
||||
"""False: OAuth 2.0 credentials have their scopes set when
|
||||
the initial token is requested and can not be changed."""
|
||||
return False
|
||||
|
||||
@_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
|
||||
def with_quota_project(self, quota_project_id):
|
||||
|
||||
return self.__class__(
|
||||
self.token,
|
||||
refresh_token=self.refresh_token,
|
||||
id_token=self.id_token,
|
||||
token_uri=self.token_uri,
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
scopes=self.scopes,
|
||||
quota_project_id=quota_project_id,
|
||||
)
|
||||
|
||||
@_helpers.copy_docstring(credentials.Credentials)
|
||||
def refresh(self, request):
|
||||
if (
|
||||
self._refresh_token is None
|
||||
or self._token_uri is None
|
||||
or self._client_id is None
|
||||
or self._client_secret is None
|
||||
):
|
||||
raise exceptions.RefreshError(
|
||||
"The credentials do not contain the necessary fields need to "
|
||||
"refresh the access token. You must specify refresh_token, "
|
||||
"token_uri, client_id, and client_secret."
|
||||
)
|
||||
|
||||
access_token, refresh_token, expiry, grant_response = _client.refresh_grant(
|
||||
request,
|
||||
self._token_uri,
|
||||
self._refresh_token,
|
||||
self._client_id,
|
||||
self._client_secret,
|
||||
self._scopes,
|
||||
)
|
||||
|
||||
self.token = access_token
|
||||
self.expiry = expiry
|
||||
self._refresh_token = refresh_token
|
||||
self._id_token = grant_response.get("id_token")
|
||||
|
||||
if self._scopes and "scopes" in grant_response:
|
||||
requested_scopes = frozenset(self._scopes)
|
||||
granted_scopes = frozenset(grant_response["scopes"].split())
|
||||
scopes_requested_but_not_granted = requested_scopes - granted_scopes
|
||||
if scopes_requested_but_not_granted:
|
||||
raise exceptions.RefreshError(
|
||||
"Not all requested scopes were granted by the "
|
||||
"authorization server, missing scopes {}.".format(
|
||||
", ".join(scopes_requested_but_not_granted)
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_authorized_user_info(cls, info, scopes=None):
|
||||
"""Creates a Credentials instance from parsed authorized user info.
|
||||
|
||||
Args:
|
||||
info (Mapping[str, str]): The authorized user info in Google
|
||||
format.
|
||||
scopes (Sequence[str]): Optional list of scopes to include in the
|
||||
credentials.
|
||||
|
||||
Returns:
|
||||
google.oauth2.credentials.Credentials: The constructed
|
||||
credentials.
|
||||
|
||||
Raises:
|
||||
ValueError: If the info is not in the expected format.
|
||||
"""
|
||||
keys_needed = set(("refresh_token", "client_id", "client_secret"))
|
||||
missing = keys_needed.difference(six.iterkeys(info))
|
||||
|
||||
if missing:
|
||||
raise ValueError(
|
||||
"Authorized user info was not in the expected format, missing "
|
||||
"fields {}.".format(", ".join(missing))
|
||||
)
|
||||
|
||||
# access token expiry (datetime obj); auto-expire if not saved
|
||||
expiry = info.get("expiry")
|
||||
if expiry:
|
||||
expiry = datetime.strptime(
|
||||
expiry.rstrip("Z").split(".")[0], "%Y-%m-%dT%H:%M:%S"
|
||||
)
|
||||
else:
|
||||
expiry = _helpers.utcnow() - _helpers.CLOCK_SKEW
|
||||
|
||||
# process scopes, which needs to be a seq
|
||||
if scopes is None and "scopes" in info:
|
||||
scopes = info.get("scopes")
|
||||
if isinstance(scopes, str):
|
||||
scopes = scopes.split(" ")
|
||||
|
||||
return cls(
|
||||
token=info.get("token"),
|
||||
refresh_token=info.get("refresh_token"),
|
||||
token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT, # always overrides
|
||||
scopes=scopes,
|
||||
client_id=info.get("client_id"),
|
||||
client_secret=info.get("client_secret"),
|
||||
quota_project_id=info.get("quota_project_id"), # may not exist
|
||||
expiry=expiry,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_authorized_user_file(cls, filename, scopes=None):
|
||||
"""Creates a Credentials instance from an authorized user json file.
|
||||
|
||||
Args:
|
||||
filename (str): The path to the authorized user json file.
|
||||
scopes (Sequence[str]): Optional list of scopes to include in the
|
||||
credentials.
|
||||
|
||||
Returns:
|
||||
google.oauth2.credentials.Credentials: The constructed
|
||||
credentials.
|
||||
|
||||
Raises:
|
||||
ValueError: If the file is not in the expected format.
|
||||
"""
|
||||
with io.open(filename, "r", encoding="utf-8") as json_file:
|
||||
data = json.load(json_file)
|
||||
return cls.from_authorized_user_info(data, scopes)
|
||||
|
||||
def to_json(self, strip=None):
|
||||
"""Utility function that creates a JSON representation of a Credentials
|
||||
object.
|
||||
|
||||
Args:
|
||||
strip (Sequence[str]): Optional list of members to exclude from the
|
||||
generated JSON.
|
||||
|
||||
Returns:
|
||||
str: A JSON representation of this instance. When converted into
|
||||
a dictionary, it can be passed to from_authorized_user_info()
|
||||
to create a new credential instance.
|
||||
"""
|
||||
prep = {
|
||||
"token": self.token,
|
||||
"refresh_token": self.refresh_token,
|
||||
"token_uri": self.token_uri,
|
||||
"client_id": self.client_id,
|
||||
"client_secret": self.client_secret,
|
||||
"scopes": self.scopes,
|
||||
}
|
||||
if self.expiry: # flatten expiry timestamp
|
||||
prep["expiry"] = self.expiry.isoformat() + "Z"
|
||||
|
||||
# Remove empty entries (those which are None)
|
||||
prep = {k: v for k, v in prep.items() if v is not None}
|
||||
|
||||
# Remove entries that explicitely need to be removed
|
||||
if strip is not None:
|
||||
prep = {k: v for k, v in prep.items() if k not in strip}
|
||||
|
||||
return json.dumps(prep)
|
||||
|
||||
|
||||
class UserAccessTokenCredentials(credentials.CredentialsWithQuotaProject):
|
||||
"""Access token credentials for user account.
|
||||
|
||||
Obtain the access token for a given user account or the current active
|
||||
user account with the ``gcloud auth print-access-token`` command.
|
||||
|
||||
Args:
|
||||
account (Optional[str]): Account to get the access token for. If not
|
||||
specified, the current active account will be used.
|
||||
quota_project_id (Optional[str]): The project ID used for quota
|
||||
and billing.
|
||||
"""
|
||||
|
||||
def __init__(self, account=None, quota_project_id=None):
|
||||
super(UserAccessTokenCredentials, self).__init__()
|
||||
self._account = account
|
||||
self._quota_project_id = quota_project_id
|
||||
|
||||
def with_account(self, account):
|
||||
"""Create a new instance with the given account.
|
||||
|
||||
Args:
|
||||
account (str): Account to get the access token for.
|
||||
|
||||
Returns:
|
||||
google.oauth2.credentials.UserAccessTokenCredentials: The created
|
||||
credentials with the given account.
|
||||
"""
|
||||
return self.__class__(account=account, quota_project_id=self._quota_project_id)
|
||||
|
||||
@_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
|
||||
def with_quota_project(self, quota_project_id):
|
||||
return self.__class__(account=self._account, quota_project_id=quota_project_id)
|
||||
|
||||
def refresh(self, request):
|
||||
"""Refreshes the access token.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): This argument is required
|
||||
by the base class interface but not used in this implementation,
|
||||
so just set it to `None`.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.UserAccessTokenError: If the access token
|
||||
refresh failed.
|
||||
"""
|
||||
self.token = _cloud_sdk.get_auth_access_token(self._account)
|
||||
|
||||
@_helpers.copy_docstring(credentials.Credentials)
|
||||
def before_request(self, request, method, url, headers):
|
||||
self.refresh(request)
|
||||
self.apply(headers)
|
266
venv/Lib/site-packages/google/oauth2/id_token.py
Normal file
266
venv/Lib/site-packages/google/oauth2/id_token.py
Normal file
|
@ -0,0 +1,266 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Google ID Token helpers.
|
||||
|
||||
Provides support for verifying `OpenID Connect ID Tokens`_, especially ones
|
||||
generated by Google infrastructure.
|
||||
|
||||
To parse and verify an ID Token issued by Google's OAuth 2.0 authorization
|
||||
server use :func:`verify_oauth2_token`. To verify an ID Token issued by
|
||||
Firebase, use :func:`verify_firebase_token`.
|
||||
|
||||
A general purpose ID Token verifier is available as :func:`verify_token`.
|
||||
|
||||
Example::
|
||||
|
||||
from google.oauth2 import id_token
|
||||
from google.auth.transport import requests
|
||||
|
||||
request = requests.Request()
|
||||
|
||||
id_info = id_token.verify_oauth2_token(
|
||||
token, request, 'my-client-id.example.com')
|
||||
|
||||
if id_info['iss'] != 'https://accounts.google.com':
|
||||
raise ValueError('Wrong issuer.')
|
||||
|
||||
userid = id_info['sub']
|
||||
|
||||
By default, this will re-fetch certificates for each verification. Because
|
||||
Google's public keys are only changed infrequently (on the order of once per
|
||||
day), you may wish to take advantage of caching to reduce latency and the
|
||||
potential for network errors. This can be accomplished using an external
|
||||
library like `CacheControl`_ to create a cache-aware
|
||||
:class:`google.auth.transport.Request`::
|
||||
|
||||
import cachecontrol
|
||||
import google.auth.transport.requests
|
||||
import requests
|
||||
|
||||
session = requests.session()
|
||||
cached_session = cachecontrol.CacheControl(session)
|
||||
request = google.auth.transport.requests.Request(session=cached_session)
|
||||
|
||||
.. _OpenID Connect ID Token:
|
||||
http://openid.net/specs/openid-connect-core-1_0.html#IDToken
|
||||
.. _CacheControl: https://cachecontrol.readthedocs.io
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import six
|
||||
from six.moves import http_client
|
||||
|
||||
from google.auth import environment_vars
|
||||
from google.auth import exceptions
|
||||
from google.auth import jwt
|
||||
|
||||
|
||||
# The URL that provides public certificates for verifying ID tokens issued
|
||||
# by Google's OAuth 2.0 authorization server.
|
||||
_GOOGLE_OAUTH2_CERTS_URL = "https://www.googleapis.com/oauth2/v1/certs"
|
||||
|
||||
# The URL that provides public certificates for verifying ID tokens issued
|
||||
# by Firebase and the Google APIs infrastructure
|
||||
_GOOGLE_APIS_CERTS_URL = (
|
||||
"https://www.googleapis.com/robot/v1/metadata/x509"
|
||||
"/securetoken@system.gserviceaccount.com"
|
||||
)
|
||||
|
||||
_GOOGLE_ISSUERS = ["accounts.google.com", "https://accounts.google.com"]
|
||||
|
||||
|
||||
def _fetch_certs(request, certs_url):
|
||||
"""Fetches certificates.
|
||||
|
||||
Google-style cerificate endpoints return JSON in the format of
|
||||
``{'key id': 'x509 certificate'}``.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): The object used to make
|
||||
HTTP requests.
|
||||
certs_url (str): The certificate endpoint URL.
|
||||
|
||||
Returns:
|
||||
Mapping[str, str]: A mapping of public key ID to x.509 certificate
|
||||
data.
|
||||
"""
|
||||
response = request(certs_url, method="GET")
|
||||
|
||||
if response.status != http_client.OK:
|
||||
raise exceptions.TransportError(
|
||||
"Could not fetch certificates at {}".format(certs_url)
|
||||
)
|
||||
|
||||
return json.loads(response.data.decode("utf-8"))
|
||||
|
||||
|
||||
def verify_token(id_token, request, audience=None, certs_url=_GOOGLE_OAUTH2_CERTS_URL):
|
||||
"""Verifies an ID token and returns the decoded token.
|
||||
|
||||
Args:
|
||||
id_token (Union[str, bytes]): The encoded token.
|
||||
request (google.auth.transport.Request): The object used to make
|
||||
HTTP requests.
|
||||
audience (str): The audience that this token is intended for. If None
|
||||
then the audience is not verified.
|
||||
certs_url (str): The URL that specifies the certificates to use to
|
||||
verify the token. This URL should return JSON in the format of
|
||||
``{'key id': 'x509 certificate'}``.
|
||||
|
||||
Returns:
|
||||
Mapping[str, Any]: The decoded token.
|
||||
"""
|
||||
certs = _fetch_certs(request, certs_url)
|
||||
|
||||
return jwt.decode(id_token, certs=certs, audience=audience)
|
||||
|
||||
|
||||
def verify_oauth2_token(id_token, request, audience=None):
|
||||
"""Verifies an ID Token issued by Google's OAuth 2.0 authorization server.
|
||||
|
||||
Args:
|
||||
id_token (Union[str, bytes]): The encoded token.
|
||||
request (google.auth.transport.Request): The object used to make
|
||||
HTTP requests.
|
||||
audience (str): The audience that this token is intended for. This is
|
||||
typically your application's OAuth 2.0 client ID. If None then the
|
||||
audience is not verified.
|
||||
|
||||
Returns:
|
||||
Mapping[str, Any]: The decoded token.
|
||||
|
||||
Raises:
|
||||
exceptions.GoogleAuthError: If the issuer is invalid.
|
||||
"""
|
||||
idinfo = verify_token(
|
||||
id_token, request, audience=audience, certs_url=_GOOGLE_OAUTH2_CERTS_URL
|
||||
)
|
||||
|
||||
if idinfo["iss"] not in _GOOGLE_ISSUERS:
|
||||
raise exceptions.GoogleAuthError(
|
||||
"Wrong issuer. 'iss' should be one of the following: {}".format(
|
||||
_GOOGLE_ISSUERS
|
||||
)
|
||||
)
|
||||
|
||||
return idinfo
|
||||
|
||||
|
||||
def verify_firebase_token(id_token, request, audience=None):
|
||||
"""Verifies an ID Token issued by Firebase Authentication.
|
||||
|
||||
Args:
|
||||
id_token (Union[str, bytes]): The encoded token.
|
||||
request (google.auth.transport.Request): The object used to make
|
||||
HTTP requests.
|
||||
audience (str): The audience that this token is intended for. This is
|
||||
typically your Firebase application ID. If None then the audience
|
||||
is not verified.
|
||||
|
||||
Returns:
|
||||
Mapping[str, Any]: The decoded token.
|
||||
"""
|
||||
return verify_token(
|
||||
id_token, request, audience=audience, certs_url=_GOOGLE_APIS_CERTS_URL
|
||||
)
|
||||
|
||||
|
||||
def fetch_id_token(request, audience):
|
||||
"""Fetch the ID Token from the current environment.
|
||||
|
||||
This function acquires ID token from the environment in the following order:
|
||||
|
||||
1. If the application is running in Compute Engine, App Engine or Cloud Run,
|
||||
then the ID token are obtained from the metadata server.
|
||||
2. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
|
||||
to the path of a valid service account JSON file, then ID token is
|
||||
acquired using this service account credentials.
|
||||
3. If metadata server doesn't exist and no valid service account credentials
|
||||
are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will
|
||||
be raised.
|
||||
|
||||
Example::
|
||||
|
||||
import google.oauth2.id_token
|
||||
import google.auth.transport.requests
|
||||
|
||||
request = google.auth.transport.requests.Request()
|
||||
target_audience = "https://pubsub.googleapis.com"
|
||||
|
||||
id_token = google.oauth2.id_token.fetch_id_token(request, target_audience)
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): A callable used to make
|
||||
HTTP requests.
|
||||
audience (str): The audience that this ID token is intended for.
|
||||
|
||||
Returns:
|
||||
str: The ID token.
|
||||
|
||||
Raises:
|
||||
~google.auth.exceptions.DefaultCredentialsError:
|
||||
If metadata server doesn't exist and no valid service account
|
||||
credentials are found.
|
||||
"""
|
||||
# 1. First try to fetch ID token from metada server if it exists. The code
|
||||
# works for GAE and Cloud Run metadata server as well.
|
||||
try:
|
||||
from google.auth import compute_engine
|
||||
|
||||
credentials = compute_engine.IDTokenCredentials(
|
||||
request, audience, use_metadata_identity_endpoint=True
|
||||
)
|
||||
credentials.refresh(request)
|
||||
return credentials.token
|
||||
except (ImportError, exceptions.TransportError, exceptions.RefreshError):
|
||||
pass
|
||||
|
||||
# 2. Try to use service account credentials to get ID token.
|
||||
|
||||
# Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
|
||||
# variable.
|
||||
credentials_filename = os.environ.get(environment_vars.CREDENTIALS)
|
||||
if not (
|
||||
credentials_filename
|
||||
and os.path.exists(credentials_filename)
|
||||
and os.path.isfile(credentials_filename)
|
||||
):
|
||||
raise exceptions.DefaultCredentialsError(
|
||||
"Neither metadata server or valid service account credentials are found."
|
||||
)
|
||||
|
||||
try:
|
||||
with open(credentials_filename, "r") as f:
|
||||
info = json.load(f)
|
||||
credentials_content = (
|
||||
(info.get("type") == "service_account") and info or None
|
||||
)
|
||||
|
||||
from google.oauth2 import service_account
|
||||
|
||||
credentials = service_account.IDTokenCredentials.from_service_account_info(
|
||||
credentials_content, target_audience=audience
|
||||
)
|
||||
except ValueError as caught_exc:
|
||||
new_exc = exceptions.DefaultCredentialsError(
|
||||
"Neither metadata server or valid service account credentials are found.",
|
||||
caught_exc,
|
||||
)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
credentials.refresh(request)
|
||||
return credentials.token
|
606
venv/Lib/site-packages/google/oauth2/service_account.py
Normal file
606
venv/Lib/site-packages/google/oauth2/service_account.py
Normal file
|
@ -0,0 +1,606 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Service Accounts: JSON Web Token (JWT) Profile for OAuth 2.0
|
||||
|
||||
This module implements the JWT Profile for OAuth 2.0 Authorization Grants
|
||||
as defined by `RFC 7523`_ with particular support for how this RFC is
|
||||
implemented in Google's infrastructure. Google refers to these credentials
|
||||
as *Service Accounts*.
|
||||
|
||||
Service accounts are used for server-to-server communication, such as
|
||||
interactions between a web application server and a Google service. The
|
||||
service account belongs to your application instead of to an individual end
|
||||
user. In contrast to other OAuth 2.0 profiles, no users are involved and your
|
||||
application "acts" as the service account.
|
||||
|
||||
Typically an application uses a service account when the application uses
|
||||
Google APIs to work with its own data rather than a user's data. For example,
|
||||
an application that uses Google Cloud Datastore for data persistence would use
|
||||
a service account to authenticate its calls to the Google Cloud Datastore API.
|
||||
However, an application that needs to access a user's Drive documents would
|
||||
use the normal OAuth 2.0 profile.
|
||||
|
||||
Additionally, Google Apps domain administrators can grant service accounts
|
||||
`domain-wide delegation`_ authority to access user data on behalf of users in
|
||||
the domain.
|
||||
|
||||
This profile uses a JWT to acquire an OAuth 2.0 access token. The JWT is used
|
||||
in place of the usual authorization token returned during the standard
|
||||
OAuth 2.0 Authorization Code grant. The JWT is only used for this purpose, as
|
||||
the acquired access token is used as the bearer token when making requests
|
||||
using these credentials.
|
||||
|
||||
This profile differs from normal OAuth 2.0 profile because no user consent
|
||||
step is required. The use of the private key allows this profile to assert
|
||||
identity directly.
|
||||
|
||||
This profile also differs from the :mod:`google.auth.jwt` authentication
|
||||
because the JWT credentials use the JWT directly as the bearer token. This
|
||||
profile instead only uses the JWT to obtain an OAuth 2.0 access token. The
|
||||
obtained OAuth 2.0 access token is used as the bearer token.
|
||||
|
||||
Domain-wide delegation
|
||||
----------------------
|
||||
|
||||
Domain-wide delegation allows a service account to access user data on
|
||||
behalf of any user in a Google Apps domain without consent from the user.
|
||||
For example, an application that uses the Google Calendar API to add events to
|
||||
the calendars of all users in a Google Apps domain would use a service account
|
||||
to access the Google Calendar API on behalf of users.
|
||||
|
||||
The Google Apps administrator must explicitly authorize the service account to
|
||||
do this. This authorization step is referred to as "delegating domain-wide
|
||||
authority" to a service account.
|
||||
|
||||
You can use domain-wise delegation by creating a set of credentials with a
|
||||
specific subject using :meth:`~Credentials.with_subject`.
|
||||
|
||||
.. _RFC 7523: https://tools.ietf.org/html/rfc7523
|
||||
"""
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
|
||||
from google.auth import _helpers
|
||||
from google.auth import _service_account_info
|
||||
from google.auth import credentials
|
||||
from google.auth import jwt
|
||||
from google.oauth2 import _client
|
||||
|
||||
_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
|
||||
|
||||
|
||||
class Credentials(
|
||||
credentials.Signing, credentials.Scoped, credentials.CredentialsWithQuotaProject
|
||||
):
|
||||
"""Service account credentials
|
||||
|
||||
Usually, you'll create these credentials with one of the helper
|
||||
constructors. To create credentials using a Google service account
|
||||
private key JSON file::
|
||||
|
||||
credentials = service_account.Credentials.from_service_account_file(
|
||||
'service-account.json')
|
||||
|
||||
Or if you already have the service account file loaded::
|
||||
|
||||
service_account_info = json.load(open('service_account.json'))
|
||||
credentials = service_account.Credentials.from_service_account_info(
|
||||
service_account_info)
|
||||
|
||||
Both helper methods pass on arguments to the constructor, so you can
|
||||
specify additional scopes and a subject if necessary::
|
||||
|
||||
credentials = service_account.Credentials.from_service_account_file(
|
||||
'service-account.json',
|
||||
scopes=['email'],
|
||||
subject='user@example.com')
|
||||
|
||||
The credentials are considered immutable. If you want to modify the scopes
|
||||
or the subject used for delegation, use :meth:`with_scopes` or
|
||||
:meth:`with_subject`::
|
||||
|
||||
scoped_credentials = credentials.with_scopes(['email'])
|
||||
delegated_credentials = credentials.with_subject(subject)
|
||||
|
||||
To add a quota project, use :meth:`with_quota_project`::
|
||||
|
||||
credentials = credentials.with_quota_project('myproject-123')
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
signer,
|
||||
service_account_email,
|
||||
token_uri,
|
||||
scopes=None,
|
||||
subject=None,
|
||||
project_id=None,
|
||||
quota_project_id=None,
|
||||
additional_claims=None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
signer (google.auth.crypt.Signer): The signer used to sign JWTs.
|
||||
service_account_email (str): The service account's email.
|
||||
scopes (Sequence[str]): Scopes to request during the authorization
|
||||
grant.
|
||||
token_uri (str): The OAuth 2.0 Token URI.
|
||||
subject (str): For domain-wide delegation, the email address of the
|
||||
user to for which to request delegated access.
|
||||
project_id (str): Project ID associated with the service account
|
||||
credential.
|
||||
quota_project_id (Optional[str]): The project ID used for quota and
|
||||
billing.
|
||||
additional_claims (Mapping[str, str]): Any additional claims for
|
||||
the JWT assertion used in the authorization grant.
|
||||
|
||||
.. note:: Typically one of the helper constructors
|
||||
:meth:`from_service_account_file` or
|
||||
:meth:`from_service_account_info` are used instead of calling the
|
||||
constructor directly.
|
||||
"""
|
||||
super(Credentials, self).__init__()
|
||||
|
||||
self._scopes = scopes
|
||||
self._signer = signer
|
||||
self._service_account_email = service_account_email
|
||||
self._subject = subject
|
||||
self._project_id = project_id
|
||||
self._quota_project_id = quota_project_id
|
||||
self._token_uri = token_uri
|
||||
|
||||
if additional_claims is not None:
|
||||
self._additional_claims = additional_claims
|
||||
else:
|
||||
self._additional_claims = {}
|
||||
|
||||
@classmethod
|
||||
def _from_signer_and_info(cls, signer, info, **kwargs):
|
||||
"""Creates a Credentials instance from a signer and service account
|
||||
info.
|
||||
|
||||
Args:
|
||||
signer (google.auth.crypt.Signer): The signer used to sign JWTs.
|
||||
info (Mapping[str, str]): The service account info.
|
||||
kwargs: Additional arguments to pass to the constructor.
|
||||
|
||||
Returns:
|
||||
google.auth.jwt.Credentials: The constructed credentials.
|
||||
|
||||
Raises:
|
||||
ValueError: If the info is not in the expected format.
|
||||
"""
|
||||
return cls(
|
||||
signer,
|
||||
service_account_email=info["client_email"],
|
||||
token_uri=info["token_uri"],
|
||||
project_id=info.get("project_id"),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_service_account_info(cls, info, **kwargs):
|
||||
"""Creates a Credentials instance from parsed service account info.
|
||||
|
||||
Args:
|
||||
info (Mapping[str, str]): The service account info in Google
|
||||
format.
|
||||
kwargs: Additional arguments to pass to the constructor.
|
||||
|
||||
Returns:
|
||||
google.auth.service_account.Credentials: The constructed
|
||||
credentials.
|
||||
|
||||
Raises:
|
||||
ValueError: If the info is not in the expected format.
|
||||
"""
|
||||
signer = _service_account_info.from_dict(
|
||||
info, require=["client_email", "token_uri"]
|
||||
)
|
||||
return cls._from_signer_and_info(signer, info, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_service_account_file(cls, filename, **kwargs):
|
||||
"""Creates a Credentials instance from a service account json file.
|
||||
|
||||
Args:
|
||||
filename (str): The path to the service account json file.
|
||||
kwargs: Additional arguments to pass to the constructor.
|
||||
|
||||
Returns:
|
||||
google.auth.service_account.Credentials: The constructed
|
||||
credentials.
|
||||
"""
|
||||
info, signer = _service_account_info.from_filename(
|
||||
filename, require=["client_email", "token_uri"]
|
||||
)
|
||||
return cls._from_signer_and_info(signer, info, **kwargs)
|
||||
|
||||
@property
|
||||
def service_account_email(self):
|
||||
"""The service account email."""
|
||||
return self._service_account_email
|
||||
|
||||
@property
|
||||
def project_id(self):
|
||||
"""Project ID associated with this credential."""
|
||||
return self._project_id
|
||||
|
||||
@property
|
||||
def requires_scopes(self):
|
||||
"""Checks if the credentials requires scopes.
|
||||
|
||||
Returns:
|
||||
bool: True if there are no scopes set otherwise False.
|
||||
"""
|
||||
return True if not self._scopes else False
|
||||
|
||||
@_helpers.copy_docstring(credentials.Scoped)
|
||||
def with_scopes(self, scopes):
|
||||
return self.__class__(
|
||||
self._signer,
|
||||
service_account_email=self._service_account_email,
|
||||
scopes=scopes,
|
||||
token_uri=self._token_uri,
|
||||
subject=self._subject,
|
||||
project_id=self._project_id,
|
||||
quota_project_id=self._quota_project_id,
|
||||
additional_claims=self._additional_claims.copy(),
|
||||
)
|
||||
|
||||
def with_subject(self, subject):
|
||||
"""Create a copy of these credentials with the specified subject.
|
||||
|
||||
Args:
|
||||
subject (str): The subject claim.
|
||||
|
||||
Returns:
|
||||
google.auth.service_account.Credentials: A new credentials
|
||||
instance.
|
||||
"""
|
||||
return self.__class__(
|
||||
self._signer,
|
||||
service_account_email=self._service_account_email,
|
||||
scopes=self._scopes,
|
||||
token_uri=self._token_uri,
|
||||
subject=subject,
|
||||
project_id=self._project_id,
|
||||
quota_project_id=self._quota_project_id,
|
||||
additional_claims=self._additional_claims.copy(),
|
||||
)
|
||||
|
||||
def with_claims(self, additional_claims):
|
||||
"""Returns a copy of these credentials with modified claims.
|
||||
|
||||
Args:
|
||||
additional_claims (Mapping[str, str]): Any additional claims for
|
||||
the JWT payload. This will be merged with the current
|
||||
additional claims.
|
||||
|
||||
Returns:
|
||||
google.auth.service_account.Credentials: A new credentials
|
||||
instance.
|
||||
"""
|
||||
new_additional_claims = copy.deepcopy(self._additional_claims)
|
||||
new_additional_claims.update(additional_claims or {})
|
||||
|
||||
return self.__class__(
|
||||
self._signer,
|
||||
service_account_email=self._service_account_email,
|
||||
scopes=self._scopes,
|
||||
token_uri=self._token_uri,
|
||||
subject=self._subject,
|
||||
project_id=self._project_id,
|
||||
quota_project_id=self._quota_project_id,
|
||||
additional_claims=new_additional_claims,
|
||||
)
|
||||
|
||||
@_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
|
||||
def with_quota_project(self, quota_project_id):
|
||||
|
||||
return self.__class__(
|
||||
self._signer,
|
||||
service_account_email=self._service_account_email,
|
||||
scopes=self._scopes,
|
||||
token_uri=self._token_uri,
|
||||
subject=self._subject,
|
||||
project_id=self._project_id,
|
||||
quota_project_id=quota_project_id,
|
||||
additional_claims=self._additional_claims.copy(),
|
||||
)
|
||||
|
||||
def _make_authorization_grant_assertion(self):
|
||||
"""Create the OAuth 2.0 assertion.
|
||||
|
||||
This assertion is used during the OAuth 2.0 grant to acquire an
|
||||
access token.
|
||||
|
||||
Returns:
|
||||
bytes: The authorization grant assertion.
|
||||
"""
|
||||
now = _helpers.utcnow()
|
||||
lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
|
||||
expiry = now + lifetime
|
||||
|
||||
payload = {
|
||||
"iat": _helpers.datetime_to_secs(now),
|
||||
"exp": _helpers.datetime_to_secs(expiry),
|
||||
# The issuer must be the service account email.
|
||||
"iss": self._service_account_email,
|
||||
# The audience must be the auth token endpoint's URI
|
||||
"aud": self._token_uri,
|
||||
"scope": _helpers.scopes_to_string(self._scopes or ()),
|
||||
}
|
||||
|
||||
payload.update(self._additional_claims)
|
||||
|
||||
# The subject can be a user email for domain-wide delegation.
|
||||
if self._subject:
|
||||
payload.setdefault("sub", self._subject)
|
||||
|
||||
token = jwt.encode(self._signer, payload)
|
||||
|
||||
return token
|
||||
|
||||
@_helpers.copy_docstring(credentials.Credentials)
|
||||
def refresh(self, request):
|
||||
assertion = self._make_authorization_grant_assertion()
|
||||
access_token, expiry, _ = _client.jwt_grant(request, self._token_uri, assertion)
|
||||
self.token = access_token
|
||||
self.expiry = expiry
|
||||
|
||||
@_helpers.copy_docstring(credentials.Signing)
|
||||
def sign_bytes(self, message):
|
||||
return self._signer.sign(message)
|
||||
|
||||
@property
|
||||
@_helpers.copy_docstring(credentials.Signing)
|
||||
def signer(self):
|
||||
return self._signer
|
||||
|
||||
@property
|
||||
@_helpers.copy_docstring(credentials.Signing)
|
||||
def signer_email(self):
|
||||
return self._service_account_email
|
||||
|
||||
|
||||
class IDTokenCredentials(credentials.Signing, credentials.CredentialsWithQuotaProject):
|
||||
"""Open ID Connect ID Token-based service account credentials.
|
||||
|
||||
These credentials are largely similar to :class:`.Credentials`, but instead
|
||||
of using an OAuth 2.0 Access Token as the bearer token, they use an Open
|
||||
ID Connect ID Token as the bearer token. These credentials are useful when
|
||||
communicating to services that require ID Tokens and can not accept access
|
||||
tokens.
|
||||
|
||||
Usually, you'll create these credentials with one of the helper
|
||||
constructors. To create credentials using a Google service account
|
||||
private key JSON file::
|
||||
|
||||
credentials = (
|
||||
service_account.IDTokenCredentials.from_service_account_file(
|
||||
'service-account.json'))
|
||||
|
||||
Or if you already have the service account file loaded::
|
||||
|
||||
service_account_info = json.load(open('service_account.json'))
|
||||
credentials = (
|
||||
service_account.IDTokenCredentials.from_service_account_info(
|
||||
service_account_info))
|
||||
|
||||
Both helper methods pass on arguments to the constructor, so you can
|
||||
specify additional scopes and a subject if necessary::
|
||||
|
||||
credentials = (
|
||||
service_account.IDTokenCredentials.from_service_account_file(
|
||||
'service-account.json',
|
||||
scopes=['email'],
|
||||
subject='user@example.com'))
|
||||
`
|
||||
The credentials are considered immutable. If you want to modify the scopes
|
||||
or the subject used for delegation, use :meth:`with_scopes` or
|
||||
:meth:`with_subject`::
|
||||
|
||||
scoped_credentials = credentials.with_scopes(['email'])
|
||||
delegated_credentials = credentials.with_subject(subject)
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
signer,
|
||||
service_account_email,
|
||||
token_uri,
|
||||
target_audience,
|
||||
additional_claims=None,
|
||||
quota_project_id=None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
signer (google.auth.crypt.Signer): The signer used to sign JWTs.
|
||||
service_account_email (str): The service account's email.
|
||||
token_uri (str): The OAuth 2.0 Token URI.
|
||||
target_audience (str): The intended audience for these credentials,
|
||||
used when requesting the ID Token. The ID Token's ``aud`` claim
|
||||
will be set to this string.
|
||||
additional_claims (Mapping[str, str]): Any additional claims for
|
||||
the JWT assertion used in the authorization grant.
|
||||
quota_project_id (Optional[str]): The project ID used for quota and billing.
|
||||
.. note:: Typically one of the helper constructors
|
||||
:meth:`from_service_account_file` or
|
||||
:meth:`from_service_account_info` are used instead of calling the
|
||||
constructor directly.
|
||||
"""
|
||||
super(IDTokenCredentials, self).__init__()
|
||||
self._signer = signer
|
||||
self._service_account_email = service_account_email
|
||||
self._token_uri = token_uri
|
||||
self._target_audience = target_audience
|
||||
self._quota_project_id = quota_project_id
|
||||
|
||||
if additional_claims is not None:
|
||||
self._additional_claims = additional_claims
|
||||
else:
|
||||
self._additional_claims = {}
|
||||
|
||||
@classmethod
|
||||
def _from_signer_and_info(cls, signer, info, **kwargs):
|
||||
"""Creates a credentials instance from a signer and service account
|
||||
info.
|
||||
|
||||
Args:
|
||||
signer (google.auth.crypt.Signer): The signer used to sign JWTs.
|
||||
info (Mapping[str, str]): The service account info.
|
||||
kwargs: Additional arguments to pass to the constructor.
|
||||
|
||||
Returns:
|
||||
google.auth.jwt.IDTokenCredentials: The constructed credentials.
|
||||
|
||||
Raises:
|
||||
ValueError: If the info is not in the expected format.
|
||||
"""
|
||||
kwargs.setdefault("service_account_email", info["client_email"])
|
||||
kwargs.setdefault("token_uri", info["token_uri"])
|
||||
return cls(signer, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_service_account_info(cls, info, **kwargs):
|
||||
"""Creates a credentials instance from parsed service account info.
|
||||
|
||||
Args:
|
||||
info (Mapping[str, str]): The service account info in Google
|
||||
format.
|
||||
kwargs: Additional arguments to pass to the constructor.
|
||||
|
||||
Returns:
|
||||
google.auth.service_account.IDTokenCredentials: The constructed
|
||||
credentials.
|
||||
|
||||
Raises:
|
||||
ValueError: If the info is not in the expected format.
|
||||
"""
|
||||
signer = _service_account_info.from_dict(
|
||||
info, require=["client_email", "token_uri"]
|
||||
)
|
||||
return cls._from_signer_and_info(signer, info, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_service_account_file(cls, filename, **kwargs):
|
||||
"""Creates a credentials instance from a service account json file.
|
||||
|
||||
Args:
|
||||
filename (str): The path to the service account json file.
|
||||
kwargs: Additional arguments to pass to the constructor.
|
||||
|
||||
Returns:
|
||||
google.auth.service_account.IDTokenCredentials: The constructed
|
||||
credentials.
|
||||
"""
|
||||
info, signer = _service_account_info.from_filename(
|
||||
filename, require=["client_email", "token_uri"]
|
||||
)
|
||||
return cls._from_signer_and_info(signer, info, **kwargs)
|
||||
|
||||
def with_target_audience(self, target_audience):
|
||||
"""Create a copy of these credentials with the specified target
|
||||
audience.
|
||||
|
||||
Args:
|
||||
target_audience (str): The intended audience for these credentials,
|
||||
used when requesting the ID Token.
|
||||
|
||||
Returns:
|
||||
google.auth.service_account.IDTokenCredentials: A new credentials
|
||||
instance.
|
||||
"""
|
||||
return self.__class__(
|
||||
self._signer,
|
||||
service_account_email=self._service_account_email,
|
||||
token_uri=self._token_uri,
|
||||
target_audience=target_audience,
|
||||
additional_claims=self._additional_claims.copy(),
|
||||
quota_project_id=self.quota_project_id,
|
||||
)
|
||||
|
||||
@_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
|
||||
def with_quota_project(self, quota_project_id):
|
||||
return self.__class__(
|
||||
self._signer,
|
||||
service_account_email=self._service_account_email,
|
||||
token_uri=self._token_uri,
|
||||
target_audience=self._target_audience,
|
||||
additional_claims=self._additional_claims.copy(),
|
||||
quota_project_id=quota_project_id,
|
||||
)
|
||||
|
||||
def _make_authorization_grant_assertion(self):
|
||||
"""Create the OAuth 2.0 assertion.
|
||||
|
||||
This assertion is used during the OAuth 2.0 grant to acquire an
|
||||
ID token.
|
||||
|
||||
Returns:
|
||||
bytes: The authorization grant assertion.
|
||||
"""
|
||||
now = _helpers.utcnow()
|
||||
lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
|
||||
expiry = now + lifetime
|
||||
|
||||
payload = {
|
||||
"iat": _helpers.datetime_to_secs(now),
|
||||
"exp": _helpers.datetime_to_secs(expiry),
|
||||
# The issuer must be the service account email.
|
||||
"iss": self.service_account_email,
|
||||
# The audience must be the auth token endpoint's URI
|
||||
"aud": self._token_uri,
|
||||
# The target audience specifies which service the ID token is
|
||||
# intended for.
|
||||
"target_audience": self._target_audience,
|
||||
}
|
||||
|
||||
payload.update(self._additional_claims)
|
||||
|
||||
token = jwt.encode(self._signer, payload)
|
||||
|
||||
return token
|
||||
|
||||
@_helpers.copy_docstring(credentials.Credentials)
|
||||
def refresh(self, request):
|
||||
assertion = self._make_authorization_grant_assertion()
|
||||
access_token, expiry, _ = _client.id_token_jwt_grant(
|
||||
request, self._token_uri, assertion
|
||||
)
|
||||
self.token = access_token
|
||||
self.expiry = expiry
|
||||
|
||||
@property
|
||||
def service_account_email(self):
|
||||
"""The service account email."""
|
||||
return self._service_account_email
|
||||
|
||||
@_helpers.copy_docstring(credentials.Signing)
|
||||
def sign_bytes(self, message):
|
||||
return self._signer.sign(message)
|
||||
|
||||
@property
|
||||
@_helpers.copy_docstring(credentials.Signing)
|
||||
def signer(self):
|
||||
return self._signer
|
||||
|
||||
@property
|
||||
@_helpers.copy_docstring(credentials.Signing)
|
||||
def signer_email(self):
|
||||
return self._service_account_email
|
Loading…
Add table
Add a link
Reference in a new issue