From 4c052f237a108457fca3d3864c5654ebd4505111 Mon Sep 17 00:00:00 2001 From: Bartosz Fabianowski Date: Mon, 25 Jan 2016 14:18:43 +0100 Subject: [PATCH] Implement user affiliation A user/profile is considered affiliated if it is managed by the same entity as the device. This is determined by having the device owner and profile owners specify a set of opaque affiliation ids each. If the sets intersect, they must have come from the same source, which means that the device owner and profile owner are controlled by the same entity. BUG=25599229 Change-Id: I393fe0de70272307ed3c811aaba4b48a5109c562 --- .../android/app/admin/DevicePolicyManager.java | 43 ++++++++++++++ .../android/app/admin/IDevicePolicyManager.aidl | 3 + .../devicepolicy/DevicePolicyManagerService.java | 57 ++++++++++++++++++ .../devicepolicy/DevicePolicyManagerTest.java | 67 ++++++++++++++++++++++ .../android/server/devicepolicy/DpmTestBase.java | 29 ++++++---- 5 files changed, 187 insertions(+), 12 deletions(-) diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index a655bfd823b1..53f7b295f2ad 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -67,6 +67,7 @@ import java.security.spec.PKCS8EncodedKeySpec; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; /** * Public interface for managing policies enforced on a device. Most clients of this class must be @@ -5193,4 +5194,46 @@ public class DevicePolicyManager { return 0; } } + + /** + * @hide + * Indicates the entity that controls the device or profile owner. A user/profile is considered + * affiliated if it is managed by the same entity as the device. + * + *

By definition, the user that the device owner runs on is always affiliated. Any other + * user/profile is considered affiliated if the following conditions are both met: + *

+ * + * @param admin Which profile or device owner this request is associated with. + * @param ids A set of opaque affiliation ids. + */ + public void setAffiliationIds(@NonNull ComponentName admin, Set ids) { + try { + mService.setAffiliationIds(admin, new ArrayList(ids)); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + + /** + * @hide + * Returns whether this user/profile is affiliated with the device. See + * {@link #setAffiliationIds} for the definition of affiliation. + * + * @return whether this user/profile is affiliated with the device. + */ + public boolean isAffiliatedUser() { + try { + return mService != null && mService.isAffiliatedUser(); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + return false; + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 82115a2880f4..57865f4c4d6c 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -267,4 +267,7 @@ interface IDevicePolicyManager { void setOrganizationColor(in ComponentName admin, in int color); int getOrganizationColor(in ComponentName admin); int getOrganizationColorForUser(int userHandle); + + void setAffiliationIds(in ComponentName admin, in List ids); + boolean isAffiliatedUser(); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index f68a2996541d..7232562e9669 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -176,6 +176,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String TAG_STATUS_BAR = "statusbar"; + private static final String TAG_AFFILIATION_ID = "affiliation-id"; + private static final String ATTR_DISABLED = "disabled"; private static final String DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML = @@ -372,6 +374,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { String mApplicationRestrictionsManagingPackage; + Set mAffiliationIds = new ArraySet<>(); + public DevicePolicyData(int userHandle) { mUserHandle = userHandle; } @@ -2026,6 +2030,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.endTag(null, DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML); } + for (String id : policy.mAffiliationIds) { + out.startTag(null, TAG_AFFILIATION_ID); + out.attribute(null, "id", id); + out.endTag(null, TAG_AFFILIATION_ID); + } + out.endTag(null, "policies"); out.endDocument(); @@ -2100,6 +2110,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { policy.mLockTaskPackages.clear(); policy.mAdminList.clear(); policy.mAdminMap.clear(); + policy.mAffiliationIds.clear(); while ((type=parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { @@ -2157,6 +2168,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { parser.getAttributeValue(null, ATTR_DISABLED)); } else if (DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML.equals(tag)) { policy.doNotAskCredentialsOnBoot = true; + } else if (TAG_AFFILIATION_ID.equals(tag)) { + policy.mAffiliationIds.add(parser.getAttributeValue(null, "id")); } else { Slog.w(LOG_TAG, "Unknown tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -7741,4 +7754,48 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { : ActiveAdmin.DEF_ORGANIZATION_COLOR; } } + + @Override + public void setAffiliationIds(ComponentName admin, List ids) { + final Set affiliationIds = new ArraySet(ids); + final int callingUserId = mInjector.userHandleGetCallingUserId(); + + synchronized (this) { + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + getUserData(callingUserId).mAffiliationIds = affiliationIds; + saveSettingsLocked(callingUserId); + if (callingUserId != UserHandle.USER_SYSTEM && isDeviceOwner(admin, callingUserId)) { + // Affiliation ids specified by the device owner are additionally stored in + // UserHandle.USER_SYSTEM's DevicePolicyData. + getUserData(UserHandle.USER_SYSTEM).mAffiliationIds = affiliationIds; + saveSettingsLocked(UserHandle.USER_SYSTEM); + } + } + } + + @Override + public boolean isAffiliatedUser() { + final int callingUserId = mInjector.userHandleGetCallingUserId(); + + synchronized (this) { + if (mOwners.getDeviceOwnerUserId() == callingUserId) { + // The user that the DO is installed on is always affiliated. + return true; + } + final ComponentName profileOwner = getProfileOwner(callingUserId); + if (profileOwner == null + || !profileOwner.getPackageName().equals(mOwners.getDeviceOwnerPackageName())) { + return false; + } + final Set userAffiliationIds = getUserData(callingUserId).mAffiliationIds; + final Set deviceAffiliationIds = + getUserData(UserHandle.USER_SYSTEM).mAffiliationIds; + for (String id : userAffiliationIds) { + if (deviceAffiliationIds.contains(id)) { + return true; + } + } + } + return false; + } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 672058bca8d7..536fb70d9068 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -34,6 +34,7 @@ import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.test.MoreAsserts; +import android.util.ArraySet; import android.util.Pair; import org.mockito.ArgumentCaptor; @@ -44,6 +45,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; @@ -1476,4 +1478,69 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertNull(dpm.getLongSupportMessage(admin1)); } } + + /** + * Test for: + * {@link DevicePolicyManager#setAffiliationIds} + * {@link DevicePolicyManager#isAffiliatedUser} + */ + public void testUserAffiliation() throws Exception { + mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS); + mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); + mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL); + + // Check that the system user is unaffiliated. + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + assertFalse(dpm.isAffiliatedUser()); + + // Set a device owner on the system user. Check that the system user becomes affiliated. + setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID); + dpm.setActiveAdmin(admin1, /* replace =*/ false); + assertTrue(dpm.setDeviceOwner(admin1, "owner-name")); + assertTrue(dpm.isAffiliatedUser()); + + // Install a profile owner whose package name matches the device owner on a test user. Check + // that the test user is unaffiliated. + mContext.binder.callingUid = DpmMockContext.CALLER_UID; + setAsProfileOwner(admin2); + assertFalse(dpm.isAffiliatedUser()); + + // Have the profile owner specify a set of affiliation ids. Check that the test user remains + // unaffiliated. + final Set userAffiliationIds = new ArraySet<>(); + userAffiliationIds.add("red"); + userAffiliationIds.add("green"); + userAffiliationIds.add("blue"); + dpm.setAffiliationIds(admin2, userAffiliationIds); + assertFalse(dpm.isAffiliatedUser()); + + // Have the device owner specify a set of affiliation ids that do not intersect with those + // specified by the profile owner. Check that the test user remains unaffiliated. + final Set deviceAffiliationIds = new ArraySet<>(); + deviceAffiliationIds.add("cyan"); + deviceAffiliationIds.add("yellow"); + deviceAffiliationIds.add("magenta"); + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + dpm.setAffiliationIds(admin1, deviceAffiliationIds); + mContext.binder.callingUid = DpmMockContext.CALLER_UID; + assertFalse(dpm.isAffiliatedUser()); + + // Have the profile owner specify a set of affiliation ids that intersect with those + // specified by the device owner. Check that the test user becomes affiliated. + userAffiliationIds.add("yellow"); + dpm.setAffiliationIds(admin2, userAffiliationIds); + assertTrue(dpm.isAffiliatedUser()); + + // Change the profile owner to one whose package name does not match the device owner. Check + // that the test user is not affiliated anymore. + dpm.clearProfileOwner(admin2); + final ComponentName admin = new ComponentName("test", "test"); + markPackageAsInstalled(admin.getPackageName(), null, DpmMockContext.CALLER_USER_HANDLE); + assertTrue(dpm.setProfileOwner(admin, "owner-name", DpmMockContext.CALLER_USER_HANDLE)); + assertFalse(dpm.isAffiliatedUser()); + + // Check that the system user remains affiliated. + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + assertTrue(dpm.isAffiliatedUser()); + } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java index c557ab7e1e93..3dc1a9ab7847 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java @@ -65,6 +65,22 @@ public abstract class DpmTestBase extends AndroidTestCase { return mMockContext; } + protected void markPackageAsInstalled(String packageName, ApplicationInfo ai, int userId) + throws Exception { + final PackageInfo pi = DpmTestUtils.cloneParcelable( + mRealTestContext.getPackageManager().getPackageInfo( + mRealTestContext.getPackageName(), 0)); + assertTrue(pi.applicationInfo.flags != 0); + + if (ai != null) { + pi.applicationInfo = ai; + } + + doReturn(pi).when(mMockContext.ipackageManager).getPackageInfo( + eq(packageName), + eq(0), + eq(userId)); + } protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid) throws Exception { @@ -124,17 +140,6 @@ public abstract class DpmTestBase extends AndroidTestCase { eq(UserHandle.getUserId(packageUid))); // Set up getPackageInfo(). - - final PackageInfo pi = DpmTestUtils.cloneParcelable( - mRealTestContext.getPackageManager().getPackageInfo( - admin.getPackageName(), 0)); - assertTrue(pi.applicationInfo.flags != 0); - - pi.applicationInfo = ai; - - doReturn(pi).when(mMockContext.ipackageManager).getPackageInfo( - eq(admin.getPackageName()), - eq(0), - eq(UserHandle.getUserId(packageUid))); + markPackageAsInstalled(admin.getPackageName(), ai, UserHandle.getUserId(packageUid)); } } -- 2.11.0