/* * Copyright 2019 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.android.apps.authenticator.util; import static android.os.Build.VERSION_CODES.KITKAT; import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SdkSuppress; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import java.io.File; import java.io.IOException; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** Unit tests for {@link FileUtilities}. */ @RunWith(AndroidJUnit4.class) @SmallTest public class FileUtilitiesTest { @Test public void testRestrictAccess_withActualDirectory() throws Exception { File dir = createTempDirInCacheDir(); try { String path = dir.getPath(); setFilePermissions(path, 0755); FileUtilities.restrictAccessToOwnerOnly(path); assertThat(getFilePermissions(path) & 0777).isEqualTo(0700); } finally { dir.delete(); } } @Test public void testRestrictAccess_withActualNonExistentDirectory() throws Exception { File dir = createTempDirInCacheDir(); assertThat(dir.delete()).isTrue(); try { FileUtilities.restrictAccessToOwnerOnly(dir.getPath()); Assert.fail(); } catch (IOException expected) {} } // TODO: Remove SDK suppression once getStat implementation no longer uses reflection // and deprecated APIs. @Test @SdkSuppress(maxSdkVersion = KITKAT) public void testGetStat_withActualDirectory() throws Exception { File dir = createTempDirInCacheDir(); try { String path = dir.getPath(); setFilePermissions(path, 0755); FileUtilities.StatStruct s1 = FileUtilities.getStat(path); assertThat(s1.mode & 0777).isEqualTo(0755); long ctime1 = s1.ctime; assertThat(s1.toString()).contains(Long.toString(ctime1)); setFilePermissions(path, 0700); FileUtilities.StatStruct s2 = FileUtilities.getStat(path); assertThat(s2.mode & 0777).isEqualTo(0700); long ctime2 = s2.ctime; assertThat(ctime2 >= ctime1).isTrue(); } finally { dir.delete(); } } @Test public void testGetStat_withActualNonExistentDirectory() throws Exception { File dir = createTempDirInCacheDir(); assertThat(dir.delete()).isTrue(); try { FileUtilities.getStat(dir.getPath()); Assert.fail(); } catch (IOException expected) {} } private static void setFilePermissions(String path, int mode) throws Exception { // IMPLEMENTATION NOTE: The code below simply invokes // android.os.FileUtils.setPermissions(path, mode, -1, -1) via Reflection. int errorCode = (Integer) Class.forName("android.os.FileUtils") .getMethod("setPermissions", String.class, int.class, int.class, int.class) .invoke(null, path, mode, -1, -1); assertThat(errorCode).isEqualTo(0); assertThat(getFilePermissions(path) & 0777).isEqualTo(mode); } private static int getFilePermissions(String path) throws Exception { // IMPLEMENTATION NOTE: The code below simply invokes // android.os.FileUtils.getPermissions(path, int[]) via Reflection. // However, getPermissions has been removed in JB MR1. As a result, we fall back to // libcore.io.StructStat libcore.io.Libcore.os.stat(path), from which we return st_mode. // Since Libcore is not available until ICS, we have to keep using FileUtils and falling back // to Libcore. try { int[] modeUidAndGid = new int[3]; int errorCode = (Integer) Class.forName("android.os.FileUtils") .getMethod("getPermissions", String.class, int[].class) .invoke(null, path, modeUidAndGid); assertThat(errorCode).isEqualTo(0); return modeUidAndGid[0]; } catch (NoSuchMethodException ignored) { // Fall back to Libcore.os.stat(path).st_mode } // Get the Libcore.os static field Object os = Class.forName("libcore.io.Libcore").getField("os").get(null); // Invoke Libcore.os.stat(String) Object structStat = os.getClass().getMethod("stat", String.class).invoke(os, path); // Get the value of st_mode field return structStat.getClass().getField("st_mode").getInt(structStat); } private File createTempDirInCacheDir() throws IOException { // IMPLEMENTATION NOTE: There's no API to create temp dir on one go. Thus, we create // a temp file, delete it, and recreate it as a directory. File file = File.createTempFile( getClass().getSimpleName(), "", InstrumentationRegistry.getInstrumentation().getContext().getCacheDir()); assertThat(file.delete()).isTrue(); assertThat(file.mkdir()).isTrue(); return file; } }