1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.devicepolicy;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.admin.PolicyValue;
22 
23 import com.android.internal.util.XmlUtils;
24 import com.android.modules.utils.TypedXmlPullParser;
25 import com.android.modules.utils.TypedXmlSerializer;
26 import com.android.server.utils.Slogf;
27 
28 import org.xmlpull.v1.XmlPullParserException;
29 
30 import java.io.IOException;
31 import java.util.LinkedHashMap;
32 import java.util.Objects;
33 
34 /**
35  * Class containing all values set for a certain policy by different admins.
36  */
37 final class PolicyState<V> {
38 
39     private static final String TAG = "PolicyState";
40     private static final String TAG_ADMIN_POLICY_ENTRY = "admin-policy-entry";
41 
42     private static final String TAG_POLICY_DEFINITION_ENTRY = "policy-definition-entry";
43     private static final String TAG_RESOLVED_VALUE_ENTRY = "resolved-value-entry";
44     private static final String TAG_ENFORCING_ADMIN_ENTRY = "enforcing-admin-entry";
45     private static final String TAG_POLICY_VALUE_ENTRY = "policy-value-entry";
46     private final PolicyDefinition<V> mPolicyDefinition;
47     private final LinkedHashMap<EnforcingAdmin, PolicyValue<V>> mPoliciesSetByAdmins =
48             new LinkedHashMap<>();
49     private PolicyValue<V> mCurrentResolvedPolicy;
50 
PolicyState(@onNull PolicyDefinition<V> policyDefinition)51     PolicyState(@NonNull PolicyDefinition<V> policyDefinition) {
52         mPolicyDefinition = Objects.requireNonNull(policyDefinition);
53     }
54 
PolicyState( @onNull PolicyDefinition<V> policyDefinition, @NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<V>> policiesSetByAdmins, PolicyValue<V> currentEnforcedPolicy)55     private PolicyState(
56             @NonNull PolicyDefinition<V> policyDefinition,
57             @NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<V>> policiesSetByAdmins,
58             PolicyValue<V> currentEnforcedPolicy) {
59         Objects.requireNonNull(policyDefinition);
60         Objects.requireNonNull(policiesSetByAdmins);
61 
62         mPolicyDefinition = policyDefinition;
63         mPoliciesSetByAdmins.putAll(policiesSetByAdmins);
64         mCurrentResolvedPolicy = currentEnforcedPolicy;
65     }
66 
67     /**
68      * Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
69      */
addPolicy(@onNull EnforcingAdmin admin, @Nullable PolicyValue<V> policy)70     boolean addPolicy(@NonNull EnforcingAdmin admin, @Nullable PolicyValue<V> policy) {
71         Objects.requireNonNull(admin);
72 
73         //LinkedHashMap doesn't update the insertion order of existing keys, removing the existing
74         // key will cause it to update.
75         mPoliciesSetByAdmins.remove(admin);
76         mPoliciesSetByAdmins.put(admin, policy);
77 
78         return resolvePolicy();
79     }
80 
81     /**
82      * Takes into account global policies set by the admin when resolving the policy, this is only
83      * relevant to local policies that can be applied globally as well.
84      *
85      * <p> Note that local policies set by an admin takes precedence over global policies set by the
86      * same admin.
87      *
88      * Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
89      */
addPolicy( @onNull EnforcingAdmin admin, @Nullable PolicyValue<V> policy, LinkedHashMap<EnforcingAdmin, PolicyValue<V>> globalPoliciesSetByAdmins)90     boolean addPolicy(
91             @NonNull EnforcingAdmin admin, @Nullable PolicyValue<V> policy,
92             LinkedHashMap<EnforcingAdmin, PolicyValue<V>> globalPoliciesSetByAdmins) {
93         mPoliciesSetByAdmins.put(Objects.requireNonNull(admin), policy);
94 
95         return resolvePolicy(globalPoliciesSetByAdmins);
96     }
97 
98     /**
99      * Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
100      */
removePolicy(@onNull EnforcingAdmin admin)101     boolean removePolicy(@NonNull EnforcingAdmin admin) {
102         Objects.requireNonNull(admin);
103 
104         if (mPoliciesSetByAdmins.remove(admin) == null) {
105             return false;
106         }
107 
108         return resolvePolicy();
109     }
110 
111     /**
112      * Takes into account global policies set by the admin when resolving the policy, this is only
113      * relevant to local policies that can be applied globally as well.
114      *
115      * <p> Note that local policies set by an admin takes precedence over global policies set by the
116      * same admin.
117      *
118      * Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
119      */
removePolicy( @onNull EnforcingAdmin admin, LinkedHashMap<EnforcingAdmin, PolicyValue<V>> globalPoliciesSetByAdmins)120     boolean removePolicy(
121             @NonNull EnforcingAdmin admin,
122             LinkedHashMap<EnforcingAdmin, PolicyValue<V>> globalPoliciesSetByAdmins) {
123         Objects.requireNonNull(admin);
124 
125         if (mPoliciesSetByAdmins.remove(admin) == null) {
126             return false;
127         }
128 
129         return resolvePolicy(globalPoliciesSetByAdmins);
130     }
131 
132     /**
133      * Takes into account global policies set by the admin when resolving the policy, this is only
134      * relevant to local policies that can be applied globally as well.
135      *
136      * <p> Note that local policies set by an admin takes precedence over global policies set by the
137      * same admin.
138      *
139      * Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
140      */
resolvePolicy(LinkedHashMap<EnforcingAdmin, PolicyValue<V>> globalPoliciesSetByAdmins)141     boolean resolvePolicy(LinkedHashMap<EnforcingAdmin, PolicyValue<V>> globalPoliciesSetByAdmins) {
142         //Non coexistable policies don't need resolving
143         if (mPolicyDefinition.isNonCoexistablePolicy()) {
144             return false;
145         }
146         // Add global policies first then override with local policies for the same admin.
147         LinkedHashMap<EnforcingAdmin, PolicyValue<V>> mergedPolicies =
148                 new LinkedHashMap<>(globalPoliciesSetByAdmins);
149         mergedPolicies.putAll(mPoliciesSetByAdmins);
150 
151         PolicyValue<V> resolvedPolicy = mPolicyDefinition.resolvePolicy(mergedPolicies);
152         boolean policyChanged = !Objects.equals(resolvedPolicy, mCurrentResolvedPolicy);
153         mCurrentResolvedPolicy = resolvedPolicy;
154 
155         return policyChanged;
156     }
157 
158     @NonNull
getPoliciesSetByAdmins()159     LinkedHashMap<EnforcingAdmin, PolicyValue<V>> getPoliciesSetByAdmins() {
160         return new LinkedHashMap<>(mPoliciesSetByAdmins);
161     }
162 
resolvePolicy()163     private boolean resolvePolicy() {
164         //Non coexistable policies don't need resolving
165         if (mPolicyDefinition.isNonCoexistablePolicy()) {
166             return false;
167         }
168         PolicyValue<V> resolvedPolicy = mPolicyDefinition.resolvePolicy(mPoliciesSetByAdmins);
169         boolean policyChanged = !Objects.equals(resolvedPolicy, mCurrentResolvedPolicy);
170         mCurrentResolvedPolicy = resolvedPolicy;
171 
172         return policyChanged;
173     }
174 
175     @Nullable
getCurrentResolvedPolicy()176     PolicyValue<V> getCurrentResolvedPolicy() {
177         return mCurrentResolvedPolicy;
178     }
179 
getParcelablePolicyState()180     android.app.admin.PolicyState<V> getParcelablePolicyState() {
181         LinkedHashMap<android.app.admin.EnforcingAdmin, PolicyValue<V>> adminPolicies =
182                 new LinkedHashMap<>();
183         for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) {
184             adminPolicies.put(admin.getParcelableAdmin(), mPoliciesSetByAdmins.get(admin));
185         }
186         return new android.app.admin.PolicyState<>(adminPolicies, mCurrentResolvedPolicy,
187                 mPolicyDefinition.getResolutionMechanism().getParcelableResolutionMechanism());
188     }
189 
190     @Override
toString()191     public String toString() {
192         return "PolicyState { mPolicyDefinition= " + mPolicyDefinition + ", mPoliciesSetByAdmins= "
193                 + mPoliciesSetByAdmins + ", mCurrentResolvedPolicy= " + mCurrentResolvedPolicy
194                 + " }";
195     }
196 
saveToXml(TypedXmlSerializer serializer)197     void saveToXml(TypedXmlSerializer serializer) throws IOException {
198         serializer.startTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY);
199         mPolicyDefinition.saveToXml(serializer);
200         serializer.endTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY);
201 
202         if (mCurrentResolvedPolicy != null) {
203             serializer.startTag(/* namespace= */ null, TAG_RESOLVED_VALUE_ENTRY);
204             mPolicyDefinition.savePolicyValueToXml(
205                     serializer, mCurrentResolvedPolicy.getValue());
206             serializer.endTag(/* namespace= */ null, TAG_RESOLVED_VALUE_ENTRY);
207         }
208 
209         for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) {
210             serializer.startTag(/* namespace= */ null, TAG_ADMIN_POLICY_ENTRY);
211 
212             if (mPoliciesSetByAdmins.get(admin) != null) {
213                 serializer.startTag(/* namespace= */ null, TAG_POLICY_VALUE_ENTRY);
214                 mPolicyDefinition.savePolicyValueToXml(
215                         serializer, mPoliciesSetByAdmins.get(admin).getValue());
216                 serializer.endTag(/* namespace= */ null, TAG_POLICY_VALUE_ENTRY);
217             }
218 
219             serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_ENTRY);
220             admin.saveToXml(serializer);
221             serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_ENTRY);
222 
223             serializer.endTag(/* namespace= */ null, TAG_ADMIN_POLICY_ENTRY);
224         }
225     }
226 
227     @Nullable
readFromXml(TypedXmlPullParser parser)228     static <V> PolicyState<V> readFromXml(TypedXmlPullParser parser)
229             throws IOException, XmlPullParserException {
230 
231         PolicyDefinition<V> policyDefinition = null;
232 
233         PolicyValue<V> currentResolvedPolicy = null;
234 
235         LinkedHashMap<EnforcingAdmin, PolicyValue<V>> policiesSetByAdmins = new LinkedHashMap<>();
236         int outerDepth = parser.getDepth();
237         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
238             String tag = parser.getName();
239             switch (tag) {
240                 case TAG_ADMIN_POLICY_ENTRY:
241                     PolicyValue<V> value = null;
242                     EnforcingAdmin admin = null;
243                     int adminPolicyDepth = parser.getDepth();
244                     while (XmlUtils.nextElementWithin(parser, adminPolicyDepth)) {
245                         String adminPolicyTag = parser.getName();
246                         switch (adminPolicyTag) {
247                             case TAG_ENFORCING_ADMIN_ENTRY:
248                                 admin = EnforcingAdmin.readFromXml(parser);
249                                 if (admin == null) {
250                                     Slogf.wtf(TAG, "Error Parsing TAG_ENFORCING_ADMIN_ENTRY, "
251                                             + "EnforcingAdmin is null");
252                                 }
253                                 break;
254                             case TAG_POLICY_VALUE_ENTRY:
255                                 value = policyDefinition.readPolicyValueFromXml(parser);
256                                 if (value == null) {
257                                     Slogf.wtf(TAG, "Error Parsing TAG_POLICY_VALUE_ENTRY, "
258                                             + "PolicyValue is null");
259                                 }
260                                 break;
261                         }
262                     }
263                     if (admin != null) {
264                         policiesSetByAdmins.put(admin, value);
265                     } else {
266                         Slogf.wtf(TAG, "Error Parsing TAG_ADMIN_POLICY_ENTRY, EnforcingAdmin "
267                                 + "is null");
268                     }
269                     break;
270                 case TAG_POLICY_DEFINITION_ENTRY:
271                     policyDefinition = PolicyDefinition.readFromXml(parser);
272                     if (policyDefinition == null) {
273                         Slogf.wtf(TAG, "Error Parsing TAG_POLICY_DEFINITION_ENTRY, "
274                                 + "PolicyDefinition is null");
275                     }
276                     break;
277 
278                 case TAG_RESOLVED_VALUE_ENTRY:
279                     if (policyDefinition == null) {
280                         Slogf.wtf(TAG, "Error Parsing TAG_RESOLVED_VALUE_ENTRY, "
281                                 + "policyDefinition is null");
282                         break;
283                     }
284                     currentResolvedPolicy = policyDefinition.readPolicyValueFromXml(parser);
285                     if (currentResolvedPolicy == null) {
286                         Slogf.wtf(TAG, "Error Parsing TAG_RESOLVED_VALUE_ENTRY, "
287                                 + "currentResolvedPolicy is null");
288                     }
289                     break;
290                 default:
291                     Slogf.wtf(TAG, "Unknown tag: " + tag);
292             }
293         }
294         if (policyDefinition != null) {
295             return new PolicyState<V>(policyDefinition, policiesSetByAdmins, currentResolvedPolicy);
296         } else {
297             Slogf.wtf(TAG, "Error parsing policyState, policyDefinition is null");
298             return null;
299         }
300     }
301 
302 
303 
getPolicyDefinition()304     PolicyDefinition<V> getPolicyDefinition() {
305         return mPolicyDefinition;
306     }
307 }
308