Merge "Add DO app bypass for start/stopTethering" into main am: 038fd312fe

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/3395339

Change-Id: I5f0841751e2f3bdbc6dd6a110e01d68eeaec9fc8
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Quang Luong 2025-03-19 07:19:15 -07:00 • committed by Automerger Merge Worker
commit f13ae6e021
5 changed files with 440 additions and 76 deletions

View file

@ -22,6 +22,7 @@ import static android.Manifest.permission.NETWORK_STACK;
import static android.Manifest.permission.TETHER_PRIVILEGED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
@ -58,6 +59,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.networkstack.apishim.SettingsShimImpl;
import com.android.networkstack.apishim.common.SettingsShim;
import com.android.networkstack.tethering.util.TetheringPermissionsUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@ -72,6 +74,7 @@ public class TetheringService extends Service {
private TetheringConnector mConnector;
private SettingsShim mSettingsShim;
private TetheringPermissionsUtils mTetheringPermissionsUtils;
@Override
public void onCreate() {
@ -81,6 +84,7 @@ public class TetheringService extends Service {
mConnector = new TetheringConnector(makeTethering(deps), TetheringService.this);
mSettingsShim = SettingsShimImpl.newInstance();
mTetheringPermissionsUtils = new TetheringPermissionsUtils(deps.getContext());
}
/**
@ -109,7 +113,11 @@ public class TetheringService extends Service {
@Override
public void tether(String iface, String callerPkg, String callingAttributionTag,
IIntResultListener listener) {
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */,
listener)) {
return;
}
mTethering.legacyTether(iface, listener);
}
@ -117,7 +125,11 @@ public class TetheringService extends Service {
@Override
public void untether(String iface, String callerPkg, String callingAttributionTag,
IIntResultListener listener) {
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */,
listener)) {
return;
}
mTethering.legacyUntether(iface, listener);
}
@ -125,32 +137,45 @@ public class TetheringService extends Service {
@Override
public void setUsbTethering(boolean enable, String callerPkg, String callingAttributionTag,
IIntResultListener listener) {
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */,
listener)) {
return;
}
mTethering.setUsbTethering(enable, listener);
}
private boolean isRequestAllowedForDeviceOwner(@NonNull TetheringRequest request) {
return request.getTetheringType() == TETHERING_WIFI
&& request.getSoftApConfiguration() != null;
}
@Override
public void startTethering(TetheringRequestParcel request, String callerPkg,
public void startTethering(TetheringRequestParcel requestParcel, String callerPkg,
String callingAttributionTag, IIntResultListener listener) {
boolean onlyAllowPrivileged = request.exemptFromEntitlementCheck
|| request.interfaceName != null;
if (checkAndNotifyCommonError(callerPkg,
callingAttributionTag,
onlyAllowPrivileged,
listener)) {
TetheringRequest request = new TetheringRequest(requestParcel);
request.setUid(getBinderCallingUid());
request.setPackageName(callerPkg);
boolean onlyAllowPrivileged = request.isExemptFromEntitlementCheck()
|| request.getInterfaceName() != null;
boolean isDeviceOwnerAllowed = mTethering.isTetheringWithSoftApConfigEnabled()
&& isRequestAllowedForDeviceOwner(request);
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, onlyAllowPrivileged,
isDeviceOwnerAllowed, listener)) {
return;
}
TetheringRequest external = new TetheringRequest(request);
external.setUid(getBinderCallingUid());
external.setPackageName(callerPkg);
mTethering.startTethering(external, callerPkg, listener);
mTethering.startTethering(request, callerPkg, listener);
}
@Override
public void stopTethering(int type, String callerPkg, String callingAttributionTag,
IIntResultListener listener) {
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */,
listener)) {
return;
}
try {
mTethering.stopTethering(type);
@ -164,9 +189,16 @@ public class TetheringService extends Service {
IIntResultListener listener) {
if (request == null) return;
if (listener == null) return;
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
request.setUid(getBinderCallingUid());
request.setPackageName(callerPkg);
boolean isDeviceOwnerAllowed = mTethering.isTetheringWithSoftApConfigEnabled()
&& isRequestAllowedForDeviceOwner(request);
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
false /* onlyAllowPrivileged */, isDeviceOwnerAllowed, listener)) {
return;
}
// Note: Whether tethering is actually stopped or not will depend on whether the request
// matches an active one with the same UID (see RequestTracker#findFuzzyMatchedRequest).
mTethering.stopTetheringRequest(request, listener);
}
@ -187,7 +219,11 @@ public class TetheringService extends Service {
+ " internal-only listener");
}
};
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */,
listener)) {
return;
}
mTethering.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
}
@ -223,7 +259,11 @@ public class TetheringService extends Service {
@Override
public void stopAllTethering(String callerPkg, String callingAttributionTag,
IIntResultListener listener) {
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */,
listener)) {
return;
}
try {
mTethering.stopAllTethering();
@ -234,8 +274,11 @@ public class TetheringService extends Service {
@Override
public void isTetheringSupported(String callerPkg, String callingAttributionTag,
IIntResultListener listener) {
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
boolean isDeviceOwnerAppAllowed = mTethering.isTetheringWithSoftApConfigEnabled();
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag,
false /* onlyAllowPrivileged */, isDeviceOwnerAppAllowed, listener)) {
return;
}
try {
listener.onResult(TETHER_ERROR_NO_ERROR);
} catch (RemoteException e) { }
@ -259,24 +302,18 @@ public class TetheringService extends Service {
mTethering.dump(fd, writer, args);
}
private boolean checkAndNotifyCommonError(final String callerPkg,
final String callingAttributionTag, final IIntResultListener listener) {
return checkAndNotifyCommonError(callerPkg, callingAttributionTag,
false /* onlyAllowPrivileged */, listener);
}
private boolean checkAndNotifyCommonError(final String callerPkg,
final String callingAttributionTag, final boolean onlyAllowPrivileged,
final IIntResultListener listener) {
final boolean isDeviceOwnerAppAllowed, final IIntResultListener listener) {
try {
if (!checkPackageNameMatchesUid(getBinderCallingUid(), callerPkg)) {
Log.e(TAG, "Package name " + callerPkg + " does not match UID "
+ getBinderCallingUid());
final int uid = getBinderCallingUid();
if (!checkPackageNameMatchesUid(uid, callerPkg)) {
Log.e(TAG, "Package name " + callerPkg + " does not match UID " + uid);
listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
return true;
}
if (!hasTetherChangePermission(callerPkg, callingAttributionTag,
onlyAllowPrivileged)) {
if (!hasTetherChangePermission(uid, callerPkg, callingAttributionTag,
onlyAllowPrivileged, isDeviceOwnerAppAllowed)) {
listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
return true;
}
@ -308,21 +345,25 @@ public class TetheringService extends Service {
return mService.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED;
}
private boolean hasTetherChangePermission(final String callerPkg,
final String callingAttributionTag, final boolean onlyAllowPrivileged) {
private boolean hasTetherChangePermission(final int uid, final String callerPkg,
final String callingAttributionTag, final boolean onlyAllowPrivileged,
final boolean isDeviceOwnerAppAllowed) {
if (onlyAllowPrivileged && !hasNetworkStackPermission()
&& !hasNetworkSettingsPermission()) return false;
if (hasTetherPrivilegedPermission()) return true;
// Allow DO apps to change tethering even if they don't have TETHER_PRIVILEGED.
if (isDeviceOwnerAppAllowed && mService.isDeviceOwner(uid, callerPkg)) {
return true;
}
// After TetheringManager moves to public API, prevent third-party apps from being able
// to change tethering with only WRITE_SETTINGS permission.
if (mTethering.isTetheringWithSoftApConfigEnabled()) return false;
if (mTethering.isTetherProvisioningRequired()) return false;
int uid = getBinderCallingUid();
// If callerPkg's uid is not same as getBinderCallingUid(),
// checkAndNoteWriteSettingsOperation will return false and the operation will be
// denied.
@ -386,6 +427,14 @@ public class TetheringService extends Service {
return Binder.getCallingUid();
}
/**
* Wrapper for {@link TetheringPermissionsUtils#isDeviceOwner(int, String)}, used for mocks.
*/
@VisibleForTesting
boolean isDeviceOwner(final int uid, final String callerPkg) {
return mTetheringPermissionsUtils.isDeviceOwner(uid, callerPkg);
}
/**
* An injection method for testing.
*/

View file

@ -0,0 +1,71 @@
/*
* Copyright (C) 2025 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.networkstack.tethering.util;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.util.Log;
import androidx.annotation.NonNull;
/**
* Utils class for checking permissions related to Tethering APIs.
*/
public class TetheringPermissionsUtils {
private static final String TAG = "TetherPermUtils";
@NonNull private final Context mContext;
public TetheringPermissionsUtils(@NonNull Context context) {
mContext = context;
}
/**
* Checks if the package name is a Device Owner.
*/
public boolean isDeviceOwner(final int uid, @NonNull final String packageName) {
Context userContext;
try {
// There is no safe way to invoke this method since tethering package might not be
// installed for a certain user on the OEM devices, refer to b/382628161.
userContext = mContext.createContextAsUser(UserHandle.getUserHandleForUid(uid),
0 /* flags */);
} catch (IllegalStateException e) {
// TODO: Add a terrible error metric for this case.
Log.e(TAG, "createContextAsUser failed, skipping Device Owner check", e);
return false;
}
DevicePolicyManager devicePolicyManager =
retrieveDevicePolicyManagerFromContext(userContext);
if (devicePolicyManager == null) return false;
return devicePolicyManager.isDeviceOwnerApp(packageName);
}
private DevicePolicyManager retrieveDevicePolicyManagerFromContext(
@NonNull final Context context) {
DevicePolicyManager devicePolicyManager =
context.getSystemService(DevicePolicyManager.class);
if (devicePolicyManager == null
&& context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_DEVICE_ADMIN)) {
Log.w(TAG, "Error retrieving DPM service");
}
return devicePolicyManager;
}
}

View file

@ -25,14 +25,18 @@ import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.ArrayMap;
import android.util.ArraySet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Set;
public class MockTetheringService extends TetheringService {
private final Tethering mTethering = mock(Tethering.class);
private final ArrayMap<String, Integer> mMockedPermissions = new ArrayMap<>();
private final ArrayMap<String, Integer> mMockedPackageUids = new ArrayMap<>();
private final Set<String> mMockedDeviceOwnerPackages = new ArraySet<>();
private int mMockCallingUid;
@Override
@ -74,6 +78,11 @@ public class MockTetheringService extends TetheringService {
return mMockCallingUid;
}
@Override
boolean isDeviceOwner(final int uid, final String callerPkg) {
return mMockedDeviceOwnerPackages.contains(callerPkg);
}
public Tethering getTethering() {
return mTethering;
}
@ -118,5 +127,19 @@ public class MockTetheringService extends TetheringService {
public void setCallingUid(int uid) {
mMockCallingUid = uid;
}
/**
* Add a mocked carrier privileges package
*/
public void addDeviceOwnerPackage(final String packageName) {
mMockedDeviceOwnerPackages.add(packageName);
}
/**
* Remove a mocked carrier privileges package
*/
public void removeDeviceOwnerPackage(final String packageName) {
mMockedDeviceOwnerPackages.remove(packageName);
}
}
}

View file

@ -21,6 +21,7 @@ import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.TETHER_PRIVILEGED;
import static android.Manifest.permission.WRITE_SETTINGS;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.net.TetheringManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_VIRTUAL;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
@ -36,6 +37,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
@ -53,6 +55,7 @@ import android.net.ITetheringEventCallback;
import android.net.TetheringManager;
import android.net.TetheringManager.TetheringRequest;
import android.net.TetheringRequestParcel;
import android.net.wifi.SoftApConfiguration;
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Handler;
@ -153,43 +156,58 @@ public final class TetheringServiceTest {
}
private void runAsNoPermission(final TestTetheringCall test) throws Exception {
runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
new String[0]);
runTetheringCall(test, true /* isTetheringAllowed */,
true /* isTetheringWithSoftApConfigEnabled */, new String[0]);
}
private void runAsTetherPrivileged(final TestTetheringCall test) throws Exception {
runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
TETHER_PRIVILEGED);
runTetheringCall(test, true /* isTetheringAllowed */,
true /* isTetheringWithSoftApConfigEnabled */, TETHER_PRIVILEGED);
}
private void runAsAccessNetworkState(final TestTetheringCall test) throws Exception {
runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
ACCESS_NETWORK_STATE);
runTetheringCall(test, true /* isTetheringAllowed */,
true /* isTetheringWithSoftApConfigEnabled */, ACCESS_NETWORK_STATE);
}
private void runAsWriteSettings(final TestTetheringCall test) throws Exception {
runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
WRITE_SETTINGS);
runTetheringCall(test, true /* isTetheringAllowed */,
true /* isTetheringWithSoftApConfigEnabled */, WRITE_SETTINGS);
}
private void runAsWriteSettingsWhenWriteSettingsAllowed(
final TestTetheringCall test) throws Exception {
runTetheringCall(test, true /* isTetheringAllowed */, true /* isWriteSettingsAllowed */,
WRITE_SETTINGS);
runTetheringCall(test, true /* isTetheringAllowed */,
false /* isTetheringWithSoftApConfigEnabled */, WRITE_SETTINGS);
}
private void runAsTetheringDisallowed(final TestTetheringCall test) throws Exception {
runTetheringCall(test, false /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
TETHER_PRIVILEGED);
runTetheringCall(test, false /* isTetheringAllowed */,
true /* isTetheringWithSoftApConfigEnabled */, TETHER_PRIVILEGED);
}
private void runAsNetworkSettings(final TestTetheringCall test) throws Exception {
runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
NETWORK_SETTINGS, TETHER_PRIVILEGED);
runTetheringCall(test, true /* isTetheringAllowed */,
true /* isTetheringWithSoftApConfigEnabled */, NETWORK_SETTINGS, TETHER_PRIVILEGED);
}
private void runAsDeviceOwner(final TestTetheringCall test) throws Exception {
mMockConnector.addDeviceOwnerPackage(TEST_CALLER_PKG);
runTetheringCall(test, true /* isTetheringAllowed */,
true /* isTetheringWithSoftApConfigEnabled */, new String[0]);
mMockConnector.removeDeviceOwnerPackage(TEST_CALLER_PKG);
}
private void runAsDeviceOwnerWhenDeviceOwnerBypassNotEnabled(final TestTetheringCall test)
throws Exception {
mMockConnector.addDeviceOwnerPackage(TEST_CALLER_PKG);
runTetheringCall(test, true /* isTetheringAllowed */,
false /* isTetheringWithSoftApConfigEnabled */, new String[0]);
mMockConnector.removeDeviceOwnerPackage(TEST_CALLER_PKG);
}
private void runTetheringCall(final TestTetheringCall test, boolean isTetheringAllowed,
boolean isWriteSettingsAllowed, String... permissions) throws Exception {
boolean isTetheringWithSoftApConfigEnabled, String... permissions) throws Exception {
// Allow the test to run even if ACCESS_NETWORK_STATE was granted at the APK level
if (!CollectionUtils.contains(permissions, ACCESS_NETWORK_STATE)) {
mMockConnector.setPermission(ACCESS_NETWORK_STATE, PERMISSION_DENIED);
@ -200,7 +218,7 @@ public final class TetheringServiceTest {
when(mTethering.isTetheringSupported()).thenReturn(true);
when(mTethering.isTetheringAllowed()).thenReturn(isTetheringAllowed);
when(mTethering.isTetheringWithSoftApConfigEnabled())
.thenReturn(!isWriteSettingsAllowed);
.thenReturn(isTetheringWithSoftApConfigEnabled);
test.runTetheringCall(new TestTetheringResult());
} finally {
mUiAutomation.dropShellPermissionIdentity();
@ -226,7 +244,7 @@ public final class TetheringServiceTest {
runAsNoPermission((result) -> {
mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@ -239,14 +257,14 @@ public final class TetheringServiceTest {
runAsWriteSettings((result) -> {
mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runTether(result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@ -274,7 +292,7 @@ public final class TetheringServiceTest {
runAsNoPermission((result) -> {
mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@ -287,14 +305,14 @@ public final class TetheringServiceTest {
runAsWriteSettings((result) -> {
mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runUnTether(result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@ -328,7 +346,7 @@ public final class TetheringServiceTest {
runAsNoPermission((result) -> {
mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG,
TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@ -341,14 +359,14 @@ public final class TetheringServiceTest {
runAsWriteSettings((result) -> {
mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG,
TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runSetUsbTethering(result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@ -367,6 +385,7 @@ public final class TetheringServiceTest {
final TetheringRequestParcel request) throws Exception {
mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetheringSupported();
verify(mTethering).isTetheringAllowed();
verify(mTethering).startTethering(
@ -381,14 +400,61 @@ public final class TetheringServiceTest {
runAsNoPermission((result) -> {
mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
// Not a Wifi request - Fail
runAsDeviceOwner((result) -> {
final TetheringRequestParcel notWifi = new TetheringRequestParcel();
notWifi.tetheringType = TETHERING_USB;
mTetheringConnector.startTethering(notWifi, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
// Request has no SoftApConfiguration - Fail
runAsDeviceOwner((result) -> {
final TetheringRequestParcel noConfig = new TetheringRequestParcel();
noConfig.tetheringType = TETHERING_WIFI;
mTetheringConnector.startTethering(noConfig, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
// Wifi request with SoftApConfiguration - Succeed
runAsDeviceOwner((result) -> {
final TetheringRequestParcel withConfig = new TetheringRequestParcel();
withConfig.tetheringType = TETHERING_WIFI;
withConfig.softApConfig = new SoftApConfiguration.Builder().build();
mTetheringConnector.startTethering(withConfig, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetheringSupported();
verify(mTethering).isTetheringAllowed();
verify(mTethering).startTethering(any(), any(), any());
result.assertResult(-1); // No result
verifyNoMoreInteractionsForTethering();
});
runAsDeviceOwnerWhenDeviceOwnerBypassNotEnabled((result) -> {
mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
runAsTetherPrivileged((result) -> {
mTetheringConnector.startTethering(request, TEST_WRONG_PACKAGE,
TEST_ATTRIBUTION_TAG, result);
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering, never()).startTethering(
eq(new TetheringRequest(request)), eq(TEST_WRONG_PACKAGE), eq(result));
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
@ -403,14 +469,14 @@ public final class TetheringServiceTest {
runAsWriteSettings((result) -> {
mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runStartTethering(result, request);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@ -418,6 +484,7 @@ public final class TetheringServiceTest {
runAsTetheringDisallowed((result) -> {
mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetheringSupported();
verify(mTethering).isTetheringAllowed();
result.assertResult(TETHER_ERROR_UNSUPPORTED);
@ -447,6 +514,7 @@ public final class TetheringServiceTest {
mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verifyNoMoreInteractionsForTethering();
});
}
@ -459,6 +527,7 @@ public final class TetheringServiceTest {
mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verifyNoMoreInteractionsForTethering();
}
@ -495,7 +564,7 @@ public final class TetheringServiceTest {
runAsNoPermission((result) -> {
mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG,
TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@ -508,14 +577,14 @@ public final class TetheringServiceTest {
runAsWriteSettings((result) -> {
mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG,
TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runStopTethering(result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@ -530,6 +599,87 @@ public final class TetheringServiceTest {
});
}
private void verifyHasPermissionForStopTetheringRequest(TetheringRequest request,
final TestTetheringResult result) throws Exception {
mTetheringConnector.stopTetheringRequest(request, TEST_CALLER_PKG,
TEST_ATTRIBUTION_TAG, result);
verify(mTethering).stopTetheringRequest(any(), any());
verify(mTethering).isTetheringSupported();
verify(mTethering).isTetheringAllowed();
reset(mTethering);
}
private void verifyDoesNotHavePermissionForStopTetheringRequest(TetheringRequest request,
final TestTetheringResult result) throws Exception {
mTetheringConnector.stopTetheringRequest(request, TEST_CALLER_PKG,
TEST_ATTRIBUTION_TAG, result);
verify(mTethering, never()).stopTetheringRequest(any(), any());
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
reset(mTethering);
}
private void verifyStopTetheringRequestWithTetheringDisallowed(TetheringRequest request,
final TestTetheringResult result) throws Exception {
mTetheringConnector.stopTetheringRequest(request, TEST_CALLER_PKG,
TEST_ATTRIBUTION_TAG, result);
verify(mTethering, never()).stopTetheringRequest(any(), any());
result.assertResult(TETHER_ERROR_UNSUPPORTED);
reset(mTethering);
}
@Test
public void testStopTetheringRequest() throws Exception {
TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
runAsNoPermission((result) -> {
verifyDoesNotHavePermissionForStopTetheringRequest(request, result);
});
runAsTetherPrivileged((result) -> {
verifyHasPermissionForStopTetheringRequest(request, result);
});
runAsWriteSettings((result) -> {
verifyDoesNotHavePermissionForStopTetheringRequest(request, result);
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
verifyHasPermissionForStopTetheringRequest(request, result);
});
runAsTetheringDisallowed((result) -> {
verifyStopTetheringRequestWithTetheringDisallowed(request, result);
});
runAsNetworkSettings((result) -> {
verifyHasPermissionForStopTetheringRequest(request, result);
});
// Not wifi -> fail
runAsDeviceOwner((result) -> {
TetheringRequest notWifi = new TetheringRequest.Builder(TETHERING_USB).build();
verifyDoesNotHavePermissionForStopTetheringRequest(notWifi, result);
});
// No config -> fail
runAsDeviceOwner((result) -> {
TetheringRequest noConfig = new TetheringRequest.Builder(TETHERING_WIFI).build();
verifyDoesNotHavePermissionForStopTetheringRequest(noConfig, result);
});
// With config -> success
TetheringRequest withConfig = new TetheringRequest.Builder(TETHERING_WIFI)
.setSoftApConfiguration(new SoftApConfiguration.Builder().build())
.build();
runAsDeviceOwner((result) -> {
verifyHasPermissionForStopTetheringRequest(withConfig, result);
});
runAsDeviceOwnerWhenDeviceOwnerBypassNotEnabled((result) -> {
verifyDoesNotHavePermissionForStopTetheringRequest(withConfig, result);
});
}
private void runRequestLatestTetheringEntitlementResult() throws Exception {
final MyResultReceiver result = new MyResultReceiver(null);
mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
@ -547,7 +697,7 @@ public final class TetheringServiceTest {
runAsNoPermission((none) -> {
mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
true /* showEntitlementUi */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@ -567,14 +717,14 @@ public final class TetheringServiceTest {
runAsWriteSettings((none) -> {
mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
true /* showEntitlementUi */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((none) -> {
runRequestLatestTetheringEntitlementResult();
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@ -668,7 +818,7 @@ public final class TetheringServiceTest {
public void testStopAllTethering() throws Exception {
runAsNoPermission((result) -> {
mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@ -680,14 +830,14 @@ public final class TetheringServiceTest {
runAsWriteSettings((result) -> {
mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runStopAllTethering(result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@ -703,6 +853,7 @@ public final class TetheringServiceTest {
private void runIsTetheringSupported(final TestTetheringResult result) throws Exception {
mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetheringSupported();
verify(mTethering).isTetheringAllowed();
result.assertResult(TETHER_ERROR_NO_ERROR);
@ -713,7 +864,7 @@ public final class TetheringServiceTest {
runAsNoPermission((result) -> {
mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@ -726,14 +877,13 @@ public final class TetheringServiceTest {
runAsWriteSettings((result) -> {
mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runIsTetheringSupported(result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@ -741,6 +891,7 @@ public final class TetheringServiceTest {
runAsTetheringDisallowed((result) -> {
mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetheringSupported();
verify(mTethering).isTetheringAllowed();
result.assertResult(TETHER_ERROR_UNSUPPORTED);

View file

@ -0,0 +1,70 @@
/*
* Copyright (C) 2024 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.networkstack.tethering.util;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
import androidx.test.filters.SmallTest;
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)
@SmallTest
public class TetheringPermissionsUtilsTest {
private static final int TEST_UID = 12345;
private static final String TEST_PACKAGE = "test.package";
@Mock Context mContext;
@Mock Context mUserContext;
@Mock DevicePolicyManager mDevicePolicyManager;
@Mock TelephonyManager mTelephonyManager;
TetheringPermissionsUtils mTetheringPermissionsUtils;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mContext.createContextAsUser(UserHandle.getUserHandleForUid(TEST_UID), 0))
.thenReturn(mUserContext);
when(mUserContext.getSystemService(DevicePolicyManager.class))
.thenReturn(mDevicePolicyManager);
when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
mTetheringPermissionsUtils = new TetheringPermissionsUtils(mContext);
}
@Test
public void testIsDeviceOwner() {
when(mDevicePolicyManager.isDeviceOwnerApp(TEST_PACKAGE)).thenReturn(false);
assertThat(mTetheringPermissionsUtils.isDeviceOwner(TEST_UID, TEST_PACKAGE)).isFalse();
when(mDevicePolicyManager.isDeviceOwnerApp(TEST_PACKAGE)).thenReturn(true);
assertThat(mTetheringPermissionsUtils.isDeviceOwner(TEST_UID, TEST_PACKAGE)).isTrue();
}
}