| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * 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 |
| * |
| * http://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.android.server.pm.permission; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.mockito.Matchers.any; |
| import static org.mockito.Matchers.anyInt; |
| import static org.mockito.Matchers.anyString; |
| import static org.mockito.Matchers.eq; |
| import static org.mockito.Mockito.when; |
| import static org.testng.Assert.assertThrows; |
| |
| import android.app.AppOpsManager; |
| import android.app.admin.DevicePolicyManager; |
| import android.content.Context; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.os.Build; |
| import android.os.Process; |
| |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| |
| @RunWith(AndroidJUnit4.class) |
| public class LegacyPermissionManagerServiceTest { |
| private static final int SYSTEM_UID = 1000; |
| private static final int SYSTEM_PID = 1234; |
| private static final int APP_UID = Process.FIRST_APPLICATION_UID; |
| private static final int APP_PID = 5678; |
| |
| private static final String CHECK_DEVICE_IDENTIFIER_MESSAGE = "testCheckDeviceIdentifierAccess"; |
| private static final String CHECK_PHONE_NUMBER_MESSAGE = "testCheckPhoneNumberAccess"; |
| |
| private LegacyPermissionManagerService mLegacyPermissionManagerService; |
| private Context mContext; |
| private String mPackageName; |
| |
| @Mock |
| private LegacyPermissionManagerService.Injector mInjector; |
| |
| @Mock |
| private AppOpsManager mAppOpsManager; |
| |
| @Mock |
| private DevicePolicyManager mDevicePolicyManager; |
| |
| @Before |
| public void setUp() { |
| MockitoAnnotations.initMocks(this); |
| |
| mContext = InstrumentationRegistry.getContext(); |
| mPackageName = mContext.getPackageName(); |
| mLegacyPermissionManagerService = new LegacyPermissionManagerService(mContext, mInjector); |
| } |
| |
| @Test |
| public void checkDeviceIdentifierAccess_callingAppUidMismatch_throwsException() { |
| // An application should only be able to query its own device identifier access, querying |
| // of any other UIDs should result in a SecurityException. |
| setupCheckDeviceIdentifierAccessTest(APP_PID, APP_UID); |
| |
| assertThrows(SecurityException.class, |
| () -> mLegacyPermissionManagerService.checkDeviceIdentifierAccess( |
| mPackageName, CHECK_DEVICE_IDENTIFIER_MESSAGE, null, |
| APP_PID, SYSTEM_UID)); |
| } |
| |
| @Test |
| public void checkDeviceIdentifierAccess_callingAppPidMismatch_throwsException() { |
| // Similar to above an app can only specify its own pid, a mismatch should result in a |
| // SecurityException. |
| setupCheckDeviceIdentifierAccessTest(APP_PID, APP_UID); |
| |
| assertThrows(SecurityException.class, |
| () -> mLegacyPermissionManagerService.checkDeviceIdentifierAccess( |
| mPackageName, CHECK_DEVICE_IDENTIFIER_MESSAGE, null, |
| SYSTEM_PID, APP_UID)); |
| } |
| |
| @Test |
| public void checkDeviceIdentifierAccess_callingAppIdWithoutAccess_returnsDenied() { |
| // An application can query its own device identifier access; this test verifies that all |
| // checks can run through completion and return denied. |
| setupCheckDeviceIdentifierAccessTest(APP_PID, APP_UID); |
| |
| int result = mLegacyPermissionManagerService.checkDeviceIdentifierAccess( |
| mPackageName, CHECK_DEVICE_IDENTIFIER_MESSAGE, null, APP_PID, |
| APP_UID); |
| |
| assertEquals(PackageManager.PERMISSION_DENIED, result); |
| } |
| |
| @Test |
| public void checkDeviceIdentifierAccess_systemUid_returnsGranted() { |
| // The system UID should always have access to device identifiers. |
| setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID); |
| int result = mLegacyPermissionManagerService.checkDeviceIdentifierAccess( |
| mPackageName, CHECK_DEVICE_IDENTIFIER_MESSAGE, null, SYSTEM_PID, |
| SYSTEM_UID); |
| |
| assertEquals(PackageManager.PERMISSION_GRANTED, result); |
| } |
| |
| @Test |
| public void checkDeviceIdentifierAccess_hasPrivilegedPermission_returnsGranted() { |
| // Apps with the READ_PRIVILEGED_PHONE_STATE permission should have access to device |
| // identifiers. |
| setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID); |
| when(mInjector.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, |
| APP_PID, APP_UID)).thenReturn(PackageManager.PERMISSION_GRANTED); |
| |
| int result = mLegacyPermissionManagerService.checkDeviceIdentifierAccess( |
| mPackageName, CHECK_DEVICE_IDENTIFIER_MESSAGE, null, APP_PID, |
| APP_UID); |
| |
| assertEquals(PackageManager.PERMISSION_GRANTED, result); |
| } |
| |
| @Test |
| public void checkDeviceIdentifierAccess_hasAppOp_returnsGranted() { |
| // Apps that have been granted the READ_DEVICE_IDENTIFIERS appop should have access to |
| // device identifiers. |
| setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID); |
| when(mAppOpsManager.noteOpNoThrow(eq(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS), |
| eq(APP_UID), eq(mPackageName), any(), any())).thenReturn( |
| AppOpsManager.MODE_ALLOWED); |
| |
| int result = mLegacyPermissionManagerService.checkDeviceIdentifierAccess( |
| mPackageName, CHECK_DEVICE_IDENTIFIER_MESSAGE, null, APP_PID, |
| APP_UID); |
| |
| assertEquals(PackageManager.PERMISSION_GRANTED, result); |
| } |
| |
| @Test |
| public void checkDeviceIdentifierAccess_hasDpmAccess_returnsGranted() { |
| // Apps that pass a DevicePolicyManager device / profile owner check should have access to |
| // device identifiers. |
| setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID); |
| when(mDevicePolicyManager.hasDeviceIdentifierAccess(mPackageName, APP_PID, |
| APP_UID)).thenReturn(true); |
| |
| int result = mLegacyPermissionManagerService.checkDeviceIdentifierAccess( |
| mPackageName, CHECK_DEVICE_IDENTIFIER_MESSAGE, null, APP_PID, |
| APP_UID); |
| |
| assertEquals(PackageManager.PERMISSION_GRANTED, result); |
| } |
| |
| @Test |
| public void checkPhoneNumberAccess_callingAppUidMismatch_throwsException() throws Exception { |
| // An app can check its own phone number access, but an exception should be |
| // thrown if an app attempts to check the phone number access of another app's UID. |
| setupCheckPhoneNumberAccessTest(APP_PID, APP_UID); |
| |
| assertThrows(SecurityException.class, |
| () -> mLegacyPermissionManagerService.checkPhoneNumberAccess( |
| mPackageName, CHECK_PHONE_NUMBER_MESSAGE, null, APP_PID, |
| SYSTEM_UID)); |
| } |
| |
| @Test |
| public void checkPhoneNumberAccess_callingAppPidMismatch_throwsException() throws Exception { |
| // An app can check its own phone number access, but an exception should be |
| // thrown if an app attempts to check the phone number access of another app's PID. |
| setupCheckPhoneNumberAccessTest(APP_PID, APP_UID); |
| |
| assertThrows(SecurityException.class, |
| () -> mLegacyPermissionManagerService.checkPhoneNumberAccess( |
| mPackageName, CHECK_PHONE_NUMBER_MESSAGE, null, SYSTEM_PID, |
| APP_UID)); |
| } |
| |
| @Test |
| public void checkPhoneNumberAccess_callingAppIdWithoutAccess_returnsDenied() throws Exception { |
| // Since an app can check its own phone number access, this test verifies all checks |
| // are performed and the expected result is returned when an app does not have access. |
| setupCheckPhoneNumberAccessTest(APP_PID, APP_UID); |
| |
| int result = mLegacyPermissionManagerService.checkPhoneNumberAccess( |
| mPackageName, CHECK_PHONE_NUMBER_MESSAGE, null, APP_PID, APP_UID); |
| |
| assertEquals(PackageManager.PERMISSION_DENIED, result); |
| } |
| |
| @Test |
| public void checkPhoneNumberAccess_nullPackageName_returnsDenied() throws Exception { |
| // While a null value can be provided for the package name only the privileged |
| // permission test would be able to proceed. Verify that a null value results in a |
| // denied response instead of a NullPointerException. |
| setupCheckPhoneNumberAccessTest(APP_PID, APP_UID); |
| |
| int result = mLegacyPermissionManagerService.checkPhoneNumberAccess(mPackageName, |
| CHECK_PHONE_NUMBER_MESSAGE, null, APP_PID, APP_UID); |
| |
| assertEquals(PackageManager.PERMISSION_DENIED, result); |
| } |
| |
| @Test |
| public void checkPhoneNumberAccess_hasPrivilegedPermission_returnsGranted() throws Exception { |
| // An app with the READ_PRIVILEGED_PHONE_STATE should have access to the phone number. |
| setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID); |
| when(mInjector.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, |
| APP_PID, APP_UID)).thenReturn(PackageManager.PERMISSION_GRANTED); |
| |
| int result = mLegacyPermissionManagerService.checkPhoneNumberAccess( |
| null, CHECK_PHONE_NUMBER_MESSAGE, null, APP_PID, APP_UID); |
| |
| assertEquals(PackageManager.PERMISSION_GRANTED, result); |
| } |
| |
| @Test |
| public void checkPhoneNumberAccess_targetPreRWithReadPhoneState_returnsGranted() |
| throws Exception { |
| // Prior to R an app could access the phone number with the READ_PHONE_STATE permission, but |
| // both the permission and the appop must be granted. If the permission is granted but the |
| // appop is not then AppOpsManager#MODE_IGNORED should be returned to indicate that this |
| // should be a silent failure. |
| setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID); |
| setPackageTargetSdk(Build.VERSION_CODES.Q); |
| |
| grantPermissionAndAppop(android.Manifest.permission.READ_PHONE_STATE, null); |
| int resultWithoutAppop = mLegacyPermissionManagerService.checkPhoneNumberAccess( |
| mPackageName, CHECK_PHONE_NUMBER_MESSAGE, null, APP_PID, APP_UID); |
| grantPermissionAndAppop(android.Manifest.permission.READ_PHONE_STATE, |
| AppOpsManager.OPSTR_READ_PHONE_STATE); |
| int resultWithAppop = mLegacyPermissionManagerService.checkPhoneNumberAccess(mPackageName, |
| CHECK_PHONE_NUMBER_MESSAGE, null, APP_PID, APP_UID); |
| |
| assertEquals(AppOpsManager.MODE_IGNORED, resultWithoutAppop); |
| assertEquals(PackageManager.PERMISSION_GRANTED, resultWithAppop); |
| } |
| |
| @Test |
| public void checkPhoneNumberAccess_targetRWithReadPhoneState_returnsDenied() throws Exception { |
| // Apps targeting R+ with just the READ_PHONE_STATE permission granted should not have |
| // access to the phone number; PERMISSION_DENIED should be returned both with and without |
| // the appop granted since this check should be skipped for target SDK R+. |
| setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID); |
| |
| grantPermissionAndAppop(android.Manifest.permission.READ_PHONE_STATE, null); |
| int resultWithoutAppop = mLegacyPermissionManagerService.checkPhoneNumberAccess( |
| mPackageName, CHECK_PHONE_NUMBER_MESSAGE, null, APP_PID, APP_UID); |
| grantPermissionAndAppop(android.Manifest.permission.READ_PHONE_STATE, |
| AppOpsManager.OPSTR_READ_PHONE_STATE); |
| int resultWithAppop = mLegacyPermissionManagerService.checkPhoneNumberAccess(mPackageName, |
| CHECK_PHONE_NUMBER_MESSAGE, null, APP_PID, APP_UID); |
| |
| assertEquals(PackageManager.PERMISSION_DENIED, resultWithoutAppop); |
| assertEquals(PackageManager.PERMISSION_DENIED, resultWithAppop); |
| } |
| |
| @Test |
| public void checkPhoneNumberAccess_smsHandler_returnsGranted() throws Exception { |
| // The default SMS handler should have the WRITE_SMS appop granted and have access to the |
| // phone number. |
| setupCheckPhoneNumberAccessTest(APP_PID, APP_UID); |
| grantPermissionAndAppop(null, AppOpsManager.OPSTR_WRITE_SMS); |
| |
| int result = mLegacyPermissionManagerService.checkPhoneNumberAccess(mPackageName, |
| CHECK_PHONE_NUMBER_MESSAGE, null, APP_PID, APP_UID); |
| |
| assertEquals(PackageManager.PERMISSION_GRANTED, result); |
| } |
| |
| @Test |
| public void checkPhoneNumberAccess_readPhoneNumbersGranted_returnsGranted() |
| throws Exception { |
| // Access to the phone number should be granted to an app with the READ_PHONE_NUMBERS |
| // permission and appop set. |
| setupCheckPhoneNumberAccessTest(APP_PID, APP_UID); |
| |
| grantPermissionAndAppop(android.Manifest.permission.READ_PHONE_NUMBERS, null); |
| int resultWithoutAppop = mLegacyPermissionManagerService.checkPhoneNumberAccess( |
| mPackageName, CHECK_PHONE_NUMBER_MESSAGE, null, APP_PID, APP_UID); |
| grantPermissionAndAppop(android.Manifest.permission.READ_PHONE_NUMBERS, |
| AppOpsManager.OPSTR_READ_PHONE_NUMBERS); |
| int resultWithAppop = mLegacyPermissionManagerService.checkPhoneNumberAccess(mPackageName, |
| CHECK_PHONE_NUMBER_MESSAGE, null, APP_PID, APP_UID); |
| |
| assertEquals(PackageManager.PERMISSION_DENIED, resultWithoutAppop); |
| assertEquals(PackageManager.PERMISSION_GRANTED, resultWithAppop); |
| } |
| |
| @Test |
| public void checkPhoneNumberAccess_readSmsGranted_returnsGranted() throws Exception { |
| // Access to the phone number should be granted to an app with the READ_SMS permission and |
| // appop set. |
| setupCheckPhoneNumberAccessTest(APP_PID, APP_UID); |
| |
| grantPermissionAndAppop(android.Manifest.permission.READ_SMS, null); |
| int resultWithoutAppop = mLegacyPermissionManagerService.checkPhoneNumberAccess( |
| mPackageName, CHECK_PHONE_NUMBER_MESSAGE, null, APP_PID, APP_UID); |
| grantPermissionAndAppop(android.Manifest.permission.READ_SMS, AppOpsManager.OPSTR_READ_SMS); |
| int resultWithAppop = mLegacyPermissionManagerService.checkPhoneNumberAccess(mPackageName, |
| CHECK_PHONE_NUMBER_MESSAGE, null, APP_PID, APP_UID); |
| |
| assertEquals(PackageManager.PERMISSION_DENIED, resultWithoutAppop); |
| assertEquals(PackageManager.PERMISSION_GRANTED, resultWithAppop); |
| } |
| |
| /** |
| * Configures device identifier access tests to fail; tests verifying access should individually |
| * set an access check to succeed to verify access when that condition is met. |
| */ |
| private void setupCheckDeviceIdentifierAccessTest(int callingPid, int callingUid) { |
| setupAccessTest(callingPid, callingUid); |
| |
| when(mDevicePolicyManager.hasDeviceIdentifierAccess(anyString(), anyInt(), |
| anyInt())).thenReturn(false); |
| when(mInjector.getSystemService(eq(Context.DEVICE_POLICY_SERVICE))).thenReturn( |
| mDevicePolicyManager); |
| } |
| |
| /** |
| * Configures phone number access tests to fail; tests verifying access should individually set |
| * an access check to succeed to verify access when that condition is met. |
| */ |
| private void setupCheckPhoneNumberAccessTest(int callingPid, int callingUid) throws Exception { |
| setupAccessTest(callingPid, callingUid); |
| setPackageTargetSdk(Build.VERSION_CODES.R); |
| } |
| |
| /** |
| * Configures the common mocks for any access tests using the provided {@code callingPid} |
| * and {@code callingUid}. |
| */ |
| private void setupAccessTest(int callingPid, int callingUid) { |
| when(mInjector.getCallingPid()).thenReturn(callingPid); |
| when(mInjector.getCallingUid()).thenReturn(callingUid); |
| |
| when(mInjector.checkPermission(anyString(), anyInt(), anyInt())).thenReturn( |
| PackageManager.PERMISSION_DENIED); |
| |
| when(mAppOpsManager.noteOpNoThrow(anyString(), anyInt(), anyString(), any(), |
| any())).thenReturn(AppOpsManager.MODE_DEFAULT); |
| when(mInjector.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOpsManager); |
| } |
| |
| /** |
| * Sets the returned {@code targetSdkVersion} for the package under test. |
| */ |
| private void setPackageTargetSdk(int targetSdkVersion) throws Exception { |
| ApplicationInfo appInfo = new ApplicationInfo(); |
| appInfo.targetSdkVersion = targetSdkVersion; |
| when(mInjector.getApplicationInfo(eq(mPackageName), eq(APP_UID))).thenReturn(appInfo); |
| } |
| |
| /** |
| * Configures the mocks to return {@code PackageManager.PERMISSION_GRANTED} for the specified |
| * {@code permission} and {@code AppOpsManager.MODE_ALLOWED} for the provided {@code appop} |
| * when queried for the package under test. |
| */ |
| private void grantPermissionAndAppop(String permission, String appop) { |
| if (permission != null) { |
| when(mInjector.checkPermission(permission, APP_PID, APP_UID)).thenReturn( |
| PackageManager.PERMISSION_GRANTED); |
| } |
| if (appop != null) { |
| when(mAppOpsManager.noteOpNoThrow(eq(appop), eq(APP_UID), eq(mPackageName), any(), |
| any())).thenReturn(AppOpsManager.MODE_ALLOWED); |
| } |
| } |
| } |