diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp index 9d746b5b7e..0908ad2d3b 100644 --- a/tests/unit/Android.bp +++ b/tests/unit/Android.bp @@ -63,6 +63,7 @@ filegroup { "java/com/android/internal/net/NetworkUtilsInternalTest.java", "java/com/android/internal/net/VpnProfileTest.java", "java/com/android/server/NetworkManagementServiceTest.java", + "java/com/android/server/VpnManagerServiceTest.java", "java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java", "java/com/android/server/connectivity/IpConnectivityMetricsTest.java", "java/com/android/server/connectivity/MultipathPolicyTrackerTest.java", diff --git a/tests/unit/java/com/android/server/VpnManagerServiceTest.java b/tests/unit/java/com/android/server/VpnManagerServiceTest.java new file mode 100644 index 0000000000..1f238bb10a --- /dev/null +++ b/tests/unit/java/com/android/server/VpnManagerServiceTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2022 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; + +import static android.os.Build.VERSION_CODES.R; + +import static com.android.testutils.ContextUtils.mockService; +import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; + +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.annotation.UserIdInt; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.INetd; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.INetworkManagementService; +import android.os.Looper; +import android.os.UserManager; + +import androidx.test.filters.SmallTest; + +import com.android.server.connectivity.Vpn; +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRunner; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; + +@RunWith(DevSdkIgnoreRunner.class) +@IgnoreUpTo(R) // VpnManagerService is not available before R +@SmallTest +public class VpnManagerServiceTest extends VpnTestBase { + @Rule + public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); + + @Spy Context mContext; + private HandlerThread mHandlerThread; + @Mock private Handler mHandler; + @Mock private Vpn mVpn; + @Mock private INetworkManagementService mNms; + @Mock private ConnectivityManager mCm; + @Mock private UserManager mUserManager; + @Mock private INetd mNetd; + @Mock private PackageManager mPackageManager; + private VpnManagerServiceDependencies mDeps; + private VpnManagerService mService; + + class VpnManagerServiceDependencies extends VpnManagerService.Dependencies { + @Override + public HandlerThread makeHandlerThread() { + return mHandlerThread; + } + + @Override + public INetworkManagementService getINetworkManagementService() { + return mNms; + } + + @Override + public INetd getNetd() { + return mNetd; + } + + @Override + public Vpn createVpn(Looper looper, Context context, INetworkManagementService nms, + INetd netd, @UserIdInt int userId) { + return mVpn; + } + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mHandlerThread = new HandlerThread("TestVpnManagerService"); + mDeps = new VpnManagerServiceDependencies(); + doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt()); + doReturn(mPackageManager).when(mContext).getPackageManager(); + setMockedPackages(mPackageManager, sPackages); + + mockService(mContext, ConnectivityManager.class, Context.CONNECTIVITY_SERVICE, mCm); + mockService(mContext, UserManager.class, Context.USER_SERVICE, mUserManager); + + doReturn(new Intent()).when(mContext).registerReceiver( + any() /* receiver */, + any() /* intentFilter */, + any() /* broadcastPermission */, + eq(mHandler) /* scheduler */); + doReturn(SYSTEM_USER).when(mUserManager).getUserInfo(eq(SYSTEM_USER_ID)); + mService = new VpnManagerService(mContext, mDeps); + } + + @Test + public void testUpdateAppExclusionList() { + // Add user to create vpn in mVpn + mService.onUserStarted(SYSTEM_USER_ID); + assertNotNull(mService.mVpns.get(SYSTEM_USER_ID)); + + // Start vpn + mService.startVpnProfile(TEST_VPN_PKG); + verify(mVpn).startVpnProfile(eq(TEST_VPN_PKG)); + + // Remove package due to package replaced. + mService.onPackageRemoved(PKGS[0], PKG_UIDS[0], true /* isReplacing */); + verify(mVpn, never()).refreshPlatformVpnAppExclusionList(); + + // Add package due to package replaced. + mService.onPackageAdded(PKGS[0], PKG_UIDS[0], true /* isReplacing */); + verify(mVpn, never()).refreshPlatformVpnAppExclusionList(); + + // Remove package + mService.onPackageRemoved(PKGS[0], PKG_UIDS[0], false /* isReplacing */); + verify(mVpn).refreshPlatformVpnAppExclusionList(); + + // Add the package back + mService.onPackageAdded(PKGS[0], PKG_UIDS[0], false /* isReplacing */); + verify(mVpn, times(2)).refreshPlatformVpnAppExclusionList(); + } +} diff --git a/tests/unit/java/com/android/server/VpnTestBase.java b/tests/unit/java/com/android/server/VpnTestBase.java new file mode 100644 index 0000000000..6113872e21 --- /dev/null +++ b/tests/unit/java/com/android/server/VpnTestBase.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022 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; + +import static android.content.pm.UserInfo.FLAG_ADMIN; +import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; +import static android.content.pm.UserInfo.FLAG_PRIMARY; +import static android.content.pm.UserInfo.FLAG_RESTRICTED; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; + +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.os.Process; +import android.os.UserHandle; +import android.util.ArrayMap; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** Common variables or methods shared between VpnTest and VpnManagerServiceTest. */ +public class VpnTestBase { + protected static final String TEST_VPN_PKG = "com.testvpn.vpn"; + /** + * Names and UIDs for some fake packages. Important points: + * - UID is ordered increasing. + * - One pair of packages have consecutive UIDs. + */ + protected static final String[] PKGS = {"com.example", "org.example", "net.example", "web.vpn"}; + protected static final int[] PKG_UIDS = {10066, 10077, 10078, 10400}; + // Mock packages + protected static final Map sPackages = new ArrayMap<>(); + static { + for (int i = 0; i < PKGS.length; i++) { + sPackages.put(PKGS[i], PKG_UIDS[i]); + } + sPackages.put(TEST_VPN_PKG, Process.myUid()); + } + + // Mock users + protected static final int SYSTEM_USER_ID = 0; + protected static final UserInfo SYSTEM_USER = new UserInfo(0, "system", UserInfo.FLAG_PRIMARY); + protected static final UserInfo PRIMARY_USER = new UserInfo(27, "Primary", + FLAG_ADMIN | FLAG_PRIMARY); + protected static final UserInfo SECONDARY_USER = new UserInfo(15, "Secondary", FLAG_ADMIN); + protected static final UserInfo RESTRICTED_PROFILE_A = new UserInfo(40, "RestrictedA", + FLAG_RESTRICTED); + protected static final UserInfo RESTRICTED_PROFILE_B = new UserInfo(42, "RestrictedB", + FLAG_RESTRICTED); + protected static final UserInfo MANAGED_PROFILE_A = new UserInfo(45, "ManagedA", + FLAG_MANAGED_PROFILE); + static { + RESTRICTED_PROFILE_A.restrictedProfileParentId = PRIMARY_USER.id; + RESTRICTED_PROFILE_B.restrictedProfileParentId = SECONDARY_USER.id; + MANAGED_PROFILE_A.profileGroupId = PRIMARY_USER.id; + } + + // Populate a fake packageName-to-UID mapping. + protected void setMockedPackages(PackageManager mockPm, final Map packages) { + try { + doAnswer(invocation -> { + final String appName = (String) invocation.getArguments()[0]; + final int userId = (int) invocation.getArguments()[1]; + + final Integer appId = packages.get(appName); + if (appId == null) { + throw new PackageManager.NameNotFoundException(appName); + } + + return UserHandle.getUid(userId, appId); + }).when(mockPm).getPackageUidAsUser(anyString(), anyInt()); + } catch (Exception e) { + } + } + + protected List toList(int[] arr) { + return Arrays.stream(arr).boxed().collect(Collectors.toList()); + } +} diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java index 50df51aedc..0891ee36d9 100644 --- a/tests/unit/java/com/android/server/connectivity/VpnTest.java +++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java @@ -20,10 +20,6 @@ import static android.Manifest.permission.BIND_VPN_SERVICE; import static android.Manifest.permission.CONTROL_VPN; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.content.pm.UserInfo.FLAG_ADMIN; -import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; -import static android.content.pm.UserInfo.FLAG_PRIMARY; -import static android.content.pm.UserInfo.FLAG_RESTRICTED; import static android.net.ConnectivityManager.NetworkCallback; import static android.net.INetd.IF_STATE_DOWN; import static android.net.INetd.IF_STATE_UP; @@ -97,10 +93,8 @@ import android.net.LinkProperties; import android.net.LocalSocket; import android.net.Network; import android.net.NetworkAgent; -import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkInfo.DetailedState; -import android.net.NetworkProvider; import android.net.RouteInfo; import android.net.UidRangeParcel; import android.net.VpnManager; @@ -122,7 +116,6 @@ import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.ConditionVariable; import android.os.INetworkManagementService; -import android.os.Looper; import android.os.ParcelFileDescriptor; import android.os.PowerWhitelistManager; import android.os.Process; @@ -146,6 +139,7 @@ import com.android.internal.util.HexDump; import com.android.modules.utils.build.SdkLevel; import com.android.server.DeviceIdleInternal; import com.android.server.IpSecService; +import com.android.server.VpnTestBase; import com.android.server.vcn.util.PersistableBundleUtils; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRunner; @@ -178,6 +172,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; @@ -192,28 +188,15 @@ import java.util.stream.Stream; */ @RunWith(DevSdkIgnoreRunner.class) @SmallTest -@IgnoreUpTo(VERSION_CODES.S_V2) -public class VpnTest { +@IgnoreUpTo(S_V2) +public class VpnTest extends VpnTestBase { private static final String TAG = "VpnTest"; @Rule public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); - // Mock users - static final UserInfo primaryUser = new UserInfo(27, "Primary", FLAG_ADMIN | FLAG_PRIMARY); - static final UserInfo secondaryUser = new UserInfo(15, "Secondary", FLAG_ADMIN); - static final UserInfo restrictedProfileA = new UserInfo(40, "RestrictedA", FLAG_RESTRICTED); - static final UserInfo restrictedProfileB = new UserInfo(42, "RestrictedB", FLAG_RESTRICTED); - static final UserInfo managedProfileA = new UserInfo(45, "ManagedA", FLAG_MANAGED_PROFILE); - static { - restrictedProfileA.restrictedProfileParentId = primaryUser.id; - restrictedProfileB.restrictedProfileParentId = secondaryUser.id; - managedProfileA.profileGroupId = primaryUser.id; - } - static final Network EGRESS_NETWORK = new Network(101); static final String EGRESS_IFACE = "wlan0"; - static final String TEST_VPN_PKG = "com.testvpn.vpn"; private static final String TEST_VPN_CLIENT = "2.4.6.8"; private static final String TEST_VPN_SERVER = "1.2.3.4"; private static final String TEST_VPN_IDENTITY = "identity"; @@ -250,23 +233,8 @@ public class VpnTest { private static final long TEST_TIMEOUT_MS = 500L; private static final String PRIMARY_USER_APP_EXCLUDE_KEY = "VPN_APP_EXCLUDED_27_com.testvpn.vpn"; - /** - * Names and UIDs for some fake packages. Important points: - * - UID is ordered increasing. - * - One pair of packages have consecutive UIDs. - */ - static final String[] PKGS = {"com.example", "org.example", "net.example", "web.vpn"}; static final String PKGS_BYTES = getPackageByteString(List.of(PKGS)); - static final int[] PKG_UIDS = {10066, 10077, 10078, 10400}; - - // Mock packages - static final Map mPackages = new ArrayMap<>(); - static { - for (int i = 0; i < PKGS.length; i++) { - mPackages.put(PKGS[i], PKG_UIDS[i]); - } - } - private static final Range PRI_USER_RANGE = uidRangeForUser(primaryUser.id); + private static final Range PRIMARY_USER_RANGE = uidRangeForUser(PRIMARY_USER.id); @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext; @Mock private UserManager mUserManager; @@ -308,7 +276,7 @@ public class VpnTest { mTestDeps = spy(new TestDeps()); when(mContext.getPackageManager()).thenReturn(mPackageManager); - setMockedPackages(mPackages); + setMockedPackages(sPackages); when(mContext.getPackageName()).thenReturn(TEST_VPN_PKG); when(mContext.getOpPackageName()).thenReturn(TEST_VPN_PKG); @@ -413,50 +381,51 @@ public class VpnTest { @Test public void testRestrictedProfilesAreAddedToVpn() { - setMockedUsers(primaryUser, secondaryUser, restrictedProfileA, restrictedProfileB); + setMockedUsers(PRIMARY_USER, SECONDARY_USER, RESTRICTED_PROFILE_A, RESTRICTED_PROFILE_B); - final Vpn vpn = createVpn(primaryUser.id); + final Vpn vpn = createVpn(PRIMARY_USER.id); // Assume the user can have restricted profiles. doReturn(true).when(mUserManager).canHaveRestrictedProfile(); final Set> ranges = - vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, null, null); + vpn.createUserAndRestrictedProfilesRanges(PRIMARY_USER.id, null, null); - assertEquals(rangeSet(PRI_USER_RANGE, uidRangeForUser(restrictedProfileA.id)), ranges); + assertEquals(rangeSet(PRIMARY_USER_RANGE, uidRangeForUser(RESTRICTED_PROFILE_A.id)), + ranges); } @Test public void testManagedProfilesAreNotAddedToVpn() { - setMockedUsers(primaryUser, managedProfileA); + setMockedUsers(PRIMARY_USER, MANAGED_PROFILE_A); - final Vpn vpn = createVpn(primaryUser.id); - final Set> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, - null, null); + final Vpn vpn = createVpn(PRIMARY_USER.id); + final Set> ranges = vpn.createUserAndRestrictedProfilesRanges( + PRIMARY_USER.id, null, null); - assertEquals(rangeSet(PRI_USER_RANGE), ranges); + assertEquals(rangeSet(PRIMARY_USER_RANGE), ranges); } @Test public void testAddUserToVpnOnlyAddsOneUser() { - setMockedUsers(primaryUser, restrictedProfileA, managedProfileA); + setMockedUsers(PRIMARY_USER, RESTRICTED_PROFILE_A, MANAGED_PROFILE_A); - final Vpn vpn = createVpn(primaryUser.id); + final Vpn vpn = createVpn(PRIMARY_USER.id); final Set> ranges = new ArraySet<>(); - vpn.addUserToRanges(ranges, primaryUser.id, null, null); + vpn.addUserToRanges(ranges, PRIMARY_USER.id, null, null); - assertEquals(rangeSet(PRI_USER_RANGE), ranges); + assertEquals(rangeSet(PRIMARY_USER_RANGE), ranges); } @Test public void testUidAllowAndDenylist() throws Exception { - final Vpn vpn = createVpn(primaryUser.id); - final Range user = PRI_USER_RANGE; + final Vpn vpn = createVpn(PRIMARY_USER.id); + final Range user = PRIMARY_USER_RANGE; final int userStart = user.getLower(); final int userStop = user.getUpper(); final String[] packages = {PKGS[0], PKGS[1], PKGS[2]}; // Allowed list - final Set> allow = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, + final Set> allow = vpn.createUserAndRestrictedProfilesRanges(PRIMARY_USER.id, Arrays.asList(packages), null /* disallowedApplications */); assertEquals(rangeSet( uidRange(userStart + PKG_UIDS[0], userStart + PKG_UIDS[0]), @@ -469,7 +438,7 @@ public class VpnTest { // Denied list final Set> disallow = - vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, + vpn.createUserAndRestrictedProfilesRanges(PRIMARY_USER.id, null /* allowedApplications */, Arrays.asList(packages)); assertEquals(rangeSet( uidRange(userStart, userStart + PKG_UIDS[0] - 1), @@ -491,7 +460,7 @@ public class VpnTest { @Test public void testGetAlwaysAndOnGetLockDown() throws Exception { - final Vpn vpn = createVpn(primaryUser.id); + final Vpn vpn = createVpn(PRIMARY_USER.id); // Default state. assertFalse(vpn.getAlwaysOn()); @@ -515,8 +484,8 @@ public class VpnTest { @Test public void testLockdownChangingPackage() throws Exception { - final Vpn vpn = createVpn(primaryUser.id); - final Range user = PRI_USER_RANGE; + final Vpn vpn = createVpn(PRIMARY_USER.id); + final Range user = PRIMARY_USER_RANGE; final int userStart = user.getLower(); final int userStop = user.getUpper(); // Set always-on without lockdown. @@ -549,8 +518,8 @@ public class VpnTest { @Test public void testLockdownAllowlist() throws Exception { - final Vpn vpn = createVpn(primaryUser.id); - final Range user = PRI_USER_RANGE; + final Vpn vpn = createVpn(PRIMARY_USER.id); + final Range user = PRIMARY_USER_RANGE; final int userStart = user.getLower(); final int userStop = user.getUpper(); // Set always-on with lockdown and allow app PKGS[2] from lockdown. @@ -660,9 +629,9 @@ public class VpnTest { @Test public void testLockdownRuleRepeatability() throws Exception { - final Vpn vpn = createVpn(primaryUser.id); + final Vpn vpn = createVpn(PRIMARY_USER.id); final UidRangeParcel[] primaryUserRangeParcel = new UidRangeParcel[] { - new UidRangeParcel(PRI_USER_RANGE.getLower(), PRI_USER_RANGE.getUpper())}; + new UidRangeParcel(PRIMARY_USER_RANGE.getLower(), PRIMARY_USER_RANGE.getUpper())}; // Given legacy lockdown is already enabled, vpn.setLockdown(true); verify(mConnectivityManager, times(1)).setRequireVpnForUids(true, @@ -693,9 +662,9 @@ public class VpnTest { @Test public void testLockdownRuleReversibility() throws Exception { doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN); - final Vpn vpn = createVpn(primaryUser.id); + final Vpn vpn = createVpn(PRIMARY_USER.id); final UidRangeParcel[] entireUser = { - new UidRangeParcel(PRI_USER_RANGE.getLower(), PRI_USER_RANGE.getUpper()) + new UidRangeParcel(PRIMARY_USER_RANGE.getLower(), PRIMARY_USER_RANGE.getUpper()) }; final UidRangeParcel[] exceptPkg0 = { new UidRangeParcel(entireUser[0].start, entireUser[0].start + PKG_UIDS[0] - 1), @@ -745,17 +714,17 @@ public class VpnTest { @Test public void testIsAlwaysOnPackageSupported() throws Exception { - final Vpn vpn = createVpn(primaryUser.id); + final Vpn vpn = createVpn(PRIMARY_USER.id); ApplicationInfo appInfo = new ApplicationInfo(); - when(mPackageManager.getApplicationInfoAsUser(eq(PKGS[0]), anyInt(), eq(primaryUser.id))) + when(mPackageManager.getApplicationInfoAsUser(eq(PKGS[0]), anyInt(), eq(PRIMARY_USER.id))) .thenReturn(appInfo); ServiceInfo svcInfo = new ServiceInfo(); ResolveInfo resInfo = new ResolveInfo(); resInfo.serviceInfo = svcInfo; when(mPackageManager.queryIntentServicesAsUser(any(), eq(PackageManager.GET_META_DATA), - eq(primaryUser.id))) + eq(PRIMARY_USER.id))) .thenReturn(Collections.singletonList(resInfo)); // null package name should return false @@ -779,9 +748,9 @@ public class VpnTest { @Test public void testNotificationShownForAlwaysOnApp() throws Exception { - final UserHandle userHandle = UserHandle.of(primaryUser.id); - final Vpn vpn = createVpn(primaryUser.id); - setMockedUsers(primaryUser); + final UserHandle userHandle = UserHandle.of(PRIMARY_USER.id); + final Vpn vpn = createVpn(PRIMARY_USER.id); + setMockedUsers(PRIMARY_USER); final InOrder order = inOrder(mNotificationManager); @@ -814,15 +783,15 @@ public class VpnTest { */ @Test public void testGetProfileNameForPackage() throws Exception { - final Vpn vpn = createVpn(primaryUser.id); - setMockedUsers(primaryUser); + final Vpn vpn = createVpn(PRIMARY_USER.id); + setMockedUsers(PRIMARY_USER); - final String expected = Credentials.PLATFORM_VPN + primaryUser.id + "_" + TEST_VPN_PKG; + final String expected = Credentials.PLATFORM_VPN + PRIMARY_USER.id + "_" + TEST_VPN_PKG; assertEquals(expected, vpn.getProfileNameForPackage(TEST_VPN_PKG)); } private Vpn createVpnAndSetupUidChecks(String... grantedOps) throws Exception { - return createVpnAndSetupUidChecks(primaryUser, grantedOps); + return createVpnAndSetupUidChecks(PRIMARY_USER, grantedOps); } private Vpn createVpnAndSetupUidChecks(UserInfo user, String... grantedOps) throws Exception { @@ -879,14 +848,11 @@ public class VpnTest { vpn.startVpnProfile(TEST_VPN_PKG); verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); - vpn.mNetworkAgent = new NetworkAgent(mContext, Looper.getMainLooper(), TAG, - new NetworkCapabilities.Builder().build(), new LinkProperties(), 10 /* score */, - new NetworkAgentConfig.Builder().build(), - new NetworkProvider(mContext, Looper.getMainLooper(), TAG)) {}; + vpn.mNetworkAgent = mMockNetworkAgent; return vpn; } - @Test @IgnoreUpTo(S_V2) + @Test public void testSetAndGetAppExclusionList() throws Exception { final Vpn vpn = prepareVpnForVerifyAppExclusionList(); verify(mVpnProfileStore, never()).put(eq(PRIMARY_USER_APP_EXCLUDE_KEY), any()); @@ -895,16 +861,90 @@ public class VpnTest { .put(eq(PRIMARY_USER_APP_EXCLUDE_KEY), eq(HexDump.hexStringToByteArray(PKGS_BYTES))); assertEquals(vpn.createUserAndRestrictedProfilesRanges( - primaryUser.id, null, Arrays.asList(PKGS)), + PRIMARY_USER.id, null, Arrays.asList(PKGS)), vpn.mNetworkCapabilities.getUids()); assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG)); } - @Test @IgnoreUpTo(S_V2) + @Test + public void testRefreshPlatformVpnAppExclusionList_updatesExcludedUids() throws Exception { + final Vpn vpn = prepareVpnForVerifyAppExclusionList(); + vpn.setAppExclusionList(TEST_VPN_PKG, Arrays.asList(PKGS)); + verify(mMockNetworkAgent).sendNetworkCapabilities(any()); + assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG)); + + reset(mMockNetworkAgent); + + // Remove one of the package + List newExcludedUids = toList(PKG_UIDS); + newExcludedUids.remove((Integer) PKG_UIDS[0]); + sPackages.remove(PKGS[0]); + vpn.refreshPlatformVpnAppExclusionList(); + + // List in keystore is not changed, but UID for the removed packages is no longer exempted. + assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG)); + assertEquals(makeVpnUidRange(PRIMARY_USER.id, newExcludedUids), + vpn.mNetworkCapabilities.getUids()); + ArgumentCaptor ncCaptor = + ArgumentCaptor.forClass(NetworkCapabilities.class); + verify(mMockNetworkAgent).sendNetworkCapabilities(ncCaptor.capture()); + assertEquals(makeVpnUidRange(PRIMARY_USER.id, newExcludedUids), + ncCaptor.getValue().getUids()); + + reset(mMockNetworkAgent); + + // Add the package back + newExcludedUids.add(PKG_UIDS[0]); + sPackages.put(PKGS[0], PKG_UIDS[0]); + vpn.refreshPlatformVpnAppExclusionList(); + + // List in keystore is not changed and the uid list should be updated in the net cap. + assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG)); + assertEquals(makeVpnUidRange(PRIMARY_USER.id, newExcludedUids), + vpn.mNetworkCapabilities.getUids()); + verify(mMockNetworkAgent).sendNetworkCapabilities(ncCaptor.capture()); + assertEquals(makeVpnUidRange(PRIMARY_USER.id, newExcludedUids), + ncCaptor.getValue().getUids()); + } + + private Set> makeVpnUidRange(int userId, List excludedList) { + final SortedSet list = new TreeSet<>(); + + final int userBase = userId * UserHandle.PER_USER_RANGE; + for (int uid : excludedList) { + final int applicationUid = UserHandle.getUid(userId, uid); + list.add(applicationUid); + list.add(Process.toSdkSandboxUid(applicationUid)); // Add Sdk Sandbox UID + } + + final int minUid = userBase; + final int maxUid = userBase + UserHandle.PER_USER_RANGE - 1; + final Set> ranges = new ArraySet<>(); + + // Iterate the list to create the ranges between each uid. + int start = minUid; + for (int uid : list) { + if (uid == start) { + start++; + } else { + ranges.add(new Range<>(start, uid - 1)); + start = uid + 1; + } + } + + // Create the range between last uid and max uid. + if (start <= maxUid) { + ranges.add(new Range<>(start, maxUid)); + } + + return ranges; + } + + @Test public void testSetAndGetAppExclusionListRestrictedUser() throws Exception { final Vpn vpn = prepareVpnForVerifyAppExclusionList(); // Mock it to restricted profile - when(mUserManager.getUserInfo(anyInt())).thenReturn(restrictedProfileA); + when(mUserManager.getUserInfo(anyInt())).thenReturn(RESTRICTED_PROFILE_A); // Restricted users cannot configure VPNs assertThrows(SecurityException.class, () -> vpn.setAppExclusionList(TEST_VPN_PKG, new ArrayList<>())); @@ -954,7 +994,7 @@ public class VpnTest { public void testProvisionVpnProfileRestrictedUser() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks( - restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); + RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); try { vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile); @@ -977,7 +1017,7 @@ public class VpnTest { public void testDeleteVpnProfileRestrictedUser() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks( - restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); + RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); try { vpn.deleteVpnProfile(TEST_VPN_PKG); @@ -1100,7 +1140,7 @@ public class VpnTest { public void testStartVpnProfileRestrictedUser() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks( - restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); + RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); try { vpn.startVpnProfile(TEST_VPN_PKG); @@ -1113,7 +1153,7 @@ public class VpnTest { public void testStopVpnProfileRestrictedUser() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks( - restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); + RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); try { vpn.stopVpnProfile(TEST_VPN_PKG); @@ -1184,7 +1224,7 @@ public class VpnTest { private void verifyVpnManagerEvent(String sessionKey, String category, int errorClass, int errorCode, VpnProfileState... profileState) { final Context userContext = - mContext.createContextAsUser(UserHandle.of(primaryUser.id), 0 /* flags */); + mContext.createContextAsUser(UserHandle.of(PRIMARY_USER.id), 0 /* flags */); final ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); final int verifyTimes = (profileState == null) ? 1 : profileState.length; @@ -1251,7 +1291,7 @@ public class VpnTest { assumeTrue(SdkLevel.isAtLeastT()); // Calling setAlwaysOnPackage() needs to hold CONTROL_VPN. doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN); - final Vpn vpn = createVpn(primaryUser.id); + final Vpn vpn = createVpn(PRIMARY_USER.id); // Enable VPN always-on for PKGS[1]. assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */, null /* lockdownAllowlist */)); @@ -1513,7 +1553,7 @@ public class VpnTest { public void testStartPlatformVpnIllegalArgumentExceptionInSetup() throws Exception { when(mIkev2SessionCreator.createIkeSession(any(), any(), any(), any(), any(), any())) .thenThrow(new IllegalArgumentException()); - final Vpn vpn = startLegacyVpn(createVpn(primaryUser.id), mVpnProfile); + final Vpn vpn = startLegacyVpn(createVpn(PRIMARY_USER.id), mVpnProfile); final NetworkCallback cb = triggerOnAvailableAndGetCallback(); verifyInterfaceSetCfgWithFlags(IF_STATE_UP); @@ -1533,18 +1573,18 @@ public class VpnTest { eq(AppOpsManager.MODE_ALLOWED)); verify(mSystemServices).settingsSecurePutStringForUser( - eq(Settings.Secure.ALWAYS_ON_VPN_APP), eq(TEST_VPN_PKG), eq(primaryUser.id)); + eq(Settings.Secure.ALWAYS_ON_VPN_APP), eq(TEST_VPN_PKG), eq(PRIMARY_USER.id)); verify(mSystemServices).settingsSecurePutIntForUser( eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN), eq(lockdownEnabled ? 1 : 0), - eq(primaryUser.id)); + eq(PRIMARY_USER.id)); verify(mSystemServices).settingsSecurePutStringForUser( - eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST), eq(""), eq(primaryUser.id)); + eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST), eq(""), eq(PRIMARY_USER.id)); } @Test public void testSetAndStartAlwaysOnVpn() throws Exception { - final Vpn vpn = createVpn(primaryUser.id); - setMockedUsers(primaryUser); + final Vpn vpn = createVpn(PRIMARY_USER.id); + setMockedUsers(PRIMARY_USER); // UID checks must return a different UID; otherwise it'll be treated as already prepared. final int uid = Process.myUid() + 1; @@ -1561,7 +1601,7 @@ public class VpnTest { } private Vpn startLegacyVpn(final Vpn vpn, final VpnProfile vpnProfile) throws Exception { - setMockedUsers(primaryUser); + setMockedUsers(PRIMARY_USER); // Dummy egress interface final LinkProperties lp = new LinkProperties(); @@ -1877,7 +1917,7 @@ public class VpnTest { doReturn(new Network(102)).when(mConnectivityManager).registerNetworkAgent(any(), any(), any(), any(), any(), any(), anyInt()); - final Vpn vpn = startLegacyVpn(createVpn(primaryUser.id), profile); + final Vpn vpn = startLegacyVpn(createVpn(PRIMARY_USER.id), profile); final TestDeps deps = (TestDeps) vpn.mDeps; testAndCleanup(() -> { @@ -1928,7 +1968,7 @@ public class VpnTest { legacyRunnerReady.open(); return new Network(102); }); - final Vpn vpn = startLegacyVpn(createVpn(primaryUser.id), profile); + final Vpn vpn = startLegacyVpn(createVpn(PRIMARY_USER.id), profile); final TestDeps deps = (TestDeps) vpn.mDeps; try { // udppsk and 1701 are the values for TYPE_L2TP_IPSEC_PSK