Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 64fee3eb authored by Qingxi Li's avatar Qingxi Li Committed by Android (Google) Code Review
Browse files

Merge "Update location permission check for ConnectivityUtil"

parents 78db9158 38c37b5c
Loading
Loading
Loading
Loading
+52 −44
Original line number Diff line number Diff line
@@ -33,28 +33,59 @@ import com.android.internal.annotations.VisibleForTesting;


/**
 * Utility methods for common functionality using by different networks.
 * The methods used for location permission and location mode checking.
 *
 * @hide
 */
public class ConnectivityUtil {
public class LocationPermissionChecker {

    private static final String TAG = "ConnectivityUtil";
    private static final String TAG = "LocationPermissionChecker";

    private final Context mContext;
    private final AppOpsManager mAppOps;
    private final AppOpsManager mAppOpsManager;
    private final UserManager mUserManager;
    private final LocationManager mLocationManager;

    public ConnectivityUtil(Context context) {
    public LocationPermissionChecker(Context context) {
        mContext = context;
        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
        mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
        mLocationManager =
            (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
    }

    /**
     * API to determine if the caller has fine/coarse location permission (depending on
     * config/targetSDK level) and the location mode is enabled for the user. SecurityException is
     * thrown if the caller has no permission or the location mode is disabled.
     * Check location permission granted by the caller.
     *
     * This API check if the location mode enabled for the caller and the caller has
     * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION.
     *
     * @param pkgName package name of the application requesting access
     * @param featureId The feature in the package
     * @param uid The uid of the package
     * @param message A message describing why the permission was checked. Only needed if this is
     *                not inside of a two-way binder call from the data receiver
     *
     * @return {@code true} returns if the caller has location permission and the location mode is
     *         enabled.
     */
    public boolean checkLocationPermission(String pkgName, @Nullable String featureId,
            int uid, @Nullable String message) {
        try {
            enforceLocationPermission(pkgName, featureId, uid, message);
            return true;
        } catch (SecurityException e) {
            return false;
        }
    }

    /**
     * Enforce the caller has location permission.
     *
     * This API determines if the location mode enabled for the caller and the caller has
     * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION.
     * SecurityException is thrown if the caller has no permission or the location mode is disabled.
     *
     * @param pkgName package name of the application requesting access
     * @param featureId The feature in the package
     * @param uid The uid of the package
@@ -62,31 +93,21 @@ public class ConnectivityUtil {
     *                not inside of a two-way binder call from the data receiver
     */
    public void enforceLocationPermission(String pkgName, @Nullable String featureId, int uid,
            @Nullable String message)
            throws SecurityException {
            @Nullable String message) throws SecurityException {

        checkPackage(uid, pkgName);

        // Location mode must be enabled
        if (!isLocationModeEnabled()) {
            // Location mode is disabled, scan results cannot be returned
            throw new SecurityException("Location mode is disabled for the device");
        }

        // LocationAccess by App: caller must have Coarse/Fine Location permission to have access to
        // location information.
        boolean canAppPackageUseLocation = checkCallersLocationPermission(pkgName, featureId,
                uid, /* coarseForTargetSdkLessThanQ */ true, message);

        // If neither caller or app has location access, there is no need to check
        // any other permissions. Deny access to scan results.
        if (!canAppPackageUseLocation) {
        if (!checkCallersLocationPermission(pkgName, featureId,
                uid, /* coarseForTargetSdkLessThanQ */ true, message)) {
            throw new SecurityException("UID " + uid + " has no location permission");
        }
        // If the User or profile is current, permission is granted
        // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission.
        if (!isCurrentProfile(uid) && !checkInteractAcrossUsersFull(uid)) {
            throw new SecurityException("UID " + uid + " profile not permitted");
        }
    }

    /**
@@ -104,6 +125,7 @@ public class ConnectivityUtil {
     */
    public boolean checkCallersLocationPermission(String pkgName, @Nullable String featureId,
            int uid, boolean coarseForTargetSdkLessThanQ, @Nullable String message) {

        boolean isTargetSdkLessThanQ = isTargetSdkLessThan(pkgName, Build.VERSION_CODES.Q, uid);

        String permissionType = Manifest.permission.ACCESS_FINE_LOCATION;
@@ -111,8 +133,7 @@ public class ConnectivityUtil {
            // Having FINE permission implies having COARSE permission (but not the reverse)
            permissionType = Manifest.permission.ACCESS_COARSE_LOCATION;
        }
        if (getUidPermission(permissionType, uid)
                == PackageManager.PERMISSION_DENIED) {
        if (getUidPermission(permissionType, uid) == PackageManager.PERMISSION_DENIED) {
            return false;
        }

@@ -134,10 +155,8 @@ public class ConnectivityUtil {
     * Retrieves a handle to LocationManager (if not already done) and check if location is enabled.
     */
    public boolean isLocationModeEnabled() {
        LocationManager locationManager =
                (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
        try {
            return locationManager.isLocationEnabledForUser(UserHandle.of(
            return mLocationManager.isLocationEnabledForUser(UserHandle.of(
                    getCurrentUser()));
        } catch (Exception e) {
            Log.e(TAG, "Failure to get location mode via API, falling back to settings", e);
@@ -166,27 +185,16 @@ public class ConnectivityUtil {

    private boolean noteAppOpAllowed(String op, String pkgName, @Nullable String featureId,
            int uid, @Nullable String message) {
        return mAppOps.noteOp(op, uid, pkgName, featureId, message) == AppOpsManager.MODE_ALLOWED;
        return mAppOpsManager.noteOp(op, uid, pkgName, featureId, message)
                == AppOpsManager.MODE_ALLOWED;
    }

    private void checkPackage(int uid, String pkgName) throws SecurityException {
    private void checkPackage(int uid, String pkgName)
            throws SecurityException {
        if (pkgName == null) {
            throw new SecurityException("Checking UID " + uid + " but Package Name is Null");
        }
        mAppOps.checkPackage(uid, pkgName);
    }

    private boolean isCurrentProfile(int uid) {
        UserHandle currentUser = UserHandle.of(getCurrentUser());
        UserHandle callingUser = UserHandle.getUserHandleForUid(uid);
        return currentUser.equals(callingUser)
                || mUserManager.isSameProfileGroup(currentUser, callingUser);
    }

    private boolean checkInteractAcrossUsersFull(int uid) {
        return getUidPermission(
                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, uid)
                == PackageManager.PERMISSION_GRANTED;
        mAppOpsManager.checkPackage(uid, pkgName);
    }

    @VisibleForTesting
+16 −41
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.Manifest;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -48,8 +49,8 @@ import org.mockito.stubbing.Answer;

import java.util.HashMap;

/** Unit tests for {@link ConnectivityUtil}. */
public class ConnectivityUtilTest {
/** Unit tests for {@link LocationPermissionChecker}. */
public class LocationPermissionCheckerTest {

    public static final String TAG = "ConnectivityUtilTest";

@@ -85,18 +86,7 @@ public class ConnectivityUtilTest {
    private boolean mThrowSecurityException;
    private Answer<Integer> mReturnPermission;
    private HashMap<String, Integer> mPermissionsList = new HashMap<String, Integer>();

    private class TestConnectivityUtil extends ConnectivityUtil {

        TestConnectivityUtil(Context context) {
            super(context);
        }

        @Override
        protected int getCurrentUser() {
            return mCurrentUser;
        }
    }
    private LocationPermissionChecker mChecker;

    @Before
    public void setUp() {
@@ -141,11 +131,12 @@ public class ConnectivityUtilTest {
        mThrowSecurityException = true;
        mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.M;
        mIsLocationEnabled = false;
        mCurrentUser = UserHandle.USER_SYSTEM;
        mCurrentUser = ActivityManager.getCurrentUser();
        mCoarseLocationPermission = PackageManager.PERMISSION_DENIED;
        mFineLocationPermission = PackageManager.PERMISSION_DENIED;
        mAllowCoarseLocationApps = AppOpsManager.MODE_ERRORED;
        mAllowFineLocationApps = AppOpsManager.MODE_ERRORED;
        mChecker = new LocationPermissionChecker(mMockContext);
    }

    private void setupMockInterface() {
@@ -189,8 +180,7 @@ public class ConnectivityUtilTest {
        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
        mUid = mCurrentUser;
        setupTestCase();
        new TestConnectivityUtil(mMockContext)
                .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
        mChecker.enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
    }

    @Test
@@ -203,8 +193,7 @@ public class ConnectivityUtilTest {
        mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED;
        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
        setupTestCase();
        new TestConnectivityUtil(mMockContext)
                .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
        mChecker.enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
    }

    @Test
@@ -217,22 +206,8 @@ public class ConnectivityUtilTest {
        setupTestCase();

        assertThrows(SecurityException.class,
                () -> new TestConnectivityUtil(mMockContext)
                        .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
    }

    @Test
    public void testenforceCanAccessScanResults_UserOrProfileNotCurrent() throws Exception {
        mIsLocationEnabled = true;
        mThrowSecurityException = false;
        mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
        mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED;
        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
        setupTestCase();

        assertThrows(SecurityException.class,
                () -> new TestConnectivityUtil(mMockContext)
                        .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
                () -> mChecker.enforceLocationPermission(
                        TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
    }

    @Test
@@ -241,8 +216,8 @@ public class ConnectivityUtilTest {
        mIsLocationEnabled = true;
        setupTestCase();
        assertThrows(SecurityException.class,
                () -> new TestConnectivityUtil(mMockContext)
                        .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
                () -> mChecker.enforceLocationPermission(
                        TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
    }

    @Test
@@ -256,8 +231,8 @@ public class ConnectivityUtilTest {
        setupTestCase();

        assertThrows(SecurityException.class,
                () -> new TestConnectivityUtil(mMockContext)
                        .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
                () -> mChecker.enforceLocationPermission(
                        TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
        verify(mMockAppOps, never()).noteOp(anyInt(), anyInt(), anyString());
    }

@@ -272,8 +247,8 @@ public class ConnectivityUtilTest {
        setupTestCase();

        assertThrows(SecurityException.class,
                () -> new TestConnectivityUtil(mMockContext)
                        .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
                () -> mChecker.enforceLocationPermission(
                        TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
    }

    private static void assertThrows(Class<? extends Exception> exceptionClass, Runnable r) {