Skip to content

Commit

Permalink
fix: Make supporting classes of AwsCredentials serializable (#1113)
Browse files Browse the repository at this point in the history
* fix: Make supporting classes of AwsCredentials serializable

* Add unit test for ExternalAccountCredentials test.
Discovered ServiceAccountImpersonationOptions needed be serializable as well.

* Fix linting

* Add serialization tests for subclasses of ExternalAccountCredentials

Note that PluggableAuthCredentials uses a non-serializable ExecutableHandler.
It's not clear that this can/should be a serializable credential source.

* Add serialVersionUID fields to classes implementing ExternalAccountCredentials
  • Loading branch information
jmahonin authored Feb 16, 2023
1 parent 46720b0 commit 82bf871
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 21 deletions.
2 changes: 2 additions & 0 deletions oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public class AwsCredentials extends ExternalAccountCredentials {
static final String AWS_IMDSV2_SESSION_TOKEN_HEADER = "x-aws-ec2-metadata-token";
static final String AWS_IMDSV2_SESSION_TOKEN_TTL_HEADER = "x-aws-ec2-metadata-token-ttl-seconds";
static final String AWS_IMDSV2_SESSION_TOKEN_TTL = "300";
private static final long serialVersionUID = -3670131891574618105L;

/**
* The AWS credential source. Stores data required to retrieve the AWS credential from the AWS
Expand All @@ -81,6 +82,7 @@ public class AwsCredentials extends ExternalAccountCredentials {
static class AwsCredentialSource extends CredentialSource {

private static final String IMDSV2_SESSION_TOKEN_URL_FIELD_NAME = "imdsv2_session_token_url";
private static final long serialVersionUID = -4180558200808134436L;

private final String regionUrl;
private final String url;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import com.google.common.base.MoreObjects;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.math.BigDecimal;
import java.net.URI;
import java.nio.charset.StandardCharsets;
Expand All @@ -65,8 +66,12 @@
*/
public abstract class ExternalAccountCredentials extends GoogleCredentials {

private static final long serialVersionUID = 8049126194174465023L;

/** Base credential source class. Dictates the retrieval method of the external credential. */
abstract static class CredentialSource {
abstract static class CredentialSource implements Serializable {

private static final long serialVersionUID = 8204657811562399944L;

CredentialSource(Map<String, Object> credentialSourceMap) {
checkNotNull(credentialSourceMap);
Expand Down Expand Up @@ -636,7 +641,9 @@ private static boolean isValidUrl(String url) {
* }
* </pre>
*/
static final class ServiceAccountImpersonationOptions {
static final class ServiceAccountImpersonationOptions implements Serializable {

private static final long serialVersionUID = 4250771921886280953L;
private static final int DEFAULT_TOKEN_LIFETIME_SECONDS = 3600;
private static final int MAXIMUM_TOKEN_LIFETIME_SECONDS = 43200;
private static final int MINIMUM_TOKEN_LIFETIME_SECONDS = 600;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,16 @@
*/
public class IdentityPoolCredentials extends ExternalAccountCredentials {

private static final long serialVersionUID = 2471046175477275881L;

/**
* The IdentityPool credential source. Dictates the retrieval method of the external credential,
* which can either be through a metadata server or a local file.
*/
static class IdentityPoolCredentialSource extends ExternalAccountCredentials.CredentialSource {

private static final long serialVersionUID = -745855247050085694L;

enum IdentityPoolCredentialSourceType {
FILE,
URL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
Expand Down Expand Up @@ -700,4 +701,9 @@ public ImpersonatedCredentials build() {
return new ImpersonatedCredentials(this);
}
}

private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException {
input.defaultReadObject();
transportFactory = newInstance(transportFactoryClassName);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.google.auth.oauth2;

import java.io.Serializable;

/** Represents the default system environment provider. */
class SystemEnvironmentProvider implements EnvironmentProvider {
class SystemEnvironmentProvider implements EnvironmentProvider, Serializable {
static final SystemEnvironmentProvider INSTANCE = new SystemEnvironmentProvider();
private static final long serialVersionUID = -4698164985883575244L;

private SystemEnvironmentProvider() {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonParser;
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
import com.google.api.client.util.Clock;
import com.google.auth.TestUtils;
import com.google.auth.oauth2.AwsCredentials.AwsCredentialSource;
import com.google.auth.oauth2.ExternalAccountCredentialsTest.MockExternalAccountCredentialsTransportFactory;
Expand All @@ -62,7 +63,7 @@

/** Tests for {@link AwsCredentials}. */
@RunWith(JUnit4.class)
public class AwsCredentialsTest {
public class AwsCredentialsTest extends BaseSerializationTest {

private static final String STS_URL = "https://sts.googleapis.com";
private static final String AWS_CREDENTIALS_URL = "https://169.254.169.254";
Expand Down Expand Up @@ -1025,6 +1026,34 @@ public void builder() {
assertEquals(credentials.getEnvironmentProvider(), SystemEnvironmentProvider.getInstance());
}

@Test
public void serialize() throws IOException, ClassNotFoundException {
List<String> scopes = Arrays.asList("scope1", "scope2");

AwsCredentials testCredentials =
(AwsCredentials)
AwsCredentials.newBuilder()
.setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY)
.setAudience("audience")
.setSubjectTokenType("subjectTokenType")
.setTokenUrl(STS_URL)
.setTokenInfoUrl("tokenInfoUrl")
.setCredentialSource(AWS_CREDENTIAL_SOURCE)
.setTokenInfoUrl("tokenInfoUrl")
.setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL)
.setQuotaProjectId("quotaProjectId")
.setClientId("clientId")
.setClientSecret("clientSecret")
.setScopes(scopes)
.build();

AwsCredentials deserializedCredentials = serializeAndDeserialize(testCredentials);
assertEquals(testCredentials, deserializedCredentials);
assertEquals(testCredentials.hashCode(), deserializedCredentials.hashCode());
assertEquals(testCredentials.toString(), deserializedCredentials.toString());
assertSame(deserializedCredentials.clock, Clock.SYSTEM);
}

private static void ValidateRequest(
MockLowLevelHttpRequest request, String expectedUrl, Map<String, String> expectedHeaders) {
assertEquals(expectedUrl, request.getUrl());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@
import static org.junit.Assert.assertEquals;
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;

import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.GenericJson;
import com.google.api.client.util.Clock;
import com.google.auth.TestUtils;
import com.google.auth.http.HttpTransportFactory;
import com.google.auth.oauth2.ExternalAccountCredentialsTest.TestExternalAccountCredentials.TestCredentialSource;
Expand All @@ -62,7 +64,7 @@

/** Tests for {@link ExternalAccountCredentials}. */
@RunWith(JUnit4.class)
public class ExternalAccountCredentialsTest {
public class ExternalAccountCredentialsTest extends BaseSerializationTest {

private static final String STS_URL = "https://sts.googleapis.com";

Expand Down Expand Up @@ -954,6 +956,37 @@ public void getRequestMetadata_withQuotaProjectId() throws IOException {
assertEquals("quotaProjectId", requestMetadata.get("x-goog-user-project").get(0));
}

@Test
public void serialize() throws IOException, ClassNotFoundException {
Map<String, Object> impersonationOpts =
new HashMap<String, Object>() {
{
put("token_lifetime_seconds", 1000);
}
};

TestExternalAccountCredentials testCredentials =
(TestExternalAccountCredentials)
TestExternalAccountCredentials.newBuilder()
.setHttpTransportFactory(transportFactory)
.setAudience("audience")
.setSubjectTokenType("subjectTokenType")
.setTokenUrl(STS_URL)
.setCredentialSource(new TestCredentialSource(FILE_CREDENTIAL_SOURCE_MAP))
.setServiceAccountImpersonationOptions(impersonationOpts)
.build();

TestExternalAccountCredentials deserializedCredentials =
serializeAndDeserialize(testCredentials);
assertEquals(testCredentials, deserializedCredentials);
assertEquals(testCredentials.hashCode(), deserializedCredentials.hashCode());
assertEquals(testCredentials.toString(), deserializedCredentials.toString());
assertEquals(
testCredentials.getServiceAccountImpersonationOptions().getLifetime(),
deserializedCredentials.getServiceAccountImpersonationOptions().getLifetime());
assertSame(deserializedCredentials.clock, Clock.SYSTEM);
}

@Test
public void validateTokenUrl_validUrls() {
List<String> validUrls =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,11 @@

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.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assert.*;

import com.google.api.client.http.HttpTransport;
import com.google.api.client.testing.http.MockHttpTransport;
import com.google.api.client.util.Clock;
import com.google.auth.TestUtils;
import com.google.auth.http.HttpTransportFactory;
import com.google.auth.oauth2.ExternalAccountAuthorizedUserCredentialsTest.MockExternalAccountAuthorizedUserCredentialsTransportFactory;
Expand All @@ -62,7 +58,7 @@

/** Test case for {@link GoogleCredentials}. */
@RunWith(JUnit4.class)
public class GoogleCredentialsTest {
public class GoogleCredentialsTest extends BaseSerializationTest {

private static final String SA_CLIENT_EMAIL =
"36680232662-vrd7ji19qe3nelgchd0ah2csanun6bnr@developer.gserviceaccount.com";
Expand Down Expand Up @@ -589,6 +585,16 @@ public void createWithQuotaProject() {
assertEquals(null, sameCredentials.getQuotaProjectId());
}

@Test
public void serialize() throws IOException, ClassNotFoundException {
final GoogleCredentials testCredentials = new GoogleCredentials.Builder().build();
GoogleCredentials deserializedCredentials = serializeAndDeserialize(testCredentials);
assertEquals(testCredentials, deserializedCredentials);
assertEquals(testCredentials.hashCode(), deserializedCredentials.hashCode());
assertEquals(testCredentials.toString(), deserializedCredentials.toString());
assertSame(deserializedCredentials.clock, Clock.SYSTEM);
}

private static void testFromStreamException(InputStream stream, String expectedMessageContent) {
try {
GoogleCredentials.fromStream(stream, DUMMY_TRANSPORT_FACTORY);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,11 @@

import static com.google.auth.oauth2.MockExternalAccountCredentialsTransport.SERVICE_ACCOUNT_IMPERSONATION_URL;
import static com.google.auth.oauth2.OAuth2Utils.JSON_FACTORY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assert.*;

import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.GenericJson;
import com.google.api.client.util.Clock;
import com.google.auth.TestUtils;
import com.google.auth.http.HttpTransportFactory;
import com.google.auth.oauth2.IdentityPoolCredentials.IdentityPoolCredentialSource;
Expand All @@ -59,7 +57,7 @@

/** Tests for {@link IdentityPoolCredentials}. */
@RunWith(JUnit4.class)
public class IdentityPoolCredentialsTest {
public class IdentityPoolCredentialsTest extends BaseSerializationTest {

private static final String STS_URL = "https://sts.googleapis.com";

Expand Down Expand Up @@ -732,6 +730,24 @@ public void builder_emptyWorkforceUserProjectWithWorkforceAudience() {
assertTrue(credentials.isWorkforcePoolConfiguration());
}

@Test
public void serialize() throws IOException, ClassNotFoundException {
IdentityPoolCredentials testCredentials =
(IdentityPoolCredentials)
IdentityPoolCredentials.newBuilder(FILE_SOURCED_CREDENTIAL)
.setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL)
.setQuotaProjectId("quotaProjectId")
.setClientId("clientId")
.setClientSecret("clientSecret")
.build();

IdentityPoolCredentials deserializedCredentials = serializeAndDeserialize(testCredentials);
assertEquals(testCredentials, deserializedCredentials);
assertEquals(testCredentials.hashCode(), deserializedCredentials.hashCode());
assertEquals(testCredentials.toString(), deserializedCredentials.toString());
assertSame(deserializedCredentials.clock, Clock.SYSTEM);
}

static InputStream writeIdentityPoolCredentialsStream(
String tokenUrl,
String url,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@
package com.google.auth.oauth2;

import static com.google.auth.oauth2.MockExternalAccountCredentialsTransport.SERVICE_ACCOUNT_IMPERSONATION_URL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.junit.Assert.*;

import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.GenericJson;
Expand All @@ -45,6 +43,7 @@
import com.google.auth.oauth2.PluggableAuthCredentials.PluggableAuthCredentialSource;
import java.io.IOException;
import java.io.InputStream;
import java.io.NotSerializableException;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashMap;
Expand All @@ -54,7 +53,7 @@
import org.junit.Test;

/** Tests for {@link PluggableAuthCredentials}. */
public class PluggableAuthCredentialsTest {
public class PluggableAuthCredentialsTest extends BaseSerializationTest {
// The default timeout for waiting for the executable to finish (30 seconds).
private static final int DEFAULT_EXECUTABLE_TIMEOUT_MS = 30 * 1000;
// The minimum timeout for waiting for the executable to finish (5 seconds).
Expand Down Expand Up @@ -436,6 +435,22 @@ public void createdScoped_clonedCredentialWithAddedScopes() {
assertEquals(credentials.getExecutableHandler(), newCredentials.getExecutableHandler());
}

@Test
public void serialize() throws IOException, ClassNotFoundException {
PluggableAuthCredentials testCredentials =
(PluggableAuthCredentials)
PluggableAuthCredentials.newBuilder(CREDENTIAL)
.setExecutableHandler(options -> "pluggableAuthToken")
.setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL)
.setQuotaProjectId("quotaProjectId")
.setClientId("clientId")
.setClientSecret("clientSecret")
.build();

// PluggableAuthCredentials are not serializable
assertThrows(NotSerializableException.class, () -> serializeAndDeserialize(testCredentials));
}

private static CredentialSource buildCredentialSource() {
return buildCredentialSource("command", null, null);
}
Expand Down

0 comments on commit 82bf871

Please sign in to comment.