Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4c052f23 authored by Bartosz Fabianowski's avatar Bartosz Fabianowski
Browse files

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
parent 9ef46c26
Loading
Loading
Loading
Loading
+43 −0
Original line number Original line Diff line number Diff line
@@ -67,6 +67,7 @@ import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Collections;
import java.util.List;
import java.util.List;
import java.util.Set;


/**
/**
 * Public interface for managing policies enforced on a device. Most clients of this class must be
 * 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;
            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.
     *
     * <p> 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:
     * <ul>
     * <li>The device owner and the user's/profile's profile owner have called this method,
     *   specifying a set of opaque affiliation ids each. If the sets specified by the device owner
     *   and a profile owner intersect, they must have come from the same source, which means that
     *   the device owner and profile owner are controlled by the same entity.</li>
     * <li>The device owner's and profile owner's package names are the same.</li>
     * </ul>
     *
     * @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<String> ids) {
        try {
            mService.setAffiliationIds(admin, new ArrayList<String>(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;
        }
    }
}
}
+3 −0
Original line number Original line Diff line number Diff line
@@ -267,4 +267,7 @@ interface IDevicePolicyManager {
    void setOrganizationColor(in ComponentName admin, in int color);
    void setOrganizationColor(in ComponentName admin, in int color);
    int getOrganizationColor(in ComponentName admin);
    int getOrganizationColor(in ComponentName admin);
    int getOrganizationColorForUser(int userHandle);
    int getOrganizationColorForUser(int userHandle);

    void setAffiliationIds(in ComponentName admin, in List<String> ids);
    boolean isAffiliatedUser();
}
}
+57 −0
Original line number Original line Diff line number Diff line
@@ -176,6 +176,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {


    private static final String TAG_STATUS_BAR = "statusbar";
    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 ATTR_DISABLED = "disabled";


    private static final String DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML =
    private static final String DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML =
@@ -372,6 +374,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {


        String mApplicationRestrictionsManagingPackage;
        String mApplicationRestrictionsManagingPackage;


        Set<String> mAffiliationIds = new ArraySet<>();

        public DevicePolicyData(int userHandle) {
        public DevicePolicyData(int userHandle) {
            mUserHandle = userHandle;
            mUserHandle = userHandle;
        }
        }
@@ -2026,6 +2030,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                out.endTag(null, DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML);
                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.endTag(null, "policies");


            out.endDocument();
            out.endDocument();
@@ -2100,6 +2110,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
            policy.mLockTaskPackages.clear();
            policy.mLockTaskPackages.clear();
            policy.mAdminList.clear();
            policy.mAdminList.clear();
            policy.mAdminMap.clear();
            policy.mAdminMap.clear();
            policy.mAffiliationIds.clear();
            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
                   && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
                   && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
@@ -2157,6 +2168,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                            parser.getAttributeValue(null, ATTR_DISABLED));
                            parser.getAttributeValue(null, ATTR_DISABLED));
                } else if (DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML.equals(tag)) {
                } else if (DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML.equals(tag)) {
                    policy.doNotAskCredentialsOnBoot = true;
                    policy.doNotAskCredentialsOnBoot = true;
                } else if (TAG_AFFILIATION_ID.equals(tag)) {
                    policy.mAffiliationIds.add(parser.getAttributeValue(null, "id"));
                } else {
                } else {
                    Slog.w(LOG_TAG, "Unknown tag: " + tag);
                    Slog.w(LOG_TAG, "Unknown tag: " + tag);
                    XmlUtils.skipCurrentTag(parser);
                    XmlUtils.skipCurrentTag(parser);
@@ -7741,4 +7754,48 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                    : ActiveAdmin.DEF_ORGANIZATION_COLOR;
                    : ActiveAdmin.DEF_ORGANIZATION_COLOR;
        }
        }
    }
    }

    @Override
    public void setAffiliationIds(ComponentName admin, List<String> ids) {
        final Set<String> affiliationIds = new ArraySet<String>(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<String> userAffiliationIds = getUserData(callingUserId).mAffiliationIds;
            final Set<String> deviceAffiliationIds =
                    getUserData(UserHandle.USER_SYSTEM).mAffiliationIds;
            for (String id : userAffiliationIds) {
                if (deviceAffiliationIds.contains(id)) {
                    return true;
                }
            }
        }
        return false;
    }
}
}
+67 −0
Original line number Original line Diff line number Diff line
@@ -34,6 +34,7 @@ import android.os.Process;
import android.os.UserHandle;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManager;
import android.test.MoreAsserts;
import android.test.MoreAsserts;
import android.util.ArraySet;
import android.util.Pair;
import android.util.Pair;


import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentCaptor;
@@ -44,6 +45,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashMap;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.Set;


import static org.mockito.Matchers.any;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyInt;
@@ -1476,4 +1478,69 @@ public class DevicePolicyManagerTest extends DpmTestBase {
            assertNull(dpm.getLongSupportMessage(admin1));
            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<String> 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<String> 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());
    }
}
}
+17 −12
Original line number Original line Diff line number Diff line
@@ -65,6 +65,22 @@ public abstract class DpmTestBase extends AndroidTestCase {
        return mMockContext;
        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)
    protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid)
            throws Exception {
            throws Exception {
@@ -124,17 +140,6 @@ public abstract class DpmTestBase extends AndroidTestCase {
                eq(UserHandle.getUserId(packageUid)));
                eq(UserHandle.getUserId(packageUid)));


        // Set up getPackageInfo().
        // Set up getPackageInfo().

        markPackageAsInstalled(admin.getPackageName(), ai, UserHandle.getUserId(packageUid));
        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)));
    }
    }
}
}