Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2019 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.permission2.cts;
     18 
     19 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
     20 import static android.app.AppOpsManager.MODE_ALLOWED;
     21 import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE;
     22 import static android.permission.cts.PermissionUtils.eventually;
     23 import static android.permission.cts.PermissionUtils.isGranted;
     24 import static android.permission2.cts.RestrictedStoragePermissionSharedUidTest.StorageState.DENIED;
     25 import static android.permission2.cts.RestrictedStoragePermissionSharedUidTest.StorageState.ISOLATED;
     26 import static android.permission2.cts.RestrictedStoragePermissionSharedUidTest.StorageState.NON_ISOLATED;
     27 
     28 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
     29 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
     30 
     31 import static com.google.common.truth.Truth.assertThat;
     32 
     33 import static java.lang.Integer.min;
     34 
     35 import android.app.AppOpsManager;
     36 import android.content.Context;
     37 import android.content.pm.PackageManager;
     38 import android.os.Build;
     39 import android.platform.test.annotations.AppModeFull;
     40 import android.util.Log;
     41 
     42 import androidx.annotation.NonNull;
     43 import androidx.test.platform.app.InstrumentationRegistry;
     44 
     45 import org.junit.After;
     46 import org.junit.Test;
     47 import org.junit.runner.RunWith;
     48 import org.junit.runners.Parameterized;
     49 import org.junit.runners.Parameterized.Parameter;
     50 import org.junit.runners.Parameterized.Parameters;
     51 
     52 import java.util.ArrayList;
     53 
     54 @AppModeFull(reason = "Instant apps cannot access other app's properties")
     55 @RunWith(Parameterized.class)
     56 public class RestrictedStoragePermissionSharedUidTest {
     57     private static final String LOG_TAG =
     58             RestrictedStoragePermissionSharedUidTest.class.getSimpleName();
     59 
     60     public enum StorageState {
     61         /** The app has non-isolated storage */
     62         NON_ISOLATED,
     63 
     64         /** The app has isolated storage */
     65         ISOLATED,
     66 
     67         /** The read-external-storage permission cannot be granted */
     68         DENIED
     69     }
     70 
     71     /**
     72      * An app that is tested
     73      */
     74     private static class TestApp {
     75         private static @NonNull Context sContext =
     76                 InstrumentationRegistry.getInstrumentation().getContext();
     77         private static @NonNull AppOpsManager sAppOpsManager =
     78                 sContext.getSystemService(AppOpsManager.class);
     79         private static @NonNull PackageManager sPackageManager = sContext.getPackageManager();
     80 
     81         private final String mApk;
     82         private final String mPkg;
     83 
     84         public final boolean isRestricted;
     85         public final boolean hasRequestedLegacyExternalStorage;
     86 
     87         TestApp(@NonNull String apk, @NonNull String pkg, boolean isRestricted,
     88                 @NonNull boolean hasRequestedLegacyExternalStorage) {
     89             mApk = apk;
     90             mPkg = pkg;
     91 
     92             this.isRestricted = isRestricted;
     93             this.hasRequestedLegacyExternalStorage = hasRequestedLegacyExternalStorage;
     94         }
     95 
     96         /**
     97          * Assert that the read-external-storage permission was granted or not granted.
     98          *
     99          * @param expectGranted {@code true} if the permission is expected to be granted
    100          */
    101         void assertStoragePermGranted(boolean expectGranted) {
    102             eventually(() -> assertThat(isGranted(mPkg, READ_EXTERNAL_STORAGE)).named(
    103                     this + " read storage granted").isEqualTo(expectGranted));
    104         }
    105 
    106         /**
    107          * Assert that the app has non-isolated storage
    108          *
    109          * @param expectGranted {@code true} if the app is expected to have non-isolated storage
    110          */
    111         void assertHasNotIsolatedStorage(boolean expectHasNotIsolatedStorage) {
    112             eventually(() -> runWithShellPermissionIdentity(() -> {
    113                 int uid = sContext.getPackageManager().getPackageUid(mPkg, 0);
    114                 if (expectHasNotIsolatedStorage) {
    115                     assertThat(sAppOpsManager.unsafeCheckOpRawNoThrow(OPSTR_LEGACY_STORAGE, uid,
    116                             mPkg)).named(this + " legacy storage mode").isEqualTo(MODE_ALLOWED);
    117                 } else {
    118                     assertThat(sAppOpsManager.unsafeCheckOpRawNoThrow(OPSTR_LEGACY_STORAGE, uid,
    119                             mPkg)).named(this + " legacy storage mode").isNotEqualTo(MODE_ALLOWED);
    120                 }
    121             }));
    122         }
    123 
    124         int getTargetSDK() throws Exception {
    125             return sPackageManager.getApplicationInfo(mPkg, 0).targetSdkVersion;
    126         }
    127 
    128         void install() {
    129             if (isRestricted) {
    130                 runShellCommand("pm install -g --restrict-permissions " + mApk);
    131             } else {
    132                 runShellCommand("pm install -g " + mApk);
    133             }
    134         }
    135 
    136         void uninstall() {
    137             runShellCommand("pm uninstall " + mPkg);
    138         }
    139 
    140         @Override
    141         public String toString() {
    142             return mPkg.substring(PKG_PREFIX.length());
    143         }
    144     }
    145 
    146     /**
    147      * Placeholder for "no app". The properties are chosen that when combined with another app, the
    148      * other app always decides the resulting property,
    149      */
    150     private static class NoApp extends TestApp {
    151         NoApp() {
    152             super("", PKG_PREFIX + "(none)", true, false);
    153         }
    154 
    155         void assertStoragePermGranted(boolean ignored) {
    156             // empty
    157         }
    158 
    159         void assertHasNotIsolatedStorage(boolean ignored) {
    160             // empty
    161         }
    162 
    163         @Override
    164         int getTargetSDK() {
    165             return 10000;
    166         }
    167 
    168         @Override
    169         public void install() {
    170             // empty
    171         }
    172 
    173         @Override
    174         public void uninstall() {
    175             // empty
    176         }
    177     }
    178 
    179     private static final String APK_PATH = "/data/local/tmp/cts/permissions2/";
    180     private static final String PKG_PREFIX = "android.permission2.cts.legacystoragewithshareduid.";
    181 
    182     private static final TestApp[] TEST_APPS = new TestApp[]{
    183             new TestApp(APK_PATH + "CtsLegacyStorageNotIsolatedWithSharedUid.apk",
    184                     PKG_PREFIX + "notisolated", false, true),
    185             new TestApp(APK_PATH + "CtsLegacyStorageIsolatedWithSharedUid.apk",
    186                     PKG_PREFIX + "isolated", false, false),
    187             new TestApp(APK_PATH + "CtsLegacyStorageRestrictedWithSharedUid.apk",
    188                     PKG_PREFIX + "restricted", true, false),
    189             new TestApp(APK_PATH + "CtsLegacyStorageRestrictedSdk28WithSharedUid.apk",
    190                     PKG_PREFIX + "restrictedsdk28", true, true),
    191             new NoApp()};
    192 
    193     /**
    194      * First app to be tested. This is the first in an entry created by {@link
    195      * #getTestAppCombinations}
    196      */
    197     @Parameter(0)
    198     public @NonNull TestApp app1;
    199 
    200     /**
    201      * Second app to be tested. This is the second in an entry created by {@link
    202      * #getTestAppCombinations}
    203      */
    204     @Parameter(1)
    205     public @NonNull TestApp app2;
    206 
    207     /**
    208      * Run this test for all combination of two tests-apps out of {@link #TEST_APPS}. This includes
    209      * the {@link NoApp}, i.e. we also test a single test-app by itself.
    210      *
    211      * @return All combinations of two test-apps
    212      */
    213     @Parameters(name = "{0} and {1}")
    214     public static Iterable<Object[]> getTestAppCombinations() {
    215         ArrayList<Object[]> parameters = new ArrayList<>();
    216 
    217         for (int firstApp = 0; firstApp < TEST_APPS.length; firstApp++) {
    218             for (int secondApp = firstApp + 1; secondApp < TEST_APPS.length; secondApp++) {
    219                 parameters.add(new Object[]{TEST_APPS[firstApp], TEST_APPS[secondApp]});
    220             }
    221         }
    222 
    223         return parameters;
    224     }
    225 
    226     @Test
    227     public void checkExceptedStorageStateForAppsSharingUid() throws Exception {
    228         app1.install();
    229         app2.install();
    230 
    231         int targetSDK = min(app1.getTargetSDK(), app2.getTargetSDK());
    232         boolean isRestricted = app1.isRestricted && app2.isRestricted;
    233         boolean hasRequestedLegacyExternalStorage =
    234                 app1.hasRequestedLegacyExternalStorage || app2.hasRequestedLegacyExternalStorage;
    235 
    236         StorageState expectedState;
    237         if (isRestricted) {
    238             if (targetSDK < Build.VERSION_CODES.Q) {
    239                 expectedState = DENIED;
    240             } else {
    241                 expectedState = ISOLATED;
    242             }
    243         } else if (hasRequestedLegacyExternalStorage) {
    244             expectedState = NON_ISOLATED;
    245         } else {
    246             expectedState = ISOLATED;
    247         }
    248 
    249         Log.i(LOG_TAG, "Expected state=" + expectedState);
    250 
    251         app1.assertStoragePermGranted(expectedState != DENIED);
    252         app2.assertStoragePermGranted(expectedState != DENIED);
    253 
    254         if (expectedState != DENIED) {
    255             app1.assertHasNotIsolatedStorage(expectedState == NON_ISOLATED);
    256             app2.assertHasNotIsolatedStorage(expectedState == NON_ISOLATED);
    257         }
    258     }
    259 
    260     @After
    261     public void uninstallAllTestPackages() {
    262         app1.uninstall();
    263         app2.uninstall();
    264     }
    265 }
    266