Merge "Update location permission check for ConnectivityUtil"
am: 1e5d7a9020
Change-Id: Ic9a61ab57335f778c856ec226ded08a86dc22fc0
This commit is contained in:
@@ -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
|
* @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 Context mContext;
|
||||||
private final AppOpsManager mAppOps;
|
private final AppOpsManager mAppOpsManager;
|
||||||
private final UserManager mUserManager;
|
private final UserManager mUserManager;
|
||||||
|
private final LocationManager mLocationManager;
|
||||||
|
|
||||||
public ConnectivityUtil(Context context) {
|
public LocationPermissionChecker(Context context) {
|
||||||
mContext = 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);
|
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
|
* Check location permission granted by the caller.
|
||||||
* 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.
|
* 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 pkgName package name of the application requesting access
|
||||||
* @param featureId The feature in the package
|
* @param featureId The feature in the package
|
||||||
* @param uid The uid of 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
|
* not inside of a two-way binder call from the data receiver
|
||||||
*/
|
*/
|
||||||
public void enforceLocationPermission(String pkgName, @Nullable String featureId, int uid,
|
public void enforceLocationPermission(String pkgName, @Nullable String featureId, int uid,
|
||||||
@Nullable String message)
|
@Nullable String message) throws SecurityException {
|
||||||
throws SecurityException {
|
|
||||||
checkPackage(uid, pkgName);
|
checkPackage(uid, pkgName);
|
||||||
|
|
||||||
// Location mode must be enabled
|
// Location mode must be enabled
|
||||||
if (!isLocationModeEnabled()) {
|
if (!isLocationModeEnabled()) {
|
||||||
// Location mode is disabled, scan results cannot be returned
|
|
||||||
throw new SecurityException("Location mode is disabled for the device");
|
throw new SecurityException("Location mode is disabled for the device");
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocationAccess by App: caller must have Coarse/Fine Location permission to have access to
|
// LocationAccess by App: caller must have Coarse/Fine Location permission to have access to
|
||||||
// location information.
|
// location information.
|
||||||
boolean canAppPackageUseLocation = checkCallersLocationPermission(pkgName, featureId,
|
if (!checkCallersLocationPermission(pkgName, featureId,
|
||||||
uid, /* coarseForTargetSdkLessThanQ */ true, message);
|
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) {
|
|
||||||
throw new SecurityException("UID " + uid + " has no location permission");
|
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,
|
public boolean checkCallersLocationPermission(String pkgName, @Nullable String featureId,
|
||||||
int uid, boolean coarseForTargetSdkLessThanQ, @Nullable String message) {
|
int uid, boolean coarseForTargetSdkLessThanQ, @Nullable String message) {
|
||||||
|
|
||||||
boolean isTargetSdkLessThanQ = isTargetSdkLessThan(pkgName, Build.VERSION_CODES.Q, uid);
|
boolean isTargetSdkLessThanQ = isTargetSdkLessThan(pkgName, Build.VERSION_CODES.Q, uid);
|
||||||
|
|
||||||
String permissionType = Manifest.permission.ACCESS_FINE_LOCATION;
|
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)
|
// Having FINE permission implies having COARSE permission (but not the reverse)
|
||||||
permissionType = Manifest.permission.ACCESS_COARSE_LOCATION;
|
permissionType = Manifest.permission.ACCESS_COARSE_LOCATION;
|
||||||
}
|
}
|
||||||
if (getUidPermission(permissionType, uid)
|
if (getUidPermission(permissionType, uid) == PackageManager.PERMISSION_DENIED) {
|
||||||
== PackageManager.PERMISSION_DENIED) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,10 +155,8 @@ public class ConnectivityUtil {
|
|||||||
* Retrieves a handle to LocationManager (if not already done) and check if location is enabled.
|
* Retrieves a handle to LocationManager (if not already done) and check if location is enabled.
|
||||||
*/
|
*/
|
||||||
public boolean isLocationModeEnabled() {
|
public boolean isLocationModeEnabled() {
|
||||||
LocationManager locationManager =
|
|
||||||
(LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
|
|
||||||
try {
|
try {
|
||||||
return locationManager.isLocationEnabledForUser(UserHandle.of(
|
return mLocationManager.isLocationEnabledForUser(UserHandle.of(
|
||||||
getCurrentUser()));
|
getCurrentUser()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Failure to get location mode via API, falling back to settings", e);
|
Log.e(TAG, "Failure to get location mode via API, falling back to settings", e);
|
||||||
@@ -166,28 +185,15 @@ public class ConnectivityUtil {
|
|||||||
|
|
||||||
private boolean noteAppOpAllowed(String op, String pkgName, @Nullable String featureId,
|
private boolean noteAppOpAllowed(String op, String pkgName, @Nullable String featureId,
|
||||||
int uid, @Nullable String message) {
|
int uid, @Nullable String message) {
|
||||||
return mAppOps.noteOp(op, uid, pkgName) == AppOpsManager.MODE_ALLOWED;
|
return mAppOpsManager.noteOp(op, uid, pkgName) == AppOpsManager.MODE_ALLOWED;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkPackage(int uid, String pkgName) throws SecurityException {
|
private void checkPackage(int uid, String pkgName)
|
||||||
|
throws SecurityException {
|
||||||
if (pkgName == null) {
|
if (pkgName == null) {
|
||||||
throw new SecurityException("Checking UID " + uid + " but Package Name is Null");
|
throw new SecurityException("Checking UID " + uid + " but Package Name is Null");
|
||||||
}
|
}
|
||||||
mAppOps.checkPackage(uid, pkgName);
|
mAppOpsManager.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.getIdentifier(), callingUser.getIdentifier());
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkInteractAcrossUsersFull(int uid) {
|
|
||||||
return getUidPermission(
|
|
||||||
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, uid)
|
|
||||||
== PackageManager.PERMISSION_GRANTED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify;
|
|||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.app.ActivityManager;
|
||||||
import android.app.AppOpsManager;
|
import android.app.AppOpsManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
@@ -47,8 +48,8 @@ import org.mockito.stubbing.Answer;
|
|||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
/** Unit tests for {@link ConnectivityUtil}. */
|
/** Unit tests for {@link LocationPermissionChecker}. */
|
||||||
public class ConnectivityUtilTest {
|
public class LocationPermissionCheckerTest {
|
||||||
|
|
||||||
public static final String TAG = "ConnectivityUtilTest";
|
public static final String TAG = "ConnectivityUtilTest";
|
||||||
|
|
||||||
@@ -84,18 +85,7 @@ public class ConnectivityUtilTest {
|
|||||||
private boolean mThrowSecurityException;
|
private boolean mThrowSecurityException;
|
||||||
private Answer<Integer> mReturnPermission;
|
private Answer<Integer> mReturnPermission;
|
||||||
private HashMap<String, Integer> mPermissionsList = new HashMap<String, Integer>();
|
private HashMap<String, Integer> mPermissionsList = new HashMap<String, Integer>();
|
||||||
|
private LocationPermissionChecker mChecker;
|
||||||
private class TestConnectivityUtil extends ConnectivityUtil {
|
|
||||||
|
|
||||||
TestConnectivityUtil(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getCurrentUser() {
|
|
||||||
return mCurrentUser;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
@@ -140,11 +130,12 @@ public class ConnectivityUtilTest {
|
|||||||
mThrowSecurityException = true;
|
mThrowSecurityException = true;
|
||||||
mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.M;
|
mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.M;
|
||||||
mIsLocationEnabled = false;
|
mIsLocationEnabled = false;
|
||||||
mCurrentUser = UserHandle.USER_SYSTEM;
|
mCurrentUser = ActivityManager.getCurrentUser();
|
||||||
mCoarseLocationPermission = PackageManager.PERMISSION_DENIED;
|
mCoarseLocationPermission = PackageManager.PERMISSION_DENIED;
|
||||||
mFineLocationPermission = PackageManager.PERMISSION_DENIED;
|
mFineLocationPermission = PackageManager.PERMISSION_DENIED;
|
||||||
mAllowCoarseLocationApps = AppOpsManager.MODE_ERRORED;
|
mAllowCoarseLocationApps = AppOpsManager.MODE_ERRORED;
|
||||||
mAllowFineLocationApps = AppOpsManager.MODE_ERRORED;
|
mAllowFineLocationApps = AppOpsManager.MODE_ERRORED;
|
||||||
|
mChecker = new LocationPermissionChecker(mMockContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupMockInterface() {
|
private void setupMockInterface() {
|
||||||
@@ -188,8 +179,7 @@ public class ConnectivityUtilTest {
|
|||||||
mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
|
mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
|
||||||
mUid = mCurrentUser;
|
mUid = mCurrentUser;
|
||||||
setupTestCase();
|
setupTestCase();
|
||||||
new TestConnectivityUtil(mMockContext)
|
mChecker.enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
|
||||||
.enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -202,8 +192,7 @@ public class ConnectivityUtilTest {
|
|||||||
mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED;
|
mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED;
|
||||||
mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
|
mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
|
||||||
setupTestCase();
|
setupTestCase();
|
||||||
new TestConnectivityUtil(mMockContext)
|
mChecker.enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
|
||||||
.enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -216,22 +205,8 @@ public class ConnectivityUtilTest {
|
|||||||
setupTestCase();
|
setupTestCase();
|
||||||
|
|
||||||
assertThrows(SecurityException.class,
|
assertThrows(SecurityException.class,
|
||||||
() -> new TestConnectivityUtil(mMockContext)
|
() -> mChecker.enforceLocationPermission(
|
||||||
.enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
|
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -240,8 +215,8 @@ public class ConnectivityUtilTest {
|
|||||||
mIsLocationEnabled = true;
|
mIsLocationEnabled = true;
|
||||||
setupTestCase();
|
setupTestCase();
|
||||||
assertThrows(SecurityException.class,
|
assertThrows(SecurityException.class,
|
||||||
() -> new TestConnectivityUtil(mMockContext)
|
() -> mChecker.enforceLocationPermission(
|
||||||
.enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
|
TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -255,8 +230,8 @@ public class ConnectivityUtilTest {
|
|||||||
setupTestCase();
|
setupTestCase();
|
||||||
|
|
||||||
assertThrows(SecurityException.class,
|
assertThrows(SecurityException.class,
|
||||||
() -> new TestConnectivityUtil(mMockContext)
|
() -> mChecker.enforceLocationPermission(
|
||||||
.enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
|
TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
|
||||||
verify(mMockAppOps, never()).noteOp(anyInt(), anyInt(), anyString());
|
verify(mMockAppOps, never()).noteOp(anyInt(), anyInt(), anyString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,8 +246,8 @@ public class ConnectivityUtilTest {
|
|||||||
setupTestCase();
|
setupTestCase();
|
||||||
|
|
||||||
assertThrows(SecurityException.class,
|
assertThrows(SecurityException.class,
|
||||||
() -> new TestConnectivityUtil(mMockContext)
|
() -> mChecker.enforceLocationPermission(
|
||||||
.enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
|
TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertThrows(Class<? extends Exception> exceptionClass, Runnable r) {
|
private static void assertThrows(Class<? extends Exception> exceptionClass, Runnable r) {
|
||||||
Reference in New Issue
Block a user