diff --git a/core/java/android/net/UidRange.java b/core/java/android/net/UidRange.java index 2e586b39b5..fd465d95a9 100644 --- a/core/java/android/net/UidRange.java +++ b/core/java/android/net/UidRange.java @@ -48,6 +48,17 @@ public final class UidRange implements Parcelable { return start / PER_USER_RANGE; } + public boolean contains(int uid) { + return start <= uid && uid <= stop; + } + + /** + * @return {@code true} if this range contains every UID contained by the {@param other} range. + */ + public boolean containsRange(UidRange other) { + return start <= other.start && other.stop <= stop; + } + @Override public int hashCode() { int result = 17; diff --git a/services/tests/servicestests/src/com/android/server/connectivity/VpnTest.java b/services/tests/servicestests/src/com/android/server/connectivity/VpnTest.java new file mode 100644 index 0000000000..3295bf5f03 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/connectivity/VpnTest.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2016 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.connectivity; + +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.Mockito.*; + +import android.annotation.UserIdInt; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.net.UidRange; +import android.os.INetworkManagementService; +import android.os.Looper; +import android.os.UserHandle; +import android.os.UserManager; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.ArrayMap; +import android.util.ArraySet; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; + +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link Vpn}. + * + * Build, install and run with: + * runtest --path src/com/android/server/connectivity/VpnTest.java + */ +public class VpnTest extends AndroidTestCase { + private static final String TAG = "VpnTest"; + + // 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; + } + + @Mock private Context mContext; + @Mock private UserManager mUserManager; + @Mock private PackageManager mPackageManager; + @Mock private INetworkManagementService mNetService; + + @Override + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager); + doNothing().when(mNetService).registerObserver(any()); + } + + @SmallTest + public void testRestrictedProfilesAreAddedToVpn() { + setMockedUsers(primaryUser, secondaryUser, restrictedProfileA, restrictedProfileB); + + final Vpn vpn = createVpn(primaryUser.id); + final Set ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, + null, null); + + assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] { + UidRange.createForUser(primaryUser.id), + UidRange.createForUser(restrictedProfileA.id) + })), ranges); + } + + @SmallTest + public void testManagedProfilesAreNotAddedToVpn() { + setMockedUsers(primaryUser, managedProfileA); + + final Vpn vpn = createVpn(primaryUser.id); + final Set ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, + null, null); + + assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] { + UidRange.createForUser(primaryUser.id) + })), ranges); + } + + @SmallTest + public void testAddUserToVpnOnlyAddsOneUser() { + setMockedUsers(primaryUser, restrictedProfileA, managedProfileA); + + final Vpn vpn = createVpn(primaryUser.id); + final Set ranges = new ArraySet<>(); + vpn.addUserToRanges(ranges, primaryUser.id, null, null); + + assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] { + UidRange.createForUser(primaryUser.id) + })), ranges); + } + + @SmallTest + public void testUidWhiteAndBlacklist() throws Exception { + final Map packages = new ArrayMap<>(); + packages.put("com.example", 66); + packages.put("org.example", 77); + packages.put("net.example", 78); + setMockedPackages(packages); + + final Vpn vpn = createVpn(primaryUser.id); + final UidRange user = UidRange.createForUser(primaryUser.id); + + // Whitelist + final Set allow = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, + new ArrayList(packages.keySet()), null); + assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] { + new UidRange(user.start + 66, user.start + 66), + new UidRange(user.start + 77, user.start + 78) + })), allow); + + // Blacklist + final Set disallow = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, + null, new ArrayList(packages.keySet())); + assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] { + new UidRange(user.start, user.start + 65), + new UidRange(user.start + 67, user.start + 76), + new UidRange(user.start + 79, user.stop) + })), disallow); + } + + /** + * @return A subclass of {@link Vpn} which is reliably: + *
    + *
  • Associated with a specific user ID
  • + *
  • Not in always-on mode
  • + *
+ */ + private Vpn createVpn(@UserIdInt int userId) { + return new Vpn(Looper.myLooper(), mContext, mNetService, userId); + } + + /** + * Populate {@link #mUserManager} with a list of fake users. + */ + private void setMockedUsers(UserInfo... users) { + final Map userMap = new ArrayMap<>(); + for (UserInfo user : users) { + userMap.put(user.id, user); + } + + doAnswer(invocation -> { + return new ArrayList(userMap.values()); + }).when(mUserManager).getUsers(); + + doAnswer(invocation -> { + final int id = (int) invocation.getArguments()[0]; + return userMap.get(id); + }).when(mUserManager).getUserInfo(anyInt()); + + doAnswer(invocation -> { + final int id = (int) invocation.getArguments()[0]; + return (userMap.get(id).flags & UserInfo.FLAG_ADMIN) != 0; + }).when(mUserManager).canHaveRestrictedProfile(anyInt()); + } + + /** + * Populate {@link #mPackageManager} with a fake packageName-to-UID mapping. + */ + private void setMockedPackages(final Map packages) { + try { + doAnswer(invocation -> { + final String appName = (String) invocation.getArguments()[0]; + final int userId = (int) invocation.getArguments()[1]; + return UserHandle.getUid(userId, packages.get(appName)); + }).when(mPackageManager).getPackageUidAsUser(anyString(), anyInt()); + } catch (Exception e) { + } + } +}