diff --git a/credentials/java/com/google/auth/CredentialTypeForMetrics.java b/credentials/java/com/google/auth/CredentialTypeForMetrics.java
new file mode 100644
index 000000000..ccea4ea7b
--- /dev/null
+++ b/credentials/java/com/google/auth/CredentialTypeForMetrics.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.auth;
+
+/**
+ * Defines the different types of credentials that can be used for metrics.
+ *
+ *
Each credential type is associated with a label that is used for reporting purposes. Add new
+ * enum constant only when corresponding configs established.
+ *
+ *
Credentials with type {@code CredentialTypeForMetrics.DO_NOT_SEND} is default value for
+ * credential implementations that do not set type specifically. It is not expected to send metrics.
+ *
+ *
+ *
+ * @see #getLabel()
+ */
+public enum CredentialTypeForMetrics {
+ USER_CREDENTIALS("u"),
+ SERVICE_ACCOUNT_CREDENTIALS_AT("sa"),
+ SERVICE_ACCOUNT_CREDENTIALS_JWT("jwt"),
+ VM_CREDENTIALS("mds"),
+ IMPERSONATED_CREDENTIALS("imp"),
+ DO_NOT_SEND("dns");
+
+ private String label;
+
+ private CredentialTypeForMetrics(String label) {
+ this.label = label;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+}
diff --git a/credentials/java/com/google/auth/Credentials.java b/credentials/java/com/google/auth/Credentials.java
index 493b970ad..b1579db61 100644
--- a/credentials/java/com/google/auth/Credentials.java
+++ b/credentials/java/com/google/auth/Credentials.java
@@ -70,6 +70,18 @@ public String getUniverseDomain() throws IOException {
return GOOGLE_DEFAULT_UNIVERSE;
}
+ /**
+ * Gets the credential type used for internal metrics header.
+ *
+ *
The default is {@code CredentialTypeForMetrics.DO_NOT_SEND}. For a credential that is
+ * established to track for metrics, this default should be overridden.
+ *
+ * @return a enum value for credential type
+ */
+ public CredentialTypeForMetrics getMetricsCredentialType() {
+ return CredentialTypeForMetrics.DO_NOT_SEND;
+ }
+
/**
* Get the current request metadata, refreshing tokens if required.
*
diff --git a/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java
index 9996c045d..c1aeb28c9 100644
--- a/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java
+++ b/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java
@@ -41,10 +41,12 @@
import com.google.api.client.http.HttpStatusCodes;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.util.GenericData;
+import com.google.auth.CredentialTypeForMetrics;
import com.google.auth.Credentials;
import com.google.auth.Retryable;
import com.google.auth.ServiceAccountSigner;
import com.google.auth.http.HttpTransportFactory;
+import com.google.auth.oauth2.MetricsUtils.RequestType;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects.ToStringHelper;
@@ -133,7 +135,6 @@ public class ComputeEngineCredentials extends GoogleCredentials
*/
private ComputeEngineCredentials(ComputeEngineCredentials.Builder builder) {
super(builder);
-
this.transportFactory =
firstNonNull(
builder.getHttpTransportFactory(),
@@ -153,6 +154,11 @@ private ComputeEngineCredentials(ComputeEngineCredentials.Builder builder) {
}
}
+ @Override
+ public CredentialTypeForMetrics getMetricsCredentialType() {
+ return CredentialTypeForMetrics.VM_CREDENTIALS;
+ }
+
/** Clones the compute engine account with the specified scopes. */
@Override
public GoogleCredentials createScoped(Collection newScopes) {
@@ -234,7 +240,8 @@ public String getUniverseDomain() throws IOException {
}
private String getUniverseDomainFromMetadata() throws IOException {
- HttpResponse response = getMetadataResponse(getUniverseDomainUrl());
+ HttpResponse response =
+ getMetadataResponse(getUniverseDomainUrl(), RequestType.UNTRACKED, false);
int statusCode = response.getStatusCode();
if (statusCode == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
return Credentials.GOOGLE_DEFAULT_UNIVERSE;
@@ -260,7 +267,8 @@ private String getUniverseDomainFromMetadata() throws IOException {
/** Refresh the access token by getting it from the GCE metadata server */
@Override
public AccessToken refreshAccessToken() throws IOException {
- HttpResponse response = getMetadataResponse(createTokenUrlWithScopes());
+ HttpResponse response =
+ getMetadataResponse(createTokenUrlWithScopes(), RequestType.ACCESS_TOKEN_REQUEST, true);
int statusCode = response.getStatusCode();
if (statusCode == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
throw new IOException(
@@ -325,7 +333,8 @@ public IdToken idTokenWithAudience(String targetAudience, List additionalFields)
+ Map additionalFields,
+ CredentialTypeForMetrics credentialTypeForMetrics)
throws IOException {
String idTokenUrl = String.format(ID_TOKEN_URL_FORMAT, serviceAccountEmail);
@@ -211,6 +215,11 @@ static IdToken getIdToken(
request.setParser(parser);
request.setThrowExceptionOnExecuteError(false);
+ MetricsUtils.setMetricsHeader(
+ request,
+ MetricsUtils.getGoogleCredentialsMetricsHeader(
+ RequestType.ID_TOKEN_REQUEST, credentialTypeForMetrics));
+
HttpResponse response = request.execute();
int statusCode = response.getStatusCode();
if (statusCode >= 400 && statusCode < HttpStatusCodes.STATUS_CODE_SERVER_ERROR) {
diff --git a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java
index f66b29a96..9430d44dd 100644
--- a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java
+++ b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java
@@ -43,9 +43,11 @@
import com.google.api.client.http.json.JsonHttpContent;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.util.GenericData;
+import com.google.auth.CredentialTypeForMetrics;
import com.google.auth.ServiceAccountSigner;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.http.HttpTransportFactory;
+import com.google.auth.oauth2.MetricsUtils.RequestType;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMap;
@@ -406,7 +408,7 @@ static ImpersonatedCredentials fromJson(
.setSourceCredentials(sourceCredentials)
.setTargetPrincipal(targetPrincipal)
.setDelegates(delegates)
- .setScopes(new ArrayList())
+ .setScopes(new ArrayList<>())
.setLifetime(DEFAULT_LIFETIME_IN_SECONDS)
.setHttpTransportFactory(transportFactory)
.setQuotaProjectId(quotaProjectId)
@@ -431,6 +433,11 @@ public GoogleCredentials createScoped(Collection scopes) {
.build();
}
+ @Override
+ public CredentialTypeForMetrics getMetricsCredentialType() {
+ return CredentialTypeForMetrics.IMPERSONATED_CREDENTIALS;
+ }
+
/**
* Clones the impersonated credentials with a new calendar.
*
@@ -508,6 +515,10 @@ public AccessToken refreshAccessToken() throws IOException {
HttpRequest request = requestFactory.buildPostRequest(url, requestContent);
adapter.initialize(request);
request.setParser(parser);
+ MetricsUtils.setMetricsHeader(
+ request,
+ MetricsUtils.getGoogleCredentialsMetricsHeader(
+ RequestType.ACCESS_TOKEN_REQUEST, getMetricsCredentialType()));
HttpResponse response = null;
try {
@@ -557,7 +568,8 @@ public IdToken idTokenWithAudience(String targetAudience, List> getRequestMetadata(URI uri) throws IOException
}
}
- private Map> getRequestMetadataForGdu(URI uri) throws IOException {
+ @Override
+ public CredentialTypeForMetrics getMetricsCredentialType() {
+ return shouldUseAssertionFlow()
+ ? CredentialTypeForMetrics.SERVICE_ACCOUNT_CREDENTIALS_AT
+ : CredentialTypeForMetrics.SERVICE_ACCOUNT_CREDENTIALS_JWT;
+ }
+
+ private boolean shouldUseAssertionFlow() {
// If scopes are provided, but we cannot use self-signed JWT or domain-wide delegation is
// configured then use scopes to get access token.
- if ((!createScopedRequired() && !useJwtAccessWithScope)
- || isConfiguredForDomainWideDelegation()) {
- return super.getRequestMetadata(uri);
- }
+ return ((!createScopedRequired() && !useJwtAccessWithScope)
+ || isConfiguredForDomainWideDelegation());
+ }
- return getRequestMetadataWithSelfSignedJwt(uri);
+ private Map> getRequestMetadataForGdu(URI uri) throws IOException {
+ return shouldUseAssertionFlow()
+ ? super.getRequestMetadata(uri)
+ : getRequestMetadataWithSelfSignedJwt(uri);
}
private Map> getRequestMetadataForNonGdu(URI uri) throws IOException {
diff --git a/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java b/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java
index 67859c33d..a2ba5a52d 100644
--- a/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java
+++ b/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java
@@ -45,7 +45,9 @@
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.util.GenericData;
import com.google.api.client.util.Preconditions;
+import com.google.auth.CredentialTypeForMetrics;
import com.google.auth.http.HttpTransportFactory;
+import com.google.auth.oauth2.MetricsUtils.RequestType;
import com.google.common.base.MoreObjects;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.ByteArrayInputStream;
@@ -97,6 +99,11 @@ private UserCredentials(Builder builder) {
"Either accessToken or refreshToken must not be null");
}
+ @Override
+ public CredentialTypeForMetrics getMetricsCredentialType() {
+ return CredentialTypeForMetrics.USER_CREDENTIALS;
+ }
+
/**
* Returns user credentials defined by JSON contents using the format supported by the Cloud SDK.
*
@@ -264,6 +271,11 @@ private GenericData doRefreshAccessToken() throws IOException {
HttpRequestFactory requestFactory = transportFactory.create().createRequestFactory();
HttpRequest request = requestFactory.buildPostRequest(new GenericUrl(tokenServerUri), content);
+
+ MetricsUtils.setMetricsHeader(
+ request,
+ MetricsUtils.getGoogleCredentialsMetricsHeader(
+ RequestType.UNTRACKED, getMetricsCredentialType()));
request.setParser(new JsonObjectParser(JSON_FACTORY));
HttpResponse response;
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java
index 399456187..10975d874 100644
--- a/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java
+++ b/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java
@@ -285,6 +285,11 @@ public void getRequestMetadata_hasAccessToken() throws IOException {
Map> metadata = credentials.getRequestMetadata(CALL_URI);
TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
+ // verify metrics header added and other header intact
+ Map> requestHeaders = transportFactory.transport.getRequest().getHeaders();
+ com.google.auth.oauth2.TestUtils.validateMetricsHeader(requestHeaders, "at", "mds");
+ assertTrue(requestHeaders.containsKey("metadata-flavor"));
+ assertTrue(requestHeaders.get("metadata-flavor").contains("Google"));
}
@Test
@@ -458,6 +463,10 @@ public void getAccount_sameAs() throws IOException {
ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
assertEquals(defaultAccountEmail, credentials.getAccount());
+
+ // metric headers are not supported for getAccount()
+ Map> headers = transportFactory.transport.getRequest().getHeaders();
+ assertFalse(headers.containsKey(MetricsUtils.API_CLIENT_HEADER));
}
@Test
@@ -949,6 +958,10 @@ public void idTokenWithAudience_full() throws IOException {
assertTrue("Full ID Token format not provided", p.containsKey("google"));
ArrayMap googleClaim = (ArrayMap) p.get("google");
assertTrue(googleClaim.containsKey("compute_engine"));
+
+ // verify metrics header
+ Map> requestHeaders = transportFactory.transport.getRequest().getHeaders();
+ com.google.auth.oauth2.TestUtils.validateMetricsHeader(requestHeaders, "it", "mds");
}
@Test
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java b/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java
index 3f0b2f1a5..f715908a1 100644
--- a/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java
+++ b/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java
@@ -468,6 +468,11 @@ public void getDefaultCredentials_compute_quotaProject() throws IOException {
assertTrue(defaultCredentials instanceof ComputeEngineCredentials);
assertEquals(QUOTA_PROJECT_FROM_ENVIRONMENT, defaultCredentials.getQuotaProjectId());
+
+ // verify metrics header
+ Map> headers = transportFactory.transport.getRequest().getHeaders();
+ com.google.auth.oauth2.TestUtils.validateMetricsHeader(headers, "mds", "untracked");
+ assertEquals("Google", headers.get("metadata-flavor").get(0));
}
@Test
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java
index 6e0b34e60..6fbeac652 100644
--- a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java
+++ b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java
@@ -471,6 +471,12 @@ public void refreshAccessToken_success() throws IOException, IllegalStateExcepti
assertEquals(ACCESS_TOKEN, targetCredentials.refreshAccessToken().getTokenValue());
assertEquals(
DEFAULT_IMPERSONATION_URL, mockTransportFactory.getTransport().getRequest().getUrl());
+
+ // verify metrics header added and authorization header intact
+ Map> requestHeader =
+ mockTransportFactory.getTransport().getRequest().getHeaders();
+ com.google.auth.oauth2.TestUtils.validateMetricsHeader(requestHeader, "at", "imp");
+ assertTrue(requestHeader.containsKey("authorization"));
}
@Test
@@ -868,6 +874,12 @@ public void idTokenWithAudience_withEmail() throws IOException {
assertEquals(TOKEN_WITH_EMAIL, tokenCredential.getAccessToken().getTokenValue());
Payload p = tokenCredential.getIdToken().getJsonWebSignature().getPayload();
assertTrue(p.containsKey("email"));
+
+ // verify metrics header
+ Map> requestHeader =
+ mockTransportFactory.getTransport().getRequest().getHeaders();
+ com.google.auth.oauth2.TestUtils.validateMetricsHeader(requestHeader, "it", "imp");
+ assertTrue(requestHeader.containsKey("authorization"));
}
@Test
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MetricsUtilsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/MetricsUtilsTest.java
index aba4d98c9..eb035d09c 100644
--- a/oauth2_http/javatests/com/google/auth/oauth2/MetricsUtilsTest.java
+++ b/oauth2_http/javatests/com/google/auth/oauth2/MetricsUtilsTest.java
@@ -33,6 +33,8 @@
import static org.junit.Assert.*;
+import com.google.auth.CredentialTypeForMetrics;
+import com.google.auth.oauth2.MetricsUtils.RequestType;
import java.util.regex.Pattern;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -40,17 +42,48 @@
@RunWith(JUnit4.class)
public class MetricsUtilsTest {
+ static final String VERSION_PATTERN =
+ "gl-java/[\\d\\._-]+ auth/\\d+\\.\\d+\\.\\d+(-sp\\.\\d+)?(-SNAPSHOT)?";
+ static final String AUTH_REQUEST_TYPE_PATTERN =
+ String.format(" %s/[\\w]+", MetricsUtils.AUTH_REQUEST_TYPE);
+ static final String CRED_TYPE_PATTERN = String.format(" %s/[\\w]+", MetricsUtils.CRED_TYPE);
+ static final String METRICS_PATTERN_FULL =
+ VERSION_PATTERN + AUTH_REQUEST_TYPE_PATTERN + CRED_TYPE_PATTERN;
+ static final String METRICS_PATTERN_NO_REQUEST_TYPE = VERSION_PATTERN + CRED_TYPE_PATTERN;
+ static final String METRICS_PATTERN_NO_CRED_TYPE = VERSION_PATTERN + AUTH_REQUEST_TYPE_PATTERN;
- public static void assertVersions(String version) {
- assertNotNull("version constant should not be null", version);
- Pattern semverPattern =
- Pattern.compile("gl-java/[\\d\\._-]+ auth/\\d+\\.\\d+\\.\\d+(-sp\\.\\d+)?(-SNAPSHOT)?");
- assertTrue(semverPattern.matcher(version).matches());
+ private static void assertPatterns(String contentToTest, String patternString) {
+ assertNotNull("metric header string should not be null", contentToTest);
+ Pattern pattern = Pattern.compile(patternString);
+ assertTrue(pattern.matcher(contentToTest).matches());
}
@Test
- public void getVersionWorks() {
+ public void getLanguageAndAuthLibraryVersionsTest() {
String version = MetricsUtils.getLanguageAndAuthLibraryVersions();
- assertVersions(version);
+ assertPatterns(version, VERSION_PATTERN);
+ }
+
+ @Test
+ public void getGoogleCredentialsMetricsHeaderTest() {
+ String metricsStringNoRequestType =
+ MetricsUtils.getGoogleCredentialsMetricsHeader(
+ RequestType.UNTRACKED, CredentialTypeForMetrics.USER_CREDENTIALS);
+ assertPatterns(metricsStringNoRequestType, METRICS_PATTERN_NO_REQUEST_TYPE);
+
+ String metricsStringNoCredType =
+ MetricsUtils.getGoogleCredentialsMetricsHeader(
+ RequestType.METADATA_SERVER_PING, CredentialTypeForMetrics.DO_NOT_SEND);
+ assertPatterns(metricsStringNoCredType, METRICS_PATTERN_NO_CRED_TYPE);
+
+ String metricsString =
+ MetricsUtils.getGoogleCredentialsMetricsHeader(
+ RequestType.ID_TOKEN_REQUEST, CredentialTypeForMetrics.SERVICE_ACCOUNT_CREDENTIALS_AT);
+ assertPatterns(metricsString, METRICS_PATTERN_FULL);
+
+ String metricsStringNoTypes =
+ MetricsUtils.getGoogleCredentialsMetricsHeader(
+ RequestType.UNTRACKED, CredentialTypeForMetrics.DO_NOT_SEND);
+ assertPatterns(metricsStringNoTypes, VERSION_PATTERN);
}
}
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java
index c1f0ac4b0..d21491027 100644
--- a/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java
+++ b/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java
@@ -62,6 +62,8 @@ public class MockMetadataServerTransport extends MockHttpTransport {
private byte[] signature;
+ private MockLowLevelHttpRequest request;
+
public MockMetadataServerTransport() {}
public MockMetadataServerTransport(String accessToken) {
@@ -101,31 +103,41 @@ public void setIdToken(String idToken) {
this.idToken = idToken;
}
+ public MockLowLevelHttpRequest getRequest() {
+ return request;
+ }
+
@Override
public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
if (url.startsWith(ComputeEngineCredentials.getTokenServerEncodedUrl())) {
- return getMockRequestForTokenEndpoint(url);
+ this.request = getMockRequestForTokenEndpoint(url);
+ return this.request;
} else if (isGetServiceAccountsUrl(url)) {
- return getMockRequestForServiceAccount(url);
+ this.request = getMockRequestForServiceAccount(url);
+ return this.request;
} else if (isSignRequestUrl(url)) {
- return getMockRequestForSign(url);
+ this.request = getMockRequestForSign(url);
+ return this.request;
} else if (isIdentityDocumentUrl(url)) {
- return getMockRequestForIdentityDocument(url);
+ this.request = getMockRequestForIdentityDocument(url);
+ return this.request;
}
- return new MockLowLevelHttpRequest(url) {
- @Override
- public LowLevelHttpResponse execute() {
- if (requestStatusCode != null) {
- return new MockLowLevelHttpResponse()
- .setStatusCode(requestStatusCode)
- .setContent("Metadata Error");
- }
-
- MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
- response.addHeader("Metadata-Flavor", "Google");
- return response;
- }
- };
+ this.request =
+ new MockLowLevelHttpRequest(url) {
+ @Override
+ public LowLevelHttpResponse execute() {
+ if (requestStatusCode != null) {
+ return new MockLowLevelHttpResponse()
+ .setStatusCode(requestStatusCode)
+ .setContent("Metadata Error");
+ }
+
+ MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
+ response.addHeader("Metadata-Flavor", "Google");
+ return response;
+ }
+ };
+ return this.request;
}
private MockLowLevelHttpRequest getMockRequestForSign(String url) {
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java
index f79338f7a..2c8accbeb 100644
--- a/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java
+++ b/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java
@@ -52,6 +52,7 @@
import com.google.api.client.testing.http.MockLowLevelHttpResponse;
import com.google.api.client.util.Clock;
import com.google.api.client.util.Joiner;
+import com.google.auth.CredentialTypeForMetrics;
import com.google.auth.Credentials;
import com.google.auth.RequestMetadataCallback;
import com.google.auth.TestUtils;
@@ -615,6 +616,11 @@ public void getRequestMetadata_customTokenServer_hasAccessToken() throws IOExcep
Map> metadata = credentials.getRequestMetadata(CALL_URI);
TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
+
+ // verify header
+ Map> accessTokenRequestHeader =
+ transportFactory.transport.getRequest().getHeaders();
+ com.google.auth.oauth2.TestUtils.validateMetricsHeader(accessTokenRequestHeader, "at", "sa");
}
@Test
@@ -857,6 +863,11 @@ public void idTokenWithAudience_oauthFlow_targetAudienceMatchesAudClaim() throws
transport.addServiceAccount(CLIENT_EMAIL, accessToken1);
TestUtils.assertContainsBearerToken(credentials.getRequestMetadata(CALL_URI), accessToken1);
+ // verify access token request metrics headers
+ Map> accessTokenRequestHeader =
+ transportFactory.transport.getRequest().getHeaders();
+ com.google.auth.oauth2.TestUtils.validateMetricsHeader(accessTokenRequestHeader, "at", "sa");
+
String targetAudience = "https://foo.bar";
IdTokenCredentials tokenCredential =
IdTokenCredentials.newBuilder()
@@ -871,6 +882,11 @@ public void idTokenWithAudience_oauthFlow_targetAudienceMatchesAudClaim() throws
assertEquals(
targetAudience,
tokenCredential.getIdToken().getJsonWebSignature().getPayload().getAudience());
+
+ // verify id token request metrics headers
+ Map> idTokenRequestHeader =
+ transportFactory.transport.getRequest().getHeaders();
+ com.google.auth.oauth2.TestUtils.validateMetricsHeader(idTokenRequestHeader, "it", "sa");
}
@Test
@@ -1590,6 +1606,12 @@ public void getRequestMetadata_withScopes_selfSignedJWT() throws IOException {
Map> metadata = credentials.getRequestMetadata(CALL_URI);
assertNotNull(((ServiceAccountCredentials) credentials).getSelfSignedJwtCredentialsWithScope());
verifyJwtAccess(metadata, "dummy.scope");
+
+ // Verify credentialType is correctly set. This is used for token usage metrics.
+ // Self signed jwt flow doesn’t call any token endpoint, thus no token request metrics.
+ assertEquals(
+ CredentialTypeForMetrics.SERVICE_ACCOUNT_CREDENTIALS_JWT,
+ credentials.getMetricsCredentialType());
}
@Test
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/TestUtils.java b/oauth2_http/javatests/com/google/auth/oauth2/TestUtils.java
new file mode 100644
index 000000000..99af8c106
--- /dev/null
+++ b/oauth2_http/javatests/com/google/auth/oauth2/TestUtils.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2024, Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.auth.oauth2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+import java.util.Map;
+
+public class TestUtils {
+ static void validateMetricsHeader(
+ Map> headers, String requestType, String credentialType) {
+ assertTrue(headers.containsKey(MetricsUtils.API_CLIENT_HEADER));
+ String actualMetricsValue = headers.get(MetricsUtils.API_CLIENT_HEADER).get(0);
+ String expectedMetricsValue;
+ if (requestType.equals("untracked")) {
+ expectedMetricsValue =
+ String.format(
+ "%s %s/%s",
+ MetricsUtils.getLanguageAndAuthLibraryVersions(),
+ MetricsUtils.CRED_TYPE,
+ credentialType);
+ } else if (credentialType.equals("untracked")) {
+ expectedMetricsValue =
+ String.format(
+ "%s %s/%s",
+ MetricsUtils.getLanguageAndAuthLibraryVersions(),
+ MetricsUtils.AUTH_REQUEST_TYPE,
+ requestType);
+ } else {
+ expectedMetricsValue =
+ String.format(
+ "%s %s/%s %s/%s",
+ MetricsUtils.getLanguageAndAuthLibraryVersions(),
+ MetricsUtils.AUTH_REQUEST_TYPE,
+ requestType,
+ MetricsUtils.CRED_TYPE,
+ credentialType);
+ }
+ assertEquals(expectedMetricsValue, actualMetricsValue);
+ }
+}
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java
index 7fc0a256f..a9019939f 100644
--- a/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java
+++ b/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java
@@ -724,6 +724,12 @@ public void IdTokenCredentials_WithUserEmailScope_success() throws IOException {
assertEquals(ACCESS_TOKEN, credentials.getAccessToken().getTokenValue());
+ // verify access token request metrics headers
+ Map> accessTokenRequestHeader =
+ transportFactory.transport.getRequest().getHeaders();
+ com.google.auth.oauth2.TestUtils.validateMetricsHeader(
+ accessTokenRequestHeader, "untracked", "u");
+
IdTokenCredentials tokenCredential =
IdTokenCredentials.newBuilder().setIdTokenProvider(credentials).build();
@@ -735,6 +741,11 @@ public void IdTokenCredentials_WithUserEmailScope_success() throws IOException {
assertEquals(DEFAULT_ID_TOKEN, tokenCredential.getAccessToken().getTokenValue());
assertEquals(DEFAULT_ID_TOKEN, tokenCredential.getIdToken().getTokenValue());
+
+ // verify id token request metrics headers, same as access token request
+ Map> idTokenRequestHeader =
+ transportFactory.transport.getRequest().getHeaders();
+ com.google.auth.oauth2.TestUtils.validateMetricsHeader(idTokenRequestHeader, "untracked", "u");
}
@Test