diff --git a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountJwtAccessCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountJwtAccessCredentials.java index 6fc338405..6e2ae2874 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountJwtAccessCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountJwtAccessCredentials.java @@ -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 credentialsCache; @@ -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); } /** @@ -115,6 +123,8 @@ 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, @@ -122,7 +132,8 @@ private ServiceAccountJwtAccessCredentials( 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); @@ -130,6 +141,11 @@ private ServiceAccountJwtAccessCredentials( 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; + } } /** @@ -160,6 +176,10 @@ static ServiceAccountJwtAccessCredentials fromJson(Map 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 @@ -169,7 +189,13 @@ static ServiceAccountJwtAccessCredentials fromJson(Map 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); } /** @@ -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( @@ -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); } /** @@ -352,7 +391,6 @@ public Map> getRequestMetadata(URI uri) throws IOException + "defaultAudience to be specified"); } } - try { JwtClaims defaultClaims = JwtClaims.newBuilder() @@ -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(); @@ -474,6 +518,7 @@ public static class Builder { private String privateKeyId; private URI defaultAudience; private String quotaProjectId; + private String universeDomain; protected Builder() {} @@ -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 @@ -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; } @@ -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); } } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountJwtAccessCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountJwtAccessCredentialsTest.java index df95ea2f3..2505b200e 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountJwtAccessCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountJwtAccessCredentialsTest.java @@ -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; @@ -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; @@ -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 @@ -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> metadata, String expectedEmail, @@ -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; + } }