1 /*
2  * Copyright 2014, 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.IntDef;
20 import android.content.Intent;
21 import android.content.IntentFilter;
22 import android.os.UserHandle;
23 import android.util.Log;
24 
25 import com.android.internal.util.XmlUtils;
26 import com.android.modules.utils.TypedXmlPullParser;
27 import com.android.modules.utils.TypedXmlSerializer;
28 import com.android.server.utils.SnapshotCache;
29 
30 import org.xmlpull.v1.XmlPullParser;
31 import org.xmlpull.v1.XmlPullParserException;
32 
33 import java.io.IOException;
34 import java.lang.annotation.Retention;
35 import java.lang.annotation.RetentionPolicy;
36 
37 /**
38  * The {@link PackageManagerService} maintains some {@link CrossProfileIntentFilter}s for each user.
39  * If an {@link Intent} matches the {@link CrossProfileIntentFilter}, then activities in the user
40  * {@link #mTargetUserId} can access it.
41  */
42 class CrossProfileIntentFilter extends WatchedIntentFilter {
43     private static final String ATTR_TARGET_USER_ID = "targetUserId";
44     private static final String ATTR_FLAGS = "flags";
45     private static final String ATTR_OWNER_PACKAGE = "ownerPackage";
46     private static final String ATTR_FILTER = "filter";
47     private static final String ATTR_ACCESS_CONTROL = "accessControl";
48 
49     //flag to decide if intent needs to be resolved cross profile if pkgName is already defined
50     public static final int FLAG_IS_PACKAGE_FOR_FILTER = 0x00000008;
51 
52     /*
53     This flag, denotes if further cross profile resolution is allowed, e.g. if profile#0 is linked
54     to profile#1 and profile#2 . When intent resolution from profile#1 is started we resolve it in
55     profile#1 and profile#0. The profile#0 is also linked to profile#2, we will only resolve in
56     profile#2 if CrossProfileIntentFilter between profile#1 and profile#0 have set flag
57     FLAG_ALLOW_CHAINED_RESOLUTION.
58      */
59     public static final int FLAG_ALLOW_CHAINED_RESOLUTION = 0x00000010;
60 
61     private static final String TAG = "CrossProfileIntentFilter";
62 
63     /**
64      * AccessControlLevel provides level of access for user to create/modify
65      * {@link CrossProfileIntentFilter} in {@link com.android.server.pm.Settings}.
66      * Each AccessControlLevel have value assigned, the higher the value
67      * implies higher restriction for creation/modification.
68      * AccessControlLevel allows us to protect against malicious changes in user's
69      * {@link CrossProfileIntentFilter}s, which might add/remove {@link CrossProfileIntentFilter}
70      * leading to unprecedented results.
71      */
72     @IntDef(prefix = {"ACCESS_LEVEL_"}, value = {
73             ACCESS_LEVEL_ALL,
74             ACCESS_LEVEL_SYSTEM,
75             ACCESS_LEVEL_SYSTEM_ADD_ONLY,
76     })
77     @Retention(RetentionPolicy.SOURCE)
78     public @interface AccessControlLevel {
79     }
80 
81     /**
82      * ACCESS_LEVEL_ALL signifies that irrespective of user we would allow
83      * access(addition/modification/removal) for CrossProfileIntentFilter.
84      * This is the default access control level.
85      */
86     public static final int ACCESS_LEVEL_ALL = 0;
87 
88     /**
89      * ACCESS_LEVEL_SYSTEM signifies that only system/root user would be able to
90      * access(addition/modification/removal) CrossProfileIntentFilter.
91      */
92     public static final int ACCESS_LEVEL_SYSTEM = 10;
93 
94     /**
95      * ACCESS_LEVEL_SYSTEM_ADD_ONLY signifies that only system/root user would be able to add
96      * CrossProfileIntentFilter but not modify/remove. Once added, it cannot be modified or removed.
97      */
98     public static final int ACCESS_LEVEL_SYSTEM_ADD_ONLY = 20;
99 
100     // If the intent matches the IntentFilter, then it can be forwarded to this userId.
101     final int mTargetUserId;
102     final String mOwnerPackage; // packageName of the app.
103     final int mFlags;
104     final int mAccessControlLevel;
105 
106     // The cache for snapshots, so they are not rebuilt if the base object has not
107     // changed.
108     final SnapshotCache<CrossProfileIntentFilter> mSnapshot;
109 
makeCache()110     private SnapshotCache makeCache() {
111         return new SnapshotCache<CrossProfileIntentFilter>(this, this) {
112             @Override
113             public CrossProfileIntentFilter createSnapshot() {
114                 CrossProfileIntentFilter s = new CrossProfileIntentFilter(mSource);
115                 s.seal();
116                 return s;
117             }};
118     }
119 
120     CrossProfileIntentFilter(IntentFilter filter, String ownerPackage, int targetUserId,
121             int flags) {
122         this(filter, ownerPackage, targetUserId, flags, ACCESS_LEVEL_ALL);
123     }
124 
125     CrossProfileIntentFilter(IntentFilter filter, String ownerPackage, int targetUserId,
126             int flags, @AccessControlLevel int accessControlLevel) {
127         super(filter);
128         mTargetUserId = targetUserId;
129         mOwnerPackage = ownerPackage;
130         mFlags = flags;
131         mAccessControlLevel = accessControlLevel;
132         mSnapshot = makeCache();
133     }
134 
135     CrossProfileIntentFilter(WatchedIntentFilter filter, String ownerPackage, int targetUserId,
136             int flags) {
137         this(filter.mFilter, ownerPackage, targetUserId, flags);
138     }
139 
140     CrossProfileIntentFilter(WatchedIntentFilter filter, String ownerPackage, int targetUserId,
141             int flags, @AccessControlLevel int accessControlLevel) {
142         this(filter.mFilter, ownerPackage, targetUserId, flags, accessControlLevel);
143     }
144 
145     // Copy constructor used only to create a snapshot.
146     private CrossProfileIntentFilter(CrossProfileIntentFilter f) {
147         super(f);
148         mTargetUserId = f.mTargetUserId;
149         mOwnerPackage = f.mOwnerPackage;
150         mFlags = f.mFlags;
151         mAccessControlLevel = f.mAccessControlLevel;
152         mSnapshot = new SnapshotCache.Sealed();
153     }
154 
155     public int getTargetUserId() {
156         return mTargetUserId;
157     }
158 
159     public int getFlags() {
160         return mFlags;
161     }
162 
163     public String getOwnerPackage() {
164         return mOwnerPackage;
165     }
166 
167     public @AccessControlLevel int getAccessControlLevel() {
168         return mAccessControlLevel;
169     }
170 
171     CrossProfileIntentFilter(TypedXmlPullParser parser) throws XmlPullParserException, IOException {
172         mTargetUserId = parser.getAttributeInt(null, ATTR_TARGET_USER_ID, UserHandle.USER_NULL);
173         mOwnerPackage = getStringFromXml(parser, ATTR_OWNER_PACKAGE, "");
174         mAccessControlLevel = parser.getAttributeInt(null, ATTR_ACCESS_CONTROL, ACCESS_LEVEL_ALL);
175         mFlags = parser.getAttributeInt(null, ATTR_FLAGS, 0);
176         mSnapshot = makeCache();
177 
178         int outerDepth = parser.getDepth();
179         String tagName = parser.getName();
180         int type;
181         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
182                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
183             tagName = parser.getName();
184             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
185                 continue;
186             } else if (type == XmlPullParser.START_TAG) {
187                 if (tagName.equals(ATTR_FILTER)) {
188                     break;
189                 } else {
190                     String msg = "Unknown element under "
191                             + Settings.TAG_CROSS_PROFILE_INTENT_FILTERS + ": " + tagName + " at "
192                             + parser.getPositionDescription();
193                     PackageManagerService.reportSettingsProblem(Log.WARN, msg);
194                     XmlUtils.skipCurrentTag(parser);
195                 }
196             }
197         }
198         if (tagName.equals(ATTR_FILTER)) {
199             mFilter.readFromXml(parser);
200         } else {
201             String msg = "Missing element under " + TAG + ": " + ATTR_FILTER +
202                     " at " + parser.getPositionDescription();
203             PackageManagerService.reportSettingsProblem(Log.WARN, msg);
204             XmlUtils.skipCurrentTag(parser);
205         }
206     }
207 
208     private String getStringFromXml(TypedXmlPullParser parser, String attribute,
209             String defaultValue) {
210         String value = parser.getAttributeValue(null, attribute);
211         if (value == null) {
212             String msg = "Missing element under " + TAG +": " + attribute + " at " +
213                     parser.getPositionDescription();
214             PackageManagerService.reportSettingsProblem(Log.WARN, msg);
215             return defaultValue;
216         } else {
217             return value;
218         }
219     }
220 
221     public void writeToXml(TypedXmlSerializer serializer) throws IOException {
222         serializer.attributeInt(null, ATTR_TARGET_USER_ID, mTargetUserId);
223         serializer.attributeInt(null, ATTR_FLAGS, mFlags);
224         serializer.attribute(null, ATTR_OWNER_PACKAGE, mOwnerPackage);
225         serializer.attributeInt(null, ATTR_ACCESS_CONTROL, mAccessControlLevel);
226         serializer.startTag(null, ATTR_FILTER);
227         mFilter.writeToXml(serializer);
228         serializer.endTag(null, ATTR_FILTER);
229     }
230 
231     @Override
232     public String toString() {
233         return "CrossProfileIntentFilter{0x" + Integer.toHexString(System.identityHashCode(this))
234                 + " " + Integer.toString(mTargetUserId) + "}";
235     }
236 
237     boolean equalsIgnoreFilter(CrossProfileIntentFilter other) {
238         return mTargetUserId == other.mTargetUserId
239                 && mOwnerPackage.equals(other.mOwnerPackage)
240                 && mFlags == other.mFlags
241                 && mAccessControlLevel == other.mAccessControlLevel;
242     }
243 
244     public CrossProfileIntentFilter snapshot() {
245         return mSnapshot.snapshot();
246     }
247 }
248