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

Commit 61b9bf9c authored by Kholoud Mohamed's avatar Kholoud Mohamed
Browse files

Persist policies set through the device policy engine

Bug: 232918480
Test: manual
Change-Id: Ia7eb2e356ae470e02b669f6eb7afd8004a4c725b
parent 4e10f847
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -3920,6 +3920,11 @@ public class DevicePolicyManager {
     */
    public static final String AUTO_TIMEZONE_POLICY = "autoTimezone";
    /**
     * @hide
     */
    public static final String PERMISSION_GRANT_POLICY_KEY = "permissionGrant";
    // TODO: Expose this as SystemAPI once we add the query API
    /**
     * @hide
@@ -3928,7 +3933,7 @@ public class DevicePolicyManager {
            @NonNull String packageName, @NonNull String permission) {
        Objects.requireNonNull(packageName);
        Objects.requireNonNull(permission);
        return "permissionGrant_" + packageName + "_" + permission;
        return PERMISSION_GRANT_POLICY_KEY + "_" + packageName + "_" + permission;
    }
    /**
+39 −0
Original line number Diff line number Diff line
/*
 * 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.devicepolicy;

import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;

import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;

final class BooleanPolicySerializer extends PolicySerializer<Boolean> {

    @Override
    void saveToXml(TypedXmlSerializer serializer, String attributeName, Boolean value)
            throws IOException {
        serializer.attributeBoolean(/* namespace= */ null, attributeName, value);
    }

    @Override
    Boolean readFromXml(TypedXmlPullParser parser, String attributeName)
            throws XmlPullParserException {
        return parser.getAttributeBoolean(/* namespace= */ null, attributeName);
    }
}
+221 −8
Original line number Diff line number Diff line
@@ -19,9 +19,25 @@ package com.android.server.devicepolicy;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Environment;
import android.os.UserHandle;
import android.util.AtomicFile;
import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;

import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;

import libcore.io.IoUtils;

import org.xmlpull.v1.XmlPullParserException;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@@ -31,9 +47,10 @@ import java.util.Objects;
 * admins on the device.
 */
final class DevicePolicyEngine {
    private static final String TAG = "DevicePolicyEngine";
    static final String TAG = "DevicePolicyEngine";

    private final Context mContext;
    // TODO(b/256849338): add more granular locks
    private final Object mLock = new Object();

    /**
@@ -133,6 +150,7 @@ final class DevicePolicyEngine {
            if (policyChanged) {
                enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(), userId);
            }
            write();
            return policyChanged;
        }
    }
@@ -159,6 +177,7 @@ final class DevicePolicyEngine {
                enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(),
                        UserHandle.USER_ALL);
            }
            write();
            return policyChanged;
        }
    }
@@ -193,15 +212,15 @@ final class DevicePolicyEngine {

        if (policyDefinition.isGlobalOnlyPolicy()) {
            throw new IllegalArgumentException("Can't set global policy "
                    + policyDefinition.getKey() + " locally.");
                    + policyDefinition.getPolicyKey() + " locally.");
        }

        if (!mUserPolicies.contains(userId)) {
            mUserPolicies.put(userId, new HashMap<>());
        }
        if (!mUserPolicies.get(userId).containsKey(policyDefinition.getKey())) {
        if (!mUserPolicies.get(userId).containsKey(policyDefinition.getPolicyKey())) {
            mUserPolicies.get(userId).put(
                    policyDefinition.getKey(), new PolicyState<>(policyDefinition));
                    policyDefinition.getPolicyKey(), new PolicyState<>(policyDefinition));
        }
        return getPolicyState(mUserPolicies.get(userId), policyDefinition);
    }
@@ -211,12 +230,12 @@ final class DevicePolicyEngine {

        if (policyDefinition.isLocalOnlyPolicy()) {
            throw new IllegalArgumentException("Can't set local policy "
                    + policyDefinition.getKey() + " globally.");
                    + policyDefinition.getPolicyKey() + " globally.");
        }

        if (!mGlobalPolicies.containsKey(policyDefinition.getKey())) {
        if (!mGlobalPolicies.containsKey(policyDefinition.getPolicyKey())) {
            mGlobalPolicies.put(
                    policyDefinition.getKey(), new PolicyState<>(policyDefinition));
                    policyDefinition.getPolicyKey(), new PolicyState<>(policyDefinition));
        }
        return getPolicyState(mGlobalPolicies, policyDefinition);
    }
@@ -228,7 +247,7 @@ final class DevicePolicyEngine {
            // we've created two policies with the same key but different types - we can only have
            // stored a PolicyState of the right type.
            PolicyState<V> policyState = (PolicyState<V>) policies.get(
                    policyDefinition.getKey());
                    policyDefinition.getPolicyKey());
            return policyState;
        } catch (ClassCastException exception) {
            // TODO: handle exception properly
@@ -245,4 +264,198 @@ final class DevicePolicyEngine {
        // TODO: notify calling admin of result (e.g. success, runtime failure, policy set by
        //  a different admin)
    }

    private void write() {
        Log.d(TAG, "Writing device policies to file.");
        new DevicePoliciesReaderWriter().writeToFileLocked();
    }

    // TODO(b/256852787): trigger resolving logic after loading policies as roles are recalculated
    //  and could result in a different enforced policy
    void load() {
        Log.d(TAG, "Reading device policies from file.");
        synchronized (mLock) {
            clear();
            new DevicePoliciesReaderWriter().readFromFileLocked();
        }
    }

    private void clear() {
        synchronized (mLock) {
            mGlobalPolicies.clear();
            mUserPolicies.clear();
        }
    }

    private class DevicePoliciesReaderWriter {
        private static final String DEVICE_POLICIES_XML = "device_policies.xml";
        private static final String TAG_USER_POLICY_ENTRY = "user-policy-entry";
        private static final String TAG_DEVICE_POLICY_ENTRY = "device-policy-entry";
        private static final String TAG_ADMINS_POLICY_ENTRY = "admins-policy-entry";
        private static final String ATTR_USER_ID = "user-id";
        private static final String ATTR_POLICY_ID = "policy-id";

        private final File mFile;

        private DevicePoliciesReaderWriter() {
            mFile = new File(Environment.getDataSystemDirectory(), DEVICE_POLICIES_XML);
        }

        void writeToFileLocked() {
            Log.d(TAG, "Writing to " + mFile);

            AtomicFile f = new AtomicFile(mFile);
            FileOutputStream outputStream = null;
            try {
                outputStream = f.startWrite();
                TypedXmlSerializer out = Xml.resolveSerializer(outputStream);

                out.startDocument(null, true);

                // Actual content
                writeInner(out);

                out.endDocument();
                out.flush();

                // Commit the content.
                f.finishWrite(outputStream);
                outputStream = null;

            } catch (IOException e) {
                Log.e(TAG, "Exception when writing", e);
                if (outputStream != null) {
                    f.failWrite(outputStream);
                }
            }
        }

        // TODO(b/256846294): Add versioning to read/write
        void writeInner(TypedXmlSerializer serializer) throws IOException {
            writeUserPoliciesInner(serializer);
            writeDevicePoliciesInner(serializer);
        }

        private void writeUserPoliciesInner(TypedXmlSerializer serializer) throws IOException {
            if (mUserPolicies != null) {
                for (int i = 0; i < mUserPolicies.size(); i++) {
                    int userId = mUserPolicies.keyAt(i);
                    for (Map.Entry<String, PolicyState<?>> policy : mUserPolicies.get(
                            userId).entrySet()) {
                        serializer.startTag(/* namespace= */ null, TAG_USER_POLICY_ENTRY);

                        serializer.attributeInt(/* namespace= */ null, ATTR_USER_ID, userId);
                        serializer.attribute(
                                /* namespace= */ null, ATTR_POLICY_ID, policy.getKey());

                        serializer.startTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
                        policy.getValue().saveToXml(serializer);
                        serializer.endTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);

                        serializer.endTag(/* namespace= */ null, TAG_USER_POLICY_ENTRY);
                    }
                }
            }
        }

        private void writeDevicePoliciesInner(TypedXmlSerializer serializer) throws IOException {
            if (mGlobalPolicies != null) {
                for (Map.Entry<String, PolicyState<?>> policy : mGlobalPolicies.entrySet()) {
                    serializer.startTag(/* namespace= */ null, TAG_DEVICE_POLICY_ENTRY);

                    serializer.attribute(/* namespace= */ null, ATTR_POLICY_ID, policy.getKey());

                    serializer.startTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
                    policy.getValue().saveToXml(serializer);
                    serializer.endTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);

                    serializer.endTag(/* namespace= */ null, TAG_DEVICE_POLICY_ENTRY);
                }
            }
        }

        void readFromFileLocked() {
            if (!mFile.exists()) {
                Log.d(TAG, "" + mFile + " doesn't exist");
                return;
            }

            Log.d(TAG, "Reading from " + mFile);
            AtomicFile f = new AtomicFile(mFile);
            InputStream input = null;
            try {
                input = f.openRead();
                TypedXmlPullParser parser = Xml.resolvePullParser(input);

                readInner(parser);

            } catch (XmlPullParserException | IOException | ClassNotFoundException e) {
                Log.e(TAG, "Error parsing resources file", e);
            } finally {
                IoUtils.closeQuietly(input);
            }
        }

        private void readInner(TypedXmlPullParser parser)
                throws IOException, XmlPullParserException, ClassNotFoundException {
            int outerDepth = parser.getDepth();
            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
                String tag = parser.getName();
                switch (tag) {
                    case TAG_USER_POLICY_ENTRY:
                        readUserPoliciesInner(parser);
                        break;
                    case TAG_DEVICE_POLICY_ENTRY:
                        readDevicePoliciesInner(parser);
                        break;
                    default:
                        Log.e(TAG, "Unknown tag " + tag);
                }
            }
        }

        private void readUserPoliciesInner(TypedXmlPullParser parser)
                throws XmlPullParserException, IOException {
            int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
            String policyKey = parser.getAttributeValue(
                    /* namespace= */ null, ATTR_POLICY_ID);
            if (!mUserPolicies.contains(userId)) {
                mUserPolicies.put(userId, new HashMap<>());
            }
            PolicyState<?> adminsPolicy = parseAdminsPolicy(parser);
            if (adminsPolicy != null) {
                mUserPolicies.get(userId).put(policyKey, adminsPolicy);
            } else {
                Log.e(TAG, "Error parsing file, " + policyKey + "doesn't have an "
                        + "AdminsPolicy.");
            }
        }

        private void readDevicePoliciesInner(TypedXmlPullParser parser)
                throws IOException, XmlPullParserException {
            String policyKey = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_ID);
            PolicyState<?> adminsPolicy = parseAdminsPolicy(parser);
            if (adminsPolicy != null) {
                mGlobalPolicies.put(policyKey, adminsPolicy);
            } else {
                Log.e(TAG, "Error parsing file, " + policyKey + "doesn't have an "
                        + "AdminsPolicy.");
            }
        }

        @Nullable
        private PolicyState<?> parseAdminsPolicy(TypedXmlPullParser parser)
                throws XmlPullParserException, IOException {
            int outerDepth = parser.getDepth();
            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
                String tag = parser.getName();
                if (tag.equals(TAG_ADMINS_POLICY_ENTRY)) {
                    return PolicyState.readFromXml(parser);
                }
                Log.e(TAG, "Unknown tag " + tag);
            }
            Log.e(TAG, "Error parsing file, AdminsPolicy not found");
            return null;
        }
    }
}
+101 −22
Original line number Diff line number Diff line
@@ -20,51 +20,91 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;

import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.role.RoleManagerLocal;
import com.android.server.LocalManagerRegistry;

import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * {@code EnforcingAdmins} can have the following authority types:
 *
 * <ul>
 *     <li> {@link #DPC_AUTHORITY} meaning it's an enterprise admin (e.g. PO, DO, COPE)
 *     <li> {@link #DEVICE_ADMIN_AUTHORITY} which is a legacy non enterprise admin
 *     <li> Or a role authority, in which case {@link #mAuthorities} contains a list of all roles
 *     held by the given {@code packageName}
 * </ul>
 *
 */
final class EnforcingAdmin {
    static final String ROLE_AUTHORITY_PREFIX = "role:";
    static final String DPC_AUTHORITY = "enterprise";
    static final String DEVICE_ADMIN_AUTHORITY = "device_admin";
    static final String DEFAULT_AUTHORITY = "default";

    private static final String ATTR_PACKAGE_NAME = "package-name";
    private static final String ATTR_CLASS_NAME = "class-name";
    private static final String ATTR_AUTHORITIES = "authorities";
    private static final String ATTR_AUTHORITIES_SEPARATOR = ";";
    private static final String ATTR_USER_ID = "user-id";
    private static final String ATTR_IS_ROLE = "is-role";

    private final String mPackageName;
    // This is needed for DPCs and device admins
    @Nullable private final ComponentName mComponentName;
    // TODO: implement lazy loading for authorities
    private final Set<String> mAuthorities;
    // This is needed for DPCs and active admins
    private final ComponentName mComponentName;
    private Set<String> mAuthorities;
    private final int mUserId;
    private final boolean mIsRoleAuthority;

    static EnforcingAdmin createEnforcingAdmin(@NonNull String packageName, int userId) {
        Objects.requireNonNull(packageName);
        return new EnforcingAdmin(packageName, getRoleAuthoritiesOrDefault(packageName, userId));
        return new EnforcingAdmin(packageName, userId);
    }

    static EnforcingAdmin createEnterpriseEnforcingAdmin(ComponentName componentName) {
    static EnforcingAdmin createEnterpriseEnforcingAdmin(@NonNull ComponentName componentName) {
        Objects.requireNonNull(componentName);
        return new EnforcingAdmin(componentName, Set.of(DPC_AUTHORITY));
        return new EnforcingAdmin(
                componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY));
    }

    static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName) {
        Objects.requireNonNull(componentName);
        return new EnforcingAdmin(componentName, Set.of(DEVICE_ADMIN_AUTHORITY));
        return new EnforcingAdmin(
                componentName.getPackageName(), componentName, Set.of(DEVICE_ADMIN_AUTHORITY));
    }

    private EnforcingAdmin(String packageName, Set<String> authorities) {
    private EnforcingAdmin(
            String packageName, ComponentName componentName, Set<String> authorities) {
        Objects.requireNonNull(packageName);
        Objects.requireNonNull(componentName);
        Objects.requireNonNull(authorities);

        // Role authorities should not be using this constructor
        mIsRoleAuthority = false;
        mPackageName = packageName;
        mComponentName = null;
        mComponentName = componentName;
        mAuthorities = new HashSet<>(authorities);
        mUserId = -1; // not needed for non role authorities
    }

    private EnforcingAdmin(ComponentName componentName, Set<String> authorities) {
        mPackageName = componentName.getPackageName();
        mComponentName = componentName;
        mAuthorities = new HashSet<>(authorities);
    private EnforcingAdmin(String packageName, int userId) {
        Objects.requireNonNull(packageName);

        // Only role authorities use this constructor.
        mIsRoleAuthority = true;
        mPackageName = packageName;
        mUserId = userId;
        mComponentName = null;
        // authorities will be loaded when needed
        mAuthorities = null;
    }

    private static Set<String> getRoleAuthoritiesOrDefault(String packageName, int userId) {
@@ -90,8 +130,15 @@ final class EnforcingAdmin {
        return roles;
    }

    private Set<String> getAuthorities() {
        if (mAuthorities == null) {
            mAuthorities = getRoleAuthoritiesOrDefault(mPackageName, mUserId);
        }
        return mAuthorities;
    }

    boolean hasAuthority(String authority) {
        return mAuthorities.contains(authority);
        return getAuthorities().contains(authority);
    }

    /**
@@ -113,24 +160,56 @@ final class EnforcingAdmin {
        EnforcingAdmin other = (EnforcingAdmin) o;
        return Objects.equals(mPackageName, other.mPackageName)
                && Objects.equals(mComponentName, other.mComponentName)
                && Objects.equals(mIsRoleAuthority, other.mIsRoleAuthority)
                && hasMatchingAuthorities(this, other);
    }

    private static boolean hasMatchingAuthorities(EnforcingAdmin admin1, EnforcingAdmin admin2) {
        if (admin1.mAuthorities.equals(admin2.mAuthorities)) {
        if (admin1.mIsRoleAuthority && admin2.mIsRoleAuthority) {
            return true;
        }
        return !admin1.hasAuthority(DPC_AUTHORITY) && !admin1.hasAuthority(DEVICE_ADMIN_AUTHORITY)
                && !admin2.hasAuthority(DPC_AUTHORITY) && !admin2.hasAuthority(
                DEVICE_ADMIN_AUTHORITY);
        return admin1.getAuthorities().equals(admin2.getAuthorities());
    }

    @Override
    public int hashCode() {
        if (mAuthorities.contains(DPC_AUTHORITY) || mAuthorities.contains(DEVICE_ADMIN_AUTHORITY)) {
            return Objects.hash(mComponentName, mAuthorities);
        } else {
        if (mIsRoleAuthority) {
            // TODO(b/256854977): should we add UserId?
            return Objects.hash(mPackageName);
        } else {
            return Objects.hash(mComponentName, getAuthorities());
        }
    }

    void saveToXml(TypedXmlSerializer serializer) throws IOException {
        serializer.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, mPackageName);
        serializer.attributeBoolean(/* namespace= */ null, ATTR_IS_ROLE, mIsRoleAuthority);
        if (mIsRoleAuthority) {
            serializer.attributeInt(/* namespace= */ null, ATTR_USER_ID, mUserId);
        } else {
            serializer.attribute(
                    /* namespace= */ null, ATTR_CLASS_NAME, mComponentName.getClassName());
            serializer.attribute(
                    /* namespace= */ null,
                    ATTR_AUTHORITIES,
                    String.join(ATTR_AUTHORITIES_SEPARATOR, getAuthorities()));
        }
    }

    static EnforcingAdmin readFromXml(TypedXmlPullParser parser)
            throws XmlPullParserException {
        String packageName = parser.getAttributeValue(/* namespace= */ null, ATTR_PACKAGE_NAME);
        boolean isRoleAuthority = parser.getAttributeBoolean(/* namespace= */ null, ATTR_IS_ROLE);
        String authoritiesStr = parser.getAttributeValue(/* namespace= */ null, ATTR_AUTHORITIES);

        if (isRoleAuthority) {
            int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
            return new EnforcingAdmin(packageName, userId);
        } else {
            String className = parser.getAttributeValue(/* namespace= */ null, ATTR_CLASS_NAME);
            ComponentName componentName = new ComponentName(packageName, className);
            Set<String> authorities = Set.of(authoritiesStr.split(ATTR_AUTHORITIES_SEPARATOR));
            return new EnforcingAdmin(packageName, componentName, authorities);
        }
    }
}
+39 −0
Original line number Diff line number Diff line
/*
 * 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.devicepolicy;

import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;

import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;

final class IntegerPolicySerializer extends PolicySerializer<Integer> {

    @Override
    void saveToXml(TypedXmlSerializer serializer, String attributeName, Integer value)
            throws IOException {
        serializer.attributeInt(/* namespace= */ null, attributeName, value);
    }

    @Override
    Integer readFromXml(TypedXmlPullParser parser, String attributeName)
            throws XmlPullParserException {
        return parser.getAttributeInt(/* namespace= */ null, attributeName);
    }
}
Loading