diff --git a/appengine/java/com/google/auth/appengine/AppEngineCredentials.java b/appengine/java/com/google/auth/appengine/AppEngineCredentials.java index 98e0389ac..04a6e707f 100644 --- a/appengine/java/com/google/auth/appengine/AppEngineCredentials.java +++ b/appengine/java/com/google/auth/appengine/AppEngineCredentials.java @@ -31,16 +31,20 @@ package com.google.auth.appengine; -import com.google.auth.oauth2.AccessToken; -import com.google.auth.oauth2.GoogleCredentials; -import com.google.common.collect.ImmutableList; import com.google.appengine.api.appidentity.AppIdentityService; import com.google.appengine.api.appidentity.AppIdentityService.GetAccessTokenResult; import com.google.appengine.api.appidentity.AppIdentityServiceFactory; +import com.google.auth.oauth2.AccessToken; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import java.io.IOException; +import java.io.ObjectInputStream; import java.util.Collection; import java.util.Date; +import java.util.Objects; /** * OAuth2 credentials representing the built-in service account for Google App ENgine. @@ -48,24 +52,27 @@ *

Fetches access tokens from the App Identity service. */ public class AppEngineCredentials extends GoogleCredentials { - - private final AppIdentityService appIdentityService; - + + private static final long serialVersionUID = -2627708355455064660L; + + private final String appIdentityServiceClassName; private final Collection scopes; - - private final boolean scopesRequired; - + private final boolean scopesRequired; + + private transient AppIdentityService appIdentityService; + public AppEngineCredentials(Collection scopes) { this(scopes, null); } public AppEngineCredentials(Collection scopes, AppIdentityService appIdentityService) { - this.scopes = ImmutableList.copyOf(scopes); + this.scopes = scopes == null ? ImmutableSet.of() : ImmutableList.copyOf(scopes); this.appIdentityService = appIdentityService != null ? appIdentityService : AppIdentityServiceFactory.getAppIdentityService(); - scopesRequired = (scopes == null || scopes.isEmpty()); + this.appIdentityServiceClassName = this.appIdentityService.getClass().getName(); + scopesRequired = this.scopes.isEmpty(); } - + /** * Refresh the access token by getting it from the App Identity service */ @@ -88,5 +95,35 @@ public boolean createScopedRequired() { @Override public GoogleCredentials createScoped(Collection scopes) { return new AppEngineCredentials(scopes, appIdentityService); - } + } + + @Override + public int hashCode() { + return Objects.hash(scopes, scopesRequired, appIdentityServiceClassName); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("scopes", scopes) + .add("scopesRequired", scopesRequired) + .add("appIdentityServiceClassName", appIdentityServiceClassName) + .toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof AppEngineCredentials)) { + return false; + } + AppEngineCredentials other = (AppEngineCredentials) obj; + return this.scopesRequired == other.scopesRequired + && Objects.equals(this.scopes, other.scopes) + && Objects.equals(this.appIdentityServiceClassName, other.appIdentityServiceClassName); + } + + private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { + input.defaultReadObject(); + appIdentityService = newInstance(appIdentityServiceClassName); + } } diff --git a/appengine/javatests/com/google/auth/appengine/AppEngineCredentialsTest.java b/appengine/javatests/com/google/auth/appengine/AppEngineCredentialsTest.java index 79c02b7cc..4ff02dde9 100644 --- a/appengine/javatests/com/google/auth/appengine/AppEngineCredentialsTest.java +++ b/appengine/javatests/com/google/auth/appengine/AppEngineCredentialsTest.java @@ -32,13 +32,15 @@ package com.google.auth.appengine; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.junit.Assert.assertNotSame; import com.google.auth.Credentials; import com.google.auth.oauth2.AccessToken; +import com.google.auth.oauth2.BaseSerializationTest; import com.google.auth.oauth2.GoogleCredentials; import org.junit.Test; @@ -58,7 +60,7 @@ * Unit tests for AppEngineCredentials */ @RunWith(JUnit4.class) -public class AppEngineCredentialsTest { +public class AppEngineCredentialsTest extends BaseSerializationTest { private static final Collection SCOPES = Collections.unmodifiableCollection(Arrays.asList("scope1", "scope2")); @@ -117,7 +119,62 @@ public void createScoped_clonesWithScopes() throws IOException { assertEquals(1, appIdentity.getGetAccessTokenCallCount()); assertContainsBearerToken(metadata, expectedAccessToken); } - + + @Test + public void equals_true() throws IOException { + final Collection emptyScopes = Collections.emptyList(); + MockAppIdentityService appIdentity = new MockAppIdentityService(); + GoogleCredentials credentials = new AppEngineCredentials(emptyScopes, appIdentity); + GoogleCredentials otherCredentials = new AppEngineCredentials(emptyScopes, appIdentity); + assertTrue(credentials.equals(credentials)); + assertTrue(credentials.equals(otherCredentials)); + assertTrue(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_scopes() throws IOException { + final Collection emptyScopes = Collections.emptyList(); + final Collection scopes = Collections.singleton("SomeScope"); + MockAppIdentityService appIdentity = new MockAppIdentityService(); + GoogleCredentials credentials = new AppEngineCredentials(emptyScopes, appIdentity); + GoogleCredentials otherCredentials = new AppEngineCredentials(scopes, appIdentity); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void toString_containsFields() throws IOException { + String expectedToString = String.format( + "AppEngineCredentials{scopes=[%s], scopesRequired=%b, appIdentityServiceClassName=%s}", + "SomeScope", + false, + MockAppIdentityService.class.getName()); + final Collection scopes = Collections.singleton("SomeScope"); + MockAppIdentityService appIdentity = new MockAppIdentityService(); + GoogleCredentials credentials = new AppEngineCredentials(scopes, appIdentity); + assertEquals(expectedToString, credentials.toString()); + } + + @Test + public void hashCode_equals() throws IOException { + final Collection emptyScopes = Collections.emptyList(); + MockAppIdentityService appIdentity = new MockAppIdentityService(); + GoogleCredentials credentials = new AppEngineCredentials(emptyScopes, appIdentity); + GoogleCredentials otherCredentials = new AppEngineCredentials(emptyScopes, appIdentity); + assertEquals(credentials.hashCode(), otherCredentials.hashCode()); + } + + @Test + public void serialize() throws IOException, ClassNotFoundException { + final Collection scopes = Collections.singleton("SomeScope"); + MockAppIdentityService appIdentity = new MockAppIdentityService(); + GoogleCredentials credentials = new AppEngineCredentials(scopes, appIdentity); + GoogleCredentials deserializedCredentials = serializeAndDeserialize(credentials); + assertEquals(credentials, deserializedCredentials); + assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); + assertEquals(credentials.toString(), deserializedCredentials.toString()); + } + private static void assertContainsBearerToken(Map> metadata, String token) { assertNotNull(metadata); assertNotNull(token); diff --git a/appengine/pom.xml b/appengine/pom.xml index 759d79440..9fef8d895 100644 --- a/appengine/pom.xml +++ b/appengine/pom.xml @@ -61,13 +61,19 @@ com.google.guava - guava-jdk5 + guava junit junit test + + com.google.auth + google-auth-library-oauth2-http + test-jar + test + diff --git a/credentials/java/com/google/auth/Credentials.java b/credentials/java/com/google/auth/Credentials.java index 4dfe5c80e..05e19ff95 100644 --- a/credentials/java/com/google/auth/Credentials.java +++ b/credentials/java/com/google/auth/Credentials.java @@ -32,6 +32,7 @@ package com.google.auth; import java.io.IOException; +import java.io.Serializable; import java.net.URI; import java.util.List; import java.util.Map; @@ -40,7 +41,9 @@ /** * Represents an abstract authorized identity instance. */ -public abstract class Credentials { +public abstract class Credentials implements Serializable { + + private static final long serialVersionUID = 808575179767517313L; /** * A constant string name describing the authentication technology. diff --git a/oauth2_http/java/com/google/auth/http/HttpCredentialsAdapter.java b/oauth2_http/java/com/google/auth/http/HttpCredentialsAdapter.java index f4eff5217..65e0dbd2f 100644 --- a/oauth2_http/java/com/google/auth/http/HttpCredentialsAdapter.java +++ b/oauth2_http/java/com/google/auth/http/HttpCredentialsAdapter.java @@ -99,7 +99,7 @@ public void initialize(HttpRequest request) throws IOException { } for (Map.Entry> entry : credentialHeaders.entrySet()) { String headerName = entry.getKey(); - List requestValues = new ArrayList(); + List requestValues = new ArrayList<>(); requestValues.addAll(entry.getValue()); requestHeaders.put(headerName, requestValues); } diff --git a/oauth2_http/java/com/google/auth/http/HttpTransportFactory.java b/oauth2_http/java/com/google/auth/http/HttpTransportFactory.java new file mode 100644 index 000000000..c80bc9356 --- /dev/null +++ b/oauth2_http/java/com/google/auth/http/HttpTransportFactory.java @@ -0,0 +1,48 @@ +/* + * Copyright 2016, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.auth.http; + +import com.google.api.client.http.HttpTransport; + +/** + * A base interface for all {@link HttpTransport} factories. + * + *

Implementation must provide a public no-arg constructor. Loading of a factory implementation + * is done via {@link java.util.ServiceLoader}. + */ +public interface HttpTransportFactory { + + /** + * Creates a {@code HttpTransport} instance. + */ + HttpTransport create(); +} diff --git a/oauth2_http/java/com/google/auth/oauth2/AccessToken.java b/oauth2_http/java/com/google/auth/oauth2/AccessToken.java index 605fffebb..090d3714a 100644 --- a/oauth2_http/java/com/google/auth/oauth2/AccessToken.java +++ b/oauth2_http/java/com/google/auth/oauth2/AccessToken.java @@ -31,12 +31,18 @@ package com.google.auth.oauth2; +import com.google.common.base.MoreObjects; + +import java.io.Serializable; import java.util.Date; +import java.util.Objects; /** * Represents a temporary OAuth2 access token and its expiration information. */ -public class AccessToken { +public class AccessToken implements Serializable { + + private static final long serialVersionUID = -8514239465808977353L; private final String tokenValue; private final Long expirationTimeMillis; @@ -70,4 +76,27 @@ public Date getExpirationTime() { Long getExpirationTimeMillis() { return expirationTimeMillis; } + + @Override + public int hashCode() { + return Objects.hash(tokenValue, expirationTimeMillis); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("tokenValue", tokenValue) + .add("expirationTimeMillis", expirationTimeMillis) + .toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof AccessToken)) { + return false; + } + AccessToken other = (AccessToken) obj; + return Objects.equals(this.tokenValue, other.tokenValue) + && Objects.equals(this.expirationTimeMillis, other.expirationTimeMillis); + } } diff --git a/oauth2_http/java/com/google/auth/oauth2/CloudShellCredentials.java b/oauth2_http/java/com/google/auth/oauth2/CloudShellCredentials.java index 1b0b07f34..2b7b08e21 100644 --- a/oauth2_http/java/com/google/auth/oauth2/CloudShellCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/CloudShellCredentials.java @@ -31,28 +31,30 @@ package com.google.auth.oauth2; -import com.google.api.client.json.JsonFactory; import com.google.api.client.json.JsonParser; +import com.google.common.base.MoreObjects; import java.io.BufferedReader; -import java.io.InputStreamReader; import java.io.IOException; +import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * OAuth2 credentials representing the built-in service account for Google Cloud Shell. */ public class CloudShellCredentials extends GoogleCredentials { + private static final long serialVersionUID = -2133257318957488451L; private final static int ACCESS_TOKEN_INDEX = 2; private final static int READ_TIMEOUT_MS = 5000; /** * The Cloud Shell back authorization channel uses serialized - * Javascript Protobufers, preceeded by the message lengeth and a + * Javascript Protobufers, preceeded by the message length and a * new line character. However, the request message has no content, * so a token request consists of an empty JsPb, and its 2 character * lenth prefix. @@ -60,11 +62,9 @@ public class CloudShellCredentials extends GoogleCredentials { protected final static String GET_AUTH_TOKEN_REQUEST = "2\n[]"; private final int authPort; - private final JsonFactory jsonFactory; - + public CloudShellCredentials(int authPort) { this.authPort = authPort; - this.jsonFactory = OAuth2Utils.JSON_FACTORY; } protected int getAuthPort() { @@ -84,7 +84,7 @@ public AccessToken refreshAccessToken() throws IOException { BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); input.readLine(); // Skip over the first line - JsonParser parser = jsonFactory.createJsonParser(input); + JsonParser parser = OAuth2Utils.JSON_FACTORY.createJsonParser(input); List messageArray = (List) parser.parseArray(ArrayList.class, Object.class); String accessToken = messageArray.get(ACCESS_TOKEN_INDEX).toString(); token = new AccessToken(accessToken, null); @@ -93,4 +93,23 @@ public AccessToken refreshAccessToken() throws IOException { } return token; } + + @Override + public int hashCode() { + return Objects.hash(authPort); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("authPort", authPort).toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof CloudShellCredentials)) { + return false; + } + CloudShellCredentials other = (CloudShellCredentials) obj; + return this.authPort == other.authPort; + } } diff --git a/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java index 51164fc8e..58e197634 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java @@ -31,19 +31,24 @@ package com.google.auth.oauth2; +import static com.google.common.base.MoreObjects.firstNonNull; + import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpHeaders; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpStatusCodes; -import com.google.api.client.http.HttpTransport; import com.google.api.client.json.JsonObjectParser; import com.google.api.client.util.GenericData; +import com.google.auth.http.HttpTransportFactory; +import com.google.common.base.MoreObjects; import java.io.IOException; import java.io.InputStream; +import java.io.ObjectInputStream; import java.net.UnknownHostException; import java.util.Date; +import java.util.Objects; /** * OAuth2 credentials representing the built-in service account for a Google Compute Engine VM. @@ -57,8 +62,11 @@ public class ComputeEngineCredentials extends GoogleCredentials { static final String METADATA_SERVER_URL = "http://metadata.google.internal"; private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. "; + private static final long serialVersionUID = -4113476462526554235L; + + private final String transportFactoryClassName; - private final HttpTransport transport; + private transient HttpTransportFactory transportFactory; /** * Constructor with minimum information and default behavior. @@ -70,10 +78,13 @@ public ComputeEngineCredentials() { /** * Constructor with overridden transport. * - * @param transport HTTP object used to get access tokens. + * @param transportFactory HTTP transport factory, creates the transport used to get access + * tokens. */ - public ComputeEngineCredentials(HttpTransport transport) { - this.transport = (transport == null) ? OAuth2Utils.HTTP_TRANSPORT : transport; + public ComputeEngineCredentials(HttpTransportFactory transportFactory) { + this.transportFactory = firstNonNull(transportFactory, + getFromServiceLoader(HttpTransportFactory.class, OAuth2Utils.HTTP_TRANSPORT_FACTORY)); + this.transportFactoryClassName = this.transportFactory.getClass().getName(); } /** @@ -82,12 +93,13 @@ public ComputeEngineCredentials(HttpTransport transport) { @Override public AccessToken refreshAccessToken() throws IOException { GenericUrl tokenUrl = new GenericUrl(TOKEN_SERVER_ENCODED_URL); - HttpRequest request = transport.createRequestFactory().buildGetRequest(tokenUrl); + HttpRequest request = + transportFactory.create().createRequestFactory().buildGetRequest(tokenUrl); JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY); request.setParser(parser); request.getHeaders().set("Metadata-Flavor", "Google"); request.setThrowExceptionOnExecuteError(false); - HttpResponse response = null; + HttpResponse response; try { response = request.execute(); } catch (UnknownHostException exception) { @@ -119,17 +131,17 @@ public AccessToken refreshAccessToken() throws IOException { int expiresInSeconds = OAuth2Utils.validateInt32( responseData, "expires_in", PARSE_ERROR_PREFIX); long expiresAtMilliseconds = clock.currentTimeMillis() + expiresInSeconds * 1000; - AccessToken access = new AccessToken(accessToken, new Date(expiresAtMilliseconds)); - return access; + return new AccessToken(accessToken, new Date(expiresAtMilliseconds)); } /** * Return whether code is running on Google Compute Engine. */ - static boolean runningOnComputeEngine(HttpTransport transport) { + static boolean runningOnComputeEngine(HttpTransportFactory transportFactory) { try { GenericUrl tokenUrl = new GenericUrl(METADATA_SERVER_URL); - HttpRequest request = transport.createRequestFactory().buildGetRequest(tokenUrl); + HttpRequest request = + transportFactory.create().createRequestFactory().buildGetRequest(tokenUrl); HttpResponse response = request.execute(); // Internet providers can return a generic response to all requests, so it is necessary // to check that metadata header is present also. @@ -138,7 +150,34 @@ static boolean runningOnComputeEngine(HttpTransport transport) { return true; } } catch (IOException expected) { + // ignore } return false; } + + @Override + public int hashCode() { + return Objects.hash(transportFactoryClassName); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("transportFactoryClassName", transportFactoryClassName) + .toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ComputeEngineCredentials)) { + return false; + } + ComputeEngineCredentials other = (ComputeEngineCredentials) obj; + return Objects.equals(this.transportFactoryClassName, other.transportFactoryClassName); + } + + private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { + input.defaultReadObject(); + transportFactory = newInstance(transportFactoryClassName); + } } diff --git a/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java b/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java index 081a5bc07..8d52d0546 100644 --- a/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java +++ b/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java @@ -31,7 +31,7 @@ package com.google.auth.oauth2; -import com.google.api.client.http.HttpTransport; +import com.google.auth.http.HttpTransportFactory; import java.io.File; import java.io.FileInputStream; @@ -87,15 +87,16 @@ class DefaultCredentialsProvider { * Compute Engine or credentials specified by an environment variable or a file in a well-known * location.

* - * @param transport the transport for Http calls. + * @param transportFactory HTTP transport factory, creates the transport used to get access + * tokens. * @return the credentials instance. * @throws IOException if the credentials cannot be created in the current environment. **/ - final GoogleCredentials getDefaultCredentials(HttpTransport transport) + final GoogleCredentials getDefaultCredentials(HttpTransportFactory transportFactory) throws IOException { synchronized (this) { if (cachedCredentials == null) { - cachedCredentials = getDefaultCredentialsUnsynchronized(transport); + cachedCredentials = getDefaultCredentialsUnsynchronized(transportFactory); } if (cachedCredentials != null) { return cachedCredentials; @@ -110,8 +111,8 @@ final GoogleCredentials getDefaultCredentials(HttpTransport transport) HELP_PERMALINK)); } - private final GoogleCredentials getDefaultCredentialsUnsynchronized(HttpTransport transport) - throws IOException { + private final GoogleCredentials getDefaultCredentialsUnsynchronized( + HttpTransportFactory transportFactory) throws IOException { // First try the environment variable GoogleCredentials credentials = null; @@ -125,7 +126,7 @@ private final GoogleCredentials getDefaultCredentialsUnsynchronized(HttpTranspor throw new IOException("File does not exist."); } credentialsStream = readStream(credentialsFile); - credentials = GoogleCredentials.fromStream(credentialsStream, transport); + credentials = GoogleCredentials.fromStream(credentialsStream, transportFactory); } catch (IOException e) { // Although it is also the cause, the message of the caught exception can have very // important information for diagnosing errors, so include its message in the @@ -149,7 +150,7 @@ private final GoogleCredentials getDefaultCredentialsUnsynchronized(HttpTranspor try { if (isFile(wellKnownFileLocation)) { credentialsStream = readStream(wellKnownFileLocation); - credentials = GoogleCredentials.fromStream(credentialsStream, transport); + credentials = GoogleCredentials.fromStream(credentialsStream, transportFactory); } } catch (IOException e) { throw new IOException(String.format( @@ -177,14 +178,14 @@ private final GoogleCredentials getDefaultCredentialsUnsynchronized(HttpTranspor // Then try Compute Engine if (credentials == null) { - credentials = tryGetComputeCredentials(transport); + credentials = tryGetComputeCredentials(transportFactory); } return credentials; } private final File getWellKnownCredentialsFile() { - File cloudConfigPath = null; + File cloudConfigPath; String os = getProperty("os.name", "").toLowerCase(Locale.US); String envPath = getEnv("CLOUDSDK_CONFIG"); if (envPath != null) { @@ -196,8 +197,7 @@ private final File getWellKnownCredentialsFile() { File configPath = new File(getProperty("user.home", ""), ".config"); cloudConfigPath = new File(configPath, CLOUDSDK_CONFIG_DIRECTORY); } - File credentialFilePath = new File(cloudConfigPath, WELL_KNOWN_CREDENTIALS_FILE); - return credentialFilePath; + return new File(cloudConfigPath, WELL_KNOWN_CREDENTIALS_FILE); } private boolean runningOnAppEngine() { @@ -208,7 +208,7 @@ private boolean runningOnAppEngine() { // SystemProperty will always be present on App Engine. return false; } - Exception cause = null; + Exception cause; Field environmentField; try { environmentField = systemPropertyClass.getField("environment"); @@ -217,17 +217,8 @@ private boolean runningOnAppEngine() { Method valueMethod = environmentType.getMethod("value"); Object environmentValueValue = valueMethod.invoke(environmentValue); return (environmentValueValue != null); - } catch (NoSuchFieldException exception) { - cause = exception; - } catch (SecurityException exception) { - cause = exception; - } catch (IllegalArgumentException exception) { - cause = exception; - } catch (IllegalAccessException exception) { - cause = exception; - } catch (NoSuchMethodException exception) { - cause = exception; - } catch (InvocationTargetException exception) { + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException + | IllegalAccessException | NoSuchMethodException | InvocationTargetException exception) { cause = exception; } throw OAuth2Utils.exceptionWithCause(new RuntimeException(String.format( @@ -254,22 +245,15 @@ private GoogleCredentials tryGetAppEngineCredential() throws IOException { if (!onAppEngine) { return null; } - Exception innerException = null; + Exception innerException; try { Class credentialClass = forName(APP_ENGINE_CREDENTIAL_CLASS); Constructor constructor = credentialClass .getConstructor(Collection.class); Collection emptyScopes = Collections.emptyList(); return (GoogleCredentials) constructor.newInstance(emptyScopes); - } catch (ClassNotFoundException e) { - innerException = e; - } catch (NoSuchMethodException e) { - innerException = e; - } catch (InstantiationException e) { - innerException = e; - } catch (IllegalAccessException e) { - innerException = e; - } catch (InvocationTargetException e) { + } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException + | IllegalAccessException | InvocationTargetException e) { innerException = e; } throw OAuth2Utils.exceptionWithCause(new IOException(String.format( @@ -279,15 +263,16 @@ private GoogleCredentials tryGetAppEngineCredential() throws IOException { APP_ENGINE_CREDENTIAL_CLASS)), innerException); } - private final GoogleCredentials tryGetComputeCredentials(HttpTransport transport) { + private final GoogleCredentials tryGetComputeCredentials(HttpTransportFactory transportFactory) { // Checking compute engine requires a round-trip, so check only once if (checkedComputeEngine) { return null; } - boolean runningOnComputeEngine = ComputeEngineCredentials.runningOnComputeEngine(transport); + boolean runningOnComputeEngine = + ComputeEngineCredentials.runningOnComputeEngine(transportFactory); checkedComputeEngine = true; if (runningOnComputeEngine) { - return new ComputeEngineCredentials(transport); + return new ComputeEngineCredentials(transportFactory); } return null; } diff --git a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java index 81e8bab8a..c27ba9a24 100644 --- a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java @@ -31,11 +31,11 @@ package com.google.auth.oauth2; -import com.google.api.client.http.HttpTransport; import com.google.api.client.json.GenericJson; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.JsonObjectParser; import com.google.api.client.util.Preconditions; +import com.google.auth.http.HttpTransportFactory; import java.io.IOException; import java.io.InputStream; @@ -46,6 +46,7 @@ */ public class GoogleCredentials extends OAuth2Credentials { + private static final long serialVersionUID = -1522852442442473691L; static final String USER_FILE_TYPE = "authorized_user"; static final String SERVICE_ACCOUNT_FILE_TYPE = "service_account"; @@ -64,7 +65,7 @@ public class GoogleCredentials extends OAuth2Credentials { * @throws IOException if the credentials cannot be created in the current environment. */ public static GoogleCredentials getApplicationDefault() throws IOException { - return getApplicationDefault(OAuth2Utils.HTTP_TRANSPORT); + return getApplicationDefault(OAuth2Utils.HTTP_TRANSPORT_FACTORY); } /** @@ -75,14 +76,15 @@ public static GoogleCredentials getApplicationDefault() throws IOException { * Compute Engine or the credentials file from the path in the environment variable * GOOGLE_APPLICATION_CREDENTIALS.

* - * @param transport the transport for Http calls. + * @param transportFactory HTTP transport factory, creates the transport used to get access + * tokens. * @return the credentials instance. * @throws IOException if the credentials cannot be created in the current environment. **/ - public static GoogleCredentials getApplicationDefault( - HttpTransport transport) throws IOException { - Preconditions.checkNotNull(transport); - return defaultCredentialsProvider.getDefaultCredentials(transport); + public static GoogleCredentials getApplicationDefault(HttpTransportFactory transportFactory) + throws IOException { + Preconditions.checkNotNull(transportFactory); + return defaultCredentialsProvider.getDefaultCredentials(transportFactory); } /** @@ -96,7 +98,7 @@ public static GoogleCredentials getApplicationDefault( * @throws IOException if the credential cannot be created from the stream. **/ public static GoogleCredentials fromStream(InputStream credentialsStream) throws IOException { - return fromStream(credentialsStream, OAuth2Utils.HTTP_TRANSPORT); + return fromStream(credentialsStream, OAuth2Utils.HTTP_TRANSPORT_FACTORY); } /** @@ -106,14 +108,15 @@ public static GoogleCredentials fromStream(InputStream credentialsStream) throws * Console or a stored user credential using the format supported by the Cloud SDK.

* * @param credentialsStream the stream with the credential definition. - * @param transport the transport for Http calls. + * @param transportFactory HTTP transport factory, creates the transport used to get access + * tokens. * @return the credential defined by the credentialsStream. * @throws IOException if the credential cannot be created from the stream. **/ - public static GoogleCredentials fromStream(InputStream credentialsStream, HttpTransport transport) - throws IOException { + public static GoogleCredentials fromStream(InputStream credentialsStream, + HttpTransportFactory transportFactory) throws IOException { Preconditions.checkNotNull(credentialsStream); - Preconditions.checkNotNull(transport); + Preconditions.checkNotNull(transportFactory); JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY; JsonObjectParser parser = new JsonObjectParser(jsonFactory); @@ -125,10 +128,10 @@ public static GoogleCredentials fromStream(InputStream credentialsStream, HttpTr throw new IOException("Error reading credentials from stream, 'type' field not specified."); } if (USER_FILE_TYPE.equals(fileType)) { - return UserCredentials.fromJson(fileContents, transport); + return UserCredentials.fromJson(fileContents, transportFactory); } if (SERVICE_ACCOUNT_FILE_TYPE.equals(fileType)) { - return ServiceAccountCredentials.fromJson(fileContents, transport); + return ServiceAccountCredentials.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/OAuth2Credentials.java b/oauth2_http/java/com/google/auth/oauth2/OAuth2Credentials.java index 6405d26da..c6f67c0e7 100644 --- a/oauth2_http/java/com/google/auth/oauth2/OAuth2Credentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/OAuth2Credentials.java @@ -35,15 +35,22 @@ import com.google.auth.Credentials; import com.google.auth.RequestMetadataCallback; import com.google.auth.http.AuthHttpConstants; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.base.Preconditions; +import com.google.common.collect.Iterables; import java.io.IOException; +import java.io.ObjectInputStream; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.ServiceLoader; import java.util.concurrent.Executor; /** @@ -51,15 +58,19 @@ */ public class OAuth2Credentials extends Credentials { + private static final long serialVersionUID = 4556936364828217687L; private static final int MINIMUM_TOKEN_MILLISECONDS = 60000; - private final Object lock = new Object(); + // byte[] is serializable, so the lock variable can be final + private final Object lock = new byte[0]; private Map> requestMetadata; private AccessToken temporaryAccess; - private List changeListeners; - // Allow clock to be overridden by test code - Clock clock = Clock.SYSTEM; + // Change listeners are not serialized + private transient List changeListeners; + // Until we expose this to the users it can remain transient and non-serializable + @VisibleForTesting + transient Clock clock = Clock.SYSTEM; /** * Default constructor. @@ -184,7 +195,7 @@ public AccessToken refreshAccessToken() throws IOException { public final void addChangeListener(CredentialsChangedListener listener) { synchronized(lock) { if (changeListeners == null) { - changeListeners = new ArrayList(); + changeListeners = new ArrayList<>(); } changeListeners.add(listener); } @@ -224,4 +235,48 @@ public interface CredentialsChangedListener { */ void onChanged(OAuth2Credentials credentials) throws IOException; } + + @Override + public int hashCode() { + return Objects.hash(requestMetadata, temporaryAccess); + } + + protected ToStringHelper toStringHelper() { + return MoreObjects.toStringHelper(this) + .add("requestMetadata", requestMetadata) + .add("temporaryAccess", temporaryAccess); + } + + @Override + public String toString() { + return toStringHelper().toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof OAuth2Credentials)) { + return false; + } + OAuth2Credentials other = (OAuth2Credentials) obj; + return Objects.equals(this.requestMetadata, other.requestMetadata) + && Objects.equals(this.temporaryAccess, other.temporaryAccess); + } + + private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { + input.defaultReadObject(); + clock = Clock.SYSTEM; + } + + @SuppressWarnings("unchecked") + protected static T newInstance(String className) throws IOException, ClassNotFoundException { + try { + return (T) Class.forName(className).newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new IOException(e); + } + } + + protected static T getFromServiceLoader(Class clazz, T defaultInstance) { + return Iterables.getFirst(ServiceLoader.load(clazz), defaultInstance); + } } diff --git a/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java b/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java index 39673c514..33ee24f8d 100644 --- a/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java +++ b/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java @@ -39,6 +39,7 @@ import com.google.api.client.json.JsonObjectParser; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.auth.http.AuthHttpConstants; +import com.google.auth.http.HttpTransportFactory; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -60,6 +61,8 @@ class OAuth2Utils { static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport(); + static final HttpTransportFactory HTTP_TRANSPORT_FACTORY = new DefaultHttpTransportFactory(); + static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance(); static final Charset UTF_8 = Charset.forName("UTF-8"); @@ -69,6 +72,13 @@ class OAuth2Utils { static final String BEARER_PREFIX = AuthHttpConstants.BEARER + " "; + static class DefaultHttpTransportFactory implements HttpTransportFactory { + + public HttpTransport create() { + return HTTP_TRANSPORT; + } + } + /** * Returns whether the headers contain the specified value as one of the entries in the * specified header. diff --git a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java index 2690110f8..c32d073d8 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java @@ -31,11 +31,12 @@ package com.google.auth.oauth2; +import static com.google.common.base.MoreObjects.firstNonNull; + import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponse; -import com.google.api.client.http.HttpTransport; import com.google.api.client.http.UrlEncodedContent; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.JsonObjectParser; @@ -44,11 +45,15 @@ import com.google.api.client.util.GenericData; import com.google.api.client.util.Joiner; import com.google.api.client.util.PemReader; -import com.google.api.client.util.SecurityUtils; import com.google.api.client.util.PemReader.Section; import com.google.api.client.util.Preconditions; +import com.google.api.client.util.SecurityUtils; +import com.google.auth.http.HttpTransportFactory; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableSet; import java.io.IOException; +import java.io.ObjectInputStream; import java.io.Reader; import java.io.StringReader; import java.net.URI; @@ -60,8 +65,8 @@ import java.security.spec.PKCS8EncodedKeySpec; import java.sql.Date; import java.util.Collection; -import java.util.Collections; import java.util.Map; +import java.util.Objects; /** * OAuth2 credentials representing a Service Account for calling Google APIs. @@ -70,6 +75,7 @@ */ public class ServiceAccountCredentials extends GoogleCredentials { + private static final long serialVersionUID = 7807543542681217978L; private static final String GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"; private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. "; @@ -77,10 +83,12 @@ public class ServiceAccountCredentials extends GoogleCredentials { private final String clientEmail; private final PrivateKey privateKey; private final String privateKeyId; - private final HttpTransport transport; + private final String transportFactoryClassName; private final URI tokenServerUri; private final Collection scopes; + private transient HttpTransportFactory transportFactory; + /** * Constructor with minimum identifying information. * @@ -106,19 +114,21 @@ public ServiceAccountCredentials( * @param privateKeyId Private key identifier for the service account. May be null. * @param scopes Scope strings for the APIs to be called. May be null or an empty collection, * which results in a credential that must have createScoped called before use. - * @param transport HTTP object used to get access tokens. + * @param transportFactory HTTP transport factory, creates the transport used to get access + * tokens. * @param tokenServerUri URI of the end point that provides tokens. */ public ServiceAccountCredentials( String clientId, String clientEmail, PrivateKey privateKey, String privateKeyId, - Collection scopes, HttpTransport transport, URI tokenServerUri) { + Collection scopes, HttpTransportFactory transportFactory, URI tokenServerUri) { this.clientId = clientId; this.clientEmail = Preconditions.checkNotNull(clientEmail); this.privateKey = Preconditions.checkNotNull(privateKey); this.privateKeyId = privateKeyId; - this.scopes = (scopes == null) ? Collections.emptyList() - : Collections.unmodifiableCollection(scopes); - this.transport = (transport == null) ? OAuth2Utils.HTTP_TRANSPORT : transport; + this.scopes = (scopes == null) ? ImmutableSet.of() : ImmutableSet.copyOf(scopes); + this.transportFactory = firstNonNull(transportFactory, + getFromServiceLoader(HttpTransportFactory.class, OAuth2Utils.HTTP_TRANSPORT_FACTORY)); + this.transportFactoryClassName = this.transportFactory.getClass().getName(); this.tokenServerUri = (tokenServerUri == null) ? OAuth2Utils.TOKEN_SERVER_URI : tokenServerUri; } @@ -127,12 +137,13 @@ public ServiceAccountCredentials( * Developers Console. * * @param json a map from the JSON representing the credentials. - * @param transport the transport for Http calls. + * @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 ServiceAccountCredentials fromJson( - Map json, HttpTransport transport) throws IOException { + Map json, HttpTransportFactory transportFactory) throws IOException { String clientId = (String) json.get("client_id"); String clientEmail = (String) json.get("client_email"); String privateKeyPkcs8 = (String) json.get("private_key"); @@ -143,7 +154,8 @@ static ServiceAccountCredentials fromJson( + "expecting 'client_id', 'client_email', 'private_key' and 'private_key_id'."); } - return fromPkcs8(clientId, clientEmail, privateKeyPkcs8, privateKeyId, null, transport, null); + return fromPkcs8(clientId, clientEmail, privateKeyPkcs8, privateKeyId, null, transportFactory, + null); } /** @@ -172,15 +184,17 @@ public static ServiceAccountCredentials fromPkcs8( * @param privateKeyId Private key identifier for the service account. May be null. * @param scopes Scope strings for the APIs to be called. May be null or an emptt collection, * which results in a credential that must have createScoped called before use. - * @param transport HTTP object used to get access tokens. + * @param transportFactory HTTP transport factory, creates the transport used to get access + * tokens. * @param tokenServerUri URI of the end point that provides tokens. */ public static ServiceAccountCredentials fromPkcs8( String clientId, String clientEmail, String privateKeyPkcs8, String privateKeyId, - Collection scopes, HttpTransport transport, URI tokenServerUri) throws IOException { + Collection scopes, HttpTransportFactory transportFactory, URI tokenServerUri) + throws IOException { PrivateKey privateKey = privateKeyFromPkcs8(privateKeyPkcs8); return new ServiceAccountCredentials( - clientId, clientEmail, privateKey, privateKeyId, scopes, transport, tokenServerUri); + clientId, clientEmail, privateKey, privateKeyId, scopes, transportFactory, tokenServerUri); } /** @@ -194,14 +208,11 @@ static PrivateKey privateKeyFromPkcs8(String privateKeyPkcs8) throws IOException } byte[] bytes = section.getBase64DecodedBytes(); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes); - Exception unexpectedException = null; + Exception unexpectedException; try { KeyFactory keyFactory = SecurityUtils.getRsaKeyFactory(); - PrivateKey privateKey = keyFactory.generatePrivate(keySpec); - return privateKey; - } catch (NoSuchAlgorithmException exception) { - unexpectedException = exception; - } catch (InvalidKeySpecException exception) { + return keyFactory.generatePrivate(keySpec); + } catch (NoSuchAlgorithmException | InvalidKeySpecException exception) { unexpectedException = exception; } throw OAuth2Utils.exceptionWithCause( @@ -234,7 +245,7 @@ public AccessToken refreshAccessToken() throws IOException { JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY; - String assertion = null; + String assertion; try { assertion = JsonWebSignature.signUsingRsaSha256( privateKey, jsonFactory, header, payload); @@ -247,7 +258,7 @@ public AccessToken refreshAccessToken() throws IOException { tokenRequest.set("assertion", assertion); UrlEncodedContent content = new UrlEncodedContent(tokenRequest); - HttpRequestFactory requestFactory = transport.createRequestFactory(); + HttpRequestFactory requestFactory = transportFactory.create().createRequestFactory(); HttpRequest request = requestFactory.buildPostRequest(new GenericUrl(tokenServerUri), content); request.setParser(new JsonObjectParser(jsonFactory)); @@ -265,12 +276,11 @@ public AccessToken refreshAccessToken() throws IOException { int expiresInSeconds = OAuth2Utils.validateInt32( responseData, "expires_in", PARSE_ERROR_PREFIX); long expiresAtMilliseconds = clock.currentTimeMillis() + expiresInSeconds * 1000; - AccessToken access = new AccessToken(accessToken, new Date(expiresAtMilliseconds)); - return access; + return new AccessToken(accessToken, new Date(expiresAtMilliseconds)); } /** - * Returns whther the scopes are empty, meaning createScoped must be called before use. + * Returns whether the scopes are empty, meaning createScoped must be called before use. */ @Override public boolean createScopedRequired() { @@ -284,8 +294,8 @@ public boolean createScopedRequired() { */ @Override public GoogleCredentials createScoped(Collection newScopes) { - return new ServiceAccountCredentials( - clientId, clientEmail, privateKey, privateKeyId, newScopes, transport, tokenServerUri); + return new ServiceAccountCredentials(clientId, clientEmail, privateKey, privateKeyId, newScopes, + transportFactory, tokenServerUri); } public final String getClientId() { @@ -307,4 +317,42 @@ public final String getPrivateKeyId() { public final Collection getScopes() { return scopes; } + + @Override + public int hashCode() { + return Objects.hash(clientId, clientEmail, privateKey, privateKeyId, transportFactoryClassName, + tokenServerUri, scopes); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("clientId", clientId) + .add("clientEmail", clientEmail) + .add("privateKeyId", privateKeyId) + .add("transportFactoryClassName", transportFactoryClassName) + .add("tokenServerUri", tokenServerUri) + .add("scopes", scopes) + .toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ServiceAccountCredentials)) { + return false; + } + ServiceAccountCredentials other = (ServiceAccountCredentials) obj; + return Objects.equals(this.clientId, other.clientId) + && Objects.equals(this.clientEmail, other.clientEmail) + && Objects.equals(this.privateKey, other.privateKey) + && Objects.equals(this.privateKeyId, other.privateKeyId) + && Objects.equals(this.transportFactoryClassName, other.transportFactoryClassName) + && Objects.equals(this.tokenServerUri, other.tokenServerUri) + && Objects.equals(this.scopes, other.scopes); + } + + private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { + input.defaultReadObject(); + transportFactory = newInstance(transportFactoryClassName); + } } diff --git a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountJwtAccessCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountJwtAccessCredentials.java index bd4e59d06..eef051e7b 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountJwtAccessCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountJwtAccessCredentials.java @@ -39,14 +39,18 @@ import com.google.auth.Credentials; import com.google.auth.RequestMetadataCallback; import com.google.auth.http.AuthHttpConstants; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; import java.io.IOException; +import java.io.ObjectInputStream; import java.net.URI; import java.security.GeneralSecurityException; import java.security.PrivateKey; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -56,6 +60,7 @@ */ public class ServiceAccountJwtAccessCredentials extends Credentials { + private static final long serialVersionUID = -7274955171379494197L; static final String JWT_ACCESS_PREFIX = OAuth2Utils.BEARER_PREFIX; private final String clientId; @@ -64,8 +69,9 @@ public class ServiceAccountJwtAccessCredentials extends Credentials { private final String privateKeyId; private final URI defaultAudience; - // Allow clock to be overriden by test code - Clock clock = Clock.SYSTEM; + // Until we expose this to the users it can remain transient and non-serializable + @VisibleForTesting + transient Clock clock = Clock.SYSTEM; /** * Constructor with minimum identifying information. @@ -201,9 +207,7 @@ public Map> getRequestMetadata(URI uri) throws IOException String assertion = getJwtAccess(uri); String authorizationHeader = JWT_ACCESS_PREFIX + assertion; List newAuthorizationHeaders = Collections.singletonList(authorizationHeader); - Map> newRequestMetadata = - Collections.singletonMap(AuthHttpConstants.AUTHORIZATION, newAuthorizationHeaders); - return newRequestMetadata; + return Collections.singletonMap(AuthHttpConstants.AUTHORIZATION, newAuthorizationHeaders); } /** @@ -231,7 +235,7 @@ private String getJwtAccess(URI uri) throws IOException { JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY; - String assertion = null; + String assertion; try { assertion = JsonWebSignature.signUsingRsaSha256( privateKey, jsonFactory, header, payload); @@ -257,4 +261,37 @@ public final PrivateKey getPrivateKey() { public final String getPrivateKeyId() { return privateKeyId; } + + @Override + public int hashCode() { + return Objects.hash(clientId, clientEmail, privateKey, privateKeyId, defaultAudience); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("clientId", clientId) + .add("clientEmail", clientEmail) + .add("privateKeyId", privateKeyId) + .add("defaultAudience", defaultAudience) + .toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ServiceAccountJwtAccessCredentials)) { + return false; + } + ServiceAccountJwtAccessCredentials other = (ServiceAccountJwtAccessCredentials) obj; + return Objects.equals(this.clientId, other.clientId) + && Objects.equals(this.clientEmail, other.clientEmail) + && Objects.equals(this.privateKey, other.privateKey) + && Objects.equals(this.privateKeyId, other.privateKeyId) + && Objects.equals(this.defaultAudience, other.defaultAudience); + } + + private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { + input.defaultReadObject(); + clock = Clock.SYSTEM; + } } diff --git a/oauth2_http/java/com/google/auth/oauth2/UserAuthorizer.java b/oauth2_http/java/com/google/auth/oauth2/UserAuthorizer.java index 955e984c3..f1e495b5e 100644 --- a/oauth2_http/java/com/google/auth/oauth2/UserAuthorizer.java +++ b/oauth2_http/java/com/google/auth/oauth2/UserAuthorizer.java @@ -35,16 +35,13 @@ import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponse; -import com.google.api.client.http.HttpTransport; import com.google.api.client.http.UrlEncodedContent; import com.google.api.client.json.GenericJson; import com.google.api.client.json.JsonObjectParser; import com.google.api.client.util.GenericData; import com.google.api.client.util.Joiner; import com.google.api.client.util.Preconditions; -import com.google.auth.oauth2.AccessToken; -import com.google.auth.oauth2.OAuth2Credentials; -import com.google.auth.oauth2.UserCredentials; +import com.google.auth.http.HttpTransportFactory; import com.google.common.collect.ImmutableList; import java.io.IOException; @@ -68,7 +65,7 @@ public class UserAuthorizer { private final TokenStore tokenStore; private final URI callbackUri; - private final HttpTransport transport; + private final HttpTransportFactory transportFactory; private final URI tokenServerUri; private final URI userAuthUri; @@ -103,16 +100,18 @@ public UserAuthorizer(ClientId clientId, Collection scopes, TokenStore t * @param scopes OAUth2 scopes defining the user consent. * @param tokenStore Implementation of a component for long term storage of tokens. * @param callbackUri URI for implementation of the OAuth2 web callback. - * @param transport HTTP transport implementation for OAuth2 API calls. + * @param transportFactory HTTP transport factory, creates the transport used to get access + * tokens. * @param tokenServerUri URI of the end point that provides tokens. * @param userAuthUri URI of the Web UI for user consent. */ public UserAuthorizer(ClientId clientId, Collection scopes, TokenStore tokenStore, - URI callbackUri, HttpTransport transport, URI tokenServerUri, URI userAuthUri) { + URI callbackUri, HttpTransportFactory transportFactory, URI tokenServerUri, URI userAuthUri) { this.clientId = Preconditions.checkNotNull(clientId); this.scopes = ImmutableList.copyOf(Preconditions.checkNotNull(scopes)); this.callbackUri = (callbackUri == null) ? DEFAULT_CALLBACK_URI : callbackUri; - this.transport = (transport == null) ? OAuth2Utils.HTTP_TRANSPORT : transport; + this.transportFactory = + (transportFactory == null) ? OAuth2Utils.HTTP_TRANSPORT_FACTORY : transportFactory; this.tokenServerUri = (tokenServerUri == null) ? OAuth2Utils.TOKEN_SERVER_URI : tokenServerUri; this.userAuthUri = (userAuthUri == null) ? OAuth2Utils.USER_AUTH_URI : userAuthUri; this.tokenStore = tokenStore; @@ -217,12 +216,12 @@ public UserCredentials getCredentials(String userId) throws IOException { tokenJson, "access_token", TOKEN_STORE_ERROR); Long expirationMillis = OAuth2Utils.validateLong( tokenJson, "expiration_time_millis", TOKEN_STORE_ERROR); - Date expirationTime = new Date(Long.valueOf(expirationMillis)); + Date expirationTime = new Date(expirationMillis); AccessToken accessToken = new AccessToken(accessTokenValue, expirationTime); String refreshToken = OAuth2Utils.validateOptionalString( tokenJson, "refresh_token", TOKEN_STORE_ERROR); UserCredentials credentials = new UserCredentials(clientId.getClientId(), - clientId.getClientSecret(), refreshToken, accessToken, transport, tokenServerUri); + clientId.getClientSecret(), refreshToken, accessToken, transportFactory, tokenServerUri); monitorCredentials(userId, credentials); return credentials; } @@ -246,7 +245,7 @@ public UserCredentials getCredentialsFromCode(String code, URI baseUri) throws I tokenData.put("redirect_uri", resolvedCallbackUri); tokenData.put("grant_type", "authorization_code"); UrlEncodedContent tokenContent = new UrlEncodedContent(tokenData); - HttpRequestFactory requestFactory = transport.createRequestFactory(); + HttpRequestFactory requestFactory = transportFactory.create().createRequestFactory(); HttpRequest tokenRequest = requestFactory.buildPostRequest( new GenericUrl(tokenServerUri), tokenContent); tokenRequest.setParser(new JsonObjectParser(OAuth2Utils.JSON_FACTORY)); @@ -262,9 +261,8 @@ public UserCredentials getCredentialsFromCode(String code, URI baseUri) throws I String refreshToken = OAuth2Utils.validateOptionalString( parsedTokens, "refresh_token", FETCH_TOKEN_ERROR); - UserCredentials credentials = new UserCredentials(clientId.getClientId(), - clientId.getClientSecret(), refreshToken, accessToken, transport, tokenServerUri); - return credentials; + return new UserCredentials(clientId.getClientId(), clientId.getClientSecret(), refreshToken, + accessToken, transportFactory, tokenServerUri); } /** @@ -318,7 +316,7 @@ public void revokeAuthorization(String userId) throws IOException { String revokeToken = (refreshToken != null) ? refreshToken : accessTokenValue; GenericUrl revokeUrl = new GenericUrl(OAuth2Utils.TOKEN_REVOKE_URI); revokeUrl.put("token", revokeToken); - HttpRequestFactory requestFactory = transport.createRequestFactory(); + HttpRequestFactory requestFactory = transportFactory.create().createRequestFactory(); HttpRequest tokenRequest = requestFactory.buildGetRequest(revokeUrl); tokenRequest.execute(); diff --git a/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java b/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java index ea6bfa7c3..90d0928e0 100644 --- a/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java @@ -31,20 +31,24 @@ package com.google.auth.oauth2; +import static com.google.common.base.MoreObjects.firstNonNull; + import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponse; -import com.google.api.client.http.HttpTransport; import com.google.api.client.http.UrlEncodedContent; import com.google.api.client.json.JsonObjectParser; import com.google.api.client.util.GenericData; import com.google.api.client.util.Preconditions; +import com.google.auth.http.HttpTransportFactory; import java.io.IOException; +import java.io.ObjectInputStream; import java.net.URI; import java.util.Date; import java.util.Map; +import java.util.Objects; /** * OAuth2 Credentials representing a user's identity and consent. @@ -53,12 +57,15 @@ public class UserCredentials extends GoogleCredentials { private static final String GRANT_TYPE = "refresh_token"; private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. "; + private static final long serialVersionUID = -4800758775038679176L; private final String clientId; private final String clientSecret; private final String refreshToken; - private final HttpTransport transport; private final URI tokenServerUri; + private final String transportFactoryClassName; + + private transient HttpTransportFactory transportFactory; /** * Constructor with minimum information and default behavior. @@ -92,17 +99,20 @@ public UserCredentials( * @param clientSecret Client ID of the credential from the console. * @param refreshToken A refresh token resulting from a OAuth2 consent flow. * @param accessToken Initial or temporary access token. - * @param transport HTTP object used to get access tokens. + * @param transportFactory HTTP transport factory, creates the transport used to get access + * tokens. * @param tokenServerUri URI of the end point that provides tokens. */ public UserCredentials(String clientId, String clientSecret, String refreshToken, - AccessToken accessToken, HttpTransport transport, URI tokenServerUri) { + AccessToken accessToken, HttpTransportFactory transportFactory, URI tokenServerUri) { super(accessToken); this.clientId = Preconditions.checkNotNull(clientId); this.clientSecret = Preconditions.checkNotNull(clientSecret); this.refreshToken = refreshToken; - this.transport = (transport == null) ? OAuth2Utils.HTTP_TRANSPORT : transport; + this.transportFactory = firstNonNull(transportFactory, + getFromServiceLoader(HttpTransportFactory.class, OAuth2Utils.HTTP_TRANSPORT_FACTORY)); this.tokenServerUri = (tokenServerUri == null) ? OAuth2Utils.TOKEN_SERVER_URI : tokenServerUri; + this.transportFactoryClassName = this.transportFactory.getClass().getName(); Preconditions.checkState(accessToken != null || refreshToken != null, "Either accessToken or refreshToken must not be null"); } @@ -111,11 +121,12 @@ public UserCredentials(String clientId, String clientSecret, String refreshToken * Returns user crentials defined by JSON contents using the format supported by the Cloud SDK. * * @param json a map from the JSON representing the credentials. - * @param transport the transport for Http calls. + * @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 UserCredentials fromJson(Map json, HttpTransport transport) + static UserCredentials fromJson(Map json, HttpTransportFactory transportFactory) throws IOException { String clientId = (String) json.get("client_id"); String clientSecret = (String) json.get("client_secret"); @@ -124,9 +135,7 @@ static UserCredentials fromJson(Map json, HttpTransport transpor throw new IOException("Error reading user credential from JSON, " + " expecting 'client_id', 'client_secret' and 'refresh_token'."); } - UserCredentials credentials = - new UserCredentials(clientId, clientSecret, refreshToken, null, transport, null); - return credentials; + return new UserCredentials(clientId, clientSecret, refreshToken, null, transportFactory, null); } /** @@ -145,7 +154,7 @@ public AccessToken refreshAccessToken() throws IOException { tokenRequest.set("grant_type", GRANT_TYPE); UrlEncodedContent content = new UrlEncodedContent(tokenRequest); - HttpRequestFactory requestFactory = transport.createRequestFactory(); + HttpRequestFactory requestFactory = transportFactory.create().createRequestFactory(); HttpRequest request = requestFactory.buildPostRequest(new GenericUrl(tokenServerUri), content); request.setParser(new JsonObjectParser(OAuth2Utils.JSON_FACTORY)); @@ -156,8 +165,7 @@ public AccessToken refreshAccessToken() throws IOException { int expiresInSeconds = OAuth2Utils.validateInt32(responseData, "expires_in", PARSE_ERROR_PREFIX); long expiresAtMilliseconds = clock.currentTimeMillis() + expiresInSeconds * 1000; - AccessToken access = new AccessToken(accessToken, new Date(expiresAtMilliseconds)); - return access; + return new AccessToken(accessToken, new Date(expiresAtMilliseconds)); } /** @@ -186,4 +194,39 @@ public final String getClientSecret() { public final String getRefreshToken() { return refreshToken; } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), clientId, clientSecret, refreshToken, tokenServerUri, + transportFactoryClassName); + } + + @Override + public String toString() { + return toStringHelper() + .add("clientId", clientId) + .add("refreshToken", refreshToken) + .add("tokenServerUri", tokenServerUri) + .add("transportFactoryClassName", transportFactoryClassName) + .toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof UserCredentials)) { + return false; + } + UserCredentials other = (UserCredentials) obj; + return super.equals(other) + && Objects.equals(this.clientId, other.clientId) + && Objects.equals(this.clientSecret, other.clientSecret) + && Objects.equals(this.refreshToken, other.refreshToken) + && Objects.equals(this.tokenServerUri, other.tokenServerUri) + && Objects.equals(this.transportFactoryClassName, other.transportFactoryClassName); + } + + private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { + input.defaultReadObject(); + transportFactory = newInstance(transportFactoryClassName); + } } diff --git a/oauth2_http/javatests/com/google/auth/TestUtils.java b/oauth2_http/javatests/com/google/auth/TestUtils.java index f45d24d47..e09941a04 100644 --- a/oauth2_http/javatests/com/google/auth/TestUtils.java +++ b/oauth2_http/javatests/com/google/auth/TestUtils.java @@ -79,8 +79,7 @@ public static void assertContainsBearerToken(Map> metadata, public static InputStream jsonToInputStream(GenericJson json) throws IOException { json.setFactory(JSON_FACTORY); String text = json.toPrettyString(); - InputStream stream = new ByteArrayInputStream(text.getBytes(UTF_8)); - return stream; + return new ByteArrayInputStream(text.getBytes(UTF_8)); } public static InputStream stringToInputStream(String text) { @@ -92,7 +91,7 @@ public static InputStream stringToInputStream(String text) { } public static Map parseQuery(String query) throws IOException { - Map map = new HashMap(); + Map map = new HashMap<>(); Iterable entries = Splitter.on('&').split(query); for (String entry : entries) { List sides = Lists.newArrayList(Splitter.on('=').split(entry)); diff --git a/oauth2_http/javatests/com/google/auth/http/HttpCredentialsAdapterTest.java b/oauth2_http/javatests/com/google/auth/http/HttpCredentialsAdapterTest.java index 060a0054e..38c465280 100644 --- a/oauth2_http/javatests/com/google/auth/http/HttpCredentialsAdapterTest.java +++ b/oauth2_http/javatests/com/google/auth/http/HttpCredentialsAdapterTest.java @@ -39,8 +39,8 @@ import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpTransport; +import com.google.auth.oauth2.GoogleCredentialsTest.MockTokenServerTransportFactory; import com.google.auth.oauth2.MockTokenCheckingTransport; -import com.google.auth.oauth2.MockTokenServerTransport; import com.google.auth.oauth2.OAuth2Credentials; import com.google.auth.oauth2.UserCredentials; @@ -64,13 +64,13 @@ public class HttpCredentialsAdapterTest { public void initialize_populatesOAuth2Credentials() throws IOException { final String accessToken = "1/MkSJoj1xsli0AccessToken_NKPY2"; final String expectedAuthorization = InternalAuthHttpConstants.BEARER_PREFIX + accessToken; - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(CLIENT_ID, CLIENT_SECRET); - transport.addRefreshToken(REFRESH_TOKEN, accessToken); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET); + transportFactory.transport.addRefreshToken(REFRESH_TOKEN, accessToken); OAuth2Credentials credentials = new UserCredentials( - CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, transport, null); + CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, transportFactory, null); HttpCredentialsAdapter adapter = new HttpCredentialsAdapter(credentials); - HttpRequestFactory requestFactory = transport.createRequestFactory(); + HttpRequestFactory requestFactory = transportFactory.transport.createRequestFactory(); HttpRequest request = requestFactory.buildGetRequest(new GenericUrl("http://foo")); adapter.initialize(request); @@ -85,24 +85,25 @@ public void initialize_populatesOAuth2Credentials_handle401() throws IOException final String accessToken = "1/MkSJoj1xsli0AccessToken_NKPY2"; final String accessToken2 = "2/MkSJoj1xsli0AccessToken_NKPY2"; - final MockTokenServerTransport tokenServerTransport = new MockTokenServerTransport(); - tokenServerTransport.addClient(CLIENT_ID, CLIENT_SECRET); - tokenServerTransport.addRefreshToken(REFRESH_TOKEN, accessToken); + MockTokenServerTransportFactory tokenServerTransportFactory = + new MockTokenServerTransportFactory(); + tokenServerTransportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET); + tokenServerTransportFactory.transport.addRefreshToken(REFRESH_TOKEN, accessToken); OAuth2Credentials credentials = new UserCredentials( - CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, tokenServerTransport, null); + CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, tokenServerTransportFactory, null); credentials.refresh(); HttpCredentialsAdapter adapter = new HttpCredentialsAdapter(credentials); HttpTransport primaryHttpTransport = - new MockTokenCheckingTransport(tokenServerTransport, REFRESH_TOKEN); + new MockTokenCheckingTransport(tokenServerTransportFactory.transport, REFRESH_TOKEN); HttpRequestFactory requestFactory = primaryHttpTransport.createRequestFactory(); HttpRequest request = requestFactory.buildGetRequest(new GenericUrl("http://foo")); adapter.initialize(request); // now switch out the access token so that the original one is invalid, // requiring a refresh of the access token - tokenServerTransport.addRefreshToken(REFRESH_TOKEN, accessToken2); + tokenServerTransportFactory.transport.addRefreshToken(REFRESH_TOKEN, accessToken2); HttpResponse response = request.execute(); @@ -115,13 +116,15 @@ public void initialize_populatesOAuth2Credentials_handle401() throws IOException public void initialize_noURI() throws IOException { final String accessToken = "1/MkSJoj1xsli0AccessToken_NKPY2"; final String expectedAuthorization = InternalAuthHttpConstants.BEARER_PREFIX + accessToken; - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(CLIENT_ID, CLIENT_SECRET); - transport.addRefreshToken(REFRESH_TOKEN, accessToken); + MockTokenServerTransportFactory tokenServerTransportFactory = + new MockTokenServerTransportFactory(); + tokenServerTransportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET); + tokenServerTransportFactory.transport.addRefreshToken(REFRESH_TOKEN, accessToken); OAuth2Credentials credentials = new UserCredentials( - CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, transport, null); + CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, tokenServerTransportFactory, null); HttpCredentialsAdapter adapter = new HttpCredentialsAdapter(credentials); - HttpRequestFactory requestFactory = transport.createRequestFactory(); + HttpRequestFactory requestFactory = + tokenServerTransportFactory.transport.createRequestFactory(); HttpRequest request = requestFactory.buildGetRequest(null); adapter.initialize(request); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/AccessTokenTest.java b/oauth2_http/javatests/com/google/auth/oauth2/AccessTokenTest.java new file mode 100644 index 000000000..1c75dcd2f --- /dev/null +++ b/oauth2_http/javatests/com/google/auth/oauth2/AccessTokenTest.java @@ -0,0 +1,109 @@ +/* + * Copyright 2016, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.auth.oauth2; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.IOException; +import java.util.Date; + +/** + * Unit tests for AccessToken + */ +@RunWith(JUnit4.class) +public class AccessTokenTest extends BaseSerializationTest { + + private static final String TOKEN = "AccessToken"; + private static final Date EXPIRATION_DATE = new Date(); + + @Test + public void constructor() { + AccessToken accessToken = new AccessToken(TOKEN, EXPIRATION_DATE); + assertEquals(TOKEN, accessToken.getTokenValue()); + assertEquals(EXPIRATION_DATE, accessToken.getExpirationTime()); + assertEquals(EXPIRATION_DATE.getTime(), (long) accessToken.getExpirationTimeMillis()); + } + + @Test + public void equals_true() throws IOException { + AccessToken accessToken = new AccessToken(TOKEN, EXPIRATION_DATE); + AccessToken otherAccessToken = new AccessToken(TOKEN, EXPIRATION_DATE); + assertTrue(accessToken.equals(otherAccessToken)); + assertTrue(otherAccessToken.equals(accessToken)); + } + + @Test + public void equals_false_token() throws IOException { + AccessToken accessToken = new AccessToken(TOKEN, EXPIRATION_DATE); + AccessToken otherAccessToken = new AccessToken("otherToken", EXPIRATION_DATE); + assertFalse(accessToken.equals(otherAccessToken)); + assertFalse(otherAccessToken.equals(accessToken)); + } + + @Test + public void equals_false_expirationDate() throws IOException { + AccessToken accessToken = new AccessToken(TOKEN, EXPIRATION_DATE); + AccessToken otherAccessToken = new AccessToken(TOKEN, new Date(EXPIRATION_DATE.getTime() + 42)); + assertFalse(accessToken.equals(otherAccessToken)); + assertFalse(otherAccessToken.equals(accessToken)); + } + + @Test + public void toString_containsFields() { + AccessToken accessToken = new AccessToken(TOKEN, EXPIRATION_DATE); + String expectedToString = String.format("AccessToken{tokenValue=%s, expirationTimeMillis=%d}", + TOKEN, EXPIRATION_DATE.getTime()); + assertEquals(expectedToString, accessToken.toString()); + } + + @Test + public void hashCode_equals() throws IOException { + AccessToken accessToken = new AccessToken(TOKEN, EXPIRATION_DATE); + AccessToken otherAccessToken = new AccessToken(TOKEN, EXPIRATION_DATE); + assertEquals(accessToken.hashCode(), otherAccessToken.hashCode()); + } + + @Test + public void serialize() throws IOException, ClassNotFoundException { + AccessToken accessToken = new AccessToken(TOKEN, EXPIRATION_DATE); + AccessToken deserializedAccessToken = serializeAndDeserialize(accessToken); + assertEquals(accessToken, deserializedAccessToken); + assertEquals(accessToken.hashCode(), deserializedAccessToken.hashCode()); + assertEquals(accessToken.toString(), deserializedAccessToken.toString()); + } +} diff --git a/oauth2_http/javatests/com/google/auth/oauth2/BaseSerializationTest.java b/oauth2_http/javatests/com/google/auth/oauth2/BaseSerializationTest.java new file mode 100644 index 000000000..5db5ca08f --- /dev/null +++ b/oauth2_http/javatests/com/google/auth/oauth2/BaseSerializationTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2016, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.auth.oauth2; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * Base class for serialization tests. + */ +public class BaseSerializationTest { + + @SuppressWarnings("unchecked") + public T serializeAndDeserialize(T obj) throws IOException, ClassNotFoundException { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try (ObjectOutputStream output = new ObjectOutputStream(bytes)) { + output.writeObject(obj); + } + try (ObjectInputStream input = + new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray()))) { + return (T) input.readObject(); + } + } +} diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ClientIdTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ClientIdTest.java index 2ae0fffb6..48031a007 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ClientIdTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ClientIdTest.java @@ -167,6 +167,7 @@ public void fromStream_invalidJson_throws() { ClientId.fromStream(stream); fail(); } catch (IOException expected) { + // Expected } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/CloudShellCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/CloudShellCredentialsTest.java index 1a6e848ef..a527afe1a 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/CloudShellCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/CloudShellCredentialsTest.java @@ -32,17 +32,20 @@ package com.google.auth.oauth2; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import com.google.api.client.util.Clock; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.BufferedReader; -import java.io.InputStreamReader; import java.io.IOException; +import java.io.InputStreamReader; import java.io.PrintWriter; -import java.lang.Runnable; -import java.lang.Thread; import java.net.ServerSocket; import java.net.Socket; @@ -50,7 +53,7 @@ * Unit tests for CloudShellCredentials */ @RunWith(JUnit4.class) -public class CloudShellCredentialsTest { +public class CloudShellCredentialsTest extends BaseSerializationTest { @Test public void refreshAccessToken() throws IOException{ @@ -84,4 +87,44 @@ public void run() { authSocket.close(); } } + + @Test + public void equals_true() throws IOException { + GoogleCredentials credentials = new CloudShellCredentials(42); + GoogleCredentials otherCredentials = new CloudShellCredentials(42); + assertTrue(credentials.equals(otherCredentials)); + assertTrue(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_authPort() throws IOException { + GoogleCredentials credentials = new CloudShellCredentials(42); + GoogleCredentials otherCredentials = new CloudShellCredentials(43); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void toString_containsFields() throws IOException { + String expectedToString = String.format("CloudShellCredentials{authPort=%d}", 42); + GoogleCredentials credentials = new CloudShellCredentials(42); + assertEquals(expectedToString, credentials.toString()); + } + + @Test + public void hashCode_equals() throws IOException { + GoogleCredentials credentials = new CloudShellCredentials(42); + GoogleCredentials otherCredentials = new CloudShellCredentials(42); + assertEquals(credentials.hashCode(), otherCredentials.hashCode()); + } + + @Test + public void serialize() throws IOException, ClassNotFoundException { + GoogleCredentials credentials = new CloudShellCredentials(42); + GoogleCredentials deserializedCredentials = serializeAndDeserialize(credentials); + assertEquals(credentials, deserializedCredentials); + assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); + assertEquals(credentials.toString(), deserializedCredentials.toString()); + assertSame(deserializedCredentials.clock, Clock.SYSTEM); + } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java index 24cbb7976..3a320395d 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java @@ -31,11 +31,18 @@ package com.google.auth.oauth2; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.api.client.http.HttpStatusCodes; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.util.Clock; import com.google.auth.TestUtils; +import com.google.auth.http.HttpTransportFactory; +import com.google.auth.oauth2.GoogleCredentialsTest.MockHttpTransportFactory; import org.junit.Test; import org.junit.runner.RunWith; @@ -50,16 +57,26 @@ * Test case for {@link ComputeEngineCredentials}. */ @RunWith(JUnit4.class) -public class ComputeEngineCredentialsTest { +public class ComputeEngineCredentialsTest extends BaseSerializationTest { private static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo"); + static class MockMetadataServerTransportFactory implements HttpTransportFactory { + + MockMetadataServerTransport transport = new MockMetadataServerTransport(); + + @Override + public HttpTransport create() { + return transport; + } + } + @Test public void getRequestMetadata_hasAccessToken() throws IOException { final String accessToken = "1/MkSJoj1xsli0AccessToken_NKPY2"; - MockMetadataServerTransport transport = new MockMetadataServerTransport(); - transport.setAccessToken(accessToken); - ComputeEngineCredentials credentials = new ComputeEngineCredentials(transport); + MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory(); + transportFactory.transport.setAccessToken(accessToken); + ComputeEngineCredentials credentials = new ComputeEngineCredentials(transportFactory); Map> metadata = credentials.getRequestMetadata(CALL_URI); @@ -69,10 +86,10 @@ public void getRequestMetadata_hasAccessToken() throws IOException { @Test public void getRequestMetadata_missingServiceAccount_throws() { final String accessToken = "1/MkSJoj1xsli0AccessToken_NKPY2"; - MockMetadataServerTransport transport = new MockMetadataServerTransport(); - transport.setAccessToken(accessToken); - transport.setTokenRequestStatusCode(HttpStatusCodes.STATUS_CODE_NOT_FOUND); - ComputeEngineCredentials credentials = new ComputeEngineCredentials(transport); + MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory(); + transportFactory.transport.setAccessToken(accessToken); + transportFactory.transport.setTokenRequestStatusCode(HttpStatusCodes.STATUS_CODE_NOT_FOUND); + ComputeEngineCredentials credentials = new ComputeEngineCredentials(transportFactory); try { credentials.getRequestMetadata(CALL_URI); @@ -87,10 +104,10 @@ public void getRequestMetadata_missingServiceAccount_throws() { public void getRequestMetadata_serverError_throws() { final String accessToken = "1/MkSJoj1xsli0AccessToken_NKPY2"; - MockMetadataServerTransport transport = new MockMetadataServerTransport(); - transport.setAccessToken(accessToken); - transport.setTokenRequestStatusCode(HttpStatusCodes.STATUS_CODE_SERVER_ERROR); - ComputeEngineCredentials credentials = new ComputeEngineCredentials(transport); + MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory(); + transportFactory.transport.setAccessToken(accessToken); + transportFactory.transport.setTokenRequestStatusCode(HttpStatusCodes.STATUS_CODE_NOT_FOUND); + ComputeEngineCredentials credentials = new ComputeEngineCredentials(transportFactory); try { credentials.getRequestMetadata(CALL_URI); @@ -101,4 +118,63 @@ public void getRequestMetadata_serverError_throws() { assertTrue(message.contains("Unexpected")); } } + + @Test + public void equals_true() throws IOException { + MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory(); + ComputeEngineCredentials credentials = new ComputeEngineCredentials(transportFactory); + ComputeEngineCredentials otherCredentials = new ComputeEngineCredentials(transportFactory); + assertTrue(credentials.equals(otherCredentials)); + assertTrue(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_transportFactory() throws IOException { + MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory(); + MockMetadataServerTransportFactory serverTransportFactory = + new MockMetadataServerTransportFactory(); + ComputeEngineCredentials credentials = new ComputeEngineCredentials(serverTransportFactory); + ComputeEngineCredentials otherCredentials = new ComputeEngineCredentials(httpTransportFactory); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void toString_containsFields() throws IOException { + MockMetadataServerTransportFactory serverTransportFactory = + new MockMetadataServerTransportFactory(); + String expectedToString = + String.format("ComputeEngineCredentials{transportFactoryClassName=%s}", + MockMetadataServerTransportFactory.class.getName()); + ComputeEngineCredentials credentials = new ComputeEngineCredentials(serverTransportFactory); + assertEquals(expectedToString, credentials.toString()); + } + + @Test + public void hashCode_equals() throws IOException { + MockMetadataServerTransportFactory serverTransportFactory = + new MockMetadataServerTransportFactory(); + ComputeEngineCredentials credentials = new ComputeEngineCredentials(serverTransportFactory); + ComputeEngineCredentials otherCredentials = + new ComputeEngineCredentials(serverTransportFactory); + assertEquals(credentials.hashCode(), otherCredentials.hashCode()); + } + + @Test + public void serialize() throws IOException, ClassNotFoundException { + MockMetadataServerTransportFactory serverTransportFactory = + new MockMetadataServerTransportFactory(); + ComputeEngineCredentials credentials = new ComputeEngineCredentials(serverTransportFactory); + GoogleCredentials deserializedCredentials = serializeAndDeserialize(credentials); + assertEquals(credentials, deserializedCredentials); + assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); + assertEquals(credentials.toString(), deserializedCredentials.toString()); + assertSame(deserializedCredentials.clock, Clock.SYSTEM); + credentials = new ComputeEngineCredentials(); + deserializedCredentials = serializeAndDeserialize(credentials); + assertEquals(credentials, deserializedCredentials); + assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); + assertEquals(credentials.toString(), deserializedCredentials.toString()); + assertSame(deserializedCredentials.clock, Clock.SYSTEM); + } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java b/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java index 9a71d2e3f..b297f26e8 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java @@ -33,33 +33,37 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertNotNull; 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.http.LowLevelHttpRequest; import com.google.api.client.http.LowLevelHttpResponse; -import com.google.api.client.http.HttpTransport; import com.google.api.client.testing.http.MockHttpTransport; import com.google.api.client.testing.http.MockLowLevelHttpRequest; import com.google.auth.TestUtils; +import com.google.auth.http.HttpTransportFactory; +import com.google.auth.oauth2.ComputeEngineCredentialsTest.MockMetadataServerTransportFactory; +import com.google.auth.oauth2.GoogleCredentialsTest.MockHttpTransportFactory; +import com.google.auth.oauth2.GoogleCredentialsTest.MockTokenServerTransportFactory; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.io.InputStream; -import java.io.IOException; import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.security.AccessControlException; import java.util.Collection; import java.util.Collections; -import java.util.Map; import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Test case for {@link DefaultCredentialsProvider}. @@ -82,13 +86,23 @@ public class DefaultCredentialsProviderTest { private static final Collection SCOPES = Collections.singletonList("dummy.scope"); private static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo"); + static class MockRequestCountingTransportFactory implements HttpTransportFactory { + + MockRequestCountingTransport transport = new MockRequestCountingTransport(); + + @Override + public HttpTransport create() { + return transport; + } + } + @Test public void getDefaultCredentials_noCredentials_throws() throws Exception { - HttpTransport transport = new MockHttpTransport(); + MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); try { - testProvider.getDefaultCredentials(transport); + testProvider.getDefaultCredentials(transportFactory); fail("No credential expected."); } catch (IOException e) { String message = e.getMessage(); @@ -98,12 +112,12 @@ public void getDefaultCredentials_noCredentials_throws() throws Exception { @Test public void getDefaultCredentials_noCredentialsSandbox_throwsNonSecurity() throws Exception { - HttpTransport transport = new MockHttpTransport(); + MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); testProvider.setFileSandbox(true); try { - testProvider.getDefaultCredentials(transport); + testProvider.getDefaultCredentials(transportFactory); fail("No credential expected."); } catch (IOException e) { String message = e.getMessage(); @@ -113,7 +127,7 @@ public void getDefaultCredentials_noCredentialsSandbox_throwsNonSecurity() throw @Test public void getDefaultCredentials_envValidSandbox_throwsNonSecurity() throws Exception { - HttpTransport transport = new MockHttpTransport(); + MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); InputStream userStream = UserCredentialsTest.writeUserStream(USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); @@ -123,7 +137,7 @@ public void getDefaultCredentials_envValidSandbox_throwsNonSecurity() throws Exc testProvider.setEnv(DefaultCredentialsProvider.CREDENTIAL_ENV_VAR, userPath); try { - testProvider.getDefaultCredentials(transport); + testProvider.getDefaultCredentials(transportFactory); fail("No credential expected."); } catch (IOException e) { String message = e.getMessage(); @@ -133,30 +147,33 @@ public void getDefaultCredentials_envValidSandbox_throwsNonSecurity() throws Exc @Test public void getDefaultCredentials_noCredentials_singleGceTestRequest() { - MockRequestCountingTransport transport = new MockRequestCountingTransport(); + MockRequestCountingTransportFactory transportFactory = + new MockRequestCountingTransportFactory(); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); try { - testProvider.getDefaultCredentials(transport); + testProvider.getDefaultCredentials(transportFactory); fail("No credential expected."); } catch (IOException expected) { + // Expected } - assertEquals(1, transport.getRequestCount()); + assertEquals(1, transportFactory.transport.getRequestCount()); try { - testProvider.getDefaultCredentials(transport); + testProvider.getDefaultCredentials(transportFactory); fail("No credential expected."); } catch (IOException expected) { + // Expected } - assertEquals(1, transport.getRequestCount()); + assertEquals(1, transportFactory.transport.getRequestCount()); } @Test public void getDefaultCredentials_caches() throws IOException { - HttpTransport transport = new MockMetadataServerTransport(); + MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory(); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); - GoogleCredentials firstCall = testProvider.getDefaultCredentials(transport); - GoogleCredentials secondCall = testProvider.getDefaultCredentials(transport); + GoogleCredentials firstCall = testProvider.getDefaultCredentials(transportFactory); + GoogleCredentials secondCall = testProvider.getDefaultCredentials(transportFactory); assertNotNull(firstCall); assertSame(firstCall, secondCall); @@ -164,14 +181,14 @@ public void getDefaultCredentials_caches() throws IOException { @Test public void getDefaultCredentials_appEngine_deployed() throws IOException { - HttpTransport transport = new MockHttpTransport(); + MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); testProvider.addType(DefaultCredentialsProvider.APP_ENGINE_CREDENTIAL_CLASS, MockAppEngineCredentials.class); testProvider.addType(DefaultCredentialsProvider.APP_ENGINE_SIGNAL_CLASS, MockAppEngineSystemProperty.class); - GoogleCredentials defaultCredential = testProvider.getDefaultCredentials(transport); + GoogleCredentials defaultCredential = testProvider.getDefaultCredentials(transportFactory); assertNotNull(defaultCredential); assertTrue(defaultCredential instanceof MockAppEngineCredentials); @@ -179,7 +196,7 @@ public void getDefaultCredentials_appEngine_deployed() throws IOException { @Test public void getDefaultCredentials_appEngineClassWithoutRuntime_NotFoundError() { - HttpTransport transport = new MockHttpTransport(); + MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); testProvider.addType(DefaultCredentialsProvider.APP_ENGINE_CREDENTIAL_CLASS, MockAppEngineCredentials.class); @@ -187,7 +204,7 @@ public void getDefaultCredentials_appEngineClassWithoutRuntime_NotFoundError() { MockOffAppEngineSystemProperty.class); try { - testProvider.getDefaultCredentials(transport); + testProvider.getDefaultCredentials(transportFactory); fail("No credential expected when not on App Engine."); } catch (IOException e) { String message = e.getMessage(); @@ -197,13 +214,13 @@ public void getDefaultCredentials_appEngineClassWithoutRuntime_NotFoundError() { @Test public void getDefaultCredentials_appEngineRuntimeWithoutClass_throwsHelpfulLoadError() { - HttpTransport transport = new MockHttpTransport(); + MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); testProvider.addType(DefaultCredentialsProvider.APP_ENGINE_SIGNAL_CLASS, MockAppEngineSystemProperty.class); try { - testProvider.getDefaultCredentials(transport); + testProvider.getDefaultCredentials(transportFactory); fail("Credential expected to fail to load if credential class not present."); } catch (IOException e) { String message = e.getMessage(); @@ -214,30 +231,32 @@ public void getDefaultCredentials_appEngineRuntimeWithoutClass_throwsHelpfulLoad @Test public void getDefaultCredentials_appEngine_singleClassLoadAttempt() { - HttpTransport transport = new MockHttpTransport(); + MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); try { - testProvider.getDefaultCredentials(transport); + testProvider.getDefaultCredentials(transportFactory); fail("No credential expected for default test provider."); } catch (IOException expected) { + // Expected } assertEquals(1, testProvider.getForNameCallCount()); // Try a second time. try { - testProvider.getDefaultCredentials(transport); + testProvider.getDefaultCredentials(transportFactory); fail("No credential expected for default test provider."); } catch (IOException expected) { + // Expected } assertEquals(1, testProvider.getForNameCallCount()); } @Test public void getDefaultCredentials_compute_providesToken() throws IOException { - MockMetadataServerTransport transport = new MockMetadataServerTransport(); - transport.setAccessToken(ACCESS_TOKEN); + MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory(); + transportFactory.transport.setAccessToken(ACCESS_TOKEN); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); - GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transport); + GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transportFactory); assertNotNull(defaultCredentials); Map> metadata = defaultCredentials.getRequestMetadata(CALL_URI); @@ -246,11 +265,11 @@ public void getDefaultCredentials_compute_providesToken() throws IOException { @Test public void getDefaultCredentials_cloudshell() throws IOException { - HttpTransport transport = new MockHttpTransport(); + MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); testProvider.setEnv(DefaultCredentialsProvider.CLOUD_SHELL_ENV_VAR, "4"); - GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transport); + GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transportFactory); assertTrue(defaultCredentials instanceof CloudShellCredentials); assertEquals(((CloudShellCredentials) defaultCredentials).getAuthPort(), 4); @@ -258,12 +277,12 @@ public void getDefaultCredentials_cloudshell() throws IOException { @Test public void getDefaultCredentials_cloudshell_withComputCredentialsPresent() throws IOException { - MockMetadataServerTransport transport = new MockMetadataServerTransport(); - transport.setAccessToken(ACCESS_TOKEN); + MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory(); + transportFactory.transport.setAccessToken(ACCESS_TOKEN); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); testProvider.setEnv(DefaultCredentialsProvider.CLOUD_SHELL_ENV_VAR, "4"); - GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transport); + GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transportFactory); assertTrue(defaultCredentials instanceof CloudShellCredentials); assertEquals(((CloudShellCredentials) defaultCredentials).getAuthPort(), 4); @@ -272,12 +291,12 @@ public void getDefaultCredentials_cloudshell_withComputCredentialsPresent() thro @Test public void getDefaultCredentials_envMissingFile_throws() { final String invalidPath = "/invalid/path"; - HttpTransport transport = new MockHttpTransport(); + MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); TestDefaultCredentialsProvider testProvider = new TestDefaultCredentialsProvider(); testProvider.setEnv(DefaultCredentialsProvider.CREDENTIAL_ENV_VAR, invalidPath); try { - testProvider.getDefaultCredentials(transport); + testProvider.getDefaultCredentials(transportFactory); fail("Non existent credential should throw exception"); } catch (IOException e) { String message = e.getMessage(); @@ -288,8 +307,8 @@ public void getDefaultCredentials_envMissingFile_throws() { @Test public void getDefaultCredentials_envServiceAccount_providesToken() throws IOException { - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addServiceAccount(SA_CLIENT_EMAIL, ACCESS_TOKEN); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addServiceAccount(SA_CLIENT_EMAIL, ACCESS_TOKEN); InputStream serviceAccountStream = ServiceAccountCredentialsTest .writeServiceAccountAccountStream( SA_CLIENT_ID, SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID); @@ -299,7 +318,7 @@ public void getDefaultCredentials_envServiceAccount_providesToken() throws IOExc testProvider.setEnv( DefaultCredentialsProvider.CREDENTIAL_ENV_VAR, serviceAccountPath); - GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transport); + GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transportFactory); assertNotNull(defaultCredentials); defaultCredentials = defaultCredentials.createScoped(SCOPES); @@ -395,12 +414,12 @@ public void getDefaultCredentials_envAndWellKnownFile_envPrecedence() throws IOE testProvider.setProperty("user.home", homeDir.getAbsolutePath()); testProvider.addFile(wellKnownFile.getAbsolutePath(), wkfStream); - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(USER_CLIENT_ID, USER_CLIENT_SECRET); - transport.addRefreshToken(refreshTokenWkf, accessTokenWkf); - transport.addRefreshToken(refreshTokenEnv, accessTokenEnv); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addClient(USER_CLIENT_ID, USER_CLIENT_SECRET); + transportFactory.transport.addRefreshToken(refreshTokenWkf, accessTokenWkf); + transportFactory.transport.addRefreshToken(refreshTokenEnv, accessTokenEnv); - testUserProvidesToken(testProvider, transport, accessTokenEnv); + testUserProvidesToken(testProvider, transportFactory, accessTokenEnv); } private static File getTempDirectory() { @@ -409,15 +428,15 @@ private static File getTempDirectory() { private void testUserProvidesToken(TestDefaultCredentialsProvider testProvider, String clientId, String clientSecret, String refreshToken) throws IOException { - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(clientId, clientSecret); - transport.addRefreshToken(refreshToken, ACCESS_TOKEN); - testUserProvidesToken(testProvider, transport, ACCESS_TOKEN); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addClient(clientId, clientSecret); + transportFactory.transport.addRefreshToken(refreshToken, ACCESS_TOKEN); + testUserProvidesToken(testProvider, transportFactory, ACCESS_TOKEN); } private void testUserProvidesToken(TestDefaultCredentialsProvider testProvider, - HttpTransport transport, String accessToken) throws IOException { - GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transport); + HttpTransportFactory transportFactory, String accessToken) throws IOException { + GoogleCredentials defaultCredentials = testProvider.getDefaultCredentials(transportFactory); assertNotNull(defaultCredentials); Map> metadata = defaultCredentials.getRequestMetadata(CALL_URI); @@ -425,6 +444,8 @@ private void testUserProvidesToken(TestDefaultCredentialsProvider testProvider, } public static class MockAppEngineCredentials extends GoogleCredentials { + private static final long serialVersionUID = 2695173591854484322L; + @SuppressWarnings("unused") public MockAppEngineCredentials(Collection scopes) { } @@ -487,23 +508,22 @@ int getRequestCount() { @Override public LowLevelHttpRequest buildRequest(String method, String url) { - MockLowLevelHttpRequest request = new MockLowLevelHttpRequest(url) { + return new MockLowLevelHttpRequest(url) { @Override public LowLevelHttpResponse execute() throws IOException { requestCount++; throw new IOException("MockRequestCountingTransport request failed."); } }; - return request; } } private static class TestDefaultCredentialsProvider extends DefaultCredentialsProvider { - private final Map> types = new HashMap>(); - private final Map variables = new HashMap(); - private final Map properties = new HashMap(); - private final Map files = new HashMap(); + private final Map> types = new HashMap<>(); + private final Map variables = new HashMap<>(); + private final Map properties = new HashMap<>(); + private final Map files = new HashMap<>(); private boolean fileSandbox = false; private int forNameCallCount = 0; diff --git a/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java index 103427202..dbe80e290 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java @@ -38,14 +38,15 @@ import com.google.api.client.http.HttpTransport; import com.google.api.client.testing.http.MockHttpTransport; import com.google.auth.TestUtils; +import com.google.auth.http.HttpTransportFactory; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.ByteArrayInputStream; -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.util.Arrays; import java.util.Collection; @@ -71,18 +72,40 @@ public class GoogleCredentialsTest { private static final String USER_CLIENT_ID = "ya29.1.AADtN_UtlxN3PuGAxrN2XQnZTVRvDyVWnYq4I6dws"; private static final String REFRESH_TOKEN = "1/Tl6awhpFjkMkSJoj1xsli0H2eL5YsMgU_NKPY2TyGWY"; private static final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2"; - private static final HttpTransport DUMMY_TRANSPORT = new MockTokenServerTransport(); + private static final HttpTransportFactory DUMMY_TRANSPORT_FACTORY = + new MockTokenServerTransportFactory(); private static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo"); private static final Collection SCOPES = Collections.unmodifiableCollection(Arrays.asList("scope1", "scope2")); + static class MockHttpTransportFactory implements HttpTransportFactory { + + MockHttpTransport transport = new MockHttpTransport(); + + @Override + public HttpTransport create() { + return transport; + } + } + + public static class MockTokenServerTransportFactory implements HttpTransportFactory { + + public MockTokenServerTransport transport = new MockTokenServerTransport(); + + @Override + public HttpTransport create() { + return transport; + } + } + @Test public void getApplicationDefault_nullTransport_throws() throws IOException { try { GoogleCredentials.getApplicationDefault(null); fail(); } catch (NullPointerException expected) { + // Expected } } @@ -93,28 +116,31 @@ public void fromStream_nullTransport_throws() throws IOException { GoogleCredentials.fromStream(stream, null); fail(); } catch (NullPointerException expected) { + // Expected } } @Test public void fromStream_nullStreamThrows() throws IOException { - HttpTransport transport = new MockHttpTransport(); + MockHttpTransportFactory transportFactory = new MockHttpTransportFactory(); try { - GoogleCredentials.fromStream(null, transport); + GoogleCredentials.fromStream(null, transportFactory); fail(); } catch (NullPointerException expected) { + // Expected } } @Test public void fromStream_serviceAccount_providesToken() throws IOException { - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addServiceAccount(SA_CLIENT_EMAIL, ACCESS_TOKEN); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addServiceAccount(SA_CLIENT_EMAIL, ACCESS_TOKEN); InputStream serviceAccountStream = ServiceAccountCredentialsTest .writeServiceAccountAccountStream( SA_CLIENT_ID, SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID); - GoogleCredentials credentials = GoogleCredentials.fromStream(serviceAccountStream, transport); + GoogleCredentials credentials = + GoogleCredentials.fromStream(serviceAccountStream, transportFactory); assertNotNull(credentials); credentials = credentials.createScoped(SCOPES); @@ -160,13 +186,13 @@ public void fromStream_serviceAccountNoPrivateKeyId_throws() throws IOException @Test public void fromStream_user_providesToken() throws IOException { - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(USER_CLIENT_ID, USER_CLIENT_SECRET); - transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addClient(USER_CLIENT_ID, USER_CLIENT_SECRET); + transportFactory.transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN); InputStream userStream = UserCredentialsTest.writeUserStream(USER_CLIENT_ID, USER_CLIENT_SECRET, REFRESH_TOKEN); - GoogleCredentials credentials = GoogleCredentials.fromStream(userStream, transport); + GoogleCredentials credentials = GoogleCredentials.fromStream(userStream, transportFactory); assertNotNull(credentials); Map> metadata = credentials.getRequestMetadata(CALL_URI); @@ -199,7 +225,7 @@ public void fromStream_userNoRefreshToken_throws() throws IOException { private void testFromStreamException(InputStream stream, String expectedMessageContent) { try { - GoogleCredentials.fromStream(stream, DUMMY_TRANSPORT); + GoogleCredentials.fromStream(stream, DUMMY_TRANSPORT_FACTORY); fail(); } catch (IOException expected) { assertTrue(expected.getMessage().contains(expectedMessageContent)); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java index 0203b6090..afa94a1c2 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java @@ -65,15 +65,14 @@ public void setTokenRequestStatusCode(Integer tokenRequestStatusCode) { public LowLevelHttpRequest buildRequest(String method, String url) throws IOException { if (url.equals(ComputeEngineCredentials.TOKEN_SERVER_ENCODED_URL)) { - MockLowLevelHttpRequest request = new MockLowLevelHttpRequest(url) { + return new MockLowLevelHttpRequest(url) { @Override public LowLevelHttpResponse execute() throws IOException { if (tokenRequestStatusCode != null) { - MockLowLevelHttpResponse response = new MockLowLevelHttpResponse() + return new MockLowLevelHttpResponse() .setStatusCode(tokenRequestStatusCode) .setContent("Token Fetch Error"); - return response; } String metadataRequestHeader = getFirstHeaderValue("Metadata-Flavor"); @@ -89,16 +88,13 @@ public LowLevelHttpResponse execute() throws IOException { refreshContents.put("token_type", "Bearer"); String refreshText = refreshContents.toPrettyString(); - MockLowLevelHttpResponse response = new MockLowLevelHttpResponse() + return new MockLowLevelHttpResponse() .setContentType(Json.MEDIA_TYPE) .setContent(refreshText); - return response; - } }; - return request; } else if (url.equals(ComputeEngineCredentials.METADATA_SERVER_URL)) { - MockLowLevelHttpRequest request = new MockLowLevelHttpRequest(url) { + return new MockLowLevelHttpRequest(url) { @Override public LowLevelHttpResponse execute() { MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); @@ -106,7 +102,6 @@ public LowLevelHttpResponse execute() { return response; } }; - return request; } return super.buildRequest(method, url); } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java index 056a7405a..06acb2eea 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/MockTokenServerTransport.java @@ -110,12 +110,12 @@ public LowLevelHttpRequest buildRequest(String method, String url) throws IOExce final String urlWithoutQUery = (questionMarkPos > 0) ? url.substring(0, questionMarkPos) : url; final String query = (questionMarkPos > 0) ? url.substring(questionMarkPos + 1) : ""; if (urlWithoutQUery.equals(tokenServerUri.toString())) { - MockLowLevelHttpRequest request = new MockLowLevelHttpRequest(url) { + return new MockLowLevelHttpRequest(url) { @Override public LowLevelHttpResponse execute() throws IOException { String content = this.getContentAsString(); Map query = TestUtils.parseQuery(content); - String accessToken = null; + String accessToken; String refreshToken = null; String foundId = query.get("client_id"); @@ -173,15 +173,13 @@ public LowLevelHttpResponse execute() throws IOException { } String refreshText = refreshContents.toPrettyString(); - MockLowLevelHttpResponse response = new MockLowLevelHttpResponse() + return new MockLowLevelHttpResponse() .setContentType(Json.MEDIA_TYPE) .setContent(refreshText); - return response; } }; - return request; } else if (urlWithoutQUery.equals(OAuth2Utils.TOKEN_REVOKE_URI.toString())) { - MockLowLevelHttpRequest request = new MockLowLevelHttpRequest(url) { + return new MockLowLevelHttpRequest(url) { @Override public LowLevelHttpResponse execute() throws IOException { Map parameters = TestUtils.parseQuery(query); @@ -192,12 +190,9 @@ public LowLevelHttpResponse execute() throws IOException { // Token could be access token or refresh token so remove keys and values refreshTokens.values().removeAll(Collections.singleton(token)); refreshTokens.remove(token); - MockLowLevelHttpResponse response = new MockLowLevelHttpResponse() - .setContentType(Json.MEDIA_TYPE); - return response; + return new MockLowLevelHttpResponse().setContentType(Json.MEDIA_TYPE); } }; - return request; } return super.buildRequest(method, url); } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/OAuth2CredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/OAuth2CredentialsTest.java index 1bfb4d177..e1ab01528 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/OAuth2CredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/OAuth2CredentialsTest.java @@ -31,15 +31,21 @@ 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.assertSame; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import com.google.api.client.util.Clock; import com.google.auth.TestClock; import com.google.auth.TestUtils; +import com.google.auth.http.AuthHttpConstants; +import com.google.auth.oauth2.GoogleCredentialsTest.MockTokenServerTransportFactory; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,14 +53,14 @@ import java.io.IOException; import java.net.URI; -import java.util.Map; import java.util.List; +import java.util.Map; /** * Test case for {@link OAuth2Credentials}. */ @RunWith(JUnit4.class) -public class OAuth2CredentialsTest { +public class OAuth2CredentialsTest extends BaseSerializationTest { private static final String CLIENT_SECRET = "jakuaL9YyieakhECKL2SwZcu"; private static final String CLIENT_ID = "ya29.1.AADtN_UtlxN3PuGAxrN2XQnZTVRvDyVWnYq4I6dws"; @@ -88,14 +94,14 @@ public void hasRequestMetadataOnly_returnsTrue() { } @Test - public void addaddChangeListener_notifiesOnRefresh() throws IOException { + public void addChangeListener_notifiesOnRefresh() throws IOException { final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2"; final String accessToken2 = "2/MkSJoj1xsli0AccessToken_NKPY2"; - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(CLIENT_ID, CLIENT_SECRET); - transport.addRefreshToken(REFRESH_TOKEN, accessToken1); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET); + transportFactory.transport.addRefreshToken(REFRESH_TOKEN, accessToken1); OAuth2Credentials userCredentials = new UserCredentials( - CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, transport, null); + CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, transportFactory, null); // Use a fixed clock so tokens don't expire userCredentials.clock = new TestClock(); TestChangeListener listener = new TestChangeListener(); @@ -110,7 +116,7 @@ public void addaddChangeListener_notifiesOnRefresh() throws IOException { assertEquals(1, listener.callCount); // Change server to a different token and refresh - transport.addRefreshToken(REFRESH_TOKEN, accessToken2); + transportFactory.transport.addRefreshToken(REFRESH_TOKEN, accessToken2); // Refresh to force getting next token userCredentials.refresh(); @@ -124,26 +130,26 @@ public void addaddChangeListener_notifiesOnRefresh() throws IOException { public void getRequestMetadata_blocking_cachesExpiringToken() throws IOException { final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2"; final String accessToken2 = "2/MkSJoj1xsli0AccessToken_NKPY2"; - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(CLIENT_ID, CLIENT_SECRET); - transport.addRefreshToken(REFRESH_TOKEN, accessToken1); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET); + transportFactory.transport.addRefreshToken(REFRESH_TOKEN, accessToken1); TestClock clock = new TestClock(); OAuth2Credentials credentials = new UserCredentials( - CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, transport, null); + CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, transportFactory, null); credentials.clock = clock; // Verify getting the first token - assertEquals(0, transport.buildRequestCount); + assertEquals(0, transportFactory.transport.buildRequestCount); Map> metadata = credentials.getRequestMetadata(CALL_URI); TestUtils.assertContainsBearerToken(metadata, accessToken1); - assertEquals(1, transport.buildRequestCount--); + assertEquals(1, transportFactory.transport.buildRequestCount--); // Change server to a different token - transport.addRefreshToken(REFRESH_TOKEN, accessToken2); + transportFactory.transport.addRefreshToken(REFRESH_TOKEN, accessToken2); // Make transport fail when used next time. IOException error = new IOException("error"); - transport.setError(error); + transportFactory.transport.setError(error); // Advance 5 minutes and verify original token clock.addToCurrentTime(5 * 60 * 1000); @@ -152,33 +158,33 @@ public void getRequestMetadata_blocking_cachesExpiringToken() throws IOException // Advance 60 minutes and verify revised token clock.addToCurrentTime(60 * 60 * 1000); - assertEquals(0, transport.buildRequestCount); + assertEquals(0, transportFactory.transport.buildRequestCount); try { credentials.getRequestMetadata(CALL_URI); fail("Should throw"); } catch (IOException e) { assertSame(error, e); - assertEquals(1, transport.buildRequestCount--); + assertEquals(1, transportFactory.transport.buildRequestCount--); } // Reset the error and try again - transport.setError(null); + transportFactory.transport.setError(null); metadata = credentials.getRequestMetadata(CALL_URI); TestUtils.assertContainsBearerToken(metadata, accessToken2); - assertEquals(1, transport.buildRequestCount--); + assertEquals(1, transportFactory.transport.buildRequestCount--); } @Test public void getRequestMetadata_async() throws IOException { final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2"; final String accessToken2 = "2/MkSJoj1xsli0AccessToken_NKPY2"; - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(CLIENT_ID, CLIENT_SECRET); - transport.addRefreshToken(REFRESH_TOKEN, accessToken1); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET); + transportFactory.transport.addRefreshToken(REFRESH_TOKEN, accessToken1); TestClock clock = new TestClock(); OAuth2Credentials credentials = new UserCredentials( - CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, transport, null); + CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, transportFactory, null); credentials.clock = clock; MockExecutor executor = new MockExecutor(); @@ -186,20 +192,20 @@ public void getRequestMetadata_async() throws IOException { // Verify getting the first token, which uses the transport and calls the callback in the // executor. credentials.getRequestMetadata(CALL_URI, executor, callback); - assertEquals(0, transport.buildRequestCount); + assertEquals(0, transportFactory.transport.buildRequestCount); assertNull(callback.metadata); assertEquals(1, executor.runTasksExhaustively()); assertNotNull(callback.metadata); TestUtils.assertContainsBearerToken(callback.metadata, accessToken1); - assertEquals(1, transport.buildRequestCount--); + assertEquals(1, transportFactory.transport.buildRequestCount--); // Change server to a different token - transport.addRefreshToken(REFRESH_TOKEN, accessToken2); + transportFactory.transport.addRefreshToken(REFRESH_TOKEN, accessToken2); // Make transport fail when used next time. IOException error = new IOException("error"); - transport.setError(error); + transportFactory.transport.setError(error); // Advance 5 minutes and verify original token. Callback is called inline. callback.reset(); @@ -214,53 +220,53 @@ public void getRequestMetadata_async() throws IOException { callback.reset(); clock.addToCurrentTime(60 * 60 * 1000); credentials.getRequestMetadata(CALL_URI, executor, callback); - assertEquals(0, transport.buildRequestCount); + assertEquals(0, transportFactory.transport.buildRequestCount); assertNull(callback.exception); assertEquals(1, executor.runTasksExhaustively()); assertSame(error, callback.exception); - assertEquals(1, transport.buildRequestCount--); + assertEquals(1, transportFactory.transport.buildRequestCount--); // Reset the error and try again - transport.setError(null); + transportFactory.transport.setError(null); callback.reset(); credentials.getRequestMetadata(CALL_URI, executor, callback); - assertEquals(0, transport.buildRequestCount); + assertEquals(0, transportFactory.transport.buildRequestCount); assertNull(callback.metadata); assertEquals(1, executor.runTasksExhaustively()); assertNotNull(callback.metadata); TestUtils.assertContainsBearerToken(callback.metadata, accessToken2); - assertEquals(1, transport.buildRequestCount--); + assertEquals(1, transportFactory.transport.buildRequestCount--); } @Test public void getRequestMetadata_async_refreshRace() throws IOException { final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2"; - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(CLIENT_ID, CLIENT_SECRET); - transport.addRefreshToken(REFRESH_TOKEN, accessToken1); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET); + transportFactory.transport.addRefreshToken(REFRESH_TOKEN, accessToken1); TestClock clock = new TestClock(); OAuth2Credentials credentials = new UserCredentials( - CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, transport, null); + CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, transportFactory, null); credentials.clock = clock; MockExecutor executor = new MockExecutor(); MockRequestMetadataCallback callback = new MockRequestMetadataCallback(); // Getting the first token, which uses the transport and calls the callback in the executor. credentials.getRequestMetadata(CALL_URI, executor, callback); - assertEquals(0, transport.buildRequestCount); + assertEquals(0, transportFactory.transport.buildRequestCount); assertNull(callback.metadata); // Asynchronous task is scheduled, but beaten by another blocking get call. assertEquals(1, executor.numTasks()); Map> metadata = credentials.getRequestMetadata(CALL_URI); - assertEquals(1, transport.buildRequestCount--); + assertEquals(1, transportFactory.transport.buildRequestCount--); TestUtils.assertContainsBearerToken(metadata, accessToken1); // When the task is run, the cached data is used. assertEquals(1, executor.runTasksExhaustively()); - assertEquals(0, transport.buildRequestCount); + assertEquals(0, transportFactory.transport.buildRequestCount); assertSame(metadata, callback.metadata); } @@ -277,31 +283,31 @@ public void getRequestMetadata_temporaryToken_hasToken() throws IOException { public void refresh_refreshesToken() throws IOException { final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2"; final String accessToken2 = "2/MkSJoj1xsli0AccessToken_NKPY2"; - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(CLIENT_ID, CLIENT_SECRET); - transport.addRefreshToken(REFRESH_TOKEN, accessToken1); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET); + transportFactory.transport.addRefreshToken(REFRESH_TOKEN, accessToken1); OAuth2Credentials userCredentials = new UserCredentials( - CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, transport, null); + CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, transportFactory, null); // Use a fixed clock so tokens don't exire userCredentials.clock = new TestClock(); // Get a first token Map> metadata = userCredentials.getRequestMetadata(CALL_URI); TestUtils.assertContainsBearerToken(metadata, accessToken1); - assertEquals(1, transport.buildRequestCount--); + assertEquals(1, transportFactory.transport.buildRequestCount--); // Change server to a different token - transport.addRefreshToken(REFRESH_TOKEN, accessToken2); + transportFactory.transport.addRefreshToken(REFRESH_TOKEN, accessToken2); // Confirm token being cached TestUtils.assertContainsBearerToken(metadata, accessToken1); - assertEquals(0, transport.buildRequestCount); + assertEquals(0, transportFactory.transport.buildRequestCount); // Refresh to force getting next token userCredentials.refresh(); metadata = userCredentials.getRequestMetadata(CALL_URI); TestUtils.assertContainsBearerToken(metadata, accessToken2); - assertEquals(1, transport.buildRequestCount--); + assertEquals(1, transportFactory.transport.buildRequestCount--); } @Test(expected = IllegalStateException.class) @@ -310,6 +316,56 @@ public void refresh_temporaryToken_throws() throws IOException { credentials.refresh(); } + @Test + public void equals_true() throws IOException { + final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2"; + OAuth2Credentials credentials = new OAuth2Credentials(new AccessToken(accessToken1, null)); + OAuth2Credentials otherCredentials = new OAuth2Credentials(new AccessToken(accessToken1, null)); + assertTrue(credentials.equals(otherCredentials)); + assertTrue(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_accessToken() throws IOException { + final String accessToken1 = "1/MkSJoj1xsli0AccessToken_NKPY2"; + final String accessToken2 = "2/MkSJoj1xsli0AccessToken_NKPY2"; + OAuth2Credentials credentials = new OAuth2Credentials(new AccessToken(accessToken1, null)); + OAuth2Credentials otherCredentials = new OAuth2Credentials(new AccessToken(accessToken2, null)); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void toString_containsFields() throws IOException { + AccessToken accessToken = new AccessToken("1/MkSJoj1xsli0AccessToken_NKPY2", null); + OAuth2Credentials credentials = new OAuth2Credentials(accessToken); + String expectedToString = + String.format("OAuth2Credentials{requestMetadata=%s, temporaryAccess=%s}", + ImmutableMap.of(AuthHttpConstants.AUTHORIZATION, + ImmutableList.of(OAuth2Utils.BEARER_PREFIX + accessToken.getTokenValue())), + accessToken.toString()); + assertEquals(expectedToString, credentials.toString()); + } + + @Test + public void hashCode_equals() throws IOException { + final String accessToken = "1/MkSJoj1xsli0AccessToken_NKPY2"; + OAuth2Credentials credentials = new OAuth2Credentials(new AccessToken(accessToken, null)); + OAuth2Credentials otherCredentials = new OAuth2Credentials(new AccessToken(accessToken, null)); + assertEquals(credentials.hashCode(), otherCredentials.hashCode()); + } + + @Test + public void serialize() throws IOException, ClassNotFoundException { + final String accessToken = "1/MkSJoj1xsli0AccessToken_NKPY2"; + OAuth2Credentials credentials = new OAuth2Credentials(new AccessToken(accessToken, null)); + OAuth2Credentials deserializedCredentials = serializeAndDeserialize(credentials); + assertEquals(credentials, deserializedCredentials); + assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); + assertEquals(credentials.toString(), deserializedCredentials.toString()); + assertSame(deserializedCredentials.clock, Clock.SYSTEM); + } + private static class TestChangeListener implements OAuth2Credentials.CredentialsChangedListener { public AccessToken accessToken = null; diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java index 5be555b38..05333a44b 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java @@ -35,18 +35,23 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.api.client.json.GenericJson; +import com.google.api.client.util.Clock; import com.google.auth.TestUtils; +import com.google.auth.oauth2.GoogleCredentialsTest.MockHttpTransportFactory; +import com.google.auth.oauth2.GoogleCredentialsTest.MockTokenServerTransportFactory; +import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.security.PrivateKey; import java.util.Arrays; @@ -59,7 +64,7 @@ * Test case for {@link ServiceAccountCredentials}. */ @RunWith(JUnit4.class) -public class ServiceAccountCredentialsTest { +public class ServiceAccountCredentialsTest extends BaseSerializationTest { private final static String SA_CLIENT_EMAIL = "36680232662-vrd7ji19qe3nelgchd0ah2csanun6bnr@developer.gserviceaccount.com"; @@ -81,7 +86,7 @@ public class ServiceAccountCredentialsTest { + "==\n-----END PRIVATE KEY-----\n"; private static final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2"; private final static Collection SCOPES = Collections.singletonList("dummy.scope"); - private final static Collection EMPTY_SCOPES = Collections.emptyList(); + private final static Collection EMPTY_SCOPES = Collections.emptyList(); private static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo"); @Test @@ -103,15 +108,16 @@ public void createdScoped_clones() throws IOException { @Test public void createdScoped_enablesAccessTokens() throws IOException { - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addServiceAccount(SA_CLIENT_EMAIL, ACCESS_TOKEN); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addServiceAccount(SA_CLIENT_EMAIL, ACCESS_TOKEN); GoogleCredentials credentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, - SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, null, transport, null); + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, null, transportFactory, null); try { credentials.getRequestMetadata(CALL_URI); fail("Should not be able to get token without scopes"); } catch (Exception expected) { + // Expected } GoogleCredentials scopedCredentials = credentials.createScoped(SCOPES); @@ -138,12 +144,12 @@ public void createScopedRequired_nonEmptyScopes_false() throws IOException { @Test public void fromJSON_hasAccessToken() throws IOException { - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addServiceAccount(SA_CLIENT_EMAIL, ACCESS_TOKEN); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addServiceAccount(SA_CLIENT_EMAIL, ACCESS_TOKEN); GenericJson json = writeServiceAccountJson( SA_CLIENT_ID, SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID); - GoogleCredentials credentials = ServiceAccountCredentials.fromJson(json, transport); + GoogleCredentials credentials = ServiceAccountCredentials.fromJson(json, transportFactory); credentials = credentials.createScoped(SCOPES); Map> metadata = credentials.getRequestMetadata(CALL_URI); @@ -152,10 +158,10 @@ public void fromJSON_hasAccessToken() throws IOException { @Test public void getRequestMetadata_hasAccessToken() throws IOException { - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addServiceAccount(SA_CLIENT_EMAIL, ACCESS_TOKEN); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addServiceAccount(SA_CLIENT_EMAIL, ACCESS_TOKEN); OAuth2Credentials credentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, - SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, transport, null); + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, transportFactory, null); Map> metadata = credentials.getRequestMetadata(CALL_URI); @@ -165,11 +171,12 @@ public void getRequestMetadata_hasAccessToken() throws IOException { @Test public void getRequestMetadata_customTokenServer_hasAccessToken() throws IOException { final URI TOKEN_SERVER = URI.create("https://foo.com/bar"); - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addServiceAccount(SA_CLIENT_EMAIL, ACCESS_TOKEN); - transport.setTokenServerUri(TOKEN_SERVER); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addServiceAccount(SA_CLIENT_EMAIL, ACCESS_TOKEN); + transportFactory.transport.setTokenServerUri(TOKEN_SERVER); OAuth2Credentials credentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, - SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, transport, TOKEN_SERVER); + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, transportFactory, + TOKEN_SERVER); Map> metadata = credentials.getRequestMetadata(CALL_URI); @@ -187,6 +194,152 @@ public void getScopes_nullReturnsEmpty() throws IOException { assertTrue(scopes.isEmpty()); } + @Test + public void equals_true() throws IOException { + final URI tokenServer = URI.create("https://foo.com/bar"); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + OAuth2Credentials credentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, transportFactory, + tokenServer); + OAuth2Credentials otherCredentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, transportFactory, + tokenServer); + assertTrue(credentials.equals(otherCredentials)); + assertTrue(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_clientId() throws IOException { + final URI tokenServer1 = URI.create("https://foo1.com/bar"); + MockTokenServerTransportFactory serverTransportFactory = new MockTokenServerTransportFactory(); + OAuth2Credentials credentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, serverTransportFactory, + tokenServer1); + OAuth2Credentials otherCredentials = ServiceAccountCredentials.fromPkcs8("otherClientId", + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, serverTransportFactory, + tokenServer1); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_email() throws IOException { + final URI tokenServer1 = URI.create("https://foo1.com/bar"); + MockTokenServerTransportFactory serverTransportFactory = new MockTokenServerTransportFactory(); + OAuth2Credentials credentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, serverTransportFactory, + tokenServer1); + OAuth2Credentials otherCredentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, + "otherEmail", SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, serverTransportFactory, + tokenServer1); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_keyId() throws IOException { + final URI tokenServer1 = URI.create("https://foo1.com/bar"); + MockTokenServerTransportFactory serverTransportFactory = new MockTokenServerTransportFactory(); + OAuth2Credentials credentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, serverTransportFactory, + tokenServer1); + OAuth2Credentials otherCredentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, "otherId", SCOPES, serverTransportFactory, + tokenServer1); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_scopes() throws IOException { + final URI tokenServer1 = URI.create("https://foo1.com/bar"); + MockTokenServerTransportFactory serverTransportFactory = new MockTokenServerTransportFactory(); + OAuth2Credentials credentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, serverTransportFactory, + tokenServer1); + OAuth2Credentials otherCredentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, ImmutableSet.of(), + serverTransportFactory, tokenServer1); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_transportFactory() throws IOException { + final URI tokenServer1 = URI.create("https://foo1.com/bar"); + MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory(); + MockTokenServerTransportFactory serverTransportFactory = new MockTokenServerTransportFactory(); + OAuth2Credentials credentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, serverTransportFactory, + tokenServer1); + OAuth2Credentials otherCredentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, httpTransportFactory, + tokenServer1); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_tokenServer() throws IOException { + final URI tokenServer1 = URI.create("https://foo1.com/bar"); + final URI tokenServer2 = URI.create("https://foo2.com/bar"); + MockTokenServerTransportFactory serverTransportFactory = new MockTokenServerTransportFactory(); + OAuth2Credentials credentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, serverTransportFactory, + tokenServer1); + OAuth2Credentials otherCredentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, serverTransportFactory, + tokenServer2); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void toString_containsFields() throws IOException { + final URI tokenServer = URI.create("https://foo.com/bar"); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + OAuth2Credentials credentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, transportFactory, + tokenServer); + String expectedToString = String.format( + "ServiceAccountCredentials{clientId=%s, clientEmail=%s, privateKeyId=%s, " + + "transportFactoryClassName=%s, tokenServerUri=%s, scopes=%s}", + SA_CLIENT_ID, + SA_CLIENT_EMAIL, + SA_PRIVATE_KEY_ID, + MockTokenServerTransportFactory.class.getName(), + tokenServer, + SCOPES); + assertEquals(expectedToString, credentials.toString()); + } + + @Test + public void hashCode_equals() throws IOException { + final URI tokenServer = URI.create("https://foo.com/bar"); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + OAuth2Credentials credentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, transportFactory, + tokenServer); + OAuth2Credentials otherCredentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, transportFactory, + tokenServer); + assertEquals(credentials.hashCode(), otherCredentials.hashCode()); + } + + @Test + public void serialize() throws IOException, ClassNotFoundException { + final URI tokenServer = URI.create("https://foo.com/bar"); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + ServiceAccountCredentials credentials = ServiceAccountCredentials.fromPkcs8(SA_CLIENT_ID, + SA_CLIENT_EMAIL, SA_PRIVATE_KEY_PKCS8, SA_PRIVATE_KEY_ID, SCOPES, transportFactory, + tokenServer); + ServiceAccountCredentials deserializedCredentials = serializeAndDeserialize(credentials); + assertEquals(credentials, deserializedCredentials); + assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); + assertEquals(credentials.toString(), deserializedCredentials.toString()); + assertSame(deserializedCredentials.clock, Clock.SYSTEM); + } + static GenericJson writeServiceAccountJson( String clientId, String clientEmail, String privateKeyPkcs8, String privateKeyId) { GenericJson json = new GenericJson(); @@ -210,7 +363,6 @@ static InputStream writeServiceAccountAccountStream(String clientId, String clie String privateKeyPkcs8, String privateKeyId) throws IOException { GenericJson json = writeServiceAccountJson(clientId, clientEmail, privateKeyPkcs8, privateKeyId); - InputStream stream = TestUtils.jsonToInputStream(json); - return stream; + return TestUtils.jsonToInputStream(json); } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountJwtAccessCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountJwtAccessCredentialsTest.java index 30859dd08..4caaa8c35 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountJwtAccessCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountJwtAccessCredentialsTest.java @@ -32,14 +32,17 @@ 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.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.client.json.webtoken.JsonWebSignature; +import com.google.api.client.util.Clock; import com.google.auth.Credentials; import com.google.auth.http.AuthHttpConstants; @@ -57,7 +60,7 @@ * Test case for {@link ServiceAccountCredentials}. */ @RunWith(JUnit4.class) -public class ServiceAccountJwtAccessCredentialsTest { +public class ServiceAccountJwtAccessCredentialsTest extends BaseSerializationTest { private final static String SA_CLIENT_EMAIL = "36680232662-vrd7ji19qe3nelgchd0ah2csanun6bnr@developer.gserviceaccount.com"; @@ -113,6 +116,7 @@ public void constructor_noEmail_throws() throws IOException { new ServiceAccountJwtAccessCredentials(SA_CLIENT_ID, null, privateKey, SA_PRIVATE_KEY_ID); fail("exception expected"); } catch (NullPointerException e) { + // Expected } } @@ -123,6 +127,7 @@ public void constructor_noPrivateKey_throws() { SA_CLIENT_ID, SA_CLIENT_EMAIL, null , SA_PRIVATE_KEY_ID); fail("exception expected"); } catch (NullPointerException e) { + // Expected } } @@ -179,6 +184,7 @@ public void getRequestMetadata_blocking_noURI_throws() throws IOException { credentials.getRequestMetadata(); fail("exception expected"); } catch (IOException e) { + // Expected } } @@ -223,6 +229,100 @@ public void getRequestMetadata_async_noURI_exception() throws IOException { assertNotNull(callback.exception); } + @Test + public void equals_true() throws IOException { + PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + ServiceAccountJwtAccessCredentials credentials = new ServiceAccountJwtAccessCredentials( + SA_CLIENT_ID, SA_CLIENT_EMAIL, privateKey, SA_PRIVATE_KEY_ID, CALL_URI); + ServiceAccountJwtAccessCredentials otherCredentials = new ServiceAccountJwtAccessCredentials( + SA_CLIENT_ID, SA_CLIENT_EMAIL, privateKey, SA_PRIVATE_KEY_ID, CALL_URI); + assertTrue(credentials.equals(otherCredentials)); + assertTrue(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_clientId() throws IOException { + PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + ServiceAccountJwtAccessCredentials credentials = new ServiceAccountJwtAccessCredentials( + SA_CLIENT_ID, SA_CLIENT_EMAIL, privateKey, SA_PRIVATE_KEY_ID, CALL_URI); + ServiceAccountJwtAccessCredentials otherCredentials = new ServiceAccountJwtAccessCredentials( + "otherClientId", SA_CLIENT_EMAIL, privateKey, SA_PRIVATE_KEY_ID, CALL_URI); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_email() throws IOException { + PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + ServiceAccountJwtAccessCredentials credentials = new ServiceAccountJwtAccessCredentials( + SA_CLIENT_ID, SA_CLIENT_EMAIL, privateKey, SA_PRIVATE_KEY_ID, CALL_URI); + ServiceAccountJwtAccessCredentials otherCredentials = new ServiceAccountJwtAccessCredentials( + SA_CLIENT_ID, "otherClientEmail", privateKey, SA_PRIVATE_KEY_ID, CALL_URI); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_keyId() throws IOException { + PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + ServiceAccountJwtAccessCredentials credentials = new ServiceAccountJwtAccessCredentials( + SA_CLIENT_ID, SA_CLIENT_EMAIL, privateKey, SA_PRIVATE_KEY_ID, CALL_URI); + ServiceAccountJwtAccessCredentials otherCredentials = new ServiceAccountJwtAccessCredentials( + SA_CLIENT_ID, SA_CLIENT_EMAIL, privateKey, "otherKeyId", CALL_URI); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_callUri() throws IOException { + PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + final URI otherCallUri = URI.create("https://foo.com/bar"); + ServiceAccountJwtAccessCredentials credentials = new ServiceAccountJwtAccessCredentials( + SA_CLIENT_ID, SA_CLIENT_EMAIL, privateKey, SA_PRIVATE_KEY_ID, CALL_URI); + ServiceAccountJwtAccessCredentials otherCredentials = new ServiceAccountJwtAccessCredentials( + SA_CLIENT_ID, SA_CLIENT_EMAIL, privateKey, SA_PRIVATE_KEY_ID, otherCallUri); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void toString_containsFields() throws IOException { + PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + ServiceAccountJwtAccessCredentials credentials = new ServiceAccountJwtAccessCredentials( + SA_CLIENT_ID, SA_CLIENT_EMAIL, privateKey, SA_PRIVATE_KEY_ID, CALL_URI); + String expectedToString = String.format( + "ServiceAccountJwtAccessCredentials{clientId=%s, clientEmail=%s, privateKeyId=%s, " + + "defaultAudience=%s}", + SA_CLIENT_ID, + SA_CLIENT_EMAIL, + SA_PRIVATE_KEY_ID, + CALL_URI); + assertEquals(expectedToString, credentials.toString()); + } + + @Test + public void hashCode_equals() throws IOException { + PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + ServiceAccountJwtAccessCredentials credentials = new ServiceAccountJwtAccessCredentials( + SA_CLIENT_ID, SA_CLIENT_EMAIL, privateKey, SA_PRIVATE_KEY_ID, CALL_URI); + ServiceAccountJwtAccessCredentials otherCredentials = new ServiceAccountJwtAccessCredentials( + SA_CLIENT_ID, SA_CLIENT_EMAIL, privateKey, SA_PRIVATE_KEY_ID, CALL_URI); + assertEquals(credentials.hashCode(), otherCredentials.hashCode()); + } + + @Test + public void serialize() throws IOException, ClassNotFoundException { + PrivateKey privateKey = ServiceAccountCredentials.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8); + ServiceAccountJwtAccessCredentials credentials = new ServiceAccountJwtAccessCredentials( + SA_CLIENT_ID, SA_CLIENT_EMAIL, privateKey, SA_PRIVATE_KEY_ID, CALL_URI); + ServiceAccountJwtAccessCredentials deserializedCredentials = + serializeAndDeserialize(credentials); + assertEquals(credentials, deserializedCredentials); + assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); + assertEquals(credentials.toString(), deserializedCredentials.toString()); + assertSame(deserializedCredentials.clock, Clock.SYSTEM); + } + private void verifyJwtAccess(Map> metadata, String expectedEmail, URI expectedAudience, String expectedKeyId) throws IOException { assertNotNull(metadata); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/UserAuthorizerTest.java b/oauth2_http/javatests/com/google/auth/oauth2/UserAuthorizerTest.java index 9b9ecd0c3..36ed90899 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/UserAuthorizerTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/UserAuthorizerTest.java @@ -38,6 +38,7 @@ import static org.junit.Assert.fail; import com.google.auth.TestUtils; +import com.google.auth.oauth2.GoogleCredentialsTest.MockTokenServerTransportFactory; import org.junit.Test; import org.junit.runner.RunWith; @@ -61,7 +62,7 @@ public class UserAuthorizerTest { private static final String CLIENT_SECRET = "jakuaL9YyieakhECKL2SwZcu"; private static final String REFRESH_TOKEN = "1/Tl6awhpFjkMkSJoj1xsli0H2eL5YsMgU_NKPY2TyGWY"; private static final String ACCESS_TOKEN_VALUE = "1/MkSJoj1xsli0AccessToken_NKPY2"; - private static final Long EXPIRATION_TIME = Long.valueOf(504000300); + private static final Long EXPIRATION_TIME = 504000300L; private static final AccessToken ACCESS_TOKEN = new AccessToken(ACCESS_TOKEN_VALUE, new Date(EXPIRATION_TIME)); private static final ClientId CLIENT_ID = new ClientId(CLIENT_ID_VALUE, CLIENT_SECRET); @@ -189,14 +190,14 @@ public void getCredentials_refreshedToken_stored() throws IOException { final String accessTokenValue2 = "2/MkSJoj1xsli0AccessToken_NKPY2"; AccessToken acessToken1 = new AccessToken(accessTokenValue1, new Date(EXPIRATION_TIME)); - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(CLIENT_ID_VALUE, CLIENT_SECRET); - transport.addRefreshToken(REFRESH_TOKEN, accessTokenValue2); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addClient(CLIENT_ID_VALUE, CLIENT_SECRET); + transportFactory.transport.addRefreshToken(REFRESH_TOKEN, accessTokenValue2); TestTokenStore tokenStore = new TestTokenStore(); UserAuthorizer authorizer = - new UserAuthorizer(CLIENT_ID, SCOPES, tokenStore, null, transport, null, null); + new UserAuthorizer(CLIENT_ID, SCOPES, tokenStore, null, transportFactory, null, null); UserCredentials originalCredentials = new UserCredentials( - CLIENT_ID_VALUE, CLIENT_SECRET, REFRESH_TOKEN, acessToken1, transport, null); + CLIENT_ID_VALUE, CLIENT_SECRET, REFRESH_TOKEN, acessToken1, transportFactory, null); authorizer.storeCredentials(USER_ID, originalCredentials); UserCredentials credentials1 = authorizer.getCredentials(USER_ID); @@ -219,12 +220,12 @@ public void getCredentials_refreshedToken_stored() throws IOException { @Test public void getCredentialsFromCode_conevertsCodeToTokens() throws IOException { - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(CLIENT_ID_VALUE, CLIENT_SECRET); - transport.addAuthorizationCode(CODE, REFRESH_TOKEN, ACCESS_TOKEN_VALUE); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addClient(CLIENT_ID_VALUE, CLIENT_SECRET); + transportFactory.transport.addAuthorizationCode(CODE, REFRESH_TOKEN, ACCESS_TOKEN_VALUE); TestTokenStore tokenStore = new TestTokenStore(); UserAuthorizer authorizer = - new UserAuthorizer(CLIENT_ID, SCOPES, tokenStore, null, transport, null, null); + new UserAuthorizer(CLIENT_ID, SCOPES, tokenStore, null, transportFactory, null, null); UserCredentials credentials = authorizer.getCredentialsFromCode(CODE, BASE_URI); @@ -244,12 +245,12 @@ public void getCredentialsFromCode_nullCode_throws() throws IOException { public void getAndStoreCredentialsFromCode_getAndStoresCredentials() throws IOException { final String accessTokenValue1 = "1/MkSJoj1xsli0AccessToken_NKPY2"; final String accessTokenValue2 = "2/MkSJoj1xsli0AccessToken_NKPY2"; - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(CLIENT_ID_VALUE, CLIENT_SECRET); - transport.addAuthorizationCode(CODE, REFRESH_TOKEN, accessTokenValue1); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addClient(CLIENT_ID_VALUE, CLIENT_SECRET); + transportFactory.transport.addAuthorizationCode(CODE, REFRESH_TOKEN, accessTokenValue1); TestTokenStore tokenStore = new TestTokenStore(); UserAuthorizer authorizer = - new UserAuthorizer(CLIENT_ID, SCOPES, tokenStore, null, transport, null, null); + new UserAuthorizer(CLIENT_ID, SCOPES, tokenStore, null, transportFactory, null, null); UserCredentials credentials1 = authorizer.getAndStoreCredentialsFromCode(USER_ID, CODE, BASE_URI); @@ -258,7 +259,7 @@ public void getAndStoreCredentialsFromCode_getAndStoresCredentials() throws IOEx assertEquals(accessTokenValue1, credentials1.getAccessToken().getTokenValue()); // Refresh the token to get update from token server - transport.addRefreshToken(REFRESH_TOKEN, accessTokenValue2); + transportFactory.transport.addRefreshToken(REFRESH_TOKEN, accessTokenValue2); credentials1.refresh(); assertEquals(REFRESH_TOKEN, credentials1.getRefreshToken()); assertEquals(accessTokenValue2, credentials1.getAccessToken().getTokenValue()); @@ -290,13 +291,13 @@ public void getAndStoreCredentialsFromCode_nullUserId_throws() throws IOExceptio @Test public void revokeAuthorization_revokesAndClears() throws IOException { TestTokenStore tokenStore = new TestTokenStore(); - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(CLIENT_ID_VALUE, CLIENT_SECRET); - transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN_VALUE); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addClient(CLIENT_ID_VALUE, CLIENT_SECRET); + transportFactory.transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN_VALUE); UserCredentials initialCredentials = new UserCredentials(CLIENT_ID_VALUE, CLIENT_SECRET, REFRESH_TOKEN, ACCESS_TOKEN); UserAuthorizer authorizer = - new UserAuthorizer(CLIENT_ID, SCOPES, tokenStore, null, transport, null, null); + new UserAuthorizer(CLIENT_ID, SCOPES, tokenStore, null, transportFactory, null, null); authorizer.storeCredentials(USER_ID, initialCredentials); UserCredentials credentials1 = authorizer.getCredentials(USER_ID); @@ -311,6 +312,7 @@ public void revokeAuthorization_revokesAndClears() throws IOException { credentials1.refresh(); fail("Credentials should not refresh after revoke."); } catch (IOException expected) { + // Expected } UserCredentials credentials2 = authorizer.getCredentials(USER_ID); assertNull(credentials2); @@ -318,7 +320,7 @@ public void revokeAuthorization_revokesAndClears() throws IOException { private static class TestTokenStore implements TokenStore { - private final Map map = new HashMap(); + private final Map map = new HashMap<>(); @Override public String load(String id) throws IOException { diff --git a/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java index 28e6d4236..807b4704b 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java @@ -34,17 +34,24 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.api.client.json.GenericJson; +import com.google.api.client.util.Clock; import com.google.auth.TestUtils; +import com.google.auth.http.AuthHttpConstants; +import com.google.auth.oauth2.GoogleCredentialsTest.MockHttpTransportFactory; +import com.google.auth.oauth2.GoogleCredentialsTest.MockTokenServerTransportFactory; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.util.Collection; import java.util.Collections; @@ -55,7 +62,7 @@ * Test case for {@link UserCredentials}. */ @RunWith(JUnit4.class) -public class UserCredentialsTest { +public class UserCredentialsTest extends BaseSerializationTest { private static final String CLIENT_SECRET = "jakuaL9YyieakhECKL2SwZcu"; private static final String CLIENT_ID = "ya29.1.AADtN_UtlxN3PuGAxrN2XQnZTVRvDyVWnYq4I6dws"; @@ -90,12 +97,12 @@ public void createScopedRequired_false() { @Test public void fromJson_hasAccessToken() throws IOException { - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(CLIENT_ID, CLIENT_SECRET); - transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET); + transportFactory.transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN); GenericJson json = writeUserJson(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN); - GoogleCredentials credentials = UserCredentials.fromJson(json, transport); + GoogleCredentials credentials = UserCredentials.fromJson(json, transportFactory); Map> metadata = credentials.getRequestMetadata(CALL_URI); TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN); @@ -103,11 +110,11 @@ public void fromJson_hasAccessToken() throws IOException { @Test public void getRequestMetadata_initialToken_hasAccessToken() throws IOException { - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(CLIENT_ID, CLIENT_SECRET); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET); AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); OAuth2Credentials userCredentials = new UserCredentials( - CLIENT_ID, CLIENT_SECRET, null, accessToken, transport, null); + CLIENT_ID, CLIENT_SECRET, null, accessToken, transportFactory, null); Map> metadata = userCredentials.getRequestMetadata(CALL_URI); @@ -116,26 +123,27 @@ public void getRequestMetadata_initialToken_hasAccessToken() throws IOException @Test public void getRequestMetadata_initialTokenRefreshed_throws() throws IOException { - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(CLIENT_ID, CLIENT_SECRET); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET); AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); OAuth2Credentials userCredentials = new UserCredentials( - CLIENT_ID, CLIENT_SECRET, null, accessToken, transport, null); + CLIENT_ID, CLIENT_SECRET, null, accessToken, transportFactory, null); try { userCredentials.refresh(); fail("Should not be able to refresh without refresh token."); } catch (IllegalStateException expected) { + // Expected } } @Test public void getRequestMetadata_fromRefreshToken_hasAccessToken() throws IOException { - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(CLIENT_ID, CLIENT_SECRET); - transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET); + transportFactory.transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN); OAuth2Credentials userCredentials = new UserCredentials( - CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, transport, null); + CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, transportFactory, null); Map> metadata = userCredentials.getRequestMetadata(CALL_URI); @@ -145,18 +153,158 @@ public void getRequestMetadata_fromRefreshToken_hasAccessToken() throws IOExcept @Test public void getRequestMetadata_customTokenServer_hasAccessToken() throws IOException { final URI TOKEN_SERVER = URI.create("https://foo.com/bar"); - MockTokenServerTransport transport = new MockTokenServerTransport(); - transport.addClient(CLIENT_ID, CLIENT_SECRET); - transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN); - transport.setTokenServerUri(TOKEN_SERVER); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET); + transportFactory.transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN); + transportFactory.transport.setTokenServerUri(TOKEN_SERVER); OAuth2Credentials userCredentials = new UserCredentials( - CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, transport, TOKEN_SERVER); + CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, null, transportFactory, TOKEN_SERVER); Map> metadata = userCredentials.getRequestMetadata(CALL_URI); TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN); } + @Test + public void equals_true() throws IOException { + final URI tokenServer = URI.create("https://foo.com/bar"); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); + OAuth2Credentials credentials = new UserCredentials( + CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, accessToken, transportFactory, tokenServer); + OAuth2Credentials otherCredentials = new UserCredentials( + CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, accessToken, transportFactory, tokenServer); + assertTrue(credentials.equals(otherCredentials)); + assertTrue(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_clientId() throws IOException { + final URI tokenServer1 = URI.create("https://foo1.com/bar"); + AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); + MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory(); + OAuth2Credentials credentials = new UserCredentials(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, + accessToken, httpTransportFactory, tokenServer1); + OAuth2Credentials otherCredentials = new UserCredentials("otherClientId", CLIENT_SECRET, + REFRESH_TOKEN, accessToken, httpTransportFactory, tokenServer1); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_clientSecret() throws IOException { + final URI tokenServer1 = URI.create("https://foo1.com/bar"); + AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); + MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory(); + OAuth2Credentials credentials = new UserCredentials(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, + accessToken, httpTransportFactory, tokenServer1); + OAuth2Credentials otherCredentials = new UserCredentials(CLIENT_ID, "otherClientSecret", + REFRESH_TOKEN, accessToken, httpTransportFactory, tokenServer1); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_refreshToken() throws IOException { + final URI tokenServer1 = URI.create("https://foo1.com/bar"); + AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); + MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory(); + OAuth2Credentials credentials = new UserCredentials(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, + accessToken, httpTransportFactory, tokenServer1); + OAuth2Credentials otherCredentials = new UserCredentials(CLIENT_ID, CLIENT_SECRET, + "otherRefreshToken", accessToken, httpTransportFactory, tokenServer1); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_accessToken() throws IOException { + final URI tokenServer1 = URI.create("https://foo1.com/bar"); + AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); + AccessToken otherAccessToken = new AccessToken("otherAccessToken", null); + MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory(); + OAuth2Credentials credentials = new UserCredentials(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, + accessToken, httpTransportFactory, tokenServer1); + OAuth2Credentials otherCredentials = new UserCredentials(CLIENT_ID, CLIENT_SECRET, + REFRESH_TOKEN, otherAccessToken, httpTransportFactory, tokenServer1); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_transportFactory() throws IOException { + final URI tokenServer1 = URI.create("https://foo1.com/bar"); + AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); + MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory(); + MockTokenServerTransportFactory serverTransportFactory = new MockTokenServerTransportFactory(); + OAuth2Credentials credentials = new UserCredentials(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, + accessToken, httpTransportFactory, tokenServer1); + OAuth2Credentials otherCredentials = new UserCredentials(CLIENT_ID, CLIENT_SECRET, + REFRESH_TOKEN, accessToken, serverTransportFactory, tokenServer1); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void equals_false_tokenServer() throws IOException { + final URI tokenServer1 = URI.create("https://foo1.com/bar"); + final URI tokenServer2 = URI.create("https://foo2.com/bar"); + AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); + MockHttpTransportFactory httpTransportFactory = new MockHttpTransportFactory(); + OAuth2Credentials credentials = new UserCredentials(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, + accessToken, httpTransportFactory, tokenServer1); + OAuth2Credentials otherCredentials = new UserCredentials(CLIENT_ID, CLIENT_SECRET, + REFRESH_TOKEN, accessToken, httpTransportFactory, tokenServer2); + assertFalse(credentials.equals(otherCredentials)); + assertFalse(otherCredentials.equals(credentials)); + } + + @Test + public void toString_containsFields() throws IOException { + AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); + final URI tokenServer = URI.create("https://foo.com/bar"); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + OAuth2Credentials credentials = new UserCredentials(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, + accessToken, transportFactory, tokenServer); + String expectedToString = String.format( + "UserCredentials{requestMetadata=%s, temporaryAccess=%s, clientId=%s, refreshToken=%s, " + + "tokenServerUri=%s, transportFactoryClassName=%s}", + ImmutableMap.of(AuthHttpConstants.AUTHORIZATION, + ImmutableList.of(OAuth2Utils.BEARER_PREFIX + accessToken.getTokenValue())), + accessToken.toString(), + CLIENT_ID, + REFRESH_TOKEN, + tokenServer, + MockTokenServerTransportFactory.class.getName()); + assertEquals(expectedToString, credentials.toString()); + } + + @Test + public void hashCode_equals() throws IOException { + final URI tokenServer = URI.create("https://foo.com/bar"); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); + OAuth2Credentials credentials = new UserCredentials( + CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, accessToken, transportFactory, tokenServer); + OAuth2Credentials otherCredentials = new UserCredentials( + CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, accessToken, transportFactory, tokenServer); + assertEquals(credentials.hashCode(), otherCredentials.hashCode()); + } + + @Test + public void serialize() throws IOException, ClassNotFoundException { + final URI tokenServer = URI.create("https://foo.com/bar"); + MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); + AccessToken accessToken = new AccessToken(ACCESS_TOKEN, null); + UserCredentials credentials = new UserCredentials( + CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN, accessToken, transportFactory, tokenServer); + UserCredentials deserializedCredentials = serializeAndDeserialize(credentials); + assertEquals(credentials, deserializedCredentials); + assertEquals(credentials.hashCode(), deserializedCredentials.hashCode()); + assertEquals(credentials.toString(), deserializedCredentials.toString()); + assertSame(deserializedCredentials.clock, Clock.SYSTEM); + } + static GenericJson writeUserJson(String clientId, String clientSecret, String refreshToken) { GenericJson json = new GenericJson(); if (clientId != null) { @@ -175,7 +323,6 @@ static GenericJson writeUserJson(String clientId, String clientSecret, String re static InputStream writeUserStream(String clientId, String clientSecret, String refreshToken) throws IOException { GenericJson json = writeUserJson(clientId, clientSecret, refreshToken); - InputStream stream = TestUtils.jsonToInputStream(json); - return stream; + return TestUtils.jsonToInputStream(json); } } diff --git a/oauth2_http/pom.xml b/oauth2_http/pom.xml index a9b5d288b..51c29d0c6 100644 --- a/oauth2_http/pom.xml +++ b/oauth2_http/pom.xml @@ -58,7 +58,7 @@ com.google.guava - guava-jdk5 + guava junit diff --git a/pom.xml b/pom.xml index b6ecff0bb..235c11289 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ 1.6 1.19.0 4.8.2 - 13.0 + 19.0 1.9.34 @@ -85,7 +85,7 @@ com.google.guava - guava-jdk5 + guava ${project.guava.version} @@ -94,6 +94,12 @@ ${project.junit.version} test + + com.google.auth + google-auth-library-oauth2-http + ${project.version} + test-jar + @@ -138,9 +144,31 @@ + + org.apache.maven.plugins + maven-jar-plugin + 3.0.2 + + + + test-jar + + + + + + maven-compiler-plugin + 3.5.1 + + 1.7 + 1.7 + UTF-8 + -Xlint:unchecked + + org.sonatype.plugins nexus-staging-maven-plugin