diff --git a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java index b62509dcf..b0f47c4e4 100644 --- a/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java @@ -39,6 +39,7 @@ import java.io.IOException; import java.io.InputStream; + import java.util.Collection; /** diff --git a/oauth2_http/java/com/google/auth/oauth2/MemoryTokensStorage.java b/oauth2_http/java/com/google/auth/oauth2/MemoryTokensStorage.java new file mode 100644 index 000000000..6940a6350 --- /dev/null +++ b/oauth2_http/java/com/google/auth/oauth2/MemoryTokensStorage.java @@ -0,0 +1,59 @@ +/* + * Copyright 2017, 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.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Represents an in-memory storage of tokens. + */ +public class MemoryTokensStorage implements TokenStore { + private final Map tokensStorage = new HashMap<>(); + + @Override + public String load(String id) throws IOException { + return tokensStorage.get(id); + } + + @Override + public void store(String id, String tokens) throws IOException { + tokensStorage.put(id, tokens); + } + + @Override + public void delete(String id) throws IOException { + tokensStorage.remove(id); + } + +} diff --git a/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java b/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java index da32539e6..96f1f877c 100644 --- a/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java +++ b/oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java @@ -40,10 +40,14 @@ import com.google.api.client.json.jackson2.JacksonFactory; import com.google.auth.http.AuthHttpConstants; import com.google.auth.http.HttpTransportFactory; +import com.google.common.io.ByteStreams; import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.math.BigDecimal; import java.net.URI; import java.nio.charset.Charset; @@ -122,6 +126,22 @@ static String validateString(Map map, String key, String errorPr return (String) value; } + /** + * Saves the end user credentials into the given file path. + * + * @param credentials InputStream containing user credentials in JSON format + * @param filePath Path to file where to store the credentials + * @throws IOException An error saving the credentials. + */ + static void writeInputStreamToFile(InputStream credentials, String filePath) throws IOException { + final OutputStream outputStream = new FileOutputStream(new File(filePath)); + try { + ByteStreams.copy(credentials, outputStream); + } finally { + outputStream.close(); + } + } + /** * Return the specified optional string from JSON or throw a helpful error message. */ diff --git a/oauth2_http/java/com/google/auth/oauth2/UserAuthorizer.java b/oauth2_http/java/com/google/auth/oauth2/UserAuthorizer.java index c1d141173..52e0f2f4d 100644 --- a/oauth2_http/java/com/google/auth/oauth2/UserAuthorizer.java +++ b/oauth2_http/java/com/google/auth/oauth2/UserAuthorizer.java @@ -122,7 +122,7 @@ public UserAuthorizer(ClientId clientId, Collection scopes, TokenStore t (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; + this.tokenStore = (tokenStore == null) ? new MemoryTokensStorage() : tokenStore; } diff --git a/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java b/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java index 298545e83..7ae0a8d11 100644 --- a/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java @@ -31,6 +31,8 @@ package com.google.auth.oauth2; +import static com.google.auth.oauth2.OAuth2Utils.JSON_FACTORY; +import static com.google.auth.oauth2.OAuth2Utils.UTF_8; import static com.google.common.base.MoreObjects.firstNonNull; import com.google.api.client.http.GenericUrl; @@ -45,6 +47,7 @@ import com.google.api.client.util.Preconditions; import com.google.auth.http.HttpTransportFactory; +import java.io.ByteArrayInputStream; import com.google.common.base.MoreObjects; import java.io.IOException; import java.io.InputStream; @@ -184,7 +187,7 @@ public static UserCredentials fromStream(InputStream credentialsStream, Preconditions.checkNotNull(credentialsStream); Preconditions.checkNotNull(transportFactory); - JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY; + JsonFactory jsonFactory = JSON_FACTORY; JsonObjectParser parser = new JsonObjectParser(jsonFactory); GenericJson fileContents = parser.parseAndClose( credentialsStream, OAuth2Utils.UTF_8, GenericJson.class); @@ -220,7 +223,7 @@ public AccessToken refreshAccessToken() throws IOException { HttpRequestFactory requestFactory = transportFactory.create().createRequestFactory(); HttpRequest request = requestFactory.buildPostRequest(new GenericUrl(tokenServerUri), content); - request.setParser(new JsonObjectParser(OAuth2Utils.JSON_FACTORY)); + request.setParser(new JsonObjectParser(JSON_FACTORY)); HttpResponse response = request.execute(); GenericData responseData = response.parseAs(GenericData.class); String accessToken = @@ -258,6 +261,47 @@ public final String getRefreshToken() { return refreshToken; } + + /** + * Returns the instance of InputStream containing the following user credentials in JSON format: + * - RefreshToken + * - ClientId + * - ClientSecret + * - ServerTokenUri + * + * @return user credentials stream + */ + private InputStream getUserCredentialsStream() throws IOException { + GenericJson json = new GenericJson(); + json.put("type", GoogleCredentials.USER_FILE_TYPE); + if (refreshToken != null) { + json.put("refresh_token", refreshToken); + } + if (tokenServerUri != null) { + json.put("token_server_uri", tokenServerUri); + } + if (clientId != null) { + json.put("client_id", clientId); + } + if (clientSecret != null) { + json.put("client_secret", clientSecret); + } + json.setFactory(JSON_FACTORY); + String text = json.toPrettyString(); + return new ByteArrayInputStream(text.getBytes(UTF_8)); + } + + /** + * Saves the end user credentials into the given file path. + * + * @param filePath Path to file where to store the credentials + * + * @throws IOException An error storing the credentials. + */ + public void save(String filePath) throws IOException { + OAuth2Utils.writeInputStreamToFile(getUserCredentialsStream(), filePath); + } + @Override public int hashCode() { return Objects.hash(super.hashCode(), clientId, clientSecret, refreshToken, tokenServerUri, diff --git a/oauth2_http/javatests/com/google/auth/oauth2/UserAuthorizerTest.java b/oauth2_http/javatests/com/google/auth/oauth2/UserAuthorizerTest.java index 8a3524181..be8656db0 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/UserAuthorizerTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/UserAuthorizerTest.java @@ -50,7 +50,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.Map; /** @@ -75,7 +74,7 @@ public class UserAuthorizerTest { @Test public void constructorMinimum() { - TestTokenStore store = new TestTokenStore(); + TokenStore store = new MemoryTokensStorage(); UserAuthorizer authorizer = UserAuthorizer.newBuilder() .setClientId(CLIENT_ID) @@ -91,7 +90,7 @@ public void constructorMinimum() { @Test public void constructorCommon() { - TestTokenStore store = new TestTokenStore(); + TokenStore store = new MemoryTokensStorage(); UserAuthorizer authorizer = UserAuthorizer.newBuilder() .setClientId(CLIENT_ID) @@ -172,7 +171,7 @@ public void getCredentials_noCredentials_returnsNull() throws IOException { UserAuthorizer authorizer = UserAuthorizer.newBuilder() .setClientId(CLIENT_ID) .setScopes(SCOPES) - .setTokenStore(new TestTokenStore()) + .setTokenStore(new MemoryTokensStorage()) .build(); UserCredentials credentials = authorizer.getCredentials(USER_ID); @@ -182,7 +181,7 @@ public void getCredentials_noCredentials_returnsNull() throws IOException { @Test public void getCredentials_storedCredentials_returnsStored() throws IOException { - TestTokenStore tokenStore = new TestTokenStore(); + TokenStore tokenStore = new MemoryTokensStorage(); UserCredentials initialCredentials = UserCredentials.newBuilder() .setClientId(CLIENT_ID_VALUE) @@ -207,7 +206,7 @@ public void getCredentials_storedCredentials_returnsStored() throws IOException @Test(expected = NullPointerException.class) public void getCredentials_nullUserId_throws() throws IOException { - TestTokenStore tokenStore = new TestTokenStore(); + TokenStore tokenStore = new MemoryTokensStorage(); UserAuthorizer authorizer = UserAuthorizer.newBuilder() .setClientId(CLIENT_ID) .setScopes(SCOPES) @@ -217,16 +216,6 @@ public void getCredentials_nullUserId_throws() throws IOException { authorizer.getCredentials(null); } - @Test(expected = IllegalStateException.class) - public void getCredentials_nullTokenStore_throws() throws IOException { - UserAuthorizer authorizer = UserAuthorizer.newBuilder() - .setClientId(CLIENT_ID) - .setScopes(SCOPES) - .build(); - - authorizer.getCredentials(USER_ID); - } - @Test public void getCredentials_refreshedToken_stored() throws IOException { final String accessTokenValue1 = "1/MkSJoj1xsli0AccessToken_NKPY2"; @@ -236,7 +225,7 @@ public void getCredentials_refreshedToken_stored() throws IOException { MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); transportFactory.transport.addClient(CLIENT_ID_VALUE, CLIENT_SECRET); transportFactory.transport.addRefreshToken(REFRESH_TOKEN, accessTokenValue2); - TestTokenStore tokenStore = new TestTokenStore(); + TokenStore tokenStore = new MemoryTokensStorage(); UserAuthorizer authorizer = UserAuthorizer.newBuilder() .setClientId(CLIENT_ID) .setScopes(SCOPES) @@ -277,7 +266,7 @@ public void getCredentialsFromCode_conevertsCodeToTokens() throws IOException { 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(); + TokenStore tokenStore = new MemoryTokensStorage(); UserAuthorizer authorizer = UserAuthorizer.newBuilder() .setClientId(CLIENT_ID) .setScopes(SCOPES) @@ -296,7 +285,7 @@ public void getCredentialsFromCode_nullCode_throws() throws IOException { UserAuthorizer authorizer = UserAuthorizer.newBuilder() .setClientId(CLIENT_ID) .setScopes(SCOPES) - .setTokenStore(new TestTokenStore()) + .setTokenStore(new MemoryTokensStorage()) .build(); authorizer.getCredentialsFromCode(null, BASE_URI); @@ -309,7 +298,7 @@ public void getAndStoreCredentialsFromCode_getAndStoresCredentials() throws IOEx MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); transportFactory.transport.addClient(CLIENT_ID_VALUE, CLIENT_SECRET); transportFactory.transport.addAuthorizationCode(CODE, REFRESH_TOKEN, accessTokenValue1); - TestTokenStore tokenStore = new TestTokenStore(); + TokenStore tokenStore = new MemoryTokensStorage(); UserAuthorizer authorizer = UserAuthorizer.newBuilder() .setClientId(CLIENT_ID) .setScopes(SCOPES) @@ -342,7 +331,7 @@ public void getAndStoreCredentialsFromCode_nullCode_throws() throws IOException UserAuthorizer authorizer = UserAuthorizer.newBuilder() .setClientId(CLIENT_ID) .setScopes(SCOPES) - .setTokenStore(new TestTokenStore()) + .setTokenStore(new MemoryTokensStorage()) .build(); authorizer.getAndStoreCredentialsFromCode(USER_ID, null, BASE_URI); @@ -353,7 +342,7 @@ public void getAndStoreCredentialsFromCode_nullUserId_throws() throws IOExceptio UserAuthorizer authorizer = UserAuthorizer.newBuilder() .setClientId(CLIENT_ID) .setScopes(SCOPES) - .setTokenStore(new TestTokenStore()) + .setTokenStore(new MemoryTokensStorage()) .build(); authorizer.getAndStoreCredentialsFromCode(null, CODE, BASE_URI); @@ -361,7 +350,7 @@ public void getAndStoreCredentialsFromCode_nullUserId_throws() throws IOExceptio @Test public void revokeAuthorization_revokesAndClears() throws IOException { - TestTokenStore tokenStore = new TestTokenStore(); + TokenStore tokenStore = new MemoryTokensStorage(); MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); transportFactory.transport.addClient(CLIENT_ID_VALUE, CLIENT_SECRET); transportFactory.transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN_VALUE); @@ -398,24 +387,4 @@ public void revokeAuthorization_revokesAndClears() throws IOException { UserCredentials credentials2 = authorizer.getCredentials(USER_ID); assertNull(credentials2); } - - private static class TestTokenStore implements TokenStore { - - private final Map map = new HashMap<>(); - - @Override - public String load(String id) throws IOException { - return map.get(id); - } - - @Override - public void store(String id, String tokens) throws IOException { - map.put(id, tokens); - } - - @Override - public void delete(String id) throws IOException { - map.remove(id); - } - } } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java index fd6e68cf3..6d2bbfc7f 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java @@ -51,9 +51,11 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.ByteArrayInputStream; import java.net.URI; import java.util.Collection; import java.util.Collections; @@ -504,6 +506,44 @@ public void fromStream_userNoRefreshToken_throws() throws IOException { testFromStreamException(userStream, "refresh_token"); } + @Test + public void saveUserCredentials_saved_throws() throws IOException { + UserCredentials userCredentials = UserCredentials.newBuilder() + .setClientId(CLIENT_ID) + .setClientSecret(CLIENT_SECRET) + .setRefreshToken(REFRESH_TOKEN) + .build(); + File file = File.createTempFile("GOOGLE_APPLICATION_CREDENTIALS", null, null); + file.deleteOnExit(); + + String filePath = file.getAbsolutePath(); + userCredentials.save(filePath); + } + + @Test + public void saveAndRestoreUserCredential_saveAndRestored_throws() throws IOException { + UserCredentials userCredentials = UserCredentials.newBuilder() + .setClientId(CLIENT_ID) + .setClientSecret(CLIENT_SECRET) + .setRefreshToken(REFRESH_TOKEN) + .build(); + + File file = File.createTempFile("GOOGLE_APPLICATION_CREDENTIALS", null, null); + file.deleteOnExit(); + + String filePath = file.getAbsolutePath(); + + userCredentials.save(filePath); + + FileInputStream inputStream = new FileInputStream(new File(filePath)); + + UserCredentials restoredCredentials = UserCredentials.fromStream(inputStream); + + assertEquals(userCredentials.getClientId(), restoredCredentials.getClientId()); + assertEquals(userCredentials.getClientSecret(), restoredCredentials.getClientSecret()); + assertEquals(userCredentials.getRefreshToken(), restoredCredentials.getRefreshToken()); + } + static GenericJson writeUserJson(String clientId, String clientSecret, String refreshToken) { GenericJson json = new GenericJson(); if (clientId != null) {