blob: acd3fcab5e52a11ae648631f9dd6d737d563ff02 [file] [log] [blame]
/*
* 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);
}
}
}