Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enable self signed jwt for service account credentials #1553

Merged
merged 1 commit into from Oct 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 22 additions & 0 deletions googleapiclient/discovery.py
Expand Up @@ -44,6 +44,7 @@
import google.api_core.client_options
from google.auth.transport import mtls
from google.auth.exceptions import MutualTLSChannelError
from google.oauth2 import service_account

try:
import google_auth_httplib2
Expand Down Expand Up @@ -188,6 +189,7 @@ def build(
adc_key_path=None,
num_retries=1,
static_discovery=None,
always_use_jwt_access=True,
):
"""Construct a Resource for interacting with an API.
Expand Down Expand Up @@ -246,6 +248,9 @@ def build(
on the value of `discoveryServiceUrl`. `static_discovery` will default to
`True` when `discoveryServiceUrl` is also not provided, otherwise it will
default to `False`.
always_use_jwt_access: Boolean, whether always use self signed JWT for service
account credentials. This only applies to
google.oauth2.service_account.Credentials.
Returns:
A Resource object with methods for interacting with the service.
Expand Down Expand Up @@ -301,6 +306,7 @@ def build(
client_options=client_options,
adc_cert_path=adc_cert_path,
adc_key_path=adc_key_path,
always_use_jwt_access=always_use_jwt_access,
)
break # exit if a service was created
except HttpError as e:
Expand Down Expand Up @@ -441,6 +447,7 @@ def build_from_document(
client_options=None,
adc_cert_path=None,
adc_key_path=None,
always_use_jwt_access=True,
):
"""Create a Resource for interacting with an API.
Expand Down Expand Up @@ -490,6 +497,9 @@ def build_from_document(
`true` in order to use this field, otherwise this field doesn't nothing.
More details on the environment variables are here:
https://google.aip.dev/auth/4114
always_use_jwt_access: Boolean, whether always use self signed JWT for service
account credentials. This only applies to
google.oauth2.service_account.Credentials.
Returns:
A Resource object with methods for interacting with the service.
Expand Down Expand Up @@ -530,6 +540,7 @@ def build_from_document(

# If an API Endpoint is provided on client options, use that as the base URL
base = urllib.parse.urljoin(service["rootUrl"], service["servicePath"])
audience_for_self_signed_jwt = base
if client_options.api_endpoint:
base = client_options.api_endpoint

Expand Down Expand Up @@ -572,6 +583,17 @@ def build_from_document(
if not client_options.scopes:
credentials = _auth.with_scopes(credentials, scopes)

# For google-auth service account credentials, enable self signed JWT if
# always_use_jwt_access is true.
if (
credentials
and isinstance(credentials, service_account.Credentials)
and always_use_jwt_access
and hasattr(service_account.Credentials, "with_always_use_jwt_access")
):
credentials = credentials.with_always_use_jwt_access(always_use_jwt_access)
credentials._create_self_signed_jwt(audience_for_self_signed_jwt)

# If credentials are provided, create an authorized http instance;
# otherwise, skip authentication.
if credentials:
Expand Down
10 changes: 10 additions & 0 deletions tests/data/service_account.json
@@ -0,0 +1,10 @@
{
"type": "service_account",
"project_id": "example-project",
"private_key_id": "1",
"private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj\n7wZgkdmM7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/\nxmVU1WeruQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYs\nSliS5qQpgyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18\npe+zpyl4+WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xk\nSBc//fy3ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABAoIBAQDGGHzQxGKX+ANk\nnQi53v/c6632dJKYXVJC+PDAz4+bzU800Y+n/bOYsWf/kCp94XcG4Lgsdd0Gx+Zq\nHD9CI1IcqqBRR2AFscsmmX6YzPLTuEKBGMW8twaYy3utlFxElMwoUEsrSWRcCA1y\nnHSDzTt871c7nxCXHxuZ6Nm/XCL7Bg8uidRTSC1sQrQyKgTPhtQdYrPQ4WZ1A4J9\nIisyDYmZodSNZe5P+LTJ6M1SCgH8KH9ZGIxv3diMwzNNpk3kxJc9yCnja4mjiGE2\nYCNusSycU5IhZwVeCTlhQGcNeV/skfg64xkiJE34c2y2ttFbdwBTPixStGaF09nU\nZ422D40BAoGBAPvVyRRsC3BF+qZdaSMFwI1yiXY7vQw5+JZh01tD28NuYdRFzjcJ\nvzT2n8LFpj5ZfZFvSMLMVEFVMgQvWnN0O6xdXvGov6qlRUSGaH9u+TCPNnIldjMP\nB8+xTwFMqI7uQr54wBB+Poq7dVRP+0oHb0NYAwUBXoEuvYo3c/nDoRcZAoGBAOWl\naLHjMv4CJbArzT8sPfic/8waSiLV9Ixs3Re5YREUTtnLq7LoymqB57UXJB3BNz/2\neCueuW71avlWlRtE/wXASj5jx6y5mIrlV4nZbVuyYff0QlcG+fgb6pcJQuO9DxMI\naqFGrWP3zye+LK87a6iR76dS9vRU+bHZpSVvGMKJAoGAFGt3TIKeQtJJyqeUWNSk\nklORNdcOMymYMIlqG+JatXQD1rR6ThgqOt8sgRyJqFCVT++YFMOAqXOBBLnaObZZ\nCFbh1fJ66BlSjoXff0W+SuOx5HuJJAa5+WtFHrPajwxeuRcNa8jwxUsB7n41wADu\nUqWWSRedVBg4Ijbw3nWwYDECgYB0pLew4z4bVuvdt+HgnJA9n0EuYowVdadpTEJg\nsoBjNHV4msLzdNqbjrAqgz6M/n8Ztg8D2PNHMNDNJPVHjJwcR7duSTA6w2p/4k28\nbvvk/45Ta3XmzlxZcZSOct3O31Cw0i2XDVc018IY5be8qendDYM08icNo7vQYkRH\n504kQQKBgQDjx60zpz8ozvm1XAj0wVhi7GwXe+5lTxiLi9Fxq721WDxPMiHDW2XL\nYXfFVy/9/GIMvEiGYdmarK1NW+VhWl1DC5xhDg0kvMfxplt4tynoq1uTsQTY31Mx\nBeF5CT/JuNYk3bEBF0H/Q3VGO1/ggVS+YezdFbLWIRoMnLj6XCFEGg==\n-----END RSA PRIVATE KEY-----\n",
"client_email": "service-account@example.com",
"client_id": "1234",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token"
}
27 changes: 27 additions & 0 deletions tests/test_discovery.py
Expand Up @@ -685,6 +685,33 @@ def test_credentials_file_from_client_options(self):
"credentials.json", scopes=None, quota_project_id=None
)

def test_self_signed_jwt_enabled(self):
service_account_file_path = os.path.join(DATA_DIR, "service_account.json")
creds = google.oauth2.service_account.Credentials.from_service_account_file(service_account_file_path)

discovery = read_datafile("logging.json")

with mock.patch("google.oauth2.service_account.Credentials._create_self_signed_jwt") as _create_self_signed_jwt:
build_from_document(
discovery,
credentials=creds,
)
_create_self_signed_jwt.assert_called_with("https://logging.googleapis.com/")

def test_self_signed_jwt_disabled(self):
service_account_file_path = os.path.join(DATA_DIR, "service_account.json")
creds = google.oauth2.service_account.Credentials.from_service_account_file(service_account_file_path)

discovery = read_datafile("logging.json")

with mock.patch("google.oauth2.service_account.Credentials._create_self_signed_jwt") as _create_self_signed_jwt:
build_from_document(
discovery,
credentials=creds,
always_use_jwt_access=False,
)
_create_self_signed_jwt.assert_not_called()


REGULAR_ENDPOINT = "https://www.googleapis.com/plus/v1/"
MTLS_ENDPOINT = "https://www.mtls.googleapis.com/plus/v1/"
Expand Down