Skip to content

feat: support ability to set universe domain in ServiceAccountJwtAccessCredentials #1754

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

Merged
merged 17 commits into from
May 28, 2025
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public class ServiceAccountJwtAccessCredentials extends Credentials
private final String privateKeyId;
private final URI defaultAudience;
private final String quotaProjectId;
private final String universeDomain;

private transient LoadingCache<JwtClaims, JwtCredentials> credentialsCache;

Expand All @@ -104,7 +105,14 @@ public class ServiceAccountJwtAccessCredentials extends Credentials
*/
private ServiceAccountJwtAccessCredentials(
String clientId, String clientEmail, PrivateKey privateKey, String privateKeyId) {
this(clientId, clientEmail, privateKey, privateKeyId, null, null);
this(
clientId,
clientEmail,
privateKey,
privateKeyId,
null,
null,
Credentials.GOOGLE_DEFAULT_UNIVERSE);
}

/**
Expand All @@ -115,21 +123,29 @@ private ServiceAccountJwtAccessCredentials(
* @param privateKey RSA private key object for the service account.
* @param privateKeyId Private key identifier for the service account. May be null.
* @param defaultAudience Audience to use if not provided by transport. May be null.
* @param universeDomain universe domain in the format some-domain.xyz. By default, sets it to
* googleapis.com
*/
private ServiceAccountJwtAccessCredentials(
String clientId,
String clientEmail,
PrivateKey privateKey,
String privateKeyId,
URI defaultAudience,
String quotaProjectId) {
String quotaProjectId,
String universeDomain) {
this.clientId = clientId;
this.clientEmail = Preconditions.checkNotNull(clientEmail);
this.privateKey = Preconditions.checkNotNull(privateKey);
this.privateKeyId = privateKeyId;
this.defaultAudience = defaultAudience;
this.credentialsCache = createCache();
this.quotaProjectId = quotaProjectId;
if (universeDomain == null || universeDomain.trim().isEmpty()) {
this.universeDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE;
} else {
this.universeDomain = universeDomain;
}
}

/**
Expand Down Expand Up @@ -160,6 +176,10 @@ static ServiceAccountJwtAccessCredentials fromJson(Map<String, Object> json, URI
String privateKeyPkcs8 = (String) json.get("private_key");
String privateKeyId = (String) json.get("private_key_id");
String quoataProjectId = (String) json.get("quota_project_id");
String rawUniverseDomain = (String) json.get("universe_domain");
String resolvedUniverseDomain =
(rawUniverseDomain == null) ? Credentials.GOOGLE_DEFAULT_UNIVERSE : rawUniverseDomain;

if (clientId == null
|| clientEmail == null
|| privateKeyPkcs8 == null
Expand All @@ -169,7 +189,13 @@ static ServiceAccountJwtAccessCredentials fromJson(Map<String, Object> json, URI
+ "expecting 'client_id', 'client_email', 'private_key' and 'private_key_id'.");
}
return ServiceAccountJwtAccessCredentials.fromPkcs8(
clientId, clientEmail, privateKeyPkcs8, privateKeyId, defaultAudience, quoataProjectId);
clientId,
clientEmail,
privateKeyPkcs8,
privateKeyId,
defaultAudience,
quoataProjectId,
resolvedUniverseDomain);
}

/**
Expand Down Expand Up @@ -207,7 +233,13 @@ public static ServiceAccountJwtAccessCredentials fromPkcs8(
URI defaultAudience)
throws IOException {
return ServiceAccountJwtAccessCredentials.fromPkcs8(
clientId, clientEmail, privateKeyPkcs8, privateKeyId, defaultAudience, null);
clientId,
clientEmail,
privateKeyPkcs8,
privateKeyId,
defaultAudience,
null,
Credentials.GOOGLE_DEFAULT_UNIVERSE);
}

static ServiceAccountJwtAccessCredentials fromPkcs8(
Expand All @@ -216,11 +248,18 @@ static ServiceAccountJwtAccessCredentials fromPkcs8(
String privateKeyPkcs8,
String privateKeyId,
URI defaultAudience,
String quotaProjectId)
String quotaProjectId,
String universeDomain)
throws IOException {
PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(privateKeyPkcs8);
return new ServiceAccountJwtAccessCredentials(
clientId, clientEmail, privateKey, privateKeyId, defaultAudience, quotaProjectId);
clientId,
clientEmail,
privateKey,
privateKeyId,
defaultAudience,
quotaProjectId,
universeDomain);
}

/**
Expand Down Expand Up @@ -352,7 +391,6 @@ public Map<String, List<String>> getRequestMetadata(URI uri) throws IOException
+ "defaultAudience to be specified");
}
}

try {
JwtClaims defaultClaims =
JwtClaims.newBuilder()
Expand Down Expand Up @@ -399,6 +437,12 @@ public final String getPrivateKeyId() {
return privateKeyId;
}

/** Returns the universe domain (example, googleapis.com) for the credentials instance. */
@Override
public final String getUniverseDomain() {
return universeDomain;
}

@Override
public String getAccount() {
return getClientEmail();
Expand Down Expand Up @@ -474,6 +518,7 @@ public static class Builder {
private String privateKeyId;
private URI defaultAudience;
private String quotaProjectId;
private String universeDomain;

protected Builder() {}

Expand All @@ -484,6 +529,7 @@ protected Builder(ServiceAccountJwtAccessCredentials credentials) {
this.privateKeyId = credentials.privateKeyId;
this.defaultAudience = credentials.defaultAudience;
this.quotaProjectId = credentials.quotaProjectId;
this.universeDomain = credentials.universeDomain;
}

@CanIgnoreReturnValue
Expand Down Expand Up @@ -522,6 +568,13 @@ public Builder setQuotaProjectId(String quotaProjectId) {
return this;
}

@CanIgnoreReturnValue
/** Sets the universe domain (example, googleapis.com). */
public Builder setUniverseDomain(String universeDomain) {
this.universeDomain = universeDomain;
return this;
}

public String getClientId() {
return clientId;
}
Expand All @@ -546,9 +599,20 @@ public String getQuotaProjectId() {
return quotaProjectId;
}

/** Returns the universe domain (example, googleapis.com) for the credentials instance. */
public String getUniverseDomain() {
return universeDomain;
}

public ServiceAccountJwtAccessCredentials build() {
return new ServiceAccountJwtAccessCredentials(
clientId, clientEmail, privateKey, privateKeyId, defaultAudience, quotaProjectId);
clientId,
clientEmail,
privateKey,
privateKeyId,
defaultAudience,
quotaProjectId,
universeDomain);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

package com.google.auth.oauth2;

import static com.google.auth.Credentials.GOOGLE_DEFAULT_UNIVERSE;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
Expand All @@ -41,6 +42,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.json.webtoken.JsonWebSignature;
Expand Down Expand Up @@ -111,6 +113,7 @@ public void constructor_allParameters_constructs() throws IOException {
assertEquals(privateKey, credentials.getPrivateKey());
assertEquals(SA_PRIVATE_KEY_ID, credentials.getPrivateKeyId());
assertEquals(QUOTA_PROJECT, credentials.getQuotaProjectId());
assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain());
}

@Test
Expand Down Expand Up @@ -829,6 +832,109 @@ public void onFailure(Throwable exception) {
assertTrue("Should have run onSuccess() callback", success.get());
}

@Test
public void fromJSON_noUniverseDomain() throws IOException {
GenericJson json =
writeServiceAccountJson(
SA_CLIENT_ID,
SA_CLIENT_EMAIL,
SA_PRIVATE_KEY_PKCS8,
"test-project-id",
SA_PRIVATE_KEY_ID,
QUOTA_PROJECT,
null);
ServiceAccountJwtAccessCredentials credentials =
ServiceAccountJwtAccessCredentials.fromJson(json, URI.create("default-aud"));
assertEquals(SA_CLIENT_ID, credentials.getClientId());
assertEquals(SA_CLIENT_EMAIL, credentials.getClientEmail());
assertEquals(
OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8), credentials.getPrivateKey());
assertEquals(QUOTA_PROJECT, credentials.getQuotaProjectId());
assertEquals(GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain());
}

@Test
public void fromJSON_UniverseDomainSet() throws IOException {
GenericJson json =
writeServiceAccountJson(
SA_CLIENT_ID,
SA_CLIENT_EMAIL,
SA_PRIVATE_KEY_PKCS8,
"test-project-id",
SA_PRIVATE_KEY_ID,
QUOTA_PROJECT,
"example.com");
ServiceAccountJwtAccessCredentials credentials =
ServiceAccountJwtAccessCredentials.fromJson(json, URI.create("default-aud"));
assertEquals(SA_CLIENT_ID, credentials.getClientId());
assertEquals(SA_CLIENT_EMAIL, credentials.getClientEmail());
assertEquals(
OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8), credentials.getPrivateKey());
assertEquals(QUOTA_PROJECT, credentials.getQuotaProjectId());
assertEquals("example.com", credentials.getUniverseDomain());
}

@Test
public void fromPkcs8_NoUniverseDomain() throws IOException {
ServiceAccountJwtAccessCredentials credentials =
ServiceAccountJwtAccessCredentials.fromPkcs8(
SA_CLIENT_ID, SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID);
assertEquals(SA_CLIENT_ID, credentials.getClientId());
assertEquals(SA_CLIENT_EMAIL, credentials.getClientEmail());
assertEquals(
OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8), credentials.getPrivateKey());
assertNull(credentials.getQuotaProjectId());
assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain());
}

@Test
public void fromPkcs8_CustomUniverseDomain() throws IOException {
ServiceAccountJwtAccessCredentials credentials =
ServiceAccountJwtAccessCredentials.fromPkcs8(
SA_CLIENT_ID,
SA_CLIENT_EMAIL,
SA_PRIVATE_KEY_PKCS8,
SA_PRIVATE_KEY_ID,
URI.create("default-aud"),
QUOTA_PROJECT,
"example.com");
assertEquals(SA_CLIENT_ID, credentials.getClientId());
assertEquals(SA_CLIENT_EMAIL, credentials.getClientEmail());
assertEquals(
OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8), credentials.getPrivateKey());
assertEquals(QUOTA_PROJECT, credentials.getQuotaProjectId());
assertEquals("example.com", credentials.getUniverseDomain());
}

@Test
public void builder_defaultUniverseDomain() throws IOException {
PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
ServiceAccountJwtAccessCredentials credentials =
ServiceAccountJwtAccessCredentials.newBuilder()
.setClientId(SA_CLIENT_ID)
.setClientEmail(SA_CLIENT_EMAIL)
.setPrivateKey(privateKey)
.setPrivateKeyId(SA_PRIVATE_KEY_ID)
.setDefaultAudience(URI.create("default-audience"))
.build();
assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain());
}

@Test
public void builder_customUniverseDomain() throws IOException {
PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
ServiceAccountJwtAccessCredentials credentials =
ServiceAccountJwtAccessCredentials.newBuilder()
.setClientId(SA_CLIENT_ID)
.setClientEmail(SA_CLIENT_EMAIL)
.setPrivateKey(privateKey)
.setPrivateKeyId(SA_PRIVATE_KEY_ID)
.setDefaultAudience(URI.create("default-audience"))
.setUniverseDomain("example.com")
.build();
assertEquals("example.com", credentials.getUniverseDomain());
}

private void verifyJwtAccess(
Map<String, List<String>> metadata,
String expectedEmail,
Expand Down Expand Up @@ -863,4 +969,38 @@ private static void testFromStreamException(InputStream stream, String expectedM
assertTrue(expected.getMessage().contains(expectedMessageContent));
}
}

private GenericJson writeServiceAccountJson(
String clientId,
String clientEmail,
String privateKeyPkcs8,
String privateKeyId,
String projectId,
String quotaProjectId,
String universeDomain) {
GenericJson json = new GenericJson();
if (clientId != null) {
json.put("client_id", clientId);
}
if (clientEmail != null) {
json.put("client_email", clientEmail);
}
if (privateKeyPkcs8 != null) {
json.put("private_key", privateKeyPkcs8);
}
if (privateKeyId != null) {
json.put("private_key_id", privateKeyId);
}
if (projectId != null) {
json.put("project_id", projectId);
}
if (quotaProjectId != null) {
json.put("quota_project_id", quotaProjectId);
}
if (universeDomain != null) {
json.put("universe_domain", universeDomain);
}
json.put("type", GoogleCredentials.SERVICE_ACCOUNT_FILE_TYPE);
return json;
}
}
Loading