1 /*
2  * Copyright (C) 2020 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.pm;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.UserIdInt;
22 import android.os.Bundle;
23 import android.os.UserHandle;
24 import android.os.UserManager;
25 import android.util.IntArray;
26 import android.util.SparseArray;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.modules.utils.TypedXmlPullParser;
30 import com.android.modules.utils.TypedXmlSerializer;
31 import com.android.server.BundleUtils;
32 
33 import org.xmlpull.v1.XmlPullParser;
34 import org.xmlpull.v1.XmlPullParserException;
35 
36 import java.io.IOException;
37 import java.io.PrintWriter;
38 import java.util.ArrayList;
39 import java.util.List;
40 
41 /**
42  * Data structure that contains the mapping of users to user restrictions.
43  *
44  * @hide
45  */
46 public class RestrictionsSet {
47 
48     private static final String USER_ID = "user_id";
49     private static final String TAG_RESTRICTIONS = "restrictions";
50     private static final String TAG_RESTRICTIONS_USER = "restrictions_user";
51 
52     /**
53      * Mapping of user restrictions.
54      * Only non-empty restriction bundles are stored.
55      * The key is the user id of the user.
56      * userId -> restrictionBundle
57      */
58     private final SparseArray<Bundle> mUserRestrictions = new SparseArray<>(0);
59 
RestrictionsSet()60     public RestrictionsSet() {
61     }
62 
RestrictionsSet(@serIdInt int userId, @NonNull Bundle restrictions)63     public RestrictionsSet(@UserIdInt int userId, @NonNull Bundle restrictions) {
64         if (restrictions.isEmpty()) {
65             throw new IllegalArgumentException("empty restriction bundle cannot be added.");
66         }
67         mUserRestrictions.put(userId, restrictions);
68     }
69 
70     /**
71      * Updates restriction bundle for a given user.
72      * If new bundle is empty, record is removed from the array.
73      *
74      * @return whether restrictions bundle is different from the old one.
75      */
updateRestrictions(@serIdInt int userId, @Nullable Bundle restrictions)76     public boolean updateRestrictions(@UserIdInt int userId, @Nullable Bundle restrictions) {
77         final boolean changed =
78                 !UserRestrictionsUtils.areEqual(mUserRestrictions.get(userId), restrictions);
79         if (!changed) {
80             return false;
81         }
82         if (!BundleUtils.isEmpty(restrictions)) {
83             mUserRestrictions.put(userId, restrictions);
84         } else {
85             mUserRestrictions.delete(userId);
86         }
87         return true;
88     }
89 
90     /**
91      * Removes a particular restriction for all users.
92      *
93      * @return whether the restriction was removed or not.
94      */
removeRestrictionsForAllUsers(String restriction)95     public boolean removeRestrictionsForAllUsers(String restriction) {
96         boolean removed = false;
97         for (int i = 0; i < mUserRestrictions.size(); i++) {
98             final Bundle restrictions = mUserRestrictions.valueAt(i);
99 
100             if (UserRestrictionsUtils.contains(restrictions, restriction)) {
101                 restrictions.remove(restriction);
102                 removed = true;
103             }
104         }
105         return removed;
106     }
107 
108     /**
109      * Moves a particular restriction from one restriction set to another, e.g. for all users.
110      */
moveRestriction(@onNull RestrictionsSet destRestrictions, String restriction)111     public void moveRestriction(@NonNull RestrictionsSet destRestrictions, String restriction) {
112         for (int i = 0; i < mUserRestrictions.size(); i++) {
113             final int userId = mUserRestrictions.keyAt(i);
114             final Bundle from = mUserRestrictions.valueAt(i);
115 
116             if (UserRestrictionsUtils.contains(from, restriction)) {
117                 from.remove(restriction);
118                 Bundle to = destRestrictions.getRestrictions(userId);
119                 if (to == null) {
120                     to = new Bundle();
121                     to.putBoolean(restriction, true);
122                     destRestrictions.updateRestrictions(userId, to);
123                 } else {
124                     to.putBoolean(restriction, true);
125                 }
126                 // Don't keep empty bundles.
127                 if (from.isEmpty()) {
128                     mUserRestrictions.removeAt(i);
129                     i--;
130                 }
131             }
132         }
133     }
134 
135     /**
136      * @return whether restrictions set has no restrictions.
137      */
isEmpty()138     public boolean isEmpty() {
139         return mUserRestrictions.size() == 0;
140     }
141 
142     /**
143      * Merge all restrictions in restrictions set into one bundle. The original user restrictions
144      * set does not get modified, instead a new bundle is returned.
145      *
146      * @return restrictions bundle containing all user restrictions.
147      */
mergeAll()148     public @NonNull Bundle mergeAll() {
149         final Bundle result = new Bundle();
150         for (int i = 0; i < mUserRestrictions.size(); i++) {
151             UserRestrictionsUtils.merge(result, mUserRestrictions.valueAt(i));
152         }
153         return result;
154     }
155 
156     /**
157      * @return list of enforcing users that enforce a particular restriction.
158      */
getEnforcingUsers(String restriction, @UserIdInt int userId)159     public @NonNull List<UserManager.EnforcingUser> getEnforcingUsers(String restriction,
160             @UserIdInt int userId) {
161         final List<UserManager.EnforcingUser> result = new ArrayList<>();
162         if (getRestrictionsNonNull(userId).containsKey(restriction)) {
163             result.add(new UserManager.EnforcingUser(userId,
164                     UserManager.RESTRICTION_SOURCE_PROFILE_OWNER));
165         }
166 
167         if (getRestrictionsNonNull(UserHandle.USER_ALL).containsKey(restriction)) {
168             result.add(new UserManager.EnforcingUser(UserHandle.USER_ALL,
169                     UserManager.RESTRICTION_SOURCE_DEVICE_OWNER));
170         }
171 
172         return result;
173     }
174 
175     /**
176      * @return list of user restrictions for a given user. Null is returned if the user does not
177      * have any restrictions.
178      */
getRestrictions(@serIdInt int userId)179     public @Nullable Bundle getRestrictions(@UserIdInt int userId) {
180         return mUserRestrictions.get(userId);
181     }
182 
183     /** @return list of user restrictions for a given user that is not null. */
getRestrictionsNonNull(@serIdInt int userId)184     public @NonNull Bundle getRestrictionsNonNull(@UserIdInt int userId) {
185         return UserRestrictionsUtils.nonNull(mUserRestrictions.get(userId));
186     }
187 
188     /**
189      * Removes a given user from the restrictions set, returning true if the user has non-empty
190      * restrictions before removal.
191      */
remove(@serIdInt int userId)192     public boolean remove(@UserIdInt int userId) {
193         boolean hasUserRestriction = mUserRestrictions.contains(userId);
194         mUserRestrictions.remove(userId);
195         return hasUserRestriction;
196     }
197 
198     /**
199      * Remove list of users and user restrictions.
200      */
removeAllRestrictions()201     public void removeAllRestrictions() {
202         mUserRestrictions.clear();
203     }
204 
205     /**
206      * Serialize a given {@link RestrictionsSet} to XML.
207      */
writeRestrictions(@onNull TypedXmlSerializer serializer, @NonNull String outerTag)208     public void writeRestrictions(@NonNull TypedXmlSerializer serializer, @NonNull String outerTag)
209             throws IOException {
210         serializer.startTag(null, outerTag);
211         for (int i = 0; i < mUserRestrictions.size(); i++) {
212             serializer.startTag(null, TAG_RESTRICTIONS_USER);
213             serializer.attributeInt(null, USER_ID, mUserRestrictions.keyAt(i));
214             UserRestrictionsUtils.writeRestrictions(serializer, mUserRestrictions.valueAt(i),
215                     TAG_RESTRICTIONS);
216             serializer.endTag(null, TAG_RESTRICTIONS_USER);
217         }
218         serializer.endTag(null, outerTag);
219     }
220 
221     /**
222      * Read restrictions from XML.
223      */
readRestrictions(@onNull TypedXmlPullParser parser, @NonNull String outerTag)224     public static RestrictionsSet readRestrictions(@NonNull TypedXmlPullParser parser,
225             @NonNull String outerTag) throws IOException, XmlPullParserException {
226         RestrictionsSet restrictionsSet = new RestrictionsSet();
227         int userId = 0;
228         int type;
229 
230         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
231             String tag = parser.getName();
232             if (type == XmlPullParser.END_TAG && outerTag.equals(tag)) {
233                 return restrictionsSet;
234             } else if (type == XmlPullParser.START_TAG && TAG_RESTRICTIONS_USER.equals(tag)) {
235                 userId = parser.getAttributeInt(null, USER_ID);
236             } else if (type == XmlPullParser.START_TAG && TAG_RESTRICTIONS.equals(tag)) {
237                 Bundle restrictions = UserRestrictionsUtils.readRestrictions(parser);
238                 restrictionsSet.updateRestrictions(userId, restrictions);
239             }
240         }
241         throw new XmlPullParserException("restrictions cannot be read as xml is malformed.");
242     }
243 
244     /**
245      * Dumps {@link RestrictionsSet}.
246      */
dumpRestrictions(PrintWriter pw, String prefix)247     public void dumpRestrictions(PrintWriter pw, String prefix) {
248         boolean noneSet = true;
249         for (int i = 0; i < mUserRestrictions.size(); i++) {
250             pw.println(prefix + "User Id: " + mUserRestrictions.keyAt(i));
251             UserRestrictionsUtils.dumpRestrictions(pw, prefix + "  ", mUserRestrictions.valueAt(i));
252             noneSet = false;
253         }
254         if (noneSet) {
255             pw.println(prefix + "none");
256         }
257     }
258 
259     /** @return list of users in this restriction set. */
getUserIds()260     public IntArray getUserIds() {
261         IntArray userIds = new IntArray(mUserRestrictions.size());
262         for (int i = 0; i < mUserRestrictions.size(); i++) {
263             userIds.add(mUserRestrictions.keyAt(i));
264         }
265         return userIds;
266     }
267 
containsKey(@serIdInt int userId)268     public boolean containsKey(@UserIdInt int userId) {
269         return mUserRestrictions.contains(userId);
270     }
271 
272     @VisibleForTesting
size()273     public int size() {
274         return mUserRestrictions.size();
275     }
276 
277     @VisibleForTesting
keyAt(int index)278     public int keyAt(int index) {
279         return mUserRestrictions.keyAt(index);
280     }
281 
282     @VisibleForTesting
valueAt(int index)283     public Bundle valueAt(int index) {
284         return mUserRestrictions.valueAt(index);
285     }
286 
287 }
288