From 3ba48447794c9e197f35219cae62f72662f01e30 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Mon, 9 Feb 2026 11:50:19 -0500 Subject: [PATCH 1/2] delete previous cloud-client as OBE, replace with cloud-client-temp contents --- .../authenticate_explicit_with_adc.py | 55 +++++ .../authenticate_implicit_with_adc.py | 46 +++++ auth/cloud-client/custom_aws_supplier.py | 119 +++++++++++ auth/cloud-client/custom_okta_supplier.py | 190 ++++++++++++++++++ .../idtoken_from_impersonated_credentials.py | 75 +++++++ .../idtoken_from_metadata_server.py | 50 +++++ .../idtoken_from_service_account.py | 50 +++++ auth/cloud-client/noxfile_config.py | 38 ++++ auth/cloud-client/verify_google_idtoken.py | 62 ++++++ 9 files changed, 685 insertions(+) create mode 100644 auth/cloud-client/authenticate_explicit_with_adc.py create mode 100644 auth/cloud-client/authenticate_implicit_with_adc.py create mode 100644 auth/cloud-client/custom_aws_supplier.py create mode 100644 auth/cloud-client/custom_okta_supplier.py create mode 100644 auth/cloud-client/idtoken_from_impersonated_credentials.py create mode 100644 auth/cloud-client/idtoken_from_metadata_server.py create mode 100644 auth/cloud-client/idtoken_from_service_account.py create mode 100644 auth/cloud-client/noxfile_config.py create mode 100644 auth/cloud-client/verify_google_idtoken.py diff --git a/auth/cloud-client/authenticate_explicit_with_adc.py b/auth/cloud-client/authenticate_explicit_with_adc.py new file mode 100644 index 00000000000..c9ce2f02af3 --- /dev/null +++ b/auth/cloud-client/authenticate_explicit_with_adc.py @@ -0,0 +1,55 @@ +# Copyright 2022 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. + +# [START auth_cloud_explicit_adc] + + +import google.auth +from google.cloud import storage +import google.oauth2.credentials + + +def authenticate_explicit_with_adc() -> None: + """ + List storage buckets by authenticating with ADC. + + // TODO(Developer): + // 1. Before running this sample, + // set up ADC as described in https://cloud.google.com/docs/authentication/external/set-up-adc + // 2. Replace the project variable. + // 3. Make sure you have the necessary permission to list storage buckets: "storage.buckets.list" + """ + + # Construct the Google credentials object which obtains the default configuration from your + # working environment. + # google.auth.default() will give you ComputeEngineCredentials + # if you are on a GCE (or other metadata server supported environments). + credentials, project_id = google.auth.default() + # If you are authenticating to a Cloud API, you can let the library include the default scope, + # https://www.googleapis.com/auth/cloud-platform, because IAM is used to provide fine-grained + # permissions for Cloud. + # If you need to provide a scope, specify it as follows: + # credentials = google.auth.default(scopes=scope) + # For more information on scopes to use, + # see: https://developers.google.com/identity/protocols/oauth2/scopes + + # Construct the Storage client. + storage_client = storage.Client(credentials=credentials, project=project_id) + buckets = storage_client.list_buckets() + print("Buckets:") + for bucket in buckets: + print(bucket.name) + print("Listed all storage buckets.") + +# [END auth_cloud_explicit_adc] diff --git a/auth/cloud-client/authenticate_implicit_with_adc.py b/auth/cloud-client/authenticate_implicit_with_adc.py new file mode 100644 index 00000000000..ed967ab880a --- /dev/null +++ b/auth/cloud-client/authenticate_implicit_with_adc.py @@ -0,0 +1,46 @@ +# Copyright 2022 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. + +# [START auth_cloud_implicit_adc] + +from google.cloud import storage + + +def authenticate_implicit_with_adc(project_id: str = "your-google-cloud-project-id") -> None: + """ + When interacting with Google Cloud Client libraries, the library can auto-detect the + credentials to use. + + // TODO(Developer): + // 1. Before running this sample, + // set up ADC as described in https://cloud.google.com/docs/authentication/external/set-up-adc + // 2. Replace the project variable. + // 3. Make sure that the user account or service account that you are using + // has the required permissions. For this sample, you must have "storage.buckets.list". + Args: + project_id: The project id of your Google Cloud project. + """ + + # This snippet demonstrates how to list buckets. + # *NOTE*: Replace the client created below with the client required for your application. + # Note that the credentials are not specified when constructing the client. + # Hence, the client library will look for credentials using ADC. + storage_client = storage.Client(project=project_id) + buckets = storage_client.list_buckets() + print("Buckets:") + for bucket in buckets: + print(bucket.name) + print("Listed all storage buckets.") + +# [END auth_cloud_implicit_adc] diff --git a/auth/cloud-client/custom_aws_supplier.py b/auth/cloud-client/custom_aws_supplier.py new file mode 100644 index 00000000000..abe858eb5b5 --- /dev/null +++ b/auth/cloud-client/custom_aws_supplier.py @@ -0,0 +1,119 @@ +# Copyright 2025 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. + +import json +import os +import sys + +import boto3 +from dotenv import load_dotenv +from google.auth.aws import AwsSecurityCredentials, AwsSecurityCredentialsSupplier +from google.auth.aws import Credentials as AwsCredentials +from google.auth.exceptions import GoogleAuthError +from google.auth.transport.requests import AuthorizedSession + +load_dotenv() + + +class CustomAwsSupplier(AwsSecurityCredentialsSupplier): + """Custom AWS Security Credentials Supplier.""" + + def __init__(self) -> None: + """Initializes the Boto3 session, prioritizing environment variables for region.""" + # Explicitly read the region from the environment first. This ensures that + # a value from a .env file is picked up reliably for local testing. + region = os.getenv("AWS_REGION") or os.getenv("AWS_DEFAULT_REGION") + + # If region is None, Boto3's discovery chain will be used when needed. + self.session = boto3.Session(region_name=region) + self._cached_region = None + print(f"[INFO] CustomAwsSupplier initialized. Region from env: {region}") + + def get_aws_region(self, context: object, request: object) -> str: + """Returns the AWS region using Boto3's default provider chain.""" + if self._cached_region: + return self._cached_region + + # Accessing region_name will use the value from the constructor if provided, + # otherwise it triggers Boto3's lazy-loading discovery (e.g., metadata service). + self._cached_region = self.session.region_name + + if not self._cached_region: + print("[ERROR] Boto3 was unable to resolve an AWS region.", file=sys.stderr) + raise GoogleAuthError("Boto3 was unable to resolve an AWS region.") + + print(f"[INFO] Boto3 resolved AWS Region: {self._cached_region}") + return self._cached_region + + def get_aws_security_credentials(self, context: object, request: object = None) -> AwsSecurityCredentials: + """Retrieves AWS security credentials using Boto3's default provider chain.""" + aws_credentials = self.session.get_credentials() + if not aws_credentials: + print("[ERROR] Unable to resolve AWS credentials.", file=sys.stderr) + raise GoogleAuthError("Unable to resolve AWS credentials from the provider chain.") + + # Instead of printing the whole key, mask everything but the last 4 characters + masked_access_key = f"{'*' * 16}{aws_credentials.access_key[-4:]}" + print(f"[INFO] Resolved AWS Access Key ID: {masked_access_key}") + + return AwsSecurityCredentials( + access_key_id=aws_credentials.access_key, + secret_access_key=aws_credentials.secret_key, + session_token=aws_credentials.token, + ) + + +def main() -> None: + """Main function to demonstrate the custom AWS supplier.""" + print("--- Starting Script ---") + + gcp_audience = os.getenv("GCP_WORKLOAD_AUDIENCE") + sa_impersonation_url = os.getenv("GCP_SERVICE_ACCOUNT_IMPERSONATION_URL") + gcs_bucket_name = os.getenv("GCS_BUCKET_NAME") + + print(f"GCP_WORKLOAD_AUDIENCE: {gcp_audience}") + print(f"GCS_BUCKET_NAME: {gcs_bucket_name}") + + if not all([gcp_audience, sa_impersonation_url, gcs_bucket_name]): + print("[ERROR] Missing required environment variables.", file=sys.stderr) + raise GoogleAuthError("Missing required environment variables.") + + custom_supplier = CustomAwsSupplier() + + credentials = AwsCredentials( + audience=gcp_audience, + subject_token_type="urn:ietf:params:aws:token-type:aws4_request", + service_account_impersonation_url=sa_impersonation_url, + aws_security_credentials_supplier=custom_supplier, + scopes=['https://www.googleapis.com/auth/devstorage.read_write'], + ) + + bucket_url = f"https://storage.googleapis.com/storage/v1/b/{gcs_bucket_name}" + print(f"Request URL: {bucket_url}") + + authed_session = AuthorizedSession(credentials) + try: + print("Attempting to make authenticated request to Google Cloud Storage...") + res = authed_session.get(bucket_url) + res.raise_for_status() + print("\n--- SUCCESS! ---") + print("Successfully authenticated and retrieved bucket data:") + print(json.dumps(res.json(), indent=2)) + except Exception as e: + print("--- FAILED --- ", file=sys.stderr) + print(e, file=sys.stderr) + exit(1) + + +if __name__ == "__main__": + main() diff --git a/auth/cloud-client/custom_okta_supplier.py b/auth/cloud-client/custom_okta_supplier.py new file mode 100644 index 00000000000..c2b35fd406f --- /dev/null +++ b/auth/cloud-client/custom_okta_supplier.py @@ -0,0 +1,190 @@ +# Copyright 2025 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. + +import json +import os +import time +import urllib.parse + +from dotenv import load_dotenv +from google.auth.exceptions import GoogleAuthError +from google.auth.identity_pool import Credentials as IdentityPoolClient +from google.auth.transport.requests import AuthorizedSession +import requests + +load_dotenv() + +# Workload Identity Pool Configuration +GCP_WORKLOAD_AUDIENCE = os.getenv("GCP_WORKLOAD_AUDIENCE") +SERVICE_ACCOUNT_IMPERSONATION_URL = os.getenv("GCP_SERVICE_ACCOUNT_IMPERSONATION_URL") +GCS_BUCKET_NAME = os.getenv("GCS_BUCKET_NAME") + +# Okta Configuration +OKTA_DOMAIN = os.getenv("OKTA_DOMAIN") +OKTA_CLIENT_ID = os.getenv("OKTA_CLIENT_ID") +OKTA_CLIENT_SECRET = os.getenv("OKTA_CLIENT_SECRET") + +# Constants +TOKEN_URL = "https://sts.googleapis.com/v1/token" +SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" + + +class OktaClientCredentialsSupplier: + """A custom SubjectTokenSupplier that authenticates with Okta. + + This supplier uses the Client Credentials grant flow for machine-to-machine + (M2M) authentication with Okta. + """ + + def __init__(self, domain: str, client_id: str, client_secret: str) -> None: + self.okta_token_url = f"{domain}/oauth2/default/v1/token" + self.client_id = client_id + self.client_secret = client_secret + self.access_token = None + self.expiry_time = 0 + print("OktaClientCredentialsSupplier initialized.") + + def get_subject_token(self, context: object, request: object = None) -> str: + """Fetches a new token if the current one is expired or missing. + + Args: + context: The context object, not used in this implementation. + + Returns: + The Okta Access token. + """ + # Check if the current token is still valid (with a 60-second buffer). + is_token_valid = self.access_token and time.time() < self.expiry_time - 60 + + if is_token_valid: + print("[Supplier] Returning cached Okta Access token.") + return self.access_token + + print( + "[Supplier] Token is missing or expired. Fetching new Okta Access token..." + ) + self._fetch_okta_access_token() + return self.access_token + + def _fetch_okta_access_token(self) -> None: + """Performs the Client Credentials grant flow with Okta.""" + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Accept": "application/json", + } + data = { + "grant_type": "client_credentials", + "scope": "gcp.test.read", + } + encoded_data = urllib.parse.urlencode(data) + + try: + response = requests.post( + self.okta_token_url, + headers=headers, + data=encoded_data, + auth=(self.client_id, self.client_secret), + ) + response.raise_for_status() + token_data = response.json() + + if "access_token" in token_data and "expires_in" in token_data: + self.access_token = token_data["access_token"] + self.expiry_time = time.time() + token_data["expires_in"] + print( + f"[Supplier] Successfully received Access Token from Okta. " + f"Expires in {token_data['expires_in']} seconds." + ) + else: + raise GoogleAuthError( + "Access token or expires_in not found in Okta response." + ) + except requests.exceptions.RequestException as e: + print(f"[Supplier] Error fetching token from Okta: {e}") + if e.response: + print(f"[Supplier] Okta response: {e.response.text}") + raise GoogleAuthError( + "Failed to authenticate with Okta using Client Credentials grant." + ) from e + + +def main() -> None: + """Main function to demonstrate the custom Okta supplier. + + TODO(Developer): + 1. Before running this sample, set up your environment variables. You can do + this by creating a .env file in the same directory as this script and + populating it with the following variables: + - GCP_WORKLOAD_AUDIENCE: The audience for the GCP workload identity pool. + - GCP_SERVICE_ACCOUNT_IMPERSONATION_URL: The URL for service account impersonation (optional). + - GCS_BUCKET_NAME: The name of the GCS bucket to access. + - OKTA_DOMAIN: Your Okta domain (e.g., https://dev-12345.okta.com). + - OKTA_CLIENT_ID: The Client ID of your Okta M2M application. + - OKTA_CLIENT_SECRET: The Client Secret of your Okta M2M application. + """ + if not all( + [ + GCP_WORKLOAD_AUDIENCE, + GCS_BUCKET_NAME, + OKTA_DOMAIN, + OKTA_CLIENT_ID, + OKTA_CLIENT_SECRET, + ] + ): + raise GoogleAuthError( + "Missing required environment variables. Please check your .env file." + ) + + # 1. Instantiate the custom supplier with Okta credentials. + okta_supplier = OktaClientCredentialsSupplier( + OKTA_DOMAIN, OKTA_CLIENT_ID, OKTA_CLIENT_SECRET + ) + + # 2. Instantiate an IdentityPoolClient. + client = IdentityPoolClient( + audience=GCP_WORKLOAD_AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + subject_token_supplier=okta_supplier, + # If you choose to provide explicit scopes: use the `scopes` parameter. + default_scopes=['https://www.googleapis.com/auth/cloud-platform'], + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + ) + + # 3. Construct the URL for the Cloud Storage JSON API. + bucket_url = f"https://storage.googleapis.com/storage/v1/b/{GCS_BUCKET_NAME}" + print(f"[Test] Getting metadata for bucket: {GCS_BUCKET_NAME}...") + print(f"[Test] Request URL: {bucket_url}") + + # 4. Use the client to make an authenticated request. + authed_session = AuthorizedSession(client) + try: + res = authed_session.get(bucket_url) + res.raise_for_status() + print("\n--- SUCCESS! ---") + print("Successfully authenticated and retrieved bucket data:") + print(json.dumps(res.json(), indent=2)) + except requests.exceptions.RequestException as e: + print("\n--- FAILED ---") + print(f"Request failed: {e}") + if e.response: + print(f"Response: {e.response.text}") + exit(1) + except GoogleAuthError as e: + print("\n--- FAILED ---") + print(f"Authentication or request failed: {e}") + exit(1) + + +if __name__ == "__main__": + main() diff --git a/auth/cloud-client/idtoken_from_impersonated_credentials.py b/auth/cloud-client/idtoken_from_impersonated_credentials.py new file mode 100644 index 00000000000..7819072d927 --- /dev/null +++ b/auth/cloud-client/idtoken_from_impersonated_credentials.py @@ -0,0 +1,75 @@ +# Copyright 2022 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. + +# [auth_cloud_idtoken_impersonated_credentials] + +import google +from google.auth import impersonated_credentials +import google.auth.transport.requests + + +def idtoken_from_impersonated_credentials( + impersonated_service_account: str, scope: str, target_audience: str) -> None: + """ + Use a service account (SA1) to impersonate as another service account (SA2) and obtain id token + for the impersonated account. + To obtain token for SA2, SA1 should have the "roles/iam.serviceAccountTokenCreator" permission + on SA2. + + Args: + impersonated_service_account: The name of the privilege-bearing service account for whom the credential is created. + Examples: name@project.service.gserviceaccount.com + + scope: Provide the scopes that you might need to request to access Google APIs, + depending on the level of access you need. + For this example, we use the cloud-wide scope and use IAM to narrow the permissions. + https://cloud.google.com/docs/authentication#authorization_for_services + For more information, see: https://developers.google.com/identity/protocols/oauth2/scopes + + target_audience: The service name for which the id token is requested. Service name refers to the + logical identifier of an API service, such as "iap.googleapis.com". + Examples: iap.googleapis.com + """ + + # Construct the GoogleCredentials object which obtains the default configuration from your + # working environment. + credentials, project_id = google.auth.default() + + # Create the impersonated credential. + target_credentials = impersonated_credentials.Credentials( + source_credentials=credentials, + target_principal=impersonated_service_account, + # delegates: The chained list of delegates required to grant the final accessToken. + # For more information, see: + # https://cloud.google.com/iam/docs/create-short-lived-credentials-direct#sa-credentials-permissions + # Delegate is NOT USED here. + delegates=[], + target_scopes=[scope], + lifetime=300) + + # Set the impersonated credential, target audience and token options. + id_creds = impersonated_credentials.IDTokenCredentials( + target_credentials, + target_audience=target_audience, + include_email=True) + + # Get the ID token. + # Once you've obtained the ID token, use it to make an authenticated call + # to the target audience. + request = google.auth.transport.requests.Request() + id_creds.refresh(request) + # token = id_creds.token + print("Generated ID token.") + +# [auth_cloud_idtoken_impersonated_credentials] diff --git a/auth/cloud-client/idtoken_from_metadata_server.py b/auth/cloud-client/idtoken_from_metadata_server.py new file mode 100644 index 00000000000..7c9277f349e --- /dev/null +++ b/auth/cloud-client/idtoken_from_metadata_server.py @@ -0,0 +1,50 @@ +# Copyright 2022 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. + +# [START auth_cloud_idtoken_metadata_server] + +import google +from google.auth import compute_engine +import google.auth.transport.requests +import google.oauth2.credentials + + +def idtoken_from_metadata_server(url: str) -> None: + """ + Use the Google Cloud metadata server in the Cloud Run (or AppEngine or Kubernetes etc.,) + environment to create an identity token and add it to the HTTP request as part of an + Authorization header. + + Args: + url: The url or target audience to obtain the ID token for. + Examples: http://www.example.com + """ + + request = google.auth.transport.requests.Request() + # Set the target audience. + # Setting "use_metadata_identity_endpoint" to "True" will make the request use the default application + # credentials. Optionally, you can also specify a specific service account to use by mentioning + # the service_account_email. + credentials = compute_engine.IDTokenCredentials( + request=request, target_audience=url, use_metadata_identity_endpoint=True + ) + + # Get the ID token. + # Once you've obtained the ID token, use it to make an authenticated call + # to the target audience. + credentials.refresh(request) + # print(credentials.token) + print("Generated ID token.") + +# [END auth_cloud_idtoken_metadata_server] diff --git a/auth/cloud-client/idtoken_from_service_account.py b/auth/cloud-client/idtoken_from_service_account.py new file mode 100644 index 00000000000..d96a4862a8b --- /dev/null +++ b/auth/cloud-client/idtoken_from_service_account.py @@ -0,0 +1,50 @@ +# Copyright 2022 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. + +# [START auth_cloud_idtoken_service_account] + +import google.auth +import google.auth.transport.requests + +from google.oauth2 import service_account + + +def get_idToken_from_serviceaccount(json_credential_path: str, target_audience: str) -> None: + """ + TODO(Developer): Replace the below variables before running the code. + + *NOTE*: + Using service account keys introduces risk; they are long-lived, and can be used by anyone + that obtains the key. Proper rotation and storage reduce this risk but do not eliminate it. + For these reasons, you should consider an alternative approach that + does not use a service account key. Several alternatives to service account keys + are described here: + https://cloud.google.com/docs/authentication/external/set-up-adc + + Args: + json_credential_path: Path to the service account json credential file. + target_audience: The url or target audience to obtain the ID token for. + Examples: http://www.abc.com + """ + + # Obtain the id token by providing the json file path and target audience. + credentials = service_account.IDTokenCredentials.from_service_account_file( + filename=json_credential_path, + target_audience=target_audience) + + request = google.auth.transport.requests.Request() + credentials.refresh(request) + print("Generated ID token.") + +# [END auth_cloud_idtoken_service_account] diff --git a/auth/cloud-client/noxfile_config.py b/auth/cloud-client/noxfile_config.py new file mode 100644 index 00000000000..e892b338fce --- /dev/null +++ b/auth/cloud-client/noxfile_config.py @@ -0,0 +1,38 @@ +# Copyright 2022 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. + +# Default TEST_CONFIG_OVERRIDE for python repos. + +# You can copy this file into your directory, then it will be inported from +# the noxfile.py. + +# The source of truth: +# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/noxfile_config.py + +TEST_CONFIG_OVERRIDE = { + # You can opt out from the test for specific Python versions. + "ignored_versions": ["2.7"], + # Old samples are opted out of enforcing Python type hints + # All new samples should feature them + "enforce_type_hints": True, + # An envvar key for determining the project id to use. Change it + # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a + # build specific Cloud project. You can also use your own string + # to use your own Cloud project. + # "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", + "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", + # A dictionary you want to inject into your test. Don't put any + # secrets here. These values will override predefined values. + "envs": {}, +} diff --git a/auth/cloud-client/verify_google_idtoken.py b/auth/cloud-client/verify_google_idtoken.py new file mode 100644 index 00000000000..8bb4c075fd7 --- /dev/null +++ b/auth/cloud-client/verify_google_idtoken.py @@ -0,0 +1,62 @@ +# Copyright 2022 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. + +# [START auth_cloud_verify_google_idtoken] + +import google +import google.auth.transport.requests +from google.oauth2 import id_token + + +def verify_google_idtoken(idtoken: str, audience: str = "iap.googleapis.com", + jwk_url: str = "https://www.googleapis.com/oauth2/v3/certs") -> None: + """ + Verifies the obtained Google id token. This is done at the receiving end of the OIDC endpoint. + The most common use case for verifying the ID token is when you are protecting + your own APIs with IAP. Google services already verify credentials as a platform, + so verifying ID tokens before making Google API calls is usually unnecessary. + + Args: + idtoken: The Google ID token to verify. + + audience: The service name for which the id token is requested. Service name refers to the + logical identifier of an API service, such as "iap.googleapis.com". + + jwk_url: To verify id tokens, get the Json Web Key endpoint (jwk). + OpenID Connect allows the use of a "Discovery document," a JSON document found at a + well-known location containing key-value pairs which provide details about the + OpenID Connect provider's configuration. + For more information on validating the jwt, see: + https://developers.google.com/identity/protocols/oauth2/openid-connect#validatinganidtoken + + Here, we validate Google's token using Google's OpenID Connect service (jwkUrl). + For more information on jwk,see: + https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets + """ + + request = google.auth.transport.requests.Request() + # Set the parameters and verify the token. + # Setting "certs_url" is optional. When verifying a Google ID token, this is set by default. + result = id_token.verify_token(idtoken, request, audience, clock_skew_in_seconds=10) + + # Verify that the token contains subject and email claims. + # Get the User id. + if not result["sub"] is None: + print(f"User id: {result['sub']}") + # Optionally, if "INCLUDE_EMAIL" was set in the token options, check if the + # email was verified. + if result.get('email_verified'): + print(f"Email verified {result['email']}") + +# [END auth_cloud_verify_google_idtoken] From 0736517be9d19fdfcb768a2bcd3db85c09c05956 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Mon, 9 Feb 2026 11:54:25 -0500 Subject: [PATCH 2/2] removes unnecessary files --- .../authenticate_explicit_with_adc.py | 55 ----- .../authenticate_implicit_with_adc.py | 46 ----- auth/cloud-client-temp/custom_aws_supplier.py | 119 ----------- .../cloud-client-temp/custom_okta_supplier.py | 190 ------------------ .../idtoken_from_impersonated_credentials.py | 75 ------- .../idtoken_from_metadata_server.py | 50 ----- .../idtoken_from_service_account.py | 50 ----- auth/cloud-client-temp/noxfile.py | 85 -------- auth/cloud-client-temp/noxfile_config.py | 38 ---- auth/cloud-client-temp/requirements.txt | 8 - auth/cloud-client-temp/snippets_test.py | 76 ------- .../verify_google_idtoken.py | 62 ------ auth/cloud-client/requirements-test.txt | 1 - auth/cloud-client/requirements.txt | 10 +- auth/cloud-client/snippets.py | 156 -------------- auth/cloud-client/snippets_test.py | 78 ++++--- 16 files changed, 58 insertions(+), 1041 deletions(-) delete mode 100644 auth/cloud-client-temp/authenticate_explicit_with_adc.py delete mode 100644 auth/cloud-client-temp/authenticate_implicit_with_adc.py delete mode 100644 auth/cloud-client-temp/custom_aws_supplier.py delete mode 100644 auth/cloud-client-temp/custom_okta_supplier.py delete mode 100644 auth/cloud-client-temp/idtoken_from_impersonated_credentials.py delete mode 100644 auth/cloud-client-temp/idtoken_from_metadata_server.py delete mode 100644 auth/cloud-client-temp/idtoken_from_service_account.py delete mode 100644 auth/cloud-client-temp/noxfile.py delete mode 100644 auth/cloud-client-temp/noxfile_config.py delete mode 100644 auth/cloud-client-temp/requirements.txt delete mode 100644 auth/cloud-client-temp/snippets_test.py delete mode 100644 auth/cloud-client-temp/verify_google_idtoken.py delete mode 100644 auth/cloud-client/requirements-test.txt delete mode 100644 auth/cloud-client/snippets.py diff --git a/auth/cloud-client-temp/authenticate_explicit_with_adc.py b/auth/cloud-client-temp/authenticate_explicit_with_adc.py deleted file mode 100644 index c9ce2f02af3..00000000000 --- a/auth/cloud-client-temp/authenticate_explicit_with_adc.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2022 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. - -# [START auth_cloud_explicit_adc] - - -import google.auth -from google.cloud import storage -import google.oauth2.credentials - - -def authenticate_explicit_with_adc() -> None: - """ - List storage buckets by authenticating with ADC. - - // TODO(Developer): - // 1. Before running this sample, - // set up ADC as described in https://cloud.google.com/docs/authentication/external/set-up-adc - // 2. Replace the project variable. - // 3. Make sure you have the necessary permission to list storage buckets: "storage.buckets.list" - """ - - # Construct the Google credentials object which obtains the default configuration from your - # working environment. - # google.auth.default() will give you ComputeEngineCredentials - # if you are on a GCE (or other metadata server supported environments). - credentials, project_id = google.auth.default() - # If you are authenticating to a Cloud API, you can let the library include the default scope, - # https://www.googleapis.com/auth/cloud-platform, because IAM is used to provide fine-grained - # permissions for Cloud. - # If you need to provide a scope, specify it as follows: - # credentials = google.auth.default(scopes=scope) - # For more information on scopes to use, - # see: https://developers.google.com/identity/protocols/oauth2/scopes - - # Construct the Storage client. - storage_client = storage.Client(credentials=credentials, project=project_id) - buckets = storage_client.list_buckets() - print("Buckets:") - for bucket in buckets: - print(bucket.name) - print("Listed all storage buckets.") - -# [END auth_cloud_explicit_adc] diff --git a/auth/cloud-client-temp/authenticate_implicit_with_adc.py b/auth/cloud-client-temp/authenticate_implicit_with_adc.py deleted file mode 100644 index ed967ab880a..00000000000 --- a/auth/cloud-client-temp/authenticate_implicit_with_adc.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2022 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. - -# [START auth_cloud_implicit_adc] - -from google.cloud import storage - - -def authenticate_implicit_with_adc(project_id: str = "your-google-cloud-project-id") -> None: - """ - When interacting with Google Cloud Client libraries, the library can auto-detect the - credentials to use. - - // TODO(Developer): - // 1. Before running this sample, - // set up ADC as described in https://cloud.google.com/docs/authentication/external/set-up-adc - // 2. Replace the project variable. - // 3. Make sure that the user account or service account that you are using - // has the required permissions. For this sample, you must have "storage.buckets.list". - Args: - project_id: The project id of your Google Cloud project. - """ - - # This snippet demonstrates how to list buckets. - # *NOTE*: Replace the client created below with the client required for your application. - # Note that the credentials are not specified when constructing the client. - # Hence, the client library will look for credentials using ADC. - storage_client = storage.Client(project=project_id) - buckets = storage_client.list_buckets() - print("Buckets:") - for bucket in buckets: - print(bucket.name) - print("Listed all storage buckets.") - -# [END auth_cloud_implicit_adc] diff --git a/auth/cloud-client-temp/custom_aws_supplier.py b/auth/cloud-client-temp/custom_aws_supplier.py deleted file mode 100644 index abe858eb5b5..00000000000 --- a/auth/cloud-client-temp/custom_aws_supplier.py +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright 2025 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. - -import json -import os -import sys - -import boto3 -from dotenv import load_dotenv -from google.auth.aws import AwsSecurityCredentials, AwsSecurityCredentialsSupplier -from google.auth.aws import Credentials as AwsCredentials -from google.auth.exceptions import GoogleAuthError -from google.auth.transport.requests import AuthorizedSession - -load_dotenv() - - -class CustomAwsSupplier(AwsSecurityCredentialsSupplier): - """Custom AWS Security Credentials Supplier.""" - - def __init__(self) -> None: - """Initializes the Boto3 session, prioritizing environment variables for region.""" - # Explicitly read the region from the environment first. This ensures that - # a value from a .env file is picked up reliably for local testing. - region = os.getenv("AWS_REGION") or os.getenv("AWS_DEFAULT_REGION") - - # If region is None, Boto3's discovery chain will be used when needed. - self.session = boto3.Session(region_name=region) - self._cached_region = None - print(f"[INFO] CustomAwsSupplier initialized. Region from env: {region}") - - def get_aws_region(self, context: object, request: object) -> str: - """Returns the AWS region using Boto3's default provider chain.""" - if self._cached_region: - return self._cached_region - - # Accessing region_name will use the value from the constructor if provided, - # otherwise it triggers Boto3's lazy-loading discovery (e.g., metadata service). - self._cached_region = self.session.region_name - - if not self._cached_region: - print("[ERROR] Boto3 was unable to resolve an AWS region.", file=sys.stderr) - raise GoogleAuthError("Boto3 was unable to resolve an AWS region.") - - print(f"[INFO] Boto3 resolved AWS Region: {self._cached_region}") - return self._cached_region - - def get_aws_security_credentials(self, context: object, request: object = None) -> AwsSecurityCredentials: - """Retrieves AWS security credentials using Boto3's default provider chain.""" - aws_credentials = self.session.get_credentials() - if not aws_credentials: - print("[ERROR] Unable to resolve AWS credentials.", file=sys.stderr) - raise GoogleAuthError("Unable to resolve AWS credentials from the provider chain.") - - # Instead of printing the whole key, mask everything but the last 4 characters - masked_access_key = f"{'*' * 16}{aws_credentials.access_key[-4:]}" - print(f"[INFO] Resolved AWS Access Key ID: {masked_access_key}") - - return AwsSecurityCredentials( - access_key_id=aws_credentials.access_key, - secret_access_key=aws_credentials.secret_key, - session_token=aws_credentials.token, - ) - - -def main() -> None: - """Main function to demonstrate the custom AWS supplier.""" - print("--- Starting Script ---") - - gcp_audience = os.getenv("GCP_WORKLOAD_AUDIENCE") - sa_impersonation_url = os.getenv("GCP_SERVICE_ACCOUNT_IMPERSONATION_URL") - gcs_bucket_name = os.getenv("GCS_BUCKET_NAME") - - print(f"GCP_WORKLOAD_AUDIENCE: {gcp_audience}") - print(f"GCS_BUCKET_NAME: {gcs_bucket_name}") - - if not all([gcp_audience, sa_impersonation_url, gcs_bucket_name]): - print("[ERROR] Missing required environment variables.", file=sys.stderr) - raise GoogleAuthError("Missing required environment variables.") - - custom_supplier = CustomAwsSupplier() - - credentials = AwsCredentials( - audience=gcp_audience, - subject_token_type="urn:ietf:params:aws:token-type:aws4_request", - service_account_impersonation_url=sa_impersonation_url, - aws_security_credentials_supplier=custom_supplier, - scopes=['https://www.googleapis.com/auth/devstorage.read_write'], - ) - - bucket_url = f"https://storage.googleapis.com/storage/v1/b/{gcs_bucket_name}" - print(f"Request URL: {bucket_url}") - - authed_session = AuthorizedSession(credentials) - try: - print("Attempting to make authenticated request to Google Cloud Storage...") - res = authed_session.get(bucket_url) - res.raise_for_status() - print("\n--- SUCCESS! ---") - print("Successfully authenticated and retrieved bucket data:") - print(json.dumps(res.json(), indent=2)) - except Exception as e: - print("--- FAILED --- ", file=sys.stderr) - print(e, file=sys.stderr) - exit(1) - - -if __name__ == "__main__": - main() diff --git a/auth/cloud-client-temp/custom_okta_supplier.py b/auth/cloud-client-temp/custom_okta_supplier.py deleted file mode 100644 index c2b35fd406f..00000000000 --- a/auth/cloud-client-temp/custom_okta_supplier.py +++ /dev/null @@ -1,190 +0,0 @@ -# Copyright 2025 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. - -import json -import os -import time -import urllib.parse - -from dotenv import load_dotenv -from google.auth.exceptions import GoogleAuthError -from google.auth.identity_pool import Credentials as IdentityPoolClient -from google.auth.transport.requests import AuthorizedSession -import requests - -load_dotenv() - -# Workload Identity Pool Configuration -GCP_WORKLOAD_AUDIENCE = os.getenv("GCP_WORKLOAD_AUDIENCE") -SERVICE_ACCOUNT_IMPERSONATION_URL = os.getenv("GCP_SERVICE_ACCOUNT_IMPERSONATION_URL") -GCS_BUCKET_NAME = os.getenv("GCS_BUCKET_NAME") - -# Okta Configuration -OKTA_DOMAIN = os.getenv("OKTA_DOMAIN") -OKTA_CLIENT_ID = os.getenv("OKTA_CLIENT_ID") -OKTA_CLIENT_SECRET = os.getenv("OKTA_CLIENT_SECRET") - -# Constants -TOKEN_URL = "https://sts.googleapis.com/v1/token" -SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" - - -class OktaClientCredentialsSupplier: - """A custom SubjectTokenSupplier that authenticates with Okta. - - This supplier uses the Client Credentials grant flow for machine-to-machine - (M2M) authentication with Okta. - """ - - def __init__(self, domain: str, client_id: str, client_secret: str) -> None: - self.okta_token_url = f"{domain}/oauth2/default/v1/token" - self.client_id = client_id - self.client_secret = client_secret - self.access_token = None - self.expiry_time = 0 - print("OktaClientCredentialsSupplier initialized.") - - def get_subject_token(self, context: object, request: object = None) -> str: - """Fetches a new token if the current one is expired or missing. - - Args: - context: The context object, not used in this implementation. - - Returns: - The Okta Access token. - """ - # Check if the current token is still valid (with a 60-second buffer). - is_token_valid = self.access_token and time.time() < self.expiry_time - 60 - - if is_token_valid: - print("[Supplier] Returning cached Okta Access token.") - return self.access_token - - print( - "[Supplier] Token is missing or expired. Fetching new Okta Access token..." - ) - self._fetch_okta_access_token() - return self.access_token - - def _fetch_okta_access_token(self) -> None: - """Performs the Client Credentials grant flow with Okta.""" - headers = { - "Content-Type": "application/x-www-form-urlencoded", - "Accept": "application/json", - } - data = { - "grant_type": "client_credentials", - "scope": "gcp.test.read", - } - encoded_data = urllib.parse.urlencode(data) - - try: - response = requests.post( - self.okta_token_url, - headers=headers, - data=encoded_data, - auth=(self.client_id, self.client_secret), - ) - response.raise_for_status() - token_data = response.json() - - if "access_token" in token_data and "expires_in" in token_data: - self.access_token = token_data["access_token"] - self.expiry_time = time.time() + token_data["expires_in"] - print( - f"[Supplier] Successfully received Access Token from Okta. " - f"Expires in {token_data['expires_in']} seconds." - ) - else: - raise GoogleAuthError( - "Access token or expires_in not found in Okta response." - ) - except requests.exceptions.RequestException as e: - print(f"[Supplier] Error fetching token from Okta: {e}") - if e.response: - print(f"[Supplier] Okta response: {e.response.text}") - raise GoogleAuthError( - "Failed to authenticate with Okta using Client Credentials grant." - ) from e - - -def main() -> None: - """Main function to demonstrate the custom Okta supplier. - - TODO(Developer): - 1. Before running this sample, set up your environment variables. You can do - this by creating a .env file in the same directory as this script and - populating it with the following variables: - - GCP_WORKLOAD_AUDIENCE: The audience for the GCP workload identity pool. - - GCP_SERVICE_ACCOUNT_IMPERSONATION_URL: The URL for service account impersonation (optional). - - GCS_BUCKET_NAME: The name of the GCS bucket to access. - - OKTA_DOMAIN: Your Okta domain (e.g., https://dev-12345.okta.com). - - OKTA_CLIENT_ID: The Client ID of your Okta M2M application. - - OKTA_CLIENT_SECRET: The Client Secret of your Okta M2M application. - """ - if not all( - [ - GCP_WORKLOAD_AUDIENCE, - GCS_BUCKET_NAME, - OKTA_DOMAIN, - OKTA_CLIENT_ID, - OKTA_CLIENT_SECRET, - ] - ): - raise GoogleAuthError( - "Missing required environment variables. Please check your .env file." - ) - - # 1. Instantiate the custom supplier with Okta credentials. - okta_supplier = OktaClientCredentialsSupplier( - OKTA_DOMAIN, OKTA_CLIENT_ID, OKTA_CLIENT_SECRET - ) - - # 2. Instantiate an IdentityPoolClient. - client = IdentityPoolClient( - audience=GCP_WORKLOAD_AUDIENCE, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - subject_token_supplier=okta_supplier, - # If you choose to provide explicit scopes: use the `scopes` parameter. - default_scopes=['https://www.googleapis.com/auth/cloud-platform'], - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - ) - - # 3. Construct the URL for the Cloud Storage JSON API. - bucket_url = f"https://storage.googleapis.com/storage/v1/b/{GCS_BUCKET_NAME}" - print(f"[Test] Getting metadata for bucket: {GCS_BUCKET_NAME}...") - print(f"[Test] Request URL: {bucket_url}") - - # 4. Use the client to make an authenticated request. - authed_session = AuthorizedSession(client) - try: - res = authed_session.get(bucket_url) - res.raise_for_status() - print("\n--- SUCCESS! ---") - print("Successfully authenticated and retrieved bucket data:") - print(json.dumps(res.json(), indent=2)) - except requests.exceptions.RequestException as e: - print("\n--- FAILED ---") - print(f"Request failed: {e}") - if e.response: - print(f"Response: {e.response.text}") - exit(1) - except GoogleAuthError as e: - print("\n--- FAILED ---") - print(f"Authentication or request failed: {e}") - exit(1) - - -if __name__ == "__main__": - main() diff --git a/auth/cloud-client-temp/idtoken_from_impersonated_credentials.py b/auth/cloud-client-temp/idtoken_from_impersonated_credentials.py deleted file mode 100644 index 7819072d927..00000000000 --- a/auth/cloud-client-temp/idtoken_from_impersonated_credentials.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright 2022 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. - -# [auth_cloud_idtoken_impersonated_credentials] - -import google -from google.auth import impersonated_credentials -import google.auth.transport.requests - - -def idtoken_from_impersonated_credentials( - impersonated_service_account: str, scope: str, target_audience: str) -> None: - """ - Use a service account (SA1) to impersonate as another service account (SA2) and obtain id token - for the impersonated account. - To obtain token for SA2, SA1 should have the "roles/iam.serviceAccountTokenCreator" permission - on SA2. - - Args: - impersonated_service_account: The name of the privilege-bearing service account for whom the credential is created. - Examples: name@project.service.gserviceaccount.com - - scope: Provide the scopes that you might need to request to access Google APIs, - depending on the level of access you need. - For this example, we use the cloud-wide scope and use IAM to narrow the permissions. - https://cloud.google.com/docs/authentication#authorization_for_services - For more information, see: https://developers.google.com/identity/protocols/oauth2/scopes - - target_audience: The service name for which the id token is requested. Service name refers to the - logical identifier of an API service, such as "iap.googleapis.com". - Examples: iap.googleapis.com - """ - - # Construct the GoogleCredentials object which obtains the default configuration from your - # working environment. - credentials, project_id = google.auth.default() - - # Create the impersonated credential. - target_credentials = impersonated_credentials.Credentials( - source_credentials=credentials, - target_principal=impersonated_service_account, - # delegates: The chained list of delegates required to grant the final accessToken. - # For more information, see: - # https://cloud.google.com/iam/docs/create-short-lived-credentials-direct#sa-credentials-permissions - # Delegate is NOT USED here. - delegates=[], - target_scopes=[scope], - lifetime=300) - - # Set the impersonated credential, target audience and token options. - id_creds = impersonated_credentials.IDTokenCredentials( - target_credentials, - target_audience=target_audience, - include_email=True) - - # Get the ID token. - # Once you've obtained the ID token, use it to make an authenticated call - # to the target audience. - request = google.auth.transport.requests.Request() - id_creds.refresh(request) - # token = id_creds.token - print("Generated ID token.") - -# [auth_cloud_idtoken_impersonated_credentials] diff --git a/auth/cloud-client-temp/idtoken_from_metadata_server.py b/auth/cloud-client-temp/idtoken_from_metadata_server.py deleted file mode 100644 index 7c9277f349e..00000000000 --- a/auth/cloud-client-temp/idtoken_from_metadata_server.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright 2022 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. - -# [START auth_cloud_idtoken_metadata_server] - -import google -from google.auth import compute_engine -import google.auth.transport.requests -import google.oauth2.credentials - - -def idtoken_from_metadata_server(url: str) -> None: - """ - Use the Google Cloud metadata server in the Cloud Run (or AppEngine or Kubernetes etc.,) - environment to create an identity token and add it to the HTTP request as part of an - Authorization header. - - Args: - url: The url or target audience to obtain the ID token for. - Examples: http://www.example.com - """ - - request = google.auth.transport.requests.Request() - # Set the target audience. - # Setting "use_metadata_identity_endpoint" to "True" will make the request use the default application - # credentials. Optionally, you can also specify a specific service account to use by mentioning - # the service_account_email. - credentials = compute_engine.IDTokenCredentials( - request=request, target_audience=url, use_metadata_identity_endpoint=True - ) - - # Get the ID token. - # Once you've obtained the ID token, use it to make an authenticated call - # to the target audience. - credentials.refresh(request) - # print(credentials.token) - print("Generated ID token.") - -# [END auth_cloud_idtoken_metadata_server] diff --git a/auth/cloud-client-temp/idtoken_from_service_account.py b/auth/cloud-client-temp/idtoken_from_service_account.py deleted file mode 100644 index d96a4862a8b..00000000000 --- a/auth/cloud-client-temp/idtoken_from_service_account.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright 2022 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. - -# [START auth_cloud_idtoken_service_account] - -import google.auth -import google.auth.transport.requests - -from google.oauth2 import service_account - - -def get_idToken_from_serviceaccount(json_credential_path: str, target_audience: str) -> None: - """ - TODO(Developer): Replace the below variables before running the code. - - *NOTE*: - Using service account keys introduces risk; they are long-lived, and can be used by anyone - that obtains the key. Proper rotation and storage reduce this risk but do not eliminate it. - For these reasons, you should consider an alternative approach that - does not use a service account key. Several alternatives to service account keys - are described here: - https://cloud.google.com/docs/authentication/external/set-up-adc - - Args: - json_credential_path: Path to the service account json credential file. - target_audience: The url or target audience to obtain the ID token for. - Examples: http://www.abc.com - """ - - # Obtain the id token by providing the json file path and target audience. - credentials = service_account.IDTokenCredentials.from_service_account_file( - filename=json_credential_path, - target_audience=target_audience) - - request = google.auth.transport.requests.Request() - credentials.refresh(request) - print("Generated ID token.") - -# [END auth_cloud_idtoken_service_account] diff --git a/auth/cloud-client-temp/noxfile.py b/auth/cloud-client-temp/noxfile.py deleted file mode 100644 index 3cdf3cf3bdb..00000000000 --- a/auth/cloud-client-temp/noxfile.py +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright 2019 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. - -import pathlib - -import nox - -CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() - -# https://github.com/psf/black/issues/2964, pin click version to 8.0.4 to -# avoid incompatiblity with black. -CLICK_VERSION = "click==8.0.4" -BLACK_VERSION = "black==19.3b0" -BLACK_PATHS = [ - "google", - "tests", - "tests_async", - "noxfile.py", - "setup.py", - "docs/conf.py", -] - - -# Error if a python version is missing -nox.options.error_on_missing_interpreters = True - -# -# Style Checks -# - - -# Linting with flake8. -# -# We ignore the following rules: -# E203: whitespace before ‘:’ -# E266: too many leading ‘#’ for block comment -# E501: line too long -# I202: Additional newline in a section of imports -# -# We also need to specify the rules which are ignored by default: -# ['E226', 'W504', 'E126', 'E123', 'W503', 'E24', 'E704', 'E121'] -FLAKE8_COMMON_ARGS = [ - "--show-source", - "--builtin=gettext", - "--max-complexity=20", - "--exclude=.nox,.cache,env,lib,generated_pb2,*_pb2.py,*_pb2_grpc.py", - "--ignore=E121,E123,E126,E203,E226,E24,E266,E501,E704,W503,W504,I202", - "--max-line-length=88", -] - - -@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]) -def unit(session): - # constraints_path = str( - # CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" - # ) - session.install("-r", "requirements.txt") - # session.install("-e", ".") - session.run( - "pytest", - f"--junitxml=unit_{session.python}_sponge_log.xml", - "snippets_test.py", - # "tests_async", - ) - - -@nox.session -def lint(session: nox.sessions.Session) -> None: - session.install("flake8") - - args = FLAKE8_COMMON_ARGS + [ - ".", - ] - session.run("flake8", *args) diff --git a/auth/cloud-client-temp/noxfile_config.py b/auth/cloud-client-temp/noxfile_config.py deleted file mode 100644 index e892b338fce..00000000000 --- a/auth/cloud-client-temp/noxfile_config.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2022 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. - -# Default TEST_CONFIG_OVERRIDE for python repos. - -# You can copy this file into your directory, then it will be inported from -# the noxfile.py. - -# The source of truth: -# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/noxfile_config.py - -TEST_CONFIG_OVERRIDE = { - # You can opt out from the test for specific Python versions. - "ignored_versions": ["2.7"], - # Old samples are opted out of enforcing Python type hints - # All new samples should feature them - "enforce_type_hints": True, - # An envvar key for determining the project id to use. Change it - # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a - # build specific Cloud project. You can also use your own string - # to use your own Cloud project. - # "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", - # A dictionary you want to inject into your test. Don't put any - # secrets here. These values will override predefined values. - "envs": {}, -} diff --git a/auth/cloud-client-temp/requirements.txt b/auth/cloud-client-temp/requirements.txt deleted file mode 100644 index 8dafe853ea0..00000000000 --- a/auth/cloud-client-temp/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -google-cloud-compute==1.42.0 -google-cloud-storage==3.8.0 -google-auth==2.47.0 -pytest===8.4.2; python_version == '3.9' -pytest==9.0.2; python_version > '3.9' -boto3>=1.26.0 -requests==2.32.5 -python-dotenv==1.2.1 diff --git a/auth/cloud-client-temp/snippets_test.py b/auth/cloud-client-temp/snippets_test.py deleted file mode 100644 index 940f27e553c..00000000000 --- a/auth/cloud-client-temp/snippets_test.py +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright 2022 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. -import os -import re - -from _pytest.capture import CaptureFixture -import google -import google.auth.transport.requests -from google.oauth2 import service_account - -import authenticate_explicit_with_adc -import authenticate_implicit_with_adc -import idtoken_from_metadata_server -import idtoken_from_service_account -# from system_tests.noxfile import SERVICE_ACCOUNT_FILE -import verify_google_idtoken - -CREDENTIALS, PROJECT = google.auth.default() -SERVICE_ACCOUNT_FILE = os.getenv("GOOGLE_APPLICATION_CREDENTIALS") - - -def test_authenticate_explicit_with_adc(capsys: CaptureFixture) -> None: - authenticate_explicit_with_adc.authenticate_explicit_with_adc() - out, err = capsys.readouterr() - assert re.search("Listed all storage buckets.", out) - - -def test_authenticate_implicit_with_adc(capsys: CaptureFixture) -> None: - authenticate_implicit_with_adc.authenticate_implicit_with_adc(PROJECT) - out, err = capsys.readouterr() - assert re.search("Listed all storage buckets.", out) - - -def test_idtoken_from_metadata_server(capsys: CaptureFixture) -> None: - idtoken_from_metadata_server.idtoken_from_metadata_server("https://www.google.com") - out, err = capsys.readouterr() - assert re.search("Generated ID token.", out) - - -def test_idtoken_from_service_account(capsys: CaptureFixture) -> None: - idtoken_from_service_account.get_idToken_from_serviceaccount( - SERVICE_ACCOUNT_FILE, - "iap.googleapis.com") - out, err = capsys.readouterr() - assert re.search("Generated ID token.", out) - - -def test_verify_google_idtoken() -> None: - idtoken = get_idtoken_from_service_account(SERVICE_ACCOUNT_FILE, "iap.googleapis.com") - - verify_google_idtoken.verify_google_idtoken( - idtoken, - "iap.googleapis.com", - "https://www.googleapis.com/oauth2/v3/certs" - ) - - -def get_idtoken_from_service_account(json_credential_path: str, target_audience: str) -> str: - credentials = service_account.IDTokenCredentials.from_service_account_file( - filename=json_credential_path, - target_audience=target_audience) - - request = google.auth.transport.requests.Request() - credentials.refresh(request) - return credentials.token diff --git a/auth/cloud-client-temp/verify_google_idtoken.py b/auth/cloud-client-temp/verify_google_idtoken.py deleted file mode 100644 index 8bb4c075fd7..00000000000 --- a/auth/cloud-client-temp/verify_google_idtoken.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2022 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. - -# [START auth_cloud_verify_google_idtoken] - -import google -import google.auth.transport.requests -from google.oauth2 import id_token - - -def verify_google_idtoken(idtoken: str, audience: str = "iap.googleapis.com", - jwk_url: str = "https://www.googleapis.com/oauth2/v3/certs") -> None: - """ - Verifies the obtained Google id token. This is done at the receiving end of the OIDC endpoint. - The most common use case for verifying the ID token is when you are protecting - your own APIs with IAP. Google services already verify credentials as a platform, - so verifying ID tokens before making Google API calls is usually unnecessary. - - Args: - idtoken: The Google ID token to verify. - - audience: The service name for which the id token is requested. Service name refers to the - logical identifier of an API service, such as "iap.googleapis.com". - - jwk_url: To verify id tokens, get the Json Web Key endpoint (jwk). - OpenID Connect allows the use of a "Discovery document," a JSON document found at a - well-known location containing key-value pairs which provide details about the - OpenID Connect provider's configuration. - For more information on validating the jwt, see: - https://developers.google.com/identity/protocols/oauth2/openid-connect#validatinganidtoken - - Here, we validate Google's token using Google's OpenID Connect service (jwkUrl). - For more information on jwk,see: - https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets - """ - - request = google.auth.transport.requests.Request() - # Set the parameters and verify the token. - # Setting "certs_url" is optional. When verifying a Google ID token, this is set by default. - result = id_token.verify_token(idtoken, request, audience, clock_skew_in_seconds=10) - - # Verify that the token contains subject and email claims. - # Get the User id. - if not result["sub"] is None: - print(f"User id: {result['sub']}") - # Optionally, if "INCLUDE_EMAIL" was set in the token options, check if the - # email was verified. - if result.get('email_verified'): - print(f"Email verified {result['email']}") - -# [END auth_cloud_verify_google_idtoken] diff --git a/auth/cloud-client/requirements-test.txt b/auth/cloud-client/requirements-test.txt deleted file mode 100644 index 15d066af319..00000000000 --- a/auth/cloud-client/requirements-test.txt +++ /dev/null @@ -1 +0,0 @@ -pytest==8.2.0 diff --git a/auth/cloud-client/requirements.txt b/auth/cloud-client/requirements.txt index 3f1d3521dc4..8dafe853ea0 100644 --- a/auth/cloud-client/requirements.txt +++ b/auth/cloud-client/requirements.txt @@ -1,2 +1,8 @@ -google-cloud-storage==2.9.0; python_version < '3.7' -google-cloud-storage==2.9.0; python_version > '3.6' +google-cloud-compute==1.42.0 +google-cloud-storage==3.8.0 +google-auth==2.47.0 +pytest===8.4.2; python_version == '3.9' +pytest==9.0.2; python_version > '3.9' +boto3>=1.26.0 +requests==2.32.5 +python-dotenv==1.2.1 diff --git a/auth/cloud-client/snippets.py b/auth/cloud-client/snippets.py deleted file mode 100644 index 274d3047e04..00000000000 --- a/auth/cloud-client/snippets.py +++ /dev/null @@ -1,156 +0,0 @@ -# Copyright 2016 Google Inc. -# -# 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. - -"""Demonstrates how to authenticate to Google Cloud Platform APIs using -the Google Cloud Client Libraries.""" - -import argparse - - -# [START auth_cloud_implicit] -def implicit(): - from google.cloud import storage - - # If you don't specify credentials when constructing the client, the - # client library will look for credentials in the environment. - storage_client = storage.Client() - - # Make an authenticated API request - buckets = list(storage_client.list_buckets()) - print(buckets) - - -# [END auth_cloud_implicit] - - -# [START auth_cloud_explicit] -def explicit(): - from google.cloud import storage - - # Explicitly use service account credentials by specifying the private key - # file. - storage_client = storage.Client.from_service_account_json("service_account.json") - - # Make an authenticated API request - buckets = list(storage_client.list_buckets()) - print(buckets) - - -# [END auth_cloud_explicit] - - -# [START auth_cloud_explicit_compute_engine] -def explicit_compute_engine(project): - from google.auth import compute_engine - from google.cloud import storage - - # Explicitly use Compute Engine credentials. These credentials are - # available on Compute Engine, App Engine Flexible, and Kubernetes Engine. - credentials = compute_engine.Credentials() - - # Create the client using the credentials and specifying a project ID. - storage_client = storage.Client(credentials=credentials, project=project) - - # Make an authenticated API request - buckets = list(storage_client.list_buckets()) - print(buckets) - - -# [END auth_cloud_explicit_compute_engine] - - -# [START auth_cloud_accesstoken_impersonated_credentials] -def accesstoken_from_impersonated_credentials( - impersonated_service_account: str, scope: str -): - from google.auth import impersonated_credentials - import google.auth.transport.requests - - """ - Use a service account (SA1) to impersonate another service account (SA2) - and obtain an ID token for the impersonated account. - To obtain a token for SA2, SA1 should have the - "roles/iam.serviceAccountTokenCreator" permission on SA2. - - Args: - impersonated_service_account: The name of the privilege-bearing service account for whom the credential is created. - Examples: name@project.service.gserviceaccount.com - - scope: Provide the scopes that you might need to request to access Google APIs, - depending on the level of access you need. - For this example, we use the cloud-wide scope and use IAM to narrow the permissions. - https://cloud.google.com/docs/authentication#authorization_for_services - For more information, see: https://developers.google.com/identity/protocols/oauth2/scopes - """ - - # Construct the GoogleCredentials object which obtains the default configuration from your - # working environment. - credentials, project_id = google.auth.default() - - # Create the impersonated credential. - target_credentials = impersonated_credentials.Credentials( - source_credentials=credentials, - target_principal=impersonated_service_account, - # delegates: The chained list of delegates required to grant the final accessToken. - # For more information, see: - # https://cloud.google.com/iam/docs/create-short-lived-credentials-direct#sa-credentials-permissions - # Delegate is NOT USED here. - delegates=[], - target_scopes=[scope], - lifetime=300, - ) - - # Get the OAuth2 token. - # Once you've obtained the OAuth2 token, use it to make an authenticated call - # to the target audience. - request = google.auth.transport.requests.Request() - target_credentials.refresh(request) - # The token field is target_credentials.token. - print("Generated OAuth2 token.") - - -# [END auth_cloud_accesstoken_impersonated_credentials] - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter - ) - - subparsers = parser.add_subparsers(dest="command") - subparsers.add_parser("implicit", help=implicit.__doc__) - subparsers.add_parser("explicit", help=explicit.__doc__) - explicit_gce_parser = subparsers.add_parser( - "explicit_compute_engine", help=explicit_compute_engine.__doc__ - ) - explicit_gce_parser.add_argument("project") - accesstoken_parser = subparsers.add_parser( - "accesstoken_from_impersonated_credentials", - help=accesstoken_from_impersonated_credentials.__doc__, - ) - accesstoken_parser.add_argument("impersonated_service_account") - accesstoken_parser.add_argument("scope") - - args = parser.parse_args() - - if args.command == "implicit": - implicit() - elif args.command == "explicit": - explicit() - elif args.command == "explicit_compute_engine": - explicit_compute_engine(args.project) - elif args.command == "accesstoken_from_impersonated_credentials": - accesstoken_from_impersonated_credentials( - args.impersonated_service_account, args.scope - ) diff --git a/auth/cloud-client/snippets_test.py b/auth/cloud-client/snippets_test.py index fc02b8f3984..940f27e553c 100644 --- a/auth/cloud-client/snippets_test.py +++ b/auth/cloud-client/snippets_test.py @@ -1,54 +1,76 @@ -# Copyright 2016 Google Inc. +# Copyright 2022 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 +# 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. - import os -from unittest import mock +import re -import google.auth +from _pytest.capture import CaptureFixture +import google +import google.auth.transport.requests +from google.oauth2 import service_account -import snippets +import authenticate_explicit_with_adc +import authenticate_implicit_with_adc +import idtoken_from_metadata_server +import idtoken_from_service_account +# from system_tests.noxfile import SERVICE_ACCOUNT_FILE +import verify_google_idtoken +CREDENTIALS, PROJECT = google.auth.default() +SERVICE_ACCOUNT_FILE = os.getenv("GOOGLE_APPLICATION_CREDENTIALS") -def test_implicit(): - snippets.implicit() +def test_authenticate_explicit_with_adc(capsys: CaptureFixture) -> None: + authenticate_explicit_with_adc.authenticate_explicit_with_adc() + out, err = capsys.readouterr() + assert re.search("Listed all storage buckets.", out) -def test_explicit(): - with open(os.environ["GOOGLE_APPLICATION_CREDENTIALS"]) as creds_file: - creds_file_data = creds_file.read() - open_mock = mock.mock_open(read_data=creds_file_data) +def test_authenticate_implicit_with_adc(capsys: CaptureFixture) -> None: + authenticate_implicit_with_adc.authenticate_implicit_with_adc(PROJECT) + out, err = capsys.readouterr() + assert re.search("Listed all storage buckets.", out) - with mock.patch("io.open", open_mock): - snippets.explicit() +def test_idtoken_from_metadata_server(capsys: CaptureFixture) -> None: + idtoken_from_metadata_server.idtoken_from_metadata_server("https://www.google.com") + out, err = capsys.readouterr() + assert re.search("Generated ID token.", out) -def test_explicit_compute_engine(): - adc, project = google.auth.default() - credentials_patch = mock.patch( - "google.auth.compute_engine.Credentials", return_value=adc - ) - with credentials_patch: - snippets.explicit_compute_engine(project) +def test_idtoken_from_service_account(capsys: CaptureFixture) -> None: + idtoken_from_service_account.get_idToken_from_serviceaccount( + SERVICE_ACCOUNT_FILE, + "iap.googleapis.com") + out, err = capsys.readouterr() + assert re.search("Generated ID token.", out) -def test_accesstoken_from_impersonated_credentials(): - impersonated_service_account = ( - "auth-samples-testing@python-docs-samples-tests.iam.gserviceaccount.com" - ) - scope = "https://www.googleapis.com/auth/cloud-platform" - snippets.accesstoken_from_impersonated_credentials( - impersonated_service_account, scope +def test_verify_google_idtoken() -> None: + idtoken = get_idtoken_from_service_account(SERVICE_ACCOUNT_FILE, "iap.googleapis.com") + + verify_google_idtoken.verify_google_idtoken( + idtoken, + "iap.googleapis.com", + "https://www.googleapis.com/oauth2/v3/certs" ) + + +def get_idtoken_from_service_account(json_credential_path: str, target_audience: str) -> str: + credentials = service_account.IDTokenCredentials.from_service_account_file( + filename=json_credential_path, + target_audience=target_audience) + + request = google.auth.transport.requests.Request() + credentials.refresh(request) + return credentials.token