Loading core/java/android/app/admin/DevicePolicyManager.java +6 −1 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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; } /** Loading services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java 0 → 100644 +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); } } services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +221 −8 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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(); /** Loading Loading @@ -133,6 +150,7 @@ final class DevicePolicyEngine { if (policyChanged) { enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(), userId); } write(); return policyChanged; } } Loading @@ -159,6 +177,7 @@ final class DevicePolicyEngine { enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(), UserHandle.USER_ALL); } write(); return policyChanged; } } Loading Loading @@ -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); } Loading @@ -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); } Loading @@ -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 Loading @@ -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; } } } services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java +101 −22 Original line number Diff line number Diff line Loading @@ -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) { Loading @@ -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); } /** Loading @@ -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); } } } services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java 0 → 100644 +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
core/java/android/app/admin/DevicePolicyManager.java +6 −1 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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; } /** Loading
services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java 0 → 100644 +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); } }
services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +221 −8 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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(); /** Loading Loading @@ -133,6 +150,7 @@ final class DevicePolicyEngine { if (policyChanged) { enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(), userId); } write(); return policyChanged; } } Loading @@ -159,6 +177,7 @@ final class DevicePolicyEngine { enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(), UserHandle.USER_ALL); } write(); return policyChanged; } } Loading Loading @@ -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); } Loading @@ -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); } Loading @@ -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 Loading @@ -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; } } }
services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java +101 −22 Original line number Diff line number Diff line Loading @@ -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) { Loading @@ -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); } /** Loading @@ -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); } } }
services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java 0 → 100644 +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); } }