From 0d4bc5c72c41f706c91620dbe2e7a3148434df7b Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Fri, 26 Jun 2020 14:40:20 -0700 Subject: [PATCH 01/10] ci: add a animal sniffer check configuration for Android API level 19 --- pom.xml | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 3d6505d19..4cff5a308 100644 --- a/pom.xml +++ b/pom.xml @@ -499,18 +499,32 @@ org.codehaus.mojo animal-sniffer-maven-plugin - - - org.codehaus.mojo.signature - java17 - 1.0 - - + java7 check + + + org.codehaus.mojo.signature + java17 + 1.0 + + + + + android + + check + + + + net.sf.androidscents.signature + android-api-level-19 + 4.4.2_r4 + + From a8537c9a42909275a71777467054ecb58fb3fd52 Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Fri, 26 Jun 2020 14:46:17 -0700 Subject: [PATCH 02/10] build(deps): update animal-sniffer-maven-plugin version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4cff5a308..aa4b3fced 100644 --- a/pom.xml +++ b/pom.xml @@ -354,7 +354,7 @@ org.codehaus.mojo animal-sniffer-maven-plugin - 1.17 + 1.18 org.apache.maven.plugins From cdaf1eabcefc7232c82d492acc5b56e313f9a85f Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Fri, 26 Jun 2020 15:04:48 -0700 Subject: [PATCH 03/10] feat: add Android comptible FileDataStoreFactory implementation This is branched from the original implementation prior to switching to NIO for Windows compatibility. --- .../util/store/FileDataStoreFactory.java | 161 ++++++++++++++++++ .../util/store/FileDataStoreFactory.java | 4 + 2 files changed, 165 insertions(+) create mode 100644 google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java diff --git a/google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java b/google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java new file mode 100644 index 000000000..86eebf9bc --- /dev/null +++ b/google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java @@ -0,0 +1,161 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.api.client.extensions.android.util.store; + +import com.google.api.client.util.IOUtils; +import com.google.api.client.util.Maps; +import com.google.api.client.util.Throwables; +import com.google.api.client.util.store.AbstractDataStoreFactory; +import com.google.api.client.util.store.AbstractMemoryDataStore; +import com.google.api.client.util.store.DataStore; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.logging.Logger; + +/** + * Thread-safe file implementation of a credential store. + * + *

For security purposes, the file's permissions are set to be accessible only by the file's + * owner. Note that Java 1.5 does not support manipulating file permissions, and must be done + * manually or using the JNI. + * + *

Note: This class was branched from the primary implementation in google-http-client to allow + * the mainline implementation to support Windows file permissions. + * + * @since 1.36 + * @author Yaniv Inbar + */ +public class FileDataStoreFactory extends AbstractDataStoreFactory { + + private static final Logger LOGGER = Logger.getLogger(FileDataStoreFactory.class.getName()); + + /** Directory to store data. */ + private final File dataDirectory; + + /** @param dataDirectory data directory */ + public FileDataStoreFactory(File dataDirectory) throws IOException { + dataDirectory = dataDirectory.getCanonicalFile(); + this.dataDirectory = dataDirectory; + // error if it is a symbolic link + if (IOUtils.isSymbolicLink(dataDirectory)) { + throw new IOException("unable to use a symbolic link: " + dataDirectory); + } + // create parent directory (if necessary) + if (!dataDirectory.exists() && !dataDirectory.mkdirs()) { + throw new IOException("unable to create directory: " + dataDirectory); + } + setPermissionsToOwnerOnly(dataDirectory); + } + + /** Returns the data directory. */ + public final File getDataDirectory() { + return dataDirectory; + } + + @Override + protected DataStore createDataStore(String id) throws IOException { + return new FileDataStore(this, dataDirectory, id); + } + + /** + * File data store that inherits from the abstract memory data store because the key-value pairs + * are stored in a memory cache, and saved in the file (see {@link #save()} when changing values. + * + * @param serializable type of the mapped value + */ + static class FileDataStore extends AbstractMemoryDataStore { + + /** File to store data. */ + private final File dataFile; + + FileDataStore(FileDataStoreFactory dataStore, File dataDirectory, String id) + throws IOException { + super(dataStore, id); + this.dataFile = new File(dataDirectory, id); + // error if it is a symbolic link + if (IOUtils.isSymbolicLink(dataFile)) { + throw new IOException("unable to use a symbolic link: " + dataFile); + } + // create new file (if necessary) + if (dataFile.createNewFile()) { + keyValueMap = Maps.newHashMap(); + // save the credentials to create a new file + save(); + } else { + // load credentials from existing file + keyValueMap = IOUtils.deserialize(new FileInputStream(dataFile)); + } + } + + @Override + public void save() throws IOException { + IOUtils.serialize(keyValueMap, new FileOutputStream(dataFile)); + } + + @Override + public FileDataStoreFactory getDataStoreFactory() { + return (FileDataStoreFactory) super.getDataStoreFactory(); + } + } + + /** + * Attempts to set the given file's permissions such that it can only be read, written, and + * executed by the file's owner. + * + * @param file the file's permissions to modify + * @throws IOException + */ + static void setPermissionsToOwnerOnly(File file) throws IOException { + // Disable access by other users if O/S allows it and set file permissions to readable and + // writable by user. Use reflection since JDK 1.5 will not have these methods + try { + Method setReadable = File.class.getMethod("setReadable", boolean.class, boolean.class); + Method setWritable = File.class.getMethod("setWritable", boolean.class, boolean.class); + Method setExecutable = File.class.getMethod("setExecutable", boolean.class, boolean.class); + if (!(Boolean) setReadable.invoke(file, false, false) + || !(Boolean) setWritable.invoke(file, false, false) + || !(Boolean) setExecutable.invoke(file, false, false)) { + LOGGER.warning("unable to change permissions for everybody: " + file); + } + if (!(Boolean) setReadable.invoke(file, true, true) + || !(Boolean) setWritable.invoke(file, true, true) + || !(Boolean) setExecutable.invoke(file, true, true)) { + LOGGER.warning("unable to change permissions for owner: " + file); + } + } catch (InvocationTargetException exception) { + Throwable cause = exception.getCause(); + Throwables.propagateIfPossible(cause, IOException.class); + // shouldn't reach this point, but just in case... + throw new RuntimeException(cause); + } catch (NoSuchMethodException exception) { + LOGGER.warning( + "Unable to set permissions for " + + file + + ", likely because you are running a version of Java prior to 1.6"); + } catch (SecurityException exception) { + // ignored + } catch (IllegalAccessException exception) { + // ignored + } catch (IllegalArgumentException exception) { + // ignored + } + } +} diff --git a/google-http-client/src/main/java/com/google/api/client/util/store/FileDataStoreFactory.java b/google-http-client/src/main/java/com/google/api/client/util/store/FileDataStoreFactory.java index a39356ff5..ed63486d5 100644 --- a/google-http-client/src/main/java/com/google/api/client/util/store/FileDataStoreFactory.java +++ b/google-http-client/src/main/java/com/google/api/client/util/store/FileDataStoreFactory.java @@ -45,6 +45,10 @@ *

For security purposes, the file's permissions are set such that the file is only accessible by * the file's owner. * + *

Note: this class is not compatible with Android lower than API level 26 (Oreo). For an implementation + * compatible with Android < 26, please use com.google.api.client.extensions.android.util.store.FileDataStoreFactory + * which is provided by com.google.http-client:google-http-client-android. + * * @since 1.16 * @author Yaniv Inbar */ From e90753acd7a499e4d1d55ee6e6e8eb1fcd08514a Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Fri, 26 Jun 2020 15:47:43 -0700 Subject: [PATCH 04/10] ci: allow java.nio.file in google-http-client --- google-http-client/pom.xml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/google-http-client/pom.xml b/google-http-client/pom.xml index eee0716d9..659c51563 100644 --- a/google-http-client/pom.xml +++ b/google-http-client/pom.xml @@ -26,6 +26,23 @@ io.opencensus:opencensus-impl + + org.codehaus.mojo + animal-sniffer-maven-plugin + + + android + + check + + + + java.nio.file.* + + + + + From 9e52cce3022e136b9a488afe266e9520ed972f88 Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Fri, 26 Jun 2020 15:58:16 -0700 Subject: [PATCH 05/10] chore: fix lint --- .../google/api/client/util/store/FileDataStoreFactory.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/google-http-client/src/main/java/com/google/api/client/util/store/FileDataStoreFactory.java b/google-http-client/src/main/java/com/google/api/client/util/store/FileDataStoreFactory.java index ed63486d5..a88de49aa 100644 --- a/google-http-client/src/main/java/com/google/api/client/util/store/FileDataStoreFactory.java +++ b/google-http-client/src/main/java/com/google/api/client/util/store/FileDataStoreFactory.java @@ -45,9 +45,10 @@ *

For security purposes, the file's permissions are set such that the file is only accessible by * the file's owner. * - *

Note: this class is not compatible with Android lower than API level 26 (Oreo). For an implementation - * compatible with Android < 26, please use com.google.api.client.extensions.android.util.store.FileDataStoreFactory - * which is provided by com.google.http-client:google-http-client-android. + *

Note: this class is not compatible with Android lower than API level 26 (Oreo). For an + * implementation compatible with Android < 26, please use + * com.google.api.client.extensions.android.util.store.FileDataStoreFactory which is provided by + * com.google.http-client:google-http-client-android. * * @since 1.16 * @author Yaniv Inbar From ef67a51fa3227619d35667e339eff97328b63411 Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Fri, 26 Jun 2020 15:58:53 -0700 Subject: [PATCH 06/10] build: put animal-sniffer-maven-plugin back to 1.17 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index aa4b3fced..4cff5a308 100644 --- a/pom.xml +++ b/pom.xml @@ -354,7 +354,7 @@ org.codehaus.mojo animal-sniffer-maven-plugin - 1.18 + 1.17 org.apache.maven.plugins From ac2e9d22ff3eed15e05fa1d154542a39fadc3b23 Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Mon, 29 Jun 2020 14:31:21 -0700 Subject: [PATCH 07/10] fix: remove reflection from file permission settings File setReadable(), setWritable(), setExecutable() was added in API level 9, so we should not need to protect android users from missing methods as we support Android API 19+. --- .../util/store/FileDataStoreFactory.java | 32 +++++-------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java b/google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java index 86eebf9bc..ad948c7fd 100644 --- a/google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java +++ b/google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java @@ -121,39 +121,23 @@ public FileDataStoreFactory getDataStoreFactory() { * executed by the file's owner. * * @param file the file's permissions to modify - * @throws IOException */ - static void setPermissionsToOwnerOnly(File file) throws IOException { + static void setPermissionsToOwnerOnly(File file) { // Disable access by other users if O/S allows it and set file permissions to readable and - // writable by user. Use reflection since JDK 1.5 will not have these methods + // writable by user. try { - Method setReadable = File.class.getMethod("setReadable", boolean.class, boolean.class); - Method setWritable = File.class.getMethod("setWritable", boolean.class, boolean.class); - Method setExecutable = File.class.getMethod("setExecutable", boolean.class, boolean.class); - if (!(Boolean) setReadable.invoke(file, false, false) - || !(Boolean) setWritable.invoke(file, false, false) - || !(Boolean) setExecutable.invoke(file, false, false)) { + if (!file.setReadable(false, false) + || !file.setWritable(false, false) + || !file.setExecutable(false, false)) { LOGGER.warning("unable to change permissions for everybody: " + file); } - if (!(Boolean) setReadable.invoke(file, true, true) - || !(Boolean) setWritable.invoke(file, true, true) - || !(Boolean) setExecutable.invoke(file, true, true)) { + if (!file.setReadable(true, true) + || !file.setWritable(true, true) + || !file.setExecutable(true, true)) { LOGGER.warning("unable to change permissions for owner: " + file); } - } catch (InvocationTargetException exception) { - Throwable cause = exception.getCause(); - Throwables.propagateIfPossible(cause, IOException.class); - // shouldn't reach this point, but just in case... - throw new RuntimeException(cause); - } catch (NoSuchMethodException exception) { - LOGGER.warning( - "Unable to set permissions for " - + file - + ", likely because you are running a version of Java prior to 1.6"); } catch (SecurityException exception) { // ignored - } catch (IllegalAccessException exception) { - // ignored } catch (IllegalArgumentException exception) { // ignored } From 932e50ac4353e11b5fb962b01895f494279a2dd1 Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Mon, 29 Jun 2020 14:39:33 -0700 Subject: [PATCH 08/10] chore: run formatter --- .../extensions/android/util/store/FileDataStoreFactory.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java b/google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java index ad948c7fd..11ddd1ded 100644 --- a/google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java +++ b/google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java @@ -17,7 +17,6 @@ import com.google.api.client.util.IOUtils; import com.google.api.client.util.Maps; -import com.google.api.client.util.Throwables; import com.google.api.client.util.store.AbstractDataStoreFactory; import com.google.api.client.util.store.AbstractMemoryDataStore; import com.google.api.client.util.store.DataStore; @@ -26,8 +25,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.Serializable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.logging.Logger; /** From da1390680f35e2e072e74d6c3205dc036c162d1c Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Mon, 29 Jun 2020 15:54:03 -0700 Subject: [PATCH 09/10] chore: remove docs mentioning Java 1.5 --- .../extensions/android/util/store/FileDataStoreFactory.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java b/google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java index 11ddd1ded..f07f8a1dd 100644 --- a/google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java +++ b/google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java @@ -31,8 +31,7 @@ * Thread-safe file implementation of a credential store. * *

For security purposes, the file's permissions are set to be accessible only by the file's - * owner. Note that Java 1.5 does not support manipulating file permissions, and must be done - * manually or using the JNI. + * owner. * *

Note: This class was branched from the primary implementation in google-http-client to allow * the mainline implementation to support Windows file permissions. From a3868b06001ec1219b8b551870e0b1cb11e9849e Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Mon, 29 Jun 2020 15:55:04 -0700 Subject: [PATCH 10/10] fix: move member assignment after input validation --- .../extensions/android/util/store/FileDataStoreFactory.java | 2 +- .../com/google/api/client/util/store/FileDataStoreFactory.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java b/google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java index f07f8a1dd..b9c808b5a 100644 --- a/google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java +++ b/google-http-client-android/src/main/java/com/google/api/client/extensions/android/util/store/FileDataStoreFactory.java @@ -49,7 +49,6 @@ public class FileDataStoreFactory extends AbstractDataStoreFactory { /** @param dataDirectory data directory */ public FileDataStoreFactory(File dataDirectory) throws IOException { dataDirectory = dataDirectory.getCanonicalFile(); - this.dataDirectory = dataDirectory; // error if it is a symbolic link if (IOUtils.isSymbolicLink(dataDirectory)) { throw new IOException("unable to use a symbolic link: " + dataDirectory); @@ -58,6 +57,7 @@ public FileDataStoreFactory(File dataDirectory) throws IOException { if (!dataDirectory.exists() && !dataDirectory.mkdirs()) { throw new IOException("unable to create directory: " + dataDirectory); } + this.dataDirectory = dataDirectory; setPermissionsToOwnerOnly(dataDirectory); } diff --git a/google-http-client/src/main/java/com/google/api/client/util/store/FileDataStoreFactory.java b/google-http-client/src/main/java/com/google/api/client/util/store/FileDataStoreFactory.java index a88de49aa..5bcab8679 100644 --- a/google-http-client/src/main/java/com/google/api/client/util/store/FileDataStoreFactory.java +++ b/google-http-client/src/main/java/com/google/api/client/util/store/FileDataStoreFactory.java @@ -66,7 +66,6 @@ public class FileDataStoreFactory extends AbstractDataStoreFactory { /** @param dataDirectory data directory */ public FileDataStoreFactory(File dataDirectory) throws IOException { dataDirectory = dataDirectory.getCanonicalFile(); - this.dataDirectory = dataDirectory; // error if it is a symbolic link if (IOUtils.isSymbolicLink(dataDirectory)) { throw new IOException("unable to use a symbolic link: " + dataDirectory); @@ -75,6 +74,7 @@ public FileDataStoreFactory(File dataDirectory) throws IOException { if (!dataDirectory.exists() && !dataDirectory.mkdirs()) { throw new IOException("unable to create directory: " + dataDirectory); } + this.dataDirectory = dataDirectory; if (IS_WINDOWS) { setPermissionsToOwnerOnlyWindows(dataDirectory);