diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java index 5ded1a140..191f584de 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @@ -200,7 +200,8 @@ private ImpersonatedCredentials initializeImpersonatedCredentials() { .build(); } - String targetPrincipal = extractTargetPrincipal(serviceAccountImpersonationUrl); + String targetPrincipal = + ImpersonatedCredentials.extractTargetPrincipal(serviceAccountImpersonationUrl); return ImpersonatedCredentials.newBuilder() .setSourceCredentials(sourceCredentials) .setHttpTransportFactory(transportFactory) @@ -359,19 +360,6 @@ protected AccessToken exchangeExternalCredentialForAccessToken( return response.getAccessToken(); } - private static String extractTargetPrincipal(String serviceAccountImpersonationUrl) { - // Extract the target principal. - int startIndex = serviceAccountImpersonationUrl.lastIndexOf('/'); - int endIndex = serviceAccountImpersonationUrl.indexOf(":generateAccessToken"); - - if (startIndex != -1 && endIndex != -1 && startIndex < endIndex) { - return serviceAccountImpersonationUrl.substring(startIndex + 1, endIndex); - } else { - throw new IllegalArgumentException( - "Unable to determine target principal from service account impersonation URL."); - } - } - /** * Retrieves the external subject token to be exchanged for a GCP access token. * diff --git a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java index 5a215322f..af7cc8ecd 100644 --- a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java @@ -78,8 +78,12 @@ public static GoogleCredentials create(AccessToken accessToken) { *
    *
  1. Credentials file pointed to by the {@code GOOGLE_APPLICATION_CREDENTIALS} environment * variable - *
  2. Credentials provided by the Google Cloud SDK {@code gcloud auth application-default - * login} command + *
  3. Credentials provided by the Google Cloud SDK. + *
      + *
    1. {@code gcloud auth application-default login} for user account credentials. + *
    2. {@code gcloud auth application-default login --impersonate-service-account} for + * impersonated service account credentials. + *
    *
  4. Google App Engine built-in credentials *
  5. Google Cloud Shell built-in credentials *
  6. Google Compute Engine built-in credentials @@ -169,6 +173,9 @@ public static GoogleCredentials fromStream( if (ExternalAccountCredentials.EXTERNAL_ACCOUNT_FILE_TYPE.equals(fileType)) { return ExternalAccountCredentials.fromJson(fileContents, transportFactory); } + if ("impersonated_service_account".equals(fileType)) { + return ImpersonatedCredentials.fromJson(fileContents, transportFactory); + } throw new IOException( String.format( "Error reading credentials from stream, 'type' value '%s' not recognized." diff --git a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java index 6ddb116cc..700ad2117 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java @@ -32,6 +32,7 @@ package com.google.auth.oauth2; import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.base.Preconditions.checkNotNull; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpContent; @@ -45,6 +46,7 @@ import com.google.auth.ServiceAccountSigner; import com.google.auth.http.HttpCredentialsAdapter; import com.google.auth.http.HttpTransportFactory; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableMap; import java.io.IOException; @@ -53,6 +55,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; @@ -85,7 +88,7 @@ * */ public class ImpersonatedCredentials extends GoogleCredentials - implements ServiceAccountSigner, IdTokenProvider { + implements ServiceAccountSigner, IdTokenProvider, QuotaProjectIdProvider { private static final long serialVersionUID = -2133257318957488431L; private static final String RFC3339 = "yyyy-MM-dd'T'HH:mm:ss'Z'"; @@ -101,12 +104,14 @@ public class ImpersonatedCredentials extends GoogleCredentials private List delegates; private List scopes; private int lifetime; + private String quotaProjectId; private final String transportFactoryClassName; private transient HttpTransportFactory transportFactory; /** - * @param sourceCredentials the source credential used as to acquire the impersonated credentials + * @param sourceCredentials the source credential used to acquire the impersonated credentials. It + * should be either a user account credential or a service account credential. * @param targetPrincipal the service account to impersonate * @param delegates the chained list of delegates required to grant the final access_token. If * set, the sequence of identities must have "Service Account Token Creator" capability @@ -144,7 +149,52 @@ public static ImpersonatedCredentials create( } /** - * @param sourceCredentials the source credential used as to acquire the impersonated credentials + * @param sourceCredentials the source credential used to acquire the impersonated credentials. It + * should be either a user account credential or a service account credential. + * @param targetPrincipal the service account to impersonate + * @param delegates the chained list of delegates required to grant the final access_token. If + * set, the sequence of identities must have "Service Account Token Creator" capability + * granted to the preceding identity. For example, if set to [serviceAccountB, + * serviceAccountC], the sourceCredential must have the Token Creator role on serviceAccountB. + * serviceAccountB must have the Token Creator on serviceAccountC. Finally, C must have Token + * Creator on target_principal. If unset, sourceCredential must have that role on + * targetPrincipal. + * @param scopes scopes to request during the authorization grant + * @param lifetime number of seconds the delegated credential should be valid. By default this + * value should be at most 3600. However, you can follow these + * instructions to set up the service account and extend the maximum lifetime to 43200 (12 + * hours). If the given lifetime is 0, default value 3600 will be used instead when creating + * the credentials. + * @param transportFactory HTTP transport factory that creates the transport used to get access + * tokens. + * @param quotaProjectId the project used for quota and billing purposes. Should be null unless + * the caller wants to use a project different from the one that owns the impersonated + * credential for billing/quota purposes. + * @return new credentials + */ + public static ImpersonatedCredentials create( + GoogleCredentials sourceCredentials, + String targetPrincipal, + List delegates, + List scopes, + int lifetime, + HttpTransportFactory transportFactory, + String quotaProjectId) { + return ImpersonatedCredentials.newBuilder() + .setSourceCredentials(sourceCredentials) + .setTargetPrincipal(targetPrincipal) + .setDelegates(delegates) + .setScopes(scopes) + .setLifetime(lifetime) + .setHttpTransportFactory(transportFactory) + .setQuotaProjectId(quotaProjectId) + .build(); + } + + /** + * @param sourceCredentials the source credential used to acquire the impersonated credentials. It + * should be either a user account credential or a service account credential. * @param targetPrincipal the service account to impersonate * @param delegates the chained list of delegates required to grant the final access_token. If * set, the sequence of identities must have "Service Account Token Creator" capability @@ -179,6 +229,19 @@ public static ImpersonatedCredentials create( .build(); } + static String extractTargetPrincipal(String serviceAccountImpersonationUrl) { + // Extract the target principal. + int startIndex = serviceAccountImpersonationUrl.lastIndexOf('/'); + int endIndex = serviceAccountImpersonationUrl.indexOf(":generateAccessToken"); + + if (startIndex != -1 && endIndex != -1 && startIndex < endIndex) { + return serviceAccountImpersonationUrl.substring(startIndex + 1, endIndex); + } else { + throw new IllegalArgumentException( + "Unable to determine target principal from service account impersonation URL."); + } + } + /** * Returns the email field of the serviceAccount that is being impersonated. * @@ -189,10 +252,33 @@ public String getAccount() { return this.targetPrincipal; } + @Override + public String getQuotaProjectId() { + return this.quotaProjectId; + } + + @VisibleForTesting + List getDelegates() { + return delegates; + } + + @VisibleForTesting + List getScopes() { + return scopes; + } + + public GoogleCredentials getSourceCredentials() { + return sourceCredentials; + } + int getLifetime() { return this.lifetime; } + public void setTransportFactory(HttpTransportFactory httpTransportFactory) { + this.transportFactory = httpTransportFactory; + } + /** * Signs the provided bytes using the private key associated with the impersonated service account * @@ -213,6 +299,89 @@ public byte[] sign(byte[] toSign) { ImmutableMap.of("delegates", this.delegates)); } + /** + * Returns impersonation account credentials defined by JSON using the format generated by gCloud. + * The source credentials in the JSON should be either user account credentials or service account + * credentials. + * + * @param json a map from the JSON representing the credentials + * @param transportFactory HTTP transport factory, creates the transport used to get access tokens + * @return the credentials defined by the JSON + * @throws IOException if the credential cannot be created from the JSON. + */ + static ImpersonatedCredentials fromJson( + Map json, HttpTransportFactory transportFactory) throws IOException { + + checkNotNull(json); + checkNotNull(transportFactory); + + List delegates = null; + Map sourceCredentialsJson; + String sourceCredentialsType; + String quotaProjectId; + String targetPrincipal; + try { + String serviceAccountImpersonationUrl = + (String) json.get("service_account_impersonation_url"); + if (json.containsKey("delegates")) { + delegates = (List) json.get("delegates"); + } + sourceCredentialsJson = (Map) json.get("source_credentials"); + sourceCredentialsType = (String) sourceCredentialsJson.get("type"); + quotaProjectId = (String) json.get("quota_project_id"); + targetPrincipal = extractTargetPrincipal(serviceAccountImpersonationUrl); + } catch (ClassCastException | NullPointerException | IllegalArgumentException e) { + throw new CredentialFormatException("An invalid input stream was provided.", e); + } + + GoogleCredentials sourceCredentials; + if (GoogleCredentials.USER_FILE_TYPE.equals(sourceCredentialsType)) { + sourceCredentials = UserCredentials.fromJson(sourceCredentialsJson, transportFactory); + } else if (GoogleCredentials.SERVICE_ACCOUNT_FILE_TYPE.equals(sourceCredentialsType)) { + sourceCredentials = + ServiceAccountCredentials.fromJson(sourceCredentialsJson, transportFactory); + } else { + throw new IOException( + String.format( + "A credential of type %s is not supported as source credential for impersonation.", + sourceCredentialsType)); + } + return ImpersonatedCredentials.newBuilder() + .setSourceCredentials(sourceCredentials) + .setTargetPrincipal(targetPrincipal) + .setDelegates(delegates) + .setScopes(new ArrayList()) + .setLifetime(DEFAULT_LIFETIME_IN_SECONDS) + .setHttpTransportFactory(transportFactory) + .setQuotaProjectId(quotaProjectId) + .build(); + } + + @Override + public boolean createScopedRequired() { + return this.scopes == null || this.scopes.isEmpty(); + } + + @Override + public GoogleCredentials createScoped(Collection scopes) { + return toBuilder() + .setScopes((List) scopes) + .setLifetime(this.lifetime) + .setDelegates(this.delegates) + .setHttpTransportFactory(this.transportFactory) + .setQuotaProjectId(this.quotaProjectId) + .build(); + } + + @Override + protected Map> getAdditionalHeaders() { + Map> headers = super.getAdditionalHeaders(); + if (quotaProjectId != null) { + return addQuotaProjectIdToRequestMetadata(quotaProjectId, headers); + } + return headers; + } + private ImpersonatedCredentials(Builder builder) { this.sourceCredentials = builder.getSourceCredentials(); this.targetPrincipal = builder.getTargetPrincipal(); @@ -223,6 +392,7 @@ private ImpersonatedCredentials(Builder builder) { firstNonNull( builder.getHttpTransportFactory(), getFromServiceLoader(HttpTransportFactory.class, OAuth2Utils.HTTP_TRANSPORT_FACTORY)); + this.quotaProjectId = builder.quotaProjectId; this.transportFactoryClassName = this.transportFactory.getClass().getName(); if (this.delegates == null) { this.delegates = new ArrayList(); @@ -318,7 +488,8 @@ public IdToken idTokenWithAudience(String targetAudience, List scopes; private int lifetime = DEFAULT_LIFETIME_IN_SECONDS; private HttpTransportFactory transportFactory; + private String quotaProjectId; protected Builder() {} @@ -425,6 +599,11 @@ public HttpTransportFactory getHttpTransportFactory() { return transportFactory; } + public Builder setQuotaProjectId(String quotaProjectId) { + this.quotaProjectId = quotaProjectId; + return this; + } + public ImpersonatedCredentials build() { return new ImpersonatedCredentials(this); } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java index a87dc84fe..36774de58 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java @@ -32,6 +32,7 @@ package com.google.auth.oauth2; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -41,6 +42,7 @@ import com.google.auth.TestUtils; import com.google.auth.http.HttpTransportFactory; import com.google.auth.oauth2.IdentityPoolCredentialsTest.MockExternalAccountCredentialsTransportFactory; +import com.google.auth.oauth2.ImpersonatedCredentialsTest.MockIAMCredentialsServiceTransportFactory; import com.google.common.collect.ImmutableList; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -270,6 +272,71 @@ public void fromStream_awsCredentials_providesToken() throws IOException { TestUtils.assertContainsBearerToken(metadata, transportFactory.transport.getAccessToken()); } + @Test + public void fromStream_Impersonation_providesToken_WithQuotaProject() throws IOException { + MockTokenServerTransportFactory transportFactoryForSource = + new MockTokenServerTransportFactory(); + transportFactoryForSource.transport.addServiceAccount( + ImpersonatedCredentialsTest.SA_CLIENT_EMAIL, ImpersonatedCredentialsTest.ACCESS_TOKEN); + + MockIAMCredentialsServiceTransportFactory transportFactory = + new MockIAMCredentialsServiceTransportFactory(); + transportFactory.transport.setTargetPrincipal( + ImpersonatedCredentialsTest.IMPERSONATED_CLIENT_EMAIL); + transportFactory.transport.setAccessToken(ImpersonatedCredentialsTest.ACCESS_TOKEN); + transportFactory.transport.setExpireTime(ImpersonatedCredentialsTest.getDefaultExpireTime()); + + InputStream impersonationCredentialsStream = + ImpersonatedCredentialsTest.writeImpersonationCredentialsStream( + ImpersonatedCredentialsTest.IMPERSONATION_URL, + ImpersonatedCredentialsTest.DELEGATES, + ImpersonatedCredentialsTest.QUOTA_PROJECT_ID); + + ImpersonatedCredentials credentials = + (ImpersonatedCredentials) + GoogleCredentials.fromStream(impersonationCredentialsStream, transportFactoryForSource); + credentials.setTransportFactory(transportFactory); + + Map> metadata = credentials.getRequestMetadata(CALL_URI); + TestUtils.assertContainsBearerToken(metadata, ImpersonatedCredentialsTest.ACCESS_TOKEN); + + assertTrue(metadata.containsKey("x-goog-user-project")); + List headerValues = metadata.get("x-goog-user-project"); + assertEquals(1, headerValues.size()); + assertEquals(ImpersonatedCredentialsTest.QUOTA_PROJECT_ID, headerValues.get(0)); + } + + @Test + public void fromStream_Impersonation_providesToken_WithoutQuotaProject() throws IOException { + MockTokenServerTransportFactory transportFactoryForSource = + new MockTokenServerTransportFactory(); + transportFactoryForSource.transport.addServiceAccount( + ImpersonatedCredentialsTest.SA_CLIENT_EMAIL, ImpersonatedCredentialsTest.ACCESS_TOKEN); + + MockIAMCredentialsServiceTransportFactory transportFactory = + new MockIAMCredentialsServiceTransportFactory(); + transportFactory.transport.setTargetPrincipal( + ImpersonatedCredentialsTest.IMPERSONATED_CLIENT_EMAIL); + transportFactory.transport.setAccessToken(ImpersonatedCredentialsTest.ACCESS_TOKEN); + transportFactory.transport.setExpireTime(ImpersonatedCredentialsTest.getDefaultExpireTime()); + + InputStream impersonationCredentialsStream = + ImpersonatedCredentialsTest.writeImpersonationCredentialsStream( + ImpersonatedCredentialsTest.IMPERSONATION_URL, + ImpersonatedCredentialsTest.DELEGATES, + null); + + ImpersonatedCredentials credentials = + (ImpersonatedCredentials) + GoogleCredentials.fromStream(impersonationCredentialsStream, transportFactoryForSource); + credentials.setTransportFactory(transportFactory); + + Map> metadata = credentials.getRequestMetadata(CALL_URI); + TestUtils.assertContainsBearerToken(metadata, ImpersonatedCredentialsTest.ACCESS_TOKEN); + + assertFalse(metadata.containsKey("x-goog-user-project")); + } + @Test public void createScoped_overloadCallsImplementation() { final AtomicReference> called = new AtomicReference<>(); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java index b7c3bd29e..8d834abde 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java @@ -33,7 +33,9 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -48,11 +50,13 @@ import com.google.api.client.testing.http.MockLowLevelHttpRequest; import com.google.api.client.util.Clock; import com.google.auth.ServiceAccountSigner.SigningException; +import com.google.auth.TestUtils; import com.google.auth.http.HttpTransportFactory; import com.google.auth.oauth2.GoogleCredentialsTest.MockTokenServerTransportFactory; import com.google.common.collect.ImmutableList; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.Charset; import java.security.PrivateKey; import java.text.SimpleDateFormat; @@ -61,6 +65,8 @@ import java.util.Calendar; import java.util.Date; import java.util.List; +import java.util.Map; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -69,7 +75,7 @@ @RunWith(JUnit4.class) public class ImpersonatedCredentialsTest extends BaseSerializationTest { - private static final String SA_CLIENT_EMAIL = + public static final String SA_CLIENT_EMAIL = "36680232662-vrd7ji19qe3nelgchd0ah2csanun6bnr@developer.gserviceaccount.com"; private static final String SA_PRIVATE_KEY_ID = "d84a4fefcf50791d4a90f2d7af17469d6282df9d"; static final String SA_PRIVATE_KEY_PKCS8 = @@ -102,18 +108,29 @@ public class ImpersonatedCredentialsTest extends BaseSerializationTest { + "lZC1yYXktMTA0MTE3LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cC" + "I6MTU2NDUzMzA0MiwiaWF0IjoxNTY0NTI5NDQyLCJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iL" + "CJzdWIiOiIxMDIxMDE1NTA4MzQyMDA3MDg1NjgifQ.redacted"; + public static final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2"; private static final String PROJECT_ID = "project-id"; - private static final String IMPERSONATED_CLIENT_EMAIL = + public static final String IMPERSONATED_CLIENT_EMAIL = "impersonated-account@iam.gserviceaccount.com"; private static final List SCOPES = Arrays.asList("https://www.googleapis.com/auth/devstorage.read_only"); - private static final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2"; private static final int VALID_LIFETIME = 300; private static final int INVALID_LIFETIME = 43210; private static JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); private static final String RFC3339 = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + public static final String IMPERSONATION_URL = + "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/" + + IMPERSONATED_CLIENT_EMAIL + + ":generateAccessToken"; + private static final String USER_ACCOUNT_CLIENT_ID = + "76408650-6qr441hur.apps.googleusercontent.com"; + private static final String USER_ACCOUNT_CLIENT_SECRET = "d-F499q74hFpdHD0T5"; + public static final String QUOTA_PROJECT_ID = "quota-project-id"; + private static final String REFRESH_TOKEN = "dasdfasdffa4ffdfadgyjirasdfadsft"; + public static final List DELEGATES = + Arrays.asList("sa1@developer.gserviceaccount.com", "sa2@developer.gserviceaccount.com"); static class MockIAMCredentialsServiceTransportFactory implements HttpTransportFactory { @@ -125,6 +142,15 @@ public HttpTransport create() { } } + private GoogleCredentials sourceCredentials; + private MockIAMCredentialsServiceTransportFactory mockTransportFactory; + + @Before + public void setup() throws IOException { + sourceCredentials = getSourceCredentials(); + mockTransportFactory = new MockIAMCredentialsServiceTransportFactory(); + } + private GoogleCredentials getSourceCredentials() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); @@ -142,16 +168,153 @@ private GoogleCredentials getSourceCredentials() throws IOException { return sourceCredentials; } + @Test() + public void fromJson_userAsSource_WithQuotaProjectId() throws IOException { + GenericJson json = + buildImpersonationCredentialsJson( + IMPERSONATION_URL, + DELEGATES, + QUOTA_PROJECT_ID, + USER_ACCOUNT_CLIENT_ID, + USER_ACCOUNT_CLIENT_SECRET, + REFRESH_TOKEN); + ImpersonatedCredentials credentials = + ImpersonatedCredentials.fromJson(json, mockTransportFactory); + assertEquals(IMPERSONATED_CLIENT_EMAIL, credentials.getAccount()); + assertEquals(QUOTA_PROJECT_ID, credentials.getQuotaProjectId()); + assertEquals(DELEGATES, credentials.getDelegates()); + assertEquals(new ArrayList(), credentials.getScopes()); + assertEquals(3600, credentials.getLifetime()); + GoogleCredentials sourceCredentials = credentials.getSourceCredentials(); + assertTrue(sourceCredentials instanceof UserCredentials); + } + + @Test() + public void fromJson_userAsSource_WithoutQuotaProjectId() throws IOException { + GenericJson json = + buildImpersonationCredentialsJson( + IMPERSONATION_URL, + DELEGATES, + null, + USER_ACCOUNT_CLIENT_ID, + USER_ACCOUNT_CLIENT_SECRET, + REFRESH_TOKEN); + ImpersonatedCredentials credentials = + ImpersonatedCredentials.fromJson(json, mockTransportFactory); + assertEquals(IMPERSONATED_CLIENT_EMAIL, credentials.getAccount()); + assertNull(credentials.getQuotaProjectId()); + assertEquals(DELEGATES, credentials.getDelegates()); + assertEquals(new ArrayList(), credentials.getScopes()); + assertEquals(3600, credentials.getLifetime()); + GoogleCredentials sourceCredentials = credentials.getSourceCredentials(); + assertTrue(sourceCredentials instanceof UserCredentials); + } + + @Test() + public void fromJson_userAsSource_MissingDelegatesField() throws IOException { + GenericJson json = + buildImpersonationCredentialsJson( + IMPERSONATION_URL, + DELEGATES, + null, + USER_ACCOUNT_CLIENT_ID, + USER_ACCOUNT_CLIENT_SECRET, + REFRESH_TOKEN); + json.remove("delegates"); + ImpersonatedCredentials credentials = + ImpersonatedCredentials.fromJson(json, mockTransportFactory); + assertEquals(IMPERSONATED_CLIENT_EMAIL, credentials.getAccount()); + assertNull(credentials.getQuotaProjectId()); + assertEquals(new ArrayList(), credentials.getDelegates()); + assertEquals(new ArrayList(), credentials.getScopes()); + assertEquals(3600, credentials.getLifetime()); + GoogleCredentials sourceCredentials = credentials.getSourceCredentials(); + assertTrue(sourceCredentials instanceof UserCredentials); + } + + @Test() + public void fromJson_ServiceAccountAsSource() throws IOException { + GenericJson json = + buildImpersonationCredentialsJson(IMPERSONATION_URL, DELEGATES, QUOTA_PROJECT_ID); + ImpersonatedCredentials credentials = + ImpersonatedCredentials.fromJson(json, mockTransportFactory); + assertEquals(IMPERSONATED_CLIENT_EMAIL, credentials.getAccount()); + assertEquals(QUOTA_PROJECT_ID, credentials.getQuotaProjectId()); + assertEquals(DELEGATES, credentials.getDelegates()); + assertEquals(new ArrayList(), credentials.getScopes()); + assertEquals(3600, credentials.getLifetime()); + GoogleCredentials sourceCredentials = credentials.getSourceCredentials(); + assertTrue(sourceCredentials instanceof ServiceAccountCredentials); + } + + @Test() + public void fromJson_InvalidFormat() throws IOException { + GenericJson json = buildInvalidCredentialsJson(); + try { + ImpersonatedCredentials.fromJson(json, mockTransportFactory); + fail("An exception should be thrown."); + } catch (CredentialFormatException e) { + assertEquals("An invalid input stream was provided.", e.getMessage()); + } + } + + @Test() + public void createScopedRequired_True() { + ImpersonatedCredentials targetCredentials = + ImpersonatedCredentials.create( + sourceCredentials, + IMPERSONATED_CLIENT_EMAIL, + null, + new ArrayList(), + VALID_LIFETIME, + mockTransportFactory); + assertTrue(targetCredentials.createScopedRequired()); + } + + @Test() + public void createScopedRequired_False() { + ImpersonatedCredentials targetCredentials = + ImpersonatedCredentials.create( + sourceCredentials, + IMPERSONATED_CLIENT_EMAIL, + null, + SCOPES, + VALID_LIFETIME, + mockTransportFactory); + assertFalse(targetCredentials.createScopedRequired()); + } + + @Test() + public void createScoped() { + ImpersonatedCredentials targetCredentials = + ImpersonatedCredentials.create( + sourceCredentials, + IMPERSONATED_CLIENT_EMAIL, + DELEGATES, + SCOPES, + VALID_LIFETIME, + mockTransportFactory, + QUOTA_PROJECT_ID); + + ImpersonatedCredentials scoped_credentials = + (ImpersonatedCredentials) targetCredentials.createScoped(Arrays.asList("scope1", "scope2")); + assertEquals(targetCredentials.getAccount(), scoped_credentials.getAccount()); + assertEquals(targetCredentials.getDelegates(), scoped_credentials.getDelegates()); + assertEquals(targetCredentials.getLifetime(), scoped_credentials.getLifetime()); + assertEquals( + targetCredentials.getSourceCredentials(), scoped_credentials.getSourceCredentials()); + assertEquals(targetCredentials.getQuotaProjectId(), scoped_credentials.getQuotaProjectId()); + assertEquals(Arrays.asList("scope1", "scope2"), scoped_credentials.getScopes()); + } + @Test() public void refreshAccessToken_unauthorized() throws IOException { - GoogleCredentials sourceCredentials = getSourceCredentials(); String expectedMessage = "The caller does not have permission"; - MockIAMCredentialsServiceTransportFactory mtransportFactory = - new MockIAMCredentialsServiceTransportFactory(); - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setTokenResponseErrorCode(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED); - mtransportFactory.transport.setTokenResponseErrorContent( + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setTokenResponseErrorCode( + HttpStatusCodes.STATUS_CODE_UNAUTHORIZED); + mockTransportFactory.transport.setTokenResponseErrorContent( generateErrorJson( HttpStatusCodes.STATUS_CODE_UNAUTHORIZED, expectedMessage, "global", "forbidden")); ImpersonatedCredentials targetCredentials = @@ -161,7 +324,7 @@ public void refreshAccessToken_unauthorized() throws IOException { null, SCOPES, VALID_LIFETIME, - mtransportFactory); + mockTransportFactory); try { targetCredentials.refreshAccessToken().getTokenValue(); @@ -175,19 +338,22 @@ public void refreshAccessToken_unauthorized() throws IOException { @Test() public void refreshAccessToken_malformedTarget() throws IOException { - GoogleCredentials sourceCredentials = getSourceCredentials(); - MockIAMCredentialsServiceTransportFactory mtransportFactory = - new MockIAMCredentialsServiceTransportFactory(); String invalidTargetEmail = "foo"; String expectedMessage = "Request contains an invalid argument"; - mtransportFactory.transport.setTargetPrincipal(invalidTargetEmail); - mtransportFactory.transport.setTokenResponseErrorCode(HttpStatusCodes.STATUS_CODE_BAD_REQUEST); - mtransportFactory.transport.setTokenResponseErrorContent( + mockTransportFactory.transport.setTargetPrincipal(invalidTargetEmail); + mockTransportFactory.transport.setTokenResponseErrorCode( + HttpStatusCodes.STATUS_CODE_BAD_REQUEST); + mockTransportFactory.transport.setTokenResponseErrorContent( generateErrorJson( HttpStatusCodes.STATUS_CODE_BAD_REQUEST, expectedMessage, "global", "badRequest")); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( - sourceCredentials, invalidTargetEmail, null, SCOPES, VALID_LIFETIME, mtransportFactory); + sourceCredentials, + invalidTargetEmail, + null, + SCOPES, + VALID_LIFETIME, + mockTransportFactory); try { targetCredentials.refreshAccessToken().getTokenValue(); @@ -199,8 +365,7 @@ public void refreshAccessToken_malformedTarget() throws IOException { } @Test() - public void credential_with_zero_lifetime() throws IOException, IllegalStateException { - GoogleCredentials sourceCredentials = getSourceCredentials(); + public void credential_with_zero_lifetime() throws IllegalStateException { ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( sourceCredentials, IMPERSONATED_CLIENT_EMAIL, null, SCOPES, 0); @@ -210,7 +375,6 @@ public void credential_with_zero_lifetime() throws IOException, IllegalStateExce @Test() public void credential_with_invalid_lifetime() throws IOException, IllegalStateException { - GoogleCredentials sourceCredentials = getSourceCredentials(); try { ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( @@ -228,7 +392,6 @@ public void credential_with_invalid_lifetime() throws IOException, IllegalStateE @Test() public void credential_with_invalid_scope() throws IOException, IllegalStateException { - GoogleCredentials sourceCredentials = getSourceCredentials(); try { ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( @@ -245,12 +408,9 @@ public void credential_with_invalid_scope() throws IOException, IllegalStateExce @Test() public void refreshAccessToken_success() throws IOException, IllegalStateException { - GoogleCredentials sourceCredentials = getSourceCredentials(); - MockIAMCredentialsServiceTransportFactory mtransportFactory = - new MockIAMCredentialsServiceTransportFactory(); - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setAccessToken(ACCESS_TOKEN); - mtransportFactory.transport.setExpireTime(getDefaultExpireTime()); + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); + mockTransportFactory.transport.setExpireTime(getDefaultExpireTime()); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( sourceCredentials, @@ -258,20 +418,59 @@ public void refreshAccessToken_success() throws IOException, IllegalStateExcepti null, SCOPES, VALID_LIFETIME, - mtransportFactory); + mockTransportFactory); assertEquals(ACCESS_TOKEN, targetCredentials.refreshAccessToken().getTokenValue()); } + @Test() + public void getRequestMetadata_withQuotaProjectId() throws IOException, IllegalStateException { + + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); + mockTransportFactory.transport.setExpireTime(getDefaultExpireTime()); + ImpersonatedCredentials targetCredentials = + ImpersonatedCredentials.create( + sourceCredentials, + IMPERSONATED_CLIENT_EMAIL, + null, + SCOPES, + VALID_LIFETIME, + mockTransportFactory, + QUOTA_PROJECT_ID); + + Map> metadata = targetCredentials.getRequestMetadata(); + assertTrue(metadata.containsKey("x-goog-user-project")); + List headerValues = metadata.get("x-goog-user-project"); + assertEquals(1, headerValues.size()); + assertEquals(QUOTA_PROJECT_ID, headerValues.get(0)); + } + + @Test() + public void getRequestMetadata_withoutQuotaProjectId() throws IOException, IllegalStateException { + + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); + mockTransportFactory.transport.setExpireTime(getDefaultExpireTime()); + ImpersonatedCredentials targetCredentials = + ImpersonatedCredentials.create( + sourceCredentials, + IMPERSONATED_CLIENT_EMAIL, + null, + SCOPES, + VALID_LIFETIME, + mockTransportFactory); + + Map> metadata = targetCredentials.getRequestMetadata(); + assertFalse(metadata.containsKey("x-goog-user-project")); + } + @Test() public void refreshAccessToken_delegates_success() throws IOException, IllegalStateException { - GoogleCredentials sourceCredentials = getSourceCredentials(); - MockIAMCredentialsServiceTransportFactory mtransportFactory = - new MockIAMCredentialsServiceTransportFactory(); - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setAccessToken(ACCESS_TOKEN); - mtransportFactory.transport.setExpireTime(getDefaultExpireTime()); + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); + mockTransportFactory.transport.setExpireTime(getDefaultExpireTime()); List delegates = Arrays.asList("delegate-account@iam.gserviceaccount.com"); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( @@ -280,21 +479,18 @@ public void refreshAccessToken_delegates_success() throws IOException, IllegalSt delegates, SCOPES, VALID_LIFETIME, - mtransportFactory); + mockTransportFactory); assertEquals(ACCESS_TOKEN, targetCredentials.refreshAccessToken().getTokenValue()); } @Test() - public void refreshAccessToken_invalidDate() throws IOException, IllegalStateException { + public void refreshAccessToken_invalidDate() throws IllegalStateException { - GoogleCredentials sourceCredentials = getSourceCredentials(); String expectedMessage = "Unparseable date"; - MockIAMCredentialsServiceTransportFactory mtransportFactory = - new MockIAMCredentialsServiceTransportFactory(); - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setAccessToken("foo"); - mtransportFactory.transport.setExpireTime("1973-09-29T15:01:23"); + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setAccessToken("foo"); + mockTransportFactory.transport.setExpireTime("1973-09-29T15:01:23"); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( sourceCredentials, @@ -302,7 +498,7 @@ public void refreshAccessToken_invalidDate() throws IOException, IllegalStateExc null, SCOPES, VALID_LIFETIME, - mtransportFactory); + mockTransportFactory); try { targetCredentials.refreshAccessToken().getTokenValue(); @@ -313,13 +509,10 @@ public void refreshAccessToken_invalidDate() throws IOException, IllegalStateExc } @Test - public void getAccount_sameAs() throws IOException { - GoogleCredentials sourceCredentials = getSourceCredentials(); - MockIAMCredentialsServiceTransportFactory mtransportFactory = - new MockIAMCredentialsServiceTransportFactory(); - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setAccessToken(ACCESS_TOKEN); - mtransportFactory.transport.setExpireTime(getDefaultExpireTime()); + public void getAccount_sameAs() { + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); + mockTransportFactory.transport.setExpireTime(getDefaultExpireTime()); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( sourceCredentials, @@ -327,19 +520,16 @@ public void getAccount_sameAs() throws IOException { null, SCOPES, VALID_LIFETIME, - mtransportFactory); + mockTransportFactory); assertEquals(IMPERSONATED_CLIENT_EMAIL, targetCredentials.getAccount()); } @Test - public void sign_sameAs() throws IOException { - GoogleCredentials sourceCredentials = getSourceCredentials(); - MockIAMCredentialsServiceTransportFactory mtransportFactory = - new MockIAMCredentialsServiceTransportFactory(); - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setAccessToken(ACCESS_TOKEN); - mtransportFactory.transport.setExpireTime(getDefaultExpireTime()); + public void sign_sameAs() { + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); + mockTransportFactory.transport.setExpireTime(getDefaultExpireTime()); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( sourceCredentials, @@ -347,24 +537,21 @@ public void sign_sameAs() throws IOException { null, SCOPES, VALID_LIFETIME, - mtransportFactory); + mockTransportFactory); byte[] expectedSignature = {0xD, 0xE, 0xA, 0xD}; - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setSignedBlob(expectedSignature); + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setSignedBlob(expectedSignature); assertArrayEquals(expectedSignature, targetCredentials.sign(expectedSignature)); } @Test public void sign_requestIncludesDelegates() throws IOException { - GoogleCredentials sourceCredentials = getSourceCredentials(); - MockIAMCredentialsServiceTransportFactory mtransportFactory = - new MockIAMCredentialsServiceTransportFactory(); - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setAccessToken(ACCESS_TOKEN); - mtransportFactory.transport.setExpireTime(getDefaultExpireTime()); + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); + mockTransportFactory.transport.setExpireTime(getDefaultExpireTime()); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( sourceCredentials, @@ -372,16 +559,16 @@ public void sign_requestIncludesDelegates() throws IOException { ImmutableList.of("delegate@example.com"), SCOPES, VALID_LIFETIME, - mtransportFactory); + mockTransportFactory); byte[] expectedSignature = {0xD, 0xE, 0xA, 0xD}; - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setSignedBlob(expectedSignature); + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setSignedBlob(expectedSignature); assertArrayEquals(expectedSignature, targetCredentials.sign(expectedSignature)); - MockLowLevelHttpRequest request = mtransportFactory.transport.getRequest(); + MockLowLevelHttpRequest request = mockTransportFactory.transport.getRequest(); GenericJson body = JSON_FACTORY .createJsonParser(request.getContentAsString()) @@ -392,22 +579,18 @@ public void sign_requestIncludesDelegates() throws IOException { } @Test - public void sign_usesSourceCredentials() throws IOException { - Date expiry = new Date(); + public void sign_usesSourceCredentials() { Calendar c = Calendar.getInstance(); - c.setTime(expiry); c.add(Calendar.DATE, 1); - expiry = c.getTime(); + Date expiry = c.getTime(); GoogleCredentials sourceCredentials = new GoogleCredentials.Builder() .setAccessToken(new AccessToken("source-token", expiry)) .build(); - MockIAMCredentialsServiceTransportFactory mtransportFactory = - new MockIAMCredentialsServiceTransportFactory(); - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setAccessToken(ACCESS_TOKEN); - mtransportFactory.transport.setExpireTime(getDefaultExpireTime()); + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); + mockTransportFactory.transport.setExpireTime(getDefaultExpireTime()); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( sourceCredentials, @@ -415,27 +598,24 @@ public void sign_usesSourceCredentials() throws IOException { ImmutableList.of("delegate@example.com"), SCOPES, VALID_LIFETIME, - mtransportFactory); + mockTransportFactory); byte[] expectedSignature = {0xD, 0xE, 0xA, 0xD}; - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setSignedBlob(expectedSignature); + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setSignedBlob(expectedSignature); assertArrayEquals(expectedSignature, targetCredentials.sign(expectedSignature)); - MockLowLevelHttpRequest request = mtransportFactory.transport.getRequest(); + MockLowLevelHttpRequest request = mockTransportFactory.transport.getRequest(); assertEquals("Bearer source-token", request.getFirstHeaderValue("Authorization")); } @Test - public void sign_accessDenied_throws() throws IOException { - GoogleCredentials sourceCredentials = getSourceCredentials(); - MockIAMCredentialsServiceTransportFactory mtransportFactory = - new MockIAMCredentialsServiceTransportFactory(); - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setAccessToken(ACCESS_TOKEN); - mtransportFactory.transport.setExpireTime(getDefaultExpireTime()); + public void sign_accessDenied_throws() { + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); + mockTransportFactory.transport.setExpireTime(getDefaultExpireTime()); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( sourceCredentials, @@ -443,13 +623,13 @@ public void sign_accessDenied_throws() throws IOException { null, SCOPES, VALID_LIFETIME, - mtransportFactory); + mockTransportFactory); byte[] expectedSignature = {0xD, 0xE, 0xA, 0xD}; - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setSignedBlob(expectedSignature); - mtransportFactory.transport.setErrorResponseCodeAndMessage( + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setSignedBlob(expectedSignature); + mockTransportFactory.transport.setErrorResponseCodeAndMessage( HttpStatusCodes.STATUS_CODE_FORBIDDEN, "Sign Error"); try { @@ -464,13 +644,10 @@ public void sign_accessDenied_throws() throws IOException { } @Test - public void sign_serverError_throws() throws IOException { - GoogleCredentials sourceCredentials = getSourceCredentials(); - MockIAMCredentialsServiceTransportFactory mtransportFactory = - new MockIAMCredentialsServiceTransportFactory(); - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setAccessToken(ACCESS_TOKEN); - mtransportFactory.transport.setExpireTime(getDefaultExpireTime()); + public void sign_serverError_throws() { + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); + mockTransportFactory.transport.setExpireTime(getDefaultExpireTime()); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( sourceCredentials, @@ -478,13 +655,13 @@ public void sign_serverError_throws() throws IOException { null, SCOPES, VALID_LIFETIME, - mtransportFactory); + mockTransportFactory); byte[] expectedSignature = {0xD, 0xE, 0xA, 0xD}; - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setSignedBlob(expectedSignature); - mtransportFactory.transport.setErrorResponseCodeAndMessage( + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setSignedBlob(expectedSignature); + mockTransportFactory.transport.setErrorResponseCodeAndMessage( HttpStatusCodes.STATUS_CODE_SERVER_ERROR, "Sign Error"); try { @@ -500,12 +677,9 @@ public void sign_serverError_throws() throws IOException { @Test public void idTokenWithAudience_sameAs() throws IOException { - GoogleCredentials sourceCredentials = getSourceCredentials(); - MockIAMCredentialsServiceTransportFactory mtransportFactory = - new MockIAMCredentialsServiceTransportFactory(); - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setAccessToken(ACCESS_TOKEN); - mtransportFactory.transport.setExpireTime(getDefaultExpireTime()); + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); + mockTransportFactory.transport.setExpireTime(getDefaultExpireTime()); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( @@ -514,9 +688,9 @@ public void idTokenWithAudience_sameAs() throws IOException { null, SCOPES, VALID_LIFETIME, - mtransportFactory); + mockTransportFactory); - mtransportFactory.transport.setIdToken(STANDARD_ID_TOKEN); + mockTransportFactory.transport.setIdToken(STANDARD_ID_TOKEN); String targetAudience = "https://foo.bar"; IdTokenCredentials tokenCredential = @@ -534,12 +708,9 @@ public void idTokenWithAudience_sameAs() throws IOException { @Test public void idTokenWithAudience_withEmail() throws IOException { - GoogleCredentials sourceCredentials = getSourceCredentials(); - MockIAMCredentialsServiceTransportFactory mtransportFactory = - new MockIAMCredentialsServiceTransportFactory(); - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setAccessToken(ACCESS_TOKEN); - mtransportFactory.transport.setExpireTime(getDefaultExpireTime()); + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); + mockTransportFactory.transport.setExpireTime(getDefaultExpireTime()); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( @@ -548,9 +719,9 @@ public void idTokenWithAudience_withEmail() throws IOException { null, SCOPES, VALID_LIFETIME, - mtransportFactory); + mockTransportFactory); - mtransportFactory.transport.setIdToken(TOKEN_WITH_EMAIL); + mockTransportFactory.transport.setIdToken(TOKEN_WITH_EMAIL); String targetAudience = "https://foo.bar"; IdTokenCredentials tokenCredential = @@ -566,13 +737,10 @@ public void idTokenWithAudience_withEmail() throws IOException { } @Test - public void idToken_withServerError() throws IOException { - GoogleCredentials sourceCredentials = getSourceCredentials(); - MockIAMCredentialsServiceTransportFactory mtransportFactory = - new MockIAMCredentialsServiceTransportFactory(); - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setAccessToken(ACCESS_TOKEN); - mtransportFactory.transport.setExpireTime(getDefaultExpireTime()); + public void idToken_withServerError() { + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); + mockTransportFactory.transport.setExpireTime(getDefaultExpireTime()); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( @@ -581,10 +749,10 @@ public void idToken_withServerError() throws IOException { null, SCOPES, VALID_LIFETIME, - mtransportFactory); + mockTransportFactory); - mtransportFactory.transport.setIdToken(STANDARD_ID_TOKEN); - mtransportFactory.transport.setErrorResponseCodeAndMessage( + mockTransportFactory.transport.setIdToken(STANDARD_ID_TOKEN); + mockTransportFactory.transport.setErrorResponseCodeAndMessage( HttpStatusCodes.STATUS_CODE_SERVER_ERROR, "Internal Server Error"); String targetAudience = "https://foo.bar"; @@ -601,13 +769,10 @@ public void idToken_withServerError() throws IOException { } @Test - public void idToken_withOtherError() throws IOException { - GoogleCredentials sourceCredentials = getSourceCredentials(); - MockIAMCredentialsServiceTransportFactory mtransportFactory = - new MockIAMCredentialsServiceTransportFactory(); - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setAccessToken(ACCESS_TOKEN); - mtransportFactory.transport.setExpireTime(getDefaultExpireTime()); + public void idToken_withOtherError() { + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); + mockTransportFactory.transport.setExpireTime(getDefaultExpireTime()); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( @@ -616,10 +781,10 @@ public void idToken_withOtherError() throws IOException { null, SCOPES, VALID_LIFETIME, - mtransportFactory); + mockTransportFactory); - mtransportFactory.transport.setIdToken(STANDARD_ID_TOKEN); - mtransportFactory.transport.setErrorResponseCodeAndMessage( + mockTransportFactory.transport.setIdToken(STANDARD_ID_TOKEN); + mockTransportFactory.transport.setErrorResponseCodeAndMessage( HttpStatusCodes.STATUS_CODE_MOVED_PERMANENTLY, "Redirect"); String targetAudience = "https://foo.bar"; @@ -637,12 +802,9 @@ public void idToken_withOtherError() throws IOException { @Test public void hashCode_equals() throws IOException { - GoogleCredentials sourceCredentials = getSourceCredentials(); - MockIAMCredentialsServiceTransportFactory mtransportFactory = - new MockIAMCredentialsServiceTransportFactory(); - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setAccessToken(ACCESS_TOKEN); - mtransportFactory.transport.setExpireTime(getDefaultExpireTime()); + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); + mockTransportFactory.transport.setExpireTime(getDefaultExpireTime()); ImpersonatedCredentials credentials = ImpersonatedCredentials.create( @@ -651,7 +813,7 @@ public void hashCode_equals() throws IOException { null, SCOPES, VALID_LIFETIME, - mtransportFactory); + mockTransportFactory); ImpersonatedCredentials otherCredentials = ImpersonatedCredentials.create( @@ -660,7 +822,7 @@ public void hashCode_equals() throws IOException { null, SCOPES, VALID_LIFETIME, - mtransportFactory); + mockTransportFactory); assertEquals(credentials.hashCode(), otherCredentials.hashCode()); } @@ -668,12 +830,9 @@ public void hashCode_equals() throws IOException { @Test public void serialize() throws IOException, ClassNotFoundException { - GoogleCredentials sourceCredentials = getSourceCredentials(); - MockIAMCredentialsServiceTransportFactory mtransportFactory = - new MockIAMCredentialsServiceTransportFactory(); - mtransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); - mtransportFactory.transport.setAccessToken(ACCESS_TOKEN); - mtransportFactory.transport.setExpireTime(getDefaultExpireTime()); + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); + mockTransportFactory.transport.setExpireTime(getDefaultExpireTime()); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( @@ -682,7 +841,7 @@ public void serialize() throws IOException, ClassNotFoundException { null, SCOPES, VALID_LIFETIME, - mtransportFactory); + mockTransportFactory); GoogleCredentials deserializedCredentials = serializeAndDeserialize(targetCredentials); assertEquals(targetCredentials, deserializedCredentials); assertEquals(targetCredentials.hashCode(), deserializedCredentials.hashCode()); @@ -690,10 +849,8 @@ public void serialize() throws IOException, ClassNotFoundException { assertSame(deserializedCredentials.clock, Clock.SYSTEM); } - private String getDefaultExpireTime() { - Date currentDate = new Date(); + public static String getDefaultExpireTime() { Calendar c = Calendar.getInstance(); - c.setTime(currentDate); c.add(Calendar.SECOND, VALID_LIFETIME); return new SimpleDateFormat(RFC3339).format(c.getTime()); } @@ -736,4 +893,69 @@ private String generateErrorJson( generator.close(); return bout.toString(); } + + static GenericJson buildImpersonationCredentialsJson( + String impersonationUrl, + List delegates, + String quotaProjectId, + String sourceClientId, + String sourceClientSecret, + String sourceRefreshToken) { + GenericJson sourceJson = new GenericJson(); + + sourceJson.put("client_id", sourceClientId); + sourceJson.put("client_secret", sourceClientSecret); + sourceJson.put("refresh_token", sourceRefreshToken); + sourceJson.put("type", "authorized_user"); + GenericJson json = new GenericJson(); + + json.put("service_account_impersonation_url", impersonationUrl); + json.put("delegates", delegates); + if (quotaProjectId != null) { + json.put("quota_project_id", quotaProjectId); + } + json.put("source_credentials", sourceJson); + json.put("type", "impersonated_service_account"); + return json; + } + + static GenericJson buildImpersonationCredentialsJson( + String impersonationUrl, List delegates, String quotaProjectId) { + GenericJson sourceJson = new GenericJson(); + sourceJson.put("type", "service_account"); + sourceJson.put("project_id", PROJECT_ID); + sourceJson.put("private_key_id", SA_PRIVATE_KEY_ID); + sourceJson.put("private_key", SA_PRIVATE_KEY_PKCS8); + sourceJson.put("client_email", SA_CLIENT_EMAIL); + sourceJson.put("client_id", "10848832332323213"); + sourceJson.put("auth_uri", "https://oauth2.googleapis.com/o/oauth2/auth"); + sourceJson.put("token_uri", "https://oauth2.googleapis.com/token"); + sourceJson.put("auth_provider_x509_cert_url", "https://www.googleapis.com/oauth2/v1/certs"); + sourceJson.put( + "client_x509_cert_url", + "https://www.googleapis.com/robot/v1/metadata/x509/chaoren-test-sc%40cloudsdktest.iam.gserviceaccount.com"); + + GenericJson json = new GenericJson(); + json.put("source_credentials", sourceJson); + json.put("service_account_impersonation_url", impersonationUrl); + json.put("delegates", delegates); + if (quotaProjectId != null) { + json.put("quota_project_id", quotaProjectId); + } + json.put("type", "impersonated_service_account"); + return json; + } + + static GenericJson buildInvalidCredentialsJson() { + GenericJson json = new GenericJson(); + json.put("service_account_impersonation_url", "mock_url"); + return json; + } + + static InputStream writeImpersonationCredentialsStream( + String impersonationUrl, List delegates, String quotaProjectId) throws IOException { + GenericJson json = + buildImpersonationCredentialsJson(impersonationUrl, delegates, quotaProjectId); + return TestUtils.jsonToInputStream(json); + } }