diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml index 675cd7c93..ab8bb03fa 100644 --- a/.github/sync-repo-settings.yaml +++ b/.github/sync-repo-settings.yaml @@ -11,6 +11,10 @@ branchProtectionRules: - 'OwlBot Post Processor' - 'Kokoro system-3.7' - 'Kokoro' + - 'Samples - Python 3.7' + - 'Samples - Python 3.8' + - 'Samples - Python 3.9' + - 'Samples - Python 3.10' permissionRules: - team: actools-python permission: admin diff --git a/.kokoro/samples-test-setup.sh b/.kokoro/samples-test-setup.sh new file mode 100755 index 000000000..938402a92 --- /dev/null +++ b/.kokoro/samples-test-setup.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# Copyright 2018 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 +# +# https://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. + +set -eo pipefail + +if [[ -z "${PROJECT_ROOT:-}" ]]; then + PROJECT_ROOT="github/google-auth-library-python" +fi + +cd "${PROJECT_ROOT}" + +# Disable buffering, so that the logs stream through. +export PYTHONUNBUFFERED=1 + +# Remove old nox +python3 -m pip uninstall --yes --quiet nox-automation + +# Install nox +python3 -m pip install --upgrade --quiet nox +python3 -m nox --version + +# Setup service account credentials. +export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/service-account.json + +# Setup project id. +export PROJECT_ID=$(cat "${KOKORO_GFILE_DIR}/project-id.txt") + +# Activate gcloud with service account credentials +gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS +gcloud config set project ${PROJECT_ID} + +# Decrypt system test secrets +./scripts/decrypt-secrets.sh + +# Run system tests which use a different noxfile +python3 -m nox -f samples/cloud-client/snippets/noxfile.py diff --git a/.kokoro/samples/lint/common.cfg b/.kokoro/samples/lint/common.cfg index f6b0c07c6..da47830eb 100644 --- a/.kokoro/samples/lint/common.cfg +++ b/.kokoro/samples/lint/common.cfg @@ -7,28 +7,25 @@ action { } } -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "lint" -} +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/test-samples.sh" -} +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" + +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" } - -# Download secrets for samples -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" \ No newline at end of file +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/build.sh" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.10/common.cfg b/.kokoro/samples/python3.10/common.cfg index de052d35f..da47830eb 100644 --- a/.kokoro/samples/python3.10/common.cfg +++ b/.kokoro/samples/python3.10/common.cfg @@ -7,34 +7,25 @@ action { } } -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "py-3.10" -} +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" -# Declare build specific Cloud project. -env_vars: { - key: "BUILD_SPECIFIC_GCLOUD_PROJECT" - value: "python-docs-samples-tests-310" -} +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/test-samples.sh" -} +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" } - -# Download secrets for samples -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" \ No newline at end of file +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/build.sh" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.7/common.cfg b/.kokoro/samples/python3.7/common.cfg index 7ca2eb0eb..da47830eb 100644 --- a/.kokoro/samples/python3.7/common.cfg +++ b/.kokoro/samples/python3.7/common.cfg @@ -7,34 +7,25 @@ action { } } -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "py-3.7" -} +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" -# Declare build specific Cloud project. -env_vars: { - key: "BUILD_SPECIFIC_GCLOUD_PROJECT" - value: "python-docs-samples-tests-py37" -} +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/test-samples.sh" -} +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" } - -# Download secrets for samples -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" \ No newline at end of file +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/build.sh" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.8/common.cfg b/.kokoro/samples/python3.8/common.cfg index fbd029e68..da47830eb 100644 --- a/.kokoro/samples/python3.8/common.cfg +++ b/.kokoro/samples/python3.8/common.cfg @@ -7,34 +7,25 @@ action { } } -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "py-3.8" -} +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" -# Declare build specific Cloud project. -env_vars: { - key: "BUILD_SPECIFIC_GCLOUD_PROJECT" - value: "python-docs-samples-tests-py38" -} +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/test-samples.sh" -} +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" } - -# Download secrets for samples -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" \ No newline at end of file +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/build.sh" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.9/common.cfg b/.kokoro/samples/python3.9/common.cfg index 07cda0ab2..da47830eb 100644 --- a/.kokoro/samples/python3.9/common.cfg +++ b/.kokoro/samples/python3.9/common.cfg @@ -7,34 +7,25 @@ action { } } -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "py-3.9" -} +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" -# Declare build specific Cloud project. -env_vars: { - key: "BUILD_SPECIFIC_GCLOUD_PROJECT" - value: "python-docs-samples-tests-py39" -} +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/test-samples.sh" -} +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" } - -# Download secrets for samples -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" \ No newline at end of file +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/build.sh" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" +} \ No newline at end of file diff --git a/samples/cloud-client/snippets/authenticate_explicit_with_adc.py b/samples/cloud-client/snippets/authenticate_explicit_with_adc.py new file mode 100644 index 000000000..8483bd7ea --- /dev/null +++ b/samples/cloud-client/snippets/authenticate_explicit_with_adc.py @@ -0,0 +1,55 @@ +# Copyright 2022 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. + +# [START auth_cloud_explicit_adc] + +from google.cloud import storage + +import google.oauth2.credentials +import google.auth + + +def authenticate_explicit_with_adc(): + """ + 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/samples/cloud-client/snippets/authenticate_implicit_with_adc.py b/samples/cloud-client/snippets/authenticate_implicit_with_adc.py new file mode 100644 index 000000000..006e75bf6 --- /dev/null +++ b/samples/cloud-client/snippets/authenticate_implicit_with_adc.py @@ -0,0 +1,46 @@ +# Copyright 2022 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. + +# [START auth_cloud_implicit_adc] + +from google.cloud import storage + + +def authenticate_implicit_with_adc(project_id="your-google-cloud-project-id"): + """ + 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] \ No newline at end of file diff --git a/samples/cloud-client/snippets/idtoken_from_impersonated_credentials.py b/samples/cloud-client/snippets/idtoken_from_impersonated_credentials.py new file mode 100644 index 000000000..a27e6cffd --- /dev/null +++ b/samples/cloud-client/snippets/idtoken_from_impersonated_credentials.py @@ -0,0 +1,75 @@ +# Copyright 2022 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. + +# [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): + """ + 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/samples/cloud-client/snippets/idtoken_from_metadata_server.py b/samples/cloud-client/snippets/idtoken_from_metadata_server.py new file mode 100644 index 000000000..00b6545cf --- /dev/null +++ b/samples/cloud-client/snippets/idtoken_from_metadata_server.py @@ -0,0 +1,50 @@ +# Copyright 2022 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. + +# [START auth_cloud_idtoken_metadata_server] + +import google +import google.oauth2.credentials +from google.auth import compute_engine +import google.auth.transport.requests + + +def idtoken_from_metadata_server(url: str): + """ + 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.abc.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/samples/cloud-client/snippets/idtoken_from_service_account.py b/samples/cloud-client/snippets/idtoken_from_service_account.py new file mode 100644 index 000000000..912035b0b --- /dev/null +++ b/samples/cloud-client/snippets/idtoken_from_service_account.py @@ -0,0 +1,50 @@ +# Copyright 2022 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. + +# [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): + """ + 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/samples/cloud-client/snippets/noxfile.py b/samples/cloud-client/snippets/noxfile.py new file mode 100644 index 000000000..e129dca59 --- /dev/null +++ b/samples/cloud-client/snippets/noxfile.py @@ -0,0 +1,48 @@ +# 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 os +import pathlib +import shutil + +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", +] + +@nox.session(python=["3.7", "3.8", "3.9", "3.10"]) +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", + ) \ No newline at end of file diff --git a/samples/cloud-client/snippets/noxfile_config.py b/samples/cloud-client/snippets/noxfile_config.py new file mode 100644 index 000000000..e892b338f --- /dev/null +++ b/samples/cloud-client/snippets/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/samples/cloud-client/snippets/requirements.txt b/samples/cloud-client/snippets/requirements.txt new file mode 100644 index 000000000..dbd3f00c4 --- /dev/null +++ b/samples/cloud-client/snippets/requirements.txt @@ -0,0 +1,4 @@ +google-cloud-compute==1.3.2 +google-cloud-storage==2.3.0 +google-auth==2.10.0 +pytest==7.1.2 \ No newline at end of file diff --git a/samples/cloud-client/snippets/snippets_test.py b/samples/cloud-client/snippets/snippets_test.py new file mode 100644 index 000000000..180882843 --- /dev/null +++ b/samples/cloud-client/snippets/snippets_test.py @@ -0,0 +1,77 @@ +# Copyright 2022 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. +import re + +from _pytest.capture import CaptureFixture + +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 + +import google +from google.oauth2 import service_account +import google.auth.transport.requests +import os + +CREDENTIALS, PROJECT = google.auth.default() +SERVICE_ACCOUNT_FILE = os.getenv("GOOGLE_APPLICATION_CREDENTIALS") + + +def test_authenticate_explicit_with_adc(capsys: CaptureFixture): + 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): + 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): + 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): + 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(): + 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): + 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/samples/cloud-client/snippets/verify_google_idtoken.py b/samples/cloud-client/snippets/verify_google_idtoken.py new file mode 100644 index 000000000..4d2216efd --- /dev/null +++ b/samples/cloud-client/snippets/verify_google_idtoken.py @@ -0,0 +1,62 @@ +# Copyright 2022 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. + +# [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="iap.googleapis.com", + jwk_url="https://www.googleapis.com/oauth2/v3/certs"): + """ + 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) + + # 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['email_verified'] == "True": + print(f"Email verified {result['email']}") + +# [END auth_cloud_verify_google_idtoken] diff --git a/system_tests/secrets.tar.enc b/system_tests/secrets.tar.enc index ec376950b..e8785c796 100644 Binary files a/system_tests/secrets.tar.enc and b/system_tests/secrets.tar.enc differ