blob: 315d5e39c94bab1cd5987f3ed5bf6354b567c4c5 [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()) {
178 if (file.isEmpty()) continue;
179 try (ParserHolder parser = getParser(file)) {
180 Persistable p;
181 while (parser.parser.getEventType() != XmlPullParser.START_TAG) {
182 parser.parser.next();
183 }
184 if (SliceClientPermissions.TAG_CLIENT.equals(parser.parser.getName())) {
185 p = SliceClientPermissions.createFrom(parser.parser, tracker);
186 } else {
187 p = SliceProviderPermissions.createFrom(parser.parser, tracker);
188 }
189 p.writeTo(out);
190 }
191 }
192
193 out.endTag(null, TAG_LIST);
194 }
195 }
196
197 public void readRestore(XmlPullParser parser) throws IOException, XmlPullParserException {
198 synchronized (this) {
199 while ((parser.getEventType() != XmlPullParser.START_TAG
200 || !TAG_LIST.equals(parser.getName()))
201 && parser.getEventType() != XmlPullParser.END_DOCUMENT) {
202 parser.next();
203 }
204 int xmlVersion = XmlUtils.readIntAttribute(parser, ATT_VERSION, 0);
205 if (xmlVersion < DB_VERSION) {
206 // No conversion support right now.
207 return;
208 }
209 while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
210 if (parser.getEventType() == XmlPullParser.START_TAG) {
211 if (SliceClientPermissions.TAG_CLIENT.equals(parser.getName())) {
212 SliceClientPermissions client = SliceClientPermissions.createFrom(parser,
213 this);
214 synchronized (mCachedClients) {
215 mCachedClients.put(client.getPkg(), client);
216 }
217 onPersistableDirty(client);
218 mHandler.sendMessageDelayed(
219 mHandler.obtainMessage(H.MSG_CLEAR_CLIENT, client.getPkg()),
220 PERMISSION_CACHE_PERIOD);
221 } else if (SliceProviderPermissions.TAG_PROVIDER.equals(parser.getName())) {
222 SliceProviderPermissions provider = SliceProviderPermissions.createFrom(
223 parser, this);
224 synchronized (mCachedProviders) {
225 mCachedProviders.put(provider.getPkg(), provider);
226 }
227 onPersistableDirty(provider);
228 mHandler.sendMessageDelayed(
229 mHandler.obtainMessage(H.MSG_CLEAR_PROVIDER, provider.getPkg()),
230 PERMISSION_CACHE_PERIOD);
231 } else {
232 parser.next();
233 }
234 } else {
235 parser.next();
236 }
237 }
238 }
239 }
240
241 private SliceClientPermissions getClient(PkgUser pkgUser) {
242 SliceClientPermissions client;
243 synchronized (mCachedClients) {
244 client = mCachedClients.get(pkgUser);
245 }
246 if (client == null) {
247 try (ParserHolder parser = getParser(SliceClientPermissions.getFileName(pkgUser))) {
248 client = SliceClientPermissions.createFrom(parser.parser, this);
249 synchronized (mCachedClients) {
250 mCachedClients.put(pkgUser, client);
251 }
252 mHandler.sendMessageDelayed(mHandler.obtainMessage(H.MSG_CLEAR_CLIENT, pkgUser),
253 PERMISSION_CACHE_PERIOD);
254 return client;
255 } catch (FileNotFoundException e) {
256 // No client exists yet.
257 } catch (IOException e) {
258 Log.e(TAG, "Can't read client", e);
259 } catch (XmlPullParserException e) {
260 Log.e(TAG, "Can't read client", e);
261 }
262 // Can't read or no permissions exist, create a clean object.
263 client = new SliceClientPermissions(pkgUser, this);
Jason Monk9c03ef42018-05-11 09:26:51 -0700264 synchronized (mCachedClients) {
265 mCachedClients.put(pkgUser, client);
266 }
Jason Monkbf3eedc2018-04-05 20:56:42 -0400267 }
268 return client;
269 }
270
271 private SliceProviderPermissions getProvider(PkgUser pkgUser) {
272 SliceProviderPermissions provider;
273 synchronized (mCachedProviders) {
274 provider = mCachedProviders.get(pkgUser);
275 }
276 if (provider == null) {
277 try (ParserHolder parser = getParser(SliceProviderPermissions.getFileName(pkgUser))) {
278 provider = SliceProviderPermissions.createFrom(parser.parser, this);
279 synchronized (mCachedProviders) {
280 mCachedProviders.put(pkgUser, provider);
281 }
282 mHandler.sendMessageDelayed(mHandler.obtainMessage(H.MSG_CLEAR_PROVIDER, pkgUser),
283 PERMISSION_CACHE_PERIOD);
284 return provider;
285 } catch (FileNotFoundException e) {
286 // No provider exists yet.
287 } catch (IOException e) {
288 Log.e(TAG, "Can't read provider", e);
289 } catch (XmlPullParserException e) {
290 Log.e(TAG, "Can't read provider", e);
291 }
292 // Can't read or no permissions exist, create a clean object.
293 provider = new SliceProviderPermissions(pkgUser, this);
Jason Monk9c03ef42018-05-11 09:26:51 -0700294 synchronized (mCachedProviders) {
295 mCachedProviders.put(pkgUser, provider);
296 }
Jason Monkbf3eedc2018-04-05 20:56:42 -0400297 }
298 return provider;
299 }
300
301 private ParserHolder getParser(String fileName)
302 throws FileNotFoundException, XmlPullParserException {
303 AtomicFile file = getFile(fileName);
304 ParserHolder holder = new ParserHolder();
305 holder.input = file.openRead();
306 holder.parser = XmlPullParserFactory.newInstance().newPullParser();
307 holder.parser.setInput(holder.input, Encoding.UTF_8.name());
308 return holder;
309 }
310
311 private AtomicFile getFile(String fileName) {
312 if (!mSliceDir.exists()) {
313 mSliceDir.mkdir();
314 }
315 return new AtomicFile(new File(mSliceDir, fileName));
316 }
317
Dan Sandler98267b32018-12-13 15:32:13 -0500318 @VisibleForTesting
319 void handlePersist() {
Jason Monkbf3eedc2018-04-05 20:56:42 -0400320 synchronized (this) {
321 for (Persistable persistable : mDirty) {
322 AtomicFile file = getFile(persistable.getFileName());
323 final FileOutputStream stream;
324 try {
325 stream = file.startWrite();
326 } catch (IOException e) {
327 Slog.w(TAG, "Failed to save access file", e);
328 return;
329 }
330
331 try {
332 XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
333 out.setOutput(stream, Encoding.UTF_8.name());
334
335 persistable.writeTo(out);
336
337 out.flush();
338 file.finishWrite(stream);
Dan Sandler98267b32018-12-13 15:32:13 -0500339 } catch (IOException | XmlPullParserException | RuntimeException e) {
Jason Monkbf3eedc2018-04-05 20:56:42 -0400340 Slog.w(TAG, "Failed to save access file, restoring backup", e);
341 file.failWrite(stream);
342 }
343 }
344 mDirty.clear();
345 }
346 }
347
Dan Sandler98267b32018-12-13 15:32:13 -0500348 // use addPersistableDirty(); this is just for tests
349 @VisibleForTesting
350 void addDirtyImmediate(Persistable obj) {
351 mDirty.add(obj);
352 }
353
Jason Monkbf3eedc2018-04-05 20:56:42 -0400354 private void handleRemove(PkgUser pkgUser) {
355 getFile(SliceClientPermissions.getFileName(pkgUser)).delete();
356 getFile(SliceProviderPermissions.getFileName(pkgUser)).delete();
357 mDirty.remove(mCachedClients.remove(pkgUser));
358 mDirty.remove(mCachedProviders.remove(pkgUser));
359 }
360
361 private final class H extends Handler {
362 private static final int MSG_ADD_DIRTY = 1;
363 private static final int MSG_PERSIST = 2;
364 private static final int MSG_REMOVE = 3;
365 private static final int MSG_CLEAR_CLIENT = 4;
366 private static final int MSG_CLEAR_PROVIDER = 5;
367
368 public H(Looper looper) {
369 super(looper);
370 }
371
372 @Override
373 public void handleMessage(Message msg) {
374 switch (msg.what) {
375 case MSG_ADD_DIRTY:
376 mDirty.add((Persistable) msg.obj);
377 break;
378 case MSG_PERSIST:
379 handlePersist();
380 break;
381 case MSG_REMOVE:
382 handleRemove((PkgUser) msg.obj);
383 break;
384 case MSG_CLEAR_CLIENT:
385 synchronized (mCachedClients) {
386 mCachedClients.remove(msg.obj);
387 }
388 break;
389 case MSG_CLEAR_PROVIDER:
390 synchronized (mCachedProviders) {
391 mCachedProviders.remove(msg.obj);
392 }
393 break;
394 }
395 }
396 }
397
398 public static class PkgUser {
399 private static final String SEPARATOR = "@";
400 private static final String FORMAT = "%s" + SEPARATOR + "%d";
401 private final String mPkg;
402 private final int mUserId;
403
404 public PkgUser(String pkg, int userId) {
405 mPkg = pkg;
406 mUserId = userId;
407 }
408
409 public PkgUser(String pkgUserStr) throws IllegalArgumentException {
410 try {
411 String[] vals = pkgUserStr.split(SEPARATOR, 2);
412 mPkg = vals[0];
413 mUserId = Integer.parseInt(vals[1]);
414 } catch (Exception e) {
415 throw new IllegalArgumentException(e);
416 }
417 }
418
419 public String getPkg() {
420 return mPkg;
421 }
422
423 public int getUserId() {
424 return mUserId;
425 }
426
427 @Override
428 public int hashCode() {
429 return mPkg.hashCode() + mUserId;
430 }
431
432 @Override
433 public boolean equals(Object obj) {
434 if (!getClass().equals(obj != null ? obj.getClass() : null)) return false;
435 PkgUser other = (PkgUser) obj;
436 return Objects.equals(other.mPkg, mPkg) && other.mUserId == mUserId;
437 }
438
439 @Override
440 public String toString() {
441 return String.format(FORMAT, mPkg, mUserId);
442 }
443 }
444
445 private class ParserHolder implements AutoCloseable {
446
447 private InputStream input;
448 private XmlPullParser parser;
449
450 @Override
451 public void close() throws IOException {
452 input.close();
453 }
454 }
455}