blob: 1d1c28f5f9b7d852e11fd4725bbbbf4e2788d932 [file] [log] [blame]
Jason Monkbf3eedc2018-04-05 20:56:42 -04001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 */
14
15package com.android.server.slice;
16
17import android.content.ContentProvider;
18import android.content.Context;
19import android.net.Uri;
20import android.os.Environment;
21import android.os.Handler;
22import android.os.Looper;
23import android.os.Message;
24import android.text.format.DateUtils;
25import android.util.ArrayMap;
26import android.util.ArraySet;
27import android.util.AtomicFile;
28import android.util.Log;
29import android.util.Slog;
30import android.util.Xml.Encoding;
31
32import com.android.internal.annotations.VisibleForTesting;
33import com.android.internal.util.XmlUtils;
34import com.android.server.slice.SliceProviderPermissions.SliceAuthority;
35
36import org.xmlpull.v1.XmlPullParser;
37import org.xmlpull.v1.XmlPullParserException;
38import org.xmlpull.v1.XmlPullParserFactory;
39import org.xmlpull.v1.XmlSerializer;
40
41import java.io.File;
42import java.io.FileNotFoundException;
43import java.io.FileOutputStream;
44import java.io.IOException;
45import java.io.InputStream;
46import java.util.Objects;
47
48public class SlicePermissionManager implements DirtyTracker {
49
50 private static final String TAG = "SlicePermissionManager";
51
52 /**
53 * The amount of time we'll cache a SliceProviderPermissions or SliceClientPermissions
54 * in case they are used again.
55 */
56 private static final long PERMISSION_CACHE_PERIOD = 5 * DateUtils.MINUTE_IN_MILLIS;
57
58 /**
59 * The amount of time we delay flushing out permission changes to disk because they usually
60 * come in short bursts.
61 */
62 private static final long WRITE_GRACE_PERIOD = 500;
63
64 private static final String SLICE_DIR = "slice";
65
66 // If/when this bumps again we'll need to write it out in the disk somewhere.
67 // Currently we don't have a central file for this in version 2 and there is no
68 // reason to add one until we actually have incompatible version bumps.
69 // This does however block us from reading backups from P-DP1 which may contain
70 // a very different XML format for perms.
71 static final int DB_VERSION = 2;
72
73 private static final String TAG_LIST = "slice-access-list";
74 private final String ATT_VERSION = "version";
75
76 private final File mSliceDir;
77 private final Context mContext;
78 private final Handler mHandler;
79 private final ArrayMap<PkgUser, SliceProviderPermissions> mCachedProviders = new ArrayMap<>();
80 private final ArrayMap<PkgUser, SliceClientPermissions> mCachedClients = new ArrayMap<>();
81 private final ArraySet<Persistable> mDirty = new ArraySet<>();
82
83 @VisibleForTesting
84 SlicePermissionManager(Context context, Looper looper, File sliceDir) {
85 mContext = context;
86 mHandler = new H(looper);
87 mSliceDir = sliceDir;
88 }
89
90 public SlicePermissionManager(Context context, Looper looper) {
91 this(context, looper, new File(Environment.getDataDirectory(), "system/" + SLICE_DIR));
92 }
93
94 public void grantFullAccess(String pkg, int userId) {
95 PkgUser pkgUser = new PkgUser(pkg, userId);
96 SliceClientPermissions client = getClient(pkgUser);
97 client.setHasFullAccess(true);
98 }
99
100 public void grantSliceAccess(String pkg, int userId, String providerPkg, int providerUser,
101 Uri uri) {
102 PkgUser pkgUser = new PkgUser(pkg, userId);
103 PkgUser providerPkgUser = new PkgUser(providerPkg, providerUser);
104
105 SliceClientPermissions client = getClient(pkgUser);
106 client.grantUri(uri, providerPkgUser);
107
108 SliceProviderPermissions provider = getProvider(providerPkgUser);
109 provider.getOrCreateAuthority(ContentProvider.getUriWithoutUserId(uri).getAuthority())
110 .addPkg(pkgUser);
111 }
112
113 public void revokeSliceAccess(String pkg, int userId, String providerPkg, int providerUser,
114 Uri uri) {
115 PkgUser pkgUser = new PkgUser(pkg, userId);
116 PkgUser providerPkgUser = new PkgUser(providerPkg, providerUser);
117
118 SliceClientPermissions client = getClient(pkgUser);
119 client.revokeUri(uri, providerPkgUser);
120 }
121
122 public void removePkg(String pkg, int userId) {
123 PkgUser pkgUser = new PkgUser(pkg, userId);
124 SliceProviderPermissions provider = getProvider(pkgUser);
125
126 for (SliceAuthority authority : provider.getAuthorities()) {
127 for (PkgUser p : authority.getPkgs()) {
128 getClient(p).removeAuthority(authority.getAuthority(), userId);
129 }
130 }
131 SliceClientPermissions client = getClient(pkgUser);
132 client.clear();
133 mHandler.obtainMessage(H.MSG_REMOVE, pkgUser);
134 }
135
Jason Monk7f01f3b2018-05-15 15:46:26 -0400136 public String[] getAllPackagesGranted(String pkg) {
137 ArraySet<String> ret = new ArraySet<>();
138 for (SliceAuthority authority : getProvider(new PkgUser(pkg, 0)).getAuthorities()) {
139 for (PkgUser pkgUser : authority.getPkgs()) {
140 ret.add(pkgUser.mPkg);
141 }
142 }
143 return ret.toArray(new String[ret.size()]);
144 }
145
Jason Monkbf3eedc2018-04-05 20:56:42 -0400146 public boolean hasFullAccess(String pkg, int userId) {
147 PkgUser pkgUser = new PkgUser(pkg, userId);
148 return getClient(pkgUser).hasFullAccess();
149 }
150
151 public boolean hasPermission(String pkg, int userId, Uri uri) {
152 PkgUser pkgUser = new PkgUser(pkg, userId);
153 SliceClientPermissions client = getClient(pkgUser);
154 int providerUserId = ContentProvider.getUserIdFromUri(uri, userId);
155 return client.hasFullAccess()
156 || client.hasPermission(ContentProvider.getUriWithoutUserId(uri), providerUserId);
157 }
158
159 @Override
160 public void onPersistableDirty(Persistable obj) {
161 mHandler.removeMessages(H.MSG_PERSIST);
162 mHandler.obtainMessage(H.MSG_ADD_DIRTY, obj).sendToTarget();
163 mHandler.sendEmptyMessageDelayed(H.MSG_PERSIST, WRITE_GRACE_PERIOD);
164 }
165
166 public void writeBackup(XmlSerializer out) throws IOException, XmlPullParserException {
167 synchronized (this) {
168 out.startTag(null, TAG_LIST);
169 out.attribute(null, ATT_VERSION, String.valueOf(DB_VERSION));
170
171 // Don't do anything with changes from the backup, because there shouldn't be any.
172 DirtyTracker tracker = obj -> { };
173 if (mHandler.hasMessages(H.MSG_PERSIST)) {
174 mHandler.removeMessages(H.MSG_PERSIST);
175 handlePersist();
176 }
177 for (String file : new File(mSliceDir.getAbsolutePath()).list()) {
Jason Monkbf3eedc2018-04-05 20:56:42 -0400178 try (ParserHolder parser = getParser(file)) {
Dan Sandlere7316812019-01-10 11:24:19 -0500179 Persistable p = null;
180 while (parser.parser.getEventType() != XmlPullParser.END_DOCUMENT) {
181 if (parser.parser.getEventType() == XmlPullParser.START_TAG) {
182 if (SliceClientPermissions.TAG_CLIENT.equals(parser.parser.getName())) {
183 p = SliceClientPermissions.createFrom(parser.parser, tracker);
184 } else {
185 p = SliceProviderPermissions.createFrom(parser.parser, tracker);
186 }
187 break;
188 }
Jason Monkbf3eedc2018-04-05 20:56:42 -0400189 parser.parser.next();
190 }
Dan Sandlere7316812019-01-10 11:24:19 -0500191 if (p != null) {
192 p.writeTo(out);
Jason Monkbf3eedc2018-04-05 20:56:42 -0400193 } else {
Dan Sandlere7316812019-01-10 11:24:19 -0500194 Slog.w(TAG, "Invalid or empty slice permissions file: " + file);
Jason Monkbf3eedc2018-04-05 20:56:42 -0400195 }
Jason Monkbf3eedc2018-04-05 20:56:42 -0400196 }
197 }
198
199 out.endTag(null, TAG_LIST);
200 }
201 }
202
203 public void readRestore(XmlPullParser parser) throws IOException, XmlPullParserException {
204 synchronized (this) {
205 while ((parser.getEventType() != XmlPullParser.START_TAG
206 || !TAG_LIST.equals(parser.getName()))
207 && parser.getEventType() != XmlPullParser.END_DOCUMENT) {
208 parser.next();
209 }
210 int xmlVersion = XmlUtils.readIntAttribute(parser, ATT_VERSION, 0);
211 if (xmlVersion < DB_VERSION) {
212 // No conversion support right now.
213 return;
214 }
215 while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
216 if (parser.getEventType() == XmlPullParser.START_TAG) {
217 if (SliceClientPermissions.TAG_CLIENT.equals(parser.getName())) {
218 SliceClientPermissions client = SliceClientPermissions.createFrom(parser,
219 this);
220 synchronized (mCachedClients) {
221 mCachedClients.put(client.getPkg(), client);
222 }
223 onPersistableDirty(client);
224 mHandler.sendMessageDelayed(
225 mHandler.obtainMessage(H.MSG_CLEAR_CLIENT, client.getPkg()),
226 PERMISSION_CACHE_PERIOD);
227 } else if (SliceProviderPermissions.TAG_PROVIDER.equals(parser.getName())) {
228 SliceProviderPermissions provider = SliceProviderPermissions.createFrom(
229 parser, this);
230 synchronized (mCachedProviders) {
231 mCachedProviders.put(provider.getPkg(), provider);
232 }
233 onPersistableDirty(provider);
234 mHandler.sendMessageDelayed(
235 mHandler.obtainMessage(H.MSG_CLEAR_PROVIDER, provider.getPkg()),
236 PERMISSION_CACHE_PERIOD);
237 } else {
238 parser.next();
239 }
240 } else {
241 parser.next();
242 }
243 }
244 }
245 }
246
247 private SliceClientPermissions getClient(PkgUser pkgUser) {
248 SliceClientPermissions client;
249 synchronized (mCachedClients) {
250 client = mCachedClients.get(pkgUser);
251 }
252 if (client == null) {
253 try (ParserHolder parser = getParser(SliceClientPermissions.getFileName(pkgUser))) {
254 client = SliceClientPermissions.createFrom(parser.parser, this);
255 synchronized (mCachedClients) {
256 mCachedClients.put(pkgUser, client);
257 }
258 mHandler.sendMessageDelayed(mHandler.obtainMessage(H.MSG_CLEAR_CLIENT, pkgUser),
259 PERMISSION_CACHE_PERIOD);
260 return client;
261 } catch (FileNotFoundException e) {
262 // No client exists yet.
263 } catch (IOException e) {
264 Log.e(TAG, "Can't read client", e);
265 } catch (XmlPullParserException e) {
266 Log.e(TAG, "Can't read client", e);
267 }
268 // Can't read or no permissions exist, create a clean object.
269 client = new SliceClientPermissions(pkgUser, this);
Jason Monk9c03ef42018-05-11 09:26:51 -0700270 synchronized (mCachedClients) {
271 mCachedClients.put(pkgUser, client);
272 }
Jason Monkbf3eedc2018-04-05 20:56:42 -0400273 }
274 return client;
275 }
276
277 private SliceProviderPermissions getProvider(PkgUser pkgUser) {
278 SliceProviderPermissions provider;
279 synchronized (mCachedProviders) {
280 provider = mCachedProviders.get(pkgUser);
281 }
282 if (provider == null) {
283 try (ParserHolder parser = getParser(SliceProviderPermissions.getFileName(pkgUser))) {
284 provider = SliceProviderPermissions.createFrom(parser.parser, this);
285 synchronized (mCachedProviders) {
286 mCachedProviders.put(pkgUser, provider);
287 }
288 mHandler.sendMessageDelayed(mHandler.obtainMessage(H.MSG_CLEAR_PROVIDER, pkgUser),
289 PERMISSION_CACHE_PERIOD);
290 return provider;
291 } catch (FileNotFoundException e) {
292 // No provider exists yet.
293 } catch (IOException e) {
294 Log.e(TAG, "Can't read provider", e);
295 } catch (XmlPullParserException e) {
296 Log.e(TAG, "Can't read provider", e);
297 }
298 // Can't read or no permissions exist, create a clean object.
299 provider = new SliceProviderPermissions(pkgUser, this);
Jason Monk9c03ef42018-05-11 09:26:51 -0700300 synchronized (mCachedProviders) {
301 mCachedProviders.put(pkgUser, provider);
302 }
Jason Monkbf3eedc2018-04-05 20:56:42 -0400303 }
304 return provider;
305 }
306
307 private ParserHolder getParser(String fileName)
308 throws FileNotFoundException, XmlPullParserException {
309 AtomicFile file = getFile(fileName);
310 ParserHolder holder = new ParserHolder();
311 holder.input = file.openRead();
312 holder.parser = XmlPullParserFactory.newInstance().newPullParser();
313 holder.parser.setInput(holder.input, Encoding.UTF_8.name());
314 return holder;
315 }
316
317 private AtomicFile getFile(String fileName) {
318 if (!mSliceDir.exists()) {
319 mSliceDir.mkdir();
320 }
321 return new AtomicFile(new File(mSliceDir, fileName));
322 }
323
Dan Sandler2d8e3d12018-12-13 15:32:13 -0500324 @VisibleForTesting
325 void handlePersist() {
Jason Monkbf3eedc2018-04-05 20:56:42 -0400326 synchronized (this) {
327 for (Persistable persistable : mDirty) {
328 AtomicFile file = getFile(persistable.getFileName());
329 final FileOutputStream stream;
330 try {
331 stream = file.startWrite();
332 } catch (IOException e) {
333 Slog.w(TAG, "Failed to save access file", e);
334 return;
335 }
336
337 try {
338 XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
339 out.setOutput(stream, Encoding.UTF_8.name());
340
341 persistable.writeTo(out);
342
343 out.flush();
344 file.finishWrite(stream);
Dan Sandler2d8e3d12018-12-13 15:32:13 -0500345 } catch (IOException | XmlPullParserException | RuntimeException e) {
Jason Monkbf3eedc2018-04-05 20:56:42 -0400346 Slog.w(TAG, "Failed to save access file, restoring backup", e);
347 file.failWrite(stream);
348 }
349 }
350 mDirty.clear();
351 }
352 }
353
Dan Sandler2d8e3d12018-12-13 15:32:13 -0500354 // use addPersistableDirty(); this is just for tests
355 @VisibleForTesting
356 void addDirtyImmediate(Persistable obj) {
357 mDirty.add(obj);
358 }
359
Jason Monkbf3eedc2018-04-05 20:56:42 -0400360 private void handleRemove(PkgUser pkgUser) {
361 getFile(SliceClientPermissions.getFileName(pkgUser)).delete();
362 getFile(SliceProviderPermissions.getFileName(pkgUser)).delete();
363 mDirty.remove(mCachedClients.remove(pkgUser));
364 mDirty.remove(mCachedProviders.remove(pkgUser));
365 }
366
367 private final class H extends Handler {
368 private static final int MSG_ADD_DIRTY = 1;
369 private static final int MSG_PERSIST = 2;
370 private static final int MSG_REMOVE = 3;
371 private static final int MSG_CLEAR_CLIENT = 4;
372 private static final int MSG_CLEAR_PROVIDER = 5;
373
374 public H(Looper looper) {
375 super(looper);
376 }
377
378 @Override
379 public void handleMessage(Message msg) {
380 switch (msg.what) {
381 case MSG_ADD_DIRTY:
382 mDirty.add((Persistable) msg.obj);
383 break;
384 case MSG_PERSIST:
385 handlePersist();
386 break;
387 case MSG_REMOVE:
388 handleRemove((PkgUser) msg.obj);
389 break;
390 case MSG_CLEAR_CLIENT:
391 synchronized (mCachedClients) {
392 mCachedClients.remove(msg.obj);
393 }
394 break;
395 case MSG_CLEAR_PROVIDER:
396 synchronized (mCachedProviders) {
397 mCachedProviders.remove(msg.obj);
398 }
399 break;
400 }
401 }
402 }
403
404 public static class PkgUser {
405 private static final String SEPARATOR = "@";
406 private static final String FORMAT = "%s" + SEPARATOR + "%d";
407 private final String mPkg;
408 private final int mUserId;
409
410 public PkgUser(String pkg, int userId) {
411 mPkg = pkg;
412 mUserId = userId;
413 }
414
415 public PkgUser(String pkgUserStr) throws IllegalArgumentException {
416 try {
417 String[] vals = pkgUserStr.split(SEPARATOR, 2);
418 mPkg = vals[0];
419 mUserId = Integer.parseInt(vals[1]);
420 } catch (Exception e) {
421 throw new IllegalArgumentException(e);
422 }
423 }
424
425 public String getPkg() {
426 return mPkg;
427 }
428
429 public int getUserId() {
430 return mUserId;
431 }
432
433 @Override
434 public int hashCode() {
435 return mPkg.hashCode() + mUserId;
436 }
437
438 @Override
439 public boolean equals(Object obj) {
440 if (!getClass().equals(obj != null ? obj.getClass() : null)) return false;
441 PkgUser other = (PkgUser) obj;
442 return Objects.equals(other.mPkg, mPkg) && other.mUserId == mUserId;
443 }
444
445 @Override
446 public String toString() {
447 return String.format(FORMAT, mPkg, mUserId);
448 }
449 }
450
451 private class ParserHolder implements AutoCloseable {
452
453 private InputStream input;
454 private XmlPullParser parser;
455
456 @Override
457 public void close() throws IOException {
458 input.close();
459 }
460 }
461}