Merge "Slice permissions++" into pi-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
964631d1a7
@@ -25,11 +25,15 @@ interface ISliceManager {
|
||||
void unpinSlice(String pkg, in Uri uri, in IBinder token);
|
||||
boolean hasSliceAccess(String pkg);
|
||||
SliceSpec[] getPinnedSpecs(in Uri uri, String pkg);
|
||||
int checkSlicePermission(in Uri uri, String pkg, int pid, int uid,
|
||||
in String[] autoGrantPermissions);
|
||||
void grantPermissionFromUser(in Uri uri, String pkg, String callingPkg, boolean allSlices);
|
||||
Uri[] getPinnedSlices(String pkg);
|
||||
|
||||
byte[] getBackupPayload(int user);
|
||||
void applyRestore(in byte[] payload, int user);
|
||||
|
||||
// Perms.
|
||||
void grantSlicePermission(String callingPkg, String toPkg, in Uri uri);
|
||||
void revokeSlicePermission(String callingPkg, String toPkg, in Uri uri);
|
||||
int checkSlicePermission(in Uri uri, String pkg, int pid, int uid,
|
||||
in String[] autoGrantPermissions);
|
||||
void grantPermissionFromUser(in Uri uri, String pkg, String callingPkg, boolean allSlices);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package android.app.slice;
|
||||
|
||||
import static android.content.pm.PackageManager.PERMISSION_DENIED;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SdkConstant;
|
||||
@@ -100,22 +102,6 @@ public class SliceManager {
|
||||
private final Context mContext;
|
||||
private final IBinder mToken = new Binder();
|
||||
|
||||
/**
|
||||
* Permission denied.
|
||||
* @hide
|
||||
*/
|
||||
public static final int PERMISSION_DENIED = -1;
|
||||
/**
|
||||
* Permission granted.
|
||||
* @hide
|
||||
*/
|
||||
public static final int PERMISSION_GRANTED = 0;
|
||||
/**
|
||||
* Permission just granted by the user, and should be granted uri permission as well.
|
||||
* @hide
|
||||
*/
|
||||
public static final int PERMISSION_USER_GRANTED = 1;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@@ -417,9 +403,11 @@ public class SliceManager {
|
||||
* @see #grantSlicePermission(String, Uri)
|
||||
*/
|
||||
public @PermissionResult int checkSlicePermission(@NonNull Uri uri, int pid, int uid) {
|
||||
// TODO: Switch off Uri permissions.
|
||||
return mContext.checkUriPermission(uri, pid, uid,
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
try {
|
||||
return mService.checkSlicePermission(uri, null, pid, uid, null);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -431,11 +419,11 @@ public class SliceManager {
|
||||
* @see #revokeSlicePermission
|
||||
*/
|
||||
public void grantSlicePermission(@NonNull String toPackage, @NonNull Uri uri) {
|
||||
// TODO: Switch off Uri permissions.
|
||||
mContext.grantUriPermission(toPackage, uri,
|
||||
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
|
||||
try {
|
||||
mService.grantSlicePermission(mContext.getPackageName(), toPackage, uri);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -453,11 +441,11 @@ public class SliceManager {
|
||||
* @see #grantSlicePermission
|
||||
*/
|
||||
public void revokeSlicePermission(@NonNull String toPackage, @NonNull Uri uri) {
|
||||
// TODO: Switch off Uri permissions.
|
||||
mContext.revokeUriPermission(toPackage, uri,
|
||||
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
|
||||
try {
|
||||
mService.revokeSlicePermission(mContext.getPackageName(), toPackage, uri);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -478,16 +466,6 @@ public class SliceManager {
|
||||
throw new SecurityException("User " + uid + " does not have slice permission for "
|
||||
+ uri + ".");
|
||||
}
|
||||
if (result == PERMISSION_USER_GRANTED) {
|
||||
// We just had a user grant of this permission and need to grant this to the app
|
||||
// permanently.
|
||||
mContext.grantUriPermission(pkg, uri.buildUpon().path("").build(),
|
||||
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
|
||||
// Notify a change has happened because we just granted a permission.
|
||||
mContext.getContentResolver().notifyChange(uri, null);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.slice;
|
||||
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A parent object that cares when a Persistable changes and will schedule a serialization
|
||||
* in response to the onPersistableDirty callback.
|
||||
*/
|
||||
public interface DirtyTracker {
|
||||
void onPersistableDirty(Persistable obj);
|
||||
|
||||
/**
|
||||
* An object that can be written to XML.
|
||||
*/
|
||||
interface Persistable {
|
||||
String getFileName();
|
||||
void writeTo(XmlSerializer out) throws IOException;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.slice;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.ContentResolver;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.server.slice.DirtyTracker.Persistable;
|
||||
import com.android.server.slice.SlicePermissionManager.PkgUser;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SliceClientPermissions implements DirtyTracker, Persistable {
|
||||
|
||||
private static final String TAG = "SliceClientPermissions";
|
||||
|
||||
static final String TAG_CLIENT = "client";
|
||||
private static final String TAG_AUTHORITY = "authority";
|
||||
private static final String TAG_PATH = "path";
|
||||
private static final String NAMESPACE = null;
|
||||
|
||||
private static final String ATTR_PKG = "pkg";
|
||||
private static final String ATTR_AUTHORITY = "authority";
|
||||
private static final String ATTR_FULL_ACCESS = "fullAccess";
|
||||
|
||||
private final PkgUser mPkg;
|
||||
// Keyed off (authority, userId) rather than the standard (pkg, userId)
|
||||
private final ArrayMap<PkgUser, SliceAuthority> mAuths = new ArrayMap<>();
|
||||
private final DirtyTracker mTracker;
|
||||
private boolean mHasFullAccess;
|
||||
|
||||
public SliceClientPermissions(@NonNull PkgUser pkg, @NonNull DirtyTracker tracker) {
|
||||
mPkg = pkg;
|
||||
mTracker = tracker;
|
||||
}
|
||||
|
||||
public PkgUser getPkg() {
|
||||
return mPkg;
|
||||
}
|
||||
|
||||
public synchronized Collection<SliceAuthority> getAuthorities() {
|
||||
return new ArrayList<>(mAuths.values());
|
||||
}
|
||||
|
||||
public synchronized SliceAuthority getOrCreateAuthority(PkgUser authority, PkgUser provider) {
|
||||
SliceAuthority ret = mAuths.get(authority);
|
||||
if (ret == null) {
|
||||
ret = new SliceAuthority(authority.getPkg(), provider, this);
|
||||
mAuths.put(authority, ret);
|
||||
onPersistableDirty(ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public synchronized SliceAuthority getAuthority(PkgUser authority) {
|
||||
return mAuths.get(authority);
|
||||
}
|
||||
|
||||
public boolean hasFullAccess() {
|
||||
return mHasFullAccess;
|
||||
}
|
||||
|
||||
public void setHasFullAccess(boolean hasFullAccess) {
|
||||
if (mHasFullAccess == hasFullAccess) return;
|
||||
mHasFullAccess = hasFullAccess;
|
||||
mTracker.onPersistableDirty(this);
|
||||
}
|
||||
|
||||
public void removeAuthority(String authority, int userId) {
|
||||
if (mAuths.remove(new PkgUser(authority, userId)) != null) {
|
||||
mTracker.onPersistableDirty(this);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean hasPermission(Uri uri, int userId) {
|
||||
if (!Objects.equals(ContentResolver.SCHEME_CONTENT, uri.getScheme())) return false;
|
||||
SliceAuthority authority = getAuthority(new PkgUser(uri.getAuthority(), userId));
|
||||
return authority != null && authority.hasPermission(uri.getPathSegments());
|
||||
}
|
||||
|
||||
public void grantUri(Uri uri, PkgUser providerPkg) {
|
||||
SliceAuthority authority = getOrCreateAuthority(
|
||||
new PkgUser(uri.getAuthority(), providerPkg.getUserId()),
|
||||
providerPkg);
|
||||
authority.addPath(uri.getPathSegments());
|
||||
}
|
||||
|
||||
public void revokeUri(Uri uri, PkgUser providerPkg) {
|
||||
SliceAuthority authority = getOrCreateAuthority(
|
||||
new PkgUser(uri.getAuthority(), providerPkg.getUserId()),
|
||||
providerPkg);
|
||||
authority.removePath(uri.getPathSegments());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
if (!mHasFullAccess && mAuths.isEmpty()) return;
|
||||
mHasFullAccess = false;
|
||||
mAuths.clear();
|
||||
onPersistableDirty(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPersistableDirty(Persistable obj) {
|
||||
mTracker.onPersistableDirty(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return getFileName(mPkg);
|
||||
}
|
||||
|
||||
public synchronized void writeTo(XmlSerializer out) throws IOException {
|
||||
out.startTag(NAMESPACE, TAG_CLIENT);
|
||||
out.attribute(NAMESPACE, ATTR_PKG, mPkg.toString());
|
||||
out.attribute(NAMESPACE, ATTR_FULL_ACCESS, mHasFullAccess ? "1" : "0");
|
||||
|
||||
final int N = mAuths.size();
|
||||
for (int i = 0; i < N; i++) {
|
||||
out.startTag(NAMESPACE, TAG_AUTHORITY);
|
||||
out.attribute(NAMESPACE, ATTR_AUTHORITY, mAuths.valueAt(i).mAuthority);
|
||||
out.attribute(NAMESPACE, ATTR_PKG, mAuths.valueAt(i).mPkg.toString());
|
||||
|
||||
mAuths.valueAt(i).writeTo(out);
|
||||
|
||||
out.endTag(NAMESPACE, TAG_AUTHORITY);
|
||||
}
|
||||
|
||||
out.endTag(NAMESPACE, TAG_CLIENT);
|
||||
}
|
||||
|
||||
public static SliceClientPermissions createFrom(XmlPullParser parser, DirtyTracker tracker)
|
||||
throws XmlPullParserException, IOException {
|
||||
// Get to the beginning of the provider.
|
||||
while (parser.getEventType() != XmlPullParser.START_TAG
|
||||
|| !TAG_CLIENT.equals(parser.getName())) {
|
||||
parser.next();
|
||||
}
|
||||
int depth = parser.getDepth();
|
||||
PkgUser pkgUser = new PkgUser(parser.getAttributeValue(NAMESPACE, ATTR_PKG));
|
||||
SliceClientPermissions provider = new SliceClientPermissions(pkgUser, tracker);
|
||||
String fullAccess = parser.getAttributeValue(NAMESPACE, ATTR_FULL_ACCESS);
|
||||
if (fullAccess == null) {
|
||||
fullAccess = "0";
|
||||
}
|
||||
provider.mHasFullAccess = Integer.parseInt(fullAccess) != 0;
|
||||
parser.next();
|
||||
|
||||
while (parser.getDepth() > depth) {
|
||||
if (parser.getEventType() == XmlPullParser.START_TAG
|
||||
&& TAG_AUTHORITY.equals(parser.getName())) {
|
||||
try {
|
||||
PkgUser pkg = new PkgUser(parser.getAttributeValue(NAMESPACE, ATTR_PKG));
|
||||
SliceAuthority authority = new SliceAuthority(
|
||||
parser.getAttributeValue(NAMESPACE, ATTR_AUTHORITY), pkg, provider);
|
||||
authority.readFrom(parser);
|
||||
provider.mAuths.put(new PkgUser(authority.getAuthority(), pkg.getUserId()),
|
||||
authority);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Slog.e(TAG, "Couldn't read PkgUser", e);
|
||||
}
|
||||
}
|
||||
|
||||
parser.next();
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
public static String getFileName(PkgUser pkg) {
|
||||
return String.format("client_%s", pkg.toString());
|
||||
}
|
||||
|
||||
public static class SliceAuthority implements Persistable {
|
||||
public static final String DELIMITER = "/";
|
||||
private final String mAuthority;
|
||||
private final DirtyTracker mTracker;
|
||||
private final PkgUser mPkg;
|
||||
private final ArraySet<String[]> mPaths = new ArraySet<>();
|
||||
|
||||
public SliceAuthority(String authority, PkgUser pkg, DirtyTracker tracker) {
|
||||
mAuthority = authority;
|
||||
mPkg = pkg;
|
||||
mTracker = tracker;
|
||||
}
|
||||
|
||||
public String getAuthority() {
|
||||
return mAuthority;
|
||||
}
|
||||
|
||||
public PkgUser getPkg() {
|
||||
return mPkg;
|
||||
}
|
||||
|
||||
void addPath(List<String> path) {
|
||||
String[] pathSegs = path.toArray(new String[path.size()]);
|
||||
for (int i = mPaths.size() - 1; i >= 0; i--) {
|
||||
String[] existing = mPaths.valueAt(i);
|
||||
if (isPathPrefixMatch(existing, pathSegs)) {
|
||||
// Nothing to add here.
|
||||
return;
|
||||
}
|
||||
if (isPathPrefixMatch(pathSegs, existing)) {
|
||||
mPaths.removeAt(i);
|
||||
}
|
||||
}
|
||||
mPaths.add(pathSegs);
|
||||
mTracker.onPersistableDirty(this);
|
||||
}
|
||||
|
||||
void removePath(List<String> path) {
|
||||
boolean changed = false;
|
||||
String[] pathSegs = path.toArray(new String[path.size()]);
|
||||
for (int i = mPaths.size() - 1; i >= 0; i--) {
|
||||
String[] existing = mPaths.valueAt(i);
|
||||
if (isPathPrefixMatch(pathSegs, existing)) {
|
||||
changed = true;
|
||||
mPaths.removeAt(i);
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
mTracker.onPersistableDirty(this);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized Collection<String[]> getPaths() {
|
||||
return new ArraySet<>(mPaths);
|
||||
}
|
||||
|
||||
public boolean hasPermission(List<String> path) {
|
||||
for (String[] p : mPaths) {
|
||||
if (isPathPrefixMatch(p, path.toArray(new String[path.size()]))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isPathPrefixMatch(String[] prefix, String[] path) {
|
||||
final int prefixSize = prefix.length;
|
||||
if (path.length < prefixSize) return false;
|
||||
|
||||
for (int i = 0; i < prefixSize; i++) {
|
||||
if (!Objects.equals(path[i], prefix[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public synchronized void writeTo(XmlSerializer out) throws IOException {
|
||||
final int N = mPaths.size();
|
||||
for (int i = 0; i < N; i++) {
|
||||
out.startTag(NAMESPACE, TAG_PATH);
|
||||
out.text(encodeSegments(mPaths.valueAt(i)));
|
||||
out.endTag(NAMESPACE, TAG_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void readFrom(XmlPullParser parser)
|
||||
throws IOException, XmlPullParserException {
|
||||
parser.next();
|
||||
int depth = parser.getDepth();
|
||||
while (parser.getDepth() >= depth) {
|
||||
if (parser.getEventType() == XmlPullParser.START_TAG
|
||||
&& TAG_PATH.equals(parser.getName())) {
|
||||
mPaths.add(decodeSegments(parser.nextText()));
|
||||
}
|
||||
parser.next();
|
||||
}
|
||||
}
|
||||
|
||||
private String encodeSegments(String[] s) {
|
||||
String[] out = new String[s.length];
|
||||
for (int i = 0; i < s.length; i++) {
|
||||
out[i] = Uri.encode(s[i]);
|
||||
}
|
||||
return TextUtils.join(DELIMITER, out);
|
||||
}
|
||||
|
||||
private String[] decodeSegments(String s) {
|
||||
String[] sets = s.split(DELIMITER, -1);
|
||||
for (int i = 0; i < sets.length; i++) {
|
||||
sets[i] = Uri.decode(sets[i]);
|
||||
}
|
||||
return sets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only for testing, no deep equality of these are done normally.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!getClass().equals(obj != null ? obj.getClass() : null)) return false;
|
||||
SliceAuthority other = (SliceAuthority) obj;
|
||||
if (mPaths.size() != other.mPaths.size()) return false;
|
||||
ArrayList<String[]> p1 = new ArrayList<>(mPaths);
|
||||
ArrayList<String[]> p2 = new ArrayList<>(other.mPaths);
|
||||
p1.sort(Comparator.comparing(o -> TextUtils.join(",", o)));
|
||||
p2.sort(Comparator.comparing(o -> TextUtils.join(",", o)));
|
||||
for (int i = 0; i < p1.size(); i++) {
|
||||
String[] a1 = p1.get(i);
|
||||
String[] a2 = p2.get(i);
|
||||
if (a1.length != a2.length) return false;
|
||||
for (int j = 0; j < a1.length; j++) {
|
||||
if (!Objects.equals(a1[j], a2[j])) return false;
|
||||
}
|
||||
}
|
||||
return Objects.equals(mAuthority, other.mAuthority)
|
||||
&& Objects.equals(mPkg, other.mPkg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("(%s, %s: %s)", mAuthority, mPkg.toString(), pathToString(mPaths));
|
||||
}
|
||||
|
||||
private String pathToString(ArraySet<String[]> paths) {
|
||||
return TextUtils.join(", ", paths.stream().map(s -> TextUtils.join("/", s))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,14 +31,15 @@ import android.app.AppOpsManager;
|
||||
import android.app.ContentProviderHolder;
|
||||
import android.app.IActivityManager;
|
||||
import android.app.slice.ISliceManager;
|
||||
import android.app.slice.SliceManager;
|
||||
import android.app.slice.SliceSpec;
|
||||
import android.app.usage.UsageStatsManagerInternal;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
@@ -51,7 +52,6 @@ import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.Slog;
|
||||
import android.util.Xml.Encoding;
|
||||
@@ -72,7 +72,6 @@ import org.xmlpull.v1.XmlSerializer;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
@@ -91,13 +90,9 @@ public class SliceManagerService extends ISliceManager.Stub {
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private final ArrayMap<Uri, PinnedSliceState> mPinnedSlicesByUri = new ArrayMap<>();
|
||||
@GuardedBy("mLock")
|
||||
private final ArraySet<SliceGrant> mUserGrants = new ArraySet<>();
|
||||
private final Handler mHandler;
|
||||
@GuardedBy("mSliceAccessFile")
|
||||
private final AtomicFile mSliceAccessFile;
|
||||
@GuardedBy("mAccessList")
|
||||
private final SliceFullAccessList mAccessList;
|
||||
|
||||
private final SlicePermissionManager mPermissions;
|
||||
private final UsageStatsManagerInternal mAppUsageStats;
|
||||
|
||||
public SliceManagerService(Context context) {
|
||||
@@ -113,24 +108,9 @@ public class SliceManagerService extends ISliceManager.Stub {
|
||||
mAssistUtils = new AssistUtils(context);
|
||||
mHandler = new Handler(looper);
|
||||
|
||||
final File systemDir = new File(Environment.getDataDirectory(), "system");
|
||||
mSliceAccessFile = new AtomicFile(new File(systemDir, "slice_access.xml"));
|
||||
mAccessList = new SliceFullAccessList(mContext);
|
||||
mAppUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
|
||||
|
||||
synchronized (mSliceAccessFile) {
|
||||
if (!mSliceAccessFile.exists()) return;
|
||||
try {
|
||||
InputStream input = mSliceAccessFile.openRead();
|
||||
XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
|
||||
parser.setInput(input, Encoding.UTF_8.name());
|
||||
synchronized (mAccessList) {
|
||||
mAccessList.readXml(parser);
|
||||
}
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
Slog.d(TAG, "Can't read slice access file", e);
|
||||
}
|
||||
}
|
||||
mPermissions = new SlicePermissionManager(mContext, looper);
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
|
||||
@@ -210,27 +190,59 @@ public class SliceManagerService extends ISliceManager.Stub {
|
||||
return getPinnedSlice(uri).getSpecs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantSlicePermission(String pkg, String toPkg, Uri uri) throws RemoteException {
|
||||
verifyCaller(pkg);
|
||||
int user = Binder.getCallingUserHandle().getIdentifier();
|
||||
enforceOwner(pkg, uri, user);
|
||||
mPermissions.grantSliceAccess(toPkg, user, pkg, user, uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revokeSlicePermission(String pkg, String toPkg, Uri uri) throws RemoteException {
|
||||
verifyCaller(pkg);
|
||||
int user = Binder.getCallingUserHandle().getIdentifier();
|
||||
enforceOwner(pkg, uri, user);
|
||||
mPermissions.revokeSliceAccess(toPkg, user, pkg, user, uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int checkSlicePermission(Uri uri, String pkg, int pid, int uid,
|
||||
String[] autoGrantPermissions) throws RemoteException {
|
||||
String[] autoGrantPermissions) {
|
||||
int userId = UserHandle.getUserId(uid);
|
||||
if (pkg == null) {
|
||||
for (String p : mContext.getPackageManager().getPackagesForUid(uid)) {
|
||||
if (checkSlicePermission(uri, p, pid, uid, autoGrantPermissions)
|
||||
== PERMISSION_GRANTED) {
|
||||
return PERMISSION_GRANTED;
|
||||
}
|
||||
}
|
||||
return PERMISSION_DENIED;
|
||||
}
|
||||
if (hasFullSliceAccess(pkg, userId)) {
|
||||
return PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
if (mPermissions.hasPermission(pkg, userId, uri)) {
|
||||
return PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
if (autoGrantPermissions != null) {
|
||||
// Need to own the Uri to call in with permissions to grant.
|
||||
enforceOwner(pkg, uri, userId);
|
||||
for (String perm : autoGrantPermissions) {
|
||||
if (mContext.checkPermission(perm, pid, uid) == PERMISSION_GRANTED) {
|
||||
int providerUser = ContentProvider.getUserIdFromUri(uri, userId);
|
||||
String providerPkg = getProviderPkg(uri, providerUser);
|
||||
mPermissions.grantSliceAccess(pkg, userId, providerPkg, providerUser, uri);
|
||||
return PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback to allowing uri permissions through.
|
||||
if (mContext.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
== PERMISSION_GRANTED) {
|
||||
return SliceManager.PERMISSION_GRANTED;
|
||||
return PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
if (hasFullSliceAccess(pkg, UserHandle.getUserId(uid))) {
|
||||
return SliceManager.PERMISSION_GRANTED;
|
||||
}
|
||||
for (String perm : autoGrantPermissions) {
|
||||
if (mContext.checkPermission(perm, pid, uid) == PERMISSION_GRANTED) {
|
||||
return SliceManager.PERMISSION_USER_GRANTED;
|
||||
}
|
||||
}
|
||||
synchronized (mLock) {
|
||||
if (mUserGrants.contains(new SliceGrant(uri, pkg, UserHandle.getUserId(uid)))) {
|
||||
return SliceManager.PERMISSION_USER_GRANTED;
|
||||
}
|
||||
}
|
||||
return SliceManager.PERMISSION_DENIED;
|
||||
return PackageManager.PERMISSION_DENIED;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -238,16 +250,17 @@ public class SliceManagerService extends ISliceManager.Stub {
|
||||
verifyCaller(callingPkg);
|
||||
getContext().enforceCallingOrSelfPermission(permission.MANAGE_SLICE_PERMISSIONS,
|
||||
"Slice granting requires MANAGE_SLICE_PERMISSIONS");
|
||||
int userId = Binder.getCallingUserHandle().getIdentifier();
|
||||
if (allSlices) {
|
||||
synchronized (mAccessList) {
|
||||
mAccessList.grantFullAccess(pkg, Binder.getCallingUserHandle().getIdentifier());
|
||||
}
|
||||
mHandler.post(mSaveAccessList);
|
||||
mPermissions.grantFullAccess(pkg, userId);
|
||||
} else {
|
||||
synchronized (mLock) {
|
||||
mUserGrants.add(new SliceGrant(uri, pkg,
|
||||
Binder.getCallingUserHandle().getIdentifier()));
|
||||
}
|
||||
// When granting, grant to all slices in the provider.
|
||||
Uri grantUri = uri.buildUpon()
|
||||
.path("")
|
||||
.build();
|
||||
int providerUser = ContentProvider.getUserIdFromUri(grantUri, userId);
|
||||
String providerPkg = getProviderPkg(grantUri, providerUser);
|
||||
mPermissions.grantSliceAccess(pkg, userId, providerPkg, providerUser, grantUri);
|
||||
}
|
||||
long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
@@ -268,19 +281,17 @@ public class SliceManagerService extends ISliceManager.Stub {
|
||||
Slog.w(TAG, "getBackupPayload: cannot backup policy for user " + user);
|
||||
return null;
|
||||
}
|
||||
synchronized(mSliceAccessFile) {
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
|
||||
out.setOutput(baos, Encoding.UTF_8.name());
|
||||
synchronized (mAccessList) {
|
||||
mAccessList.writeXml(out, user);
|
||||
}
|
||||
out.flush();
|
||||
return baos.toByteArray();
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
|
||||
}
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
|
||||
out.setOutput(baos, Encoding.UTF_8.name());
|
||||
|
||||
mPermissions.writeBackup(out);
|
||||
|
||||
out.flush();
|
||||
return baos.toByteArray();
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -299,27 +310,21 @@ public class SliceManagerService extends ISliceManager.Stub {
|
||||
Slog.w(TAG, "applyRestore: cannot restore policy for user " + user);
|
||||
return;
|
||||
}
|
||||
synchronized(mSliceAccessFile) {
|
||||
final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
|
||||
try {
|
||||
XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
|
||||
parser.setInput(bais, Encoding.UTF_8.name());
|
||||
synchronized (mAccessList) {
|
||||
mAccessList.readXml(parser);
|
||||
}
|
||||
mHandler.post(mSaveAccessList);
|
||||
} catch (NumberFormatException | XmlPullParserException | IOException e) {
|
||||
Slog.w(TAG, "applyRestore: error reading payload", e);
|
||||
}
|
||||
final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
|
||||
try {
|
||||
XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
|
||||
parser.setInput(bais, Encoding.UTF_8.name());
|
||||
mPermissions.readRestore(parser);
|
||||
} catch (NumberFormatException | XmlPullParserException | IOException e) {
|
||||
Slog.w(TAG, "applyRestore: error reading payload", e);
|
||||
}
|
||||
}
|
||||
|
||||
/// ----- internal code -----
|
||||
private void removeFullAccess(String pkg, int userId) {
|
||||
synchronized (mAccessList) {
|
||||
mAccessList.removeGrant(pkg, userId);
|
||||
private void enforceOwner(String pkg, Uri uri, int user) {
|
||||
if (!Objects.equals(getProviderPkg(uri, user), pkg) || pkg == null) {
|
||||
throw new SecurityException("Caller must own " + uri);
|
||||
}
|
||||
mHandler.post(mSaveAccessList);
|
||||
}
|
||||
|
||||
protected void removePinnedSlice(Uri uri) {
|
||||
@@ -368,19 +373,7 @@ public class SliceManagerService extends ISliceManager.Stub {
|
||||
}
|
||||
|
||||
protected int checkAccess(String pkg, Uri uri, int uid, int pid) {
|
||||
int user = UserHandle.getUserId(uid);
|
||||
// Check for default launcher/assistant.
|
||||
if (!hasFullSliceAccess(pkg, user)) {
|
||||
// Also allow things with uri access.
|
||||
if (getContext().checkUriPermission(uri, pid, uid,
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != PERMISSION_GRANTED) {
|
||||
// Last fallback (if the calling app owns the authority, then it can have access).
|
||||
if (!Objects.equals(getProviderPkg(uri, user), pkg)) {
|
||||
return PERMISSION_DENIED;
|
||||
}
|
||||
}
|
||||
}
|
||||
return PERMISSION_GRANTED;
|
||||
return checkSlicePermission(uri, pkg, uid, pid, null);
|
||||
}
|
||||
|
||||
private String getProviderPkg(Uri uri, int user) {
|
||||
@@ -425,15 +418,11 @@ public class SliceManagerService extends ISliceManager.Stub {
|
||||
private void enforceAccess(String pkg, Uri uri) throws RemoteException {
|
||||
if (checkAccess(pkg, uri, Binder.getCallingUid(), Binder.getCallingPid())
|
||||
!= PERMISSION_GRANTED) {
|
||||
throw new SecurityException("Access to slice " + uri + " is required");
|
||||
}
|
||||
enforceCrossUser(pkg, uri);
|
||||
}
|
||||
|
||||
private void enforceFullAccess(String pkg, String name, Uri uri) {
|
||||
int user = Binder.getCallingUserHandle().getIdentifier();
|
||||
if (!hasFullSliceAccess(pkg, user)) {
|
||||
throw new SecurityException(String.format("Call %s requires full slice access", name));
|
||||
int userId = ContentProvider.getUserIdFromUri(uri,
|
||||
Binder.getCallingUserHandle().getIdentifier());
|
||||
if (!Objects.equals(pkg, getProviderPkg(uri, userId))) {
|
||||
throw new SecurityException("Access to slice " + uri + " is required");
|
||||
}
|
||||
}
|
||||
enforceCrossUser(pkg, uri);
|
||||
}
|
||||
@@ -513,9 +502,7 @@ public class SliceManagerService extends ISliceManager.Stub {
|
||||
}
|
||||
|
||||
private boolean isGrantedFullAccess(String pkg, int userId) {
|
||||
synchronized (mAccessList) {
|
||||
return mAccessList.hasFullAccess(pkg, userId);
|
||||
}
|
||||
return mPermissions.hasFullAccess(pkg, userId);
|
||||
}
|
||||
|
||||
private static ServiceThread createHandler() {
|
||||
@@ -525,34 +512,6 @@ public class SliceManagerService extends ISliceManager.Stub {
|
||||
return handlerThread;
|
||||
}
|
||||
|
||||
private final Runnable mSaveAccessList = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (mSliceAccessFile) {
|
||||
final FileOutputStream stream;
|
||||
try {
|
||||
stream = mSliceAccessFile.startWrite();
|
||||
} catch (IOException e) {
|
||||
Slog.w(TAG, "Failed to save access file", e);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
|
||||
out.setOutput(stream, Encoding.UTF_8.name());
|
||||
synchronized (mAccessList) {
|
||||
mAccessList.writeXml(out, UserHandle.USER_ALL);
|
||||
}
|
||||
out.flush();
|
||||
mSliceAccessFile.finishWrite(stream);
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
Slog.w(TAG, "Failed to save access file, restoring backup", e);
|
||||
mSliceAccessFile.failWrite(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
@@ -572,11 +531,11 @@ public class SliceManagerService extends ISliceManager.Stub {
|
||||
final boolean replacing =
|
||||
intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
|
||||
if (!replacing) {
|
||||
removeFullAccess(pkg, userId);
|
||||
mPermissions.removePkg(pkg, userId);
|
||||
}
|
||||
break;
|
||||
case Intent.ACTION_PACKAGE_DATA_CLEARED:
|
||||
removeFullAccess(pkg, userId);
|
||||
mPermissions.removePkg(pkg, userId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,432 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.slice;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.util.Xml.Encoding;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.XmlUtils;
|
||||
import com.android.server.slice.SliceProviderPermissions.SliceAuthority;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
|
||||
public class SlicePermissionManager implements DirtyTracker {
|
||||
|
||||
private static final String TAG = "SlicePermissionManager";
|
||||
|
||||
/**
|
||||
* The amount of time we'll cache a SliceProviderPermissions or SliceClientPermissions
|
||||
* in case they are used again.
|
||||
*/
|
||||
private static final long PERMISSION_CACHE_PERIOD = 5 * DateUtils.MINUTE_IN_MILLIS;
|
||||
|
||||
/**
|
||||
* The amount of time we delay flushing out permission changes to disk because they usually
|
||||
* come in short bursts.
|
||||
*/
|
||||
private static final long WRITE_GRACE_PERIOD = 500;
|
||||
|
||||
private static final String SLICE_DIR = "slice";
|
||||
|
||||
// If/when this bumps again we'll need to write it out in the disk somewhere.
|
||||
// Currently we don't have a central file for this in version 2 and there is no
|
||||
// reason to add one until we actually have incompatible version bumps.
|
||||
// This does however block us from reading backups from P-DP1 which may contain
|
||||
// a very different XML format for perms.
|
||||
static final int DB_VERSION = 2;
|
||||
|
||||
private static final String TAG_LIST = "slice-access-list";
|
||||
private final String ATT_VERSION = "version";
|
||||
|
||||
private final File mSliceDir;
|
||||
private final Context mContext;
|
||||
private final Handler mHandler;
|
||||
private final ArrayMap<PkgUser, SliceProviderPermissions> mCachedProviders = new ArrayMap<>();
|
||||
private final ArrayMap<PkgUser, SliceClientPermissions> mCachedClients = new ArrayMap<>();
|
||||
private final ArraySet<Persistable> mDirty = new ArraySet<>();
|
||||
|
||||
@VisibleForTesting
|
||||
SlicePermissionManager(Context context, Looper looper, File sliceDir) {
|
||||
mContext = context;
|
||||
mHandler = new H(looper);
|
||||
mSliceDir = sliceDir;
|
||||
}
|
||||
|
||||
public SlicePermissionManager(Context context, Looper looper) {
|
||||
this(context, looper, new File(Environment.getDataDirectory(), "system/" + SLICE_DIR));
|
||||
}
|
||||
|
||||
public void grantFullAccess(String pkg, int userId) {
|
||||
PkgUser pkgUser = new PkgUser(pkg, userId);
|
||||
SliceClientPermissions client = getClient(pkgUser);
|
||||
client.setHasFullAccess(true);
|
||||
}
|
||||
|
||||
public void grantSliceAccess(String pkg, int userId, String providerPkg, int providerUser,
|
||||
Uri uri) {
|
||||
PkgUser pkgUser = new PkgUser(pkg, userId);
|
||||
PkgUser providerPkgUser = new PkgUser(providerPkg, providerUser);
|
||||
|
||||
SliceClientPermissions client = getClient(pkgUser);
|
||||
client.grantUri(uri, providerPkgUser);
|
||||
|
||||
SliceProviderPermissions provider = getProvider(providerPkgUser);
|
||||
provider.getOrCreateAuthority(ContentProvider.getUriWithoutUserId(uri).getAuthority())
|
||||
.addPkg(pkgUser);
|
||||
}
|
||||
|
||||
public void revokeSliceAccess(String pkg, int userId, String providerPkg, int providerUser,
|
||||
Uri uri) {
|
||||
PkgUser pkgUser = new PkgUser(pkg, userId);
|
||||
PkgUser providerPkgUser = new PkgUser(providerPkg, providerUser);
|
||||
|
||||
SliceClientPermissions client = getClient(pkgUser);
|
||||
client.revokeUri(uri, providerPkgUser);
|
||||
}
|
||||
|
||||
public void removePkg(String pkg, int userId) {
|
||||
PkgUser pkgUser = new PkgUser(pkg, userId);
|
||||
SliceProviderPermissions provider = getProvider(pkgUser);
|
||||
|
||||
for (SliceAuthority authority : provider.getAuthorities()) {
|
||||
for (PkgUser p : authority.getPkgs()) {
|
||||
getClient(p).removeAuthority(authority.getAuthority(), userId);
|
||||
}
|
||||
}
|
||||
SliceClientPermissions client = getClient(pkgUser);
|
||||
client.clear();
|
||||
mHandler.obtainMessage(H.MSG_REMOVE, pkgUser);
|
||||
}
|
||||
|
||||
public boolean hasFullAccess(String pkg, int userId) {
|
||||
PkgUser pkgUser = new PkgUser(pkg, userId);
|
||||
return getClient(pkgUser).hasFullAccess();
|
||||
}
|
||||
|
||||
public boolean hasPermission(String pkg, int userId, Uri uri) {
|
||||
PkgUser pkgUser = new PkgUser(pkg, userId);
|
||||
SliceClientPermissions client = getClient(pkgUser);
|
||||
int providerUserId = ContentProvider.getUserIdFromUri(uri, userId);
|
||||
return client.hasFullAccess()
|
||||
|| client.hasPermission(ContentProvider.getUriWithoutUserId(uri), providerUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPersistableDirty(Persistable obj) {
|
||||
mHandler.removeMessages(H.MSG_PERSIST);
|
||||
mHandler.obtainMessage(H.MSG_ADD_DIRTY, obj).sendToTarget();
|
||||
mHandler.sendEmptyMessageDelayed(H.MSG_PERSIST, WRITE_GRACE_PERIOD);
|
||||
}
|
||||
|
||||
public void writeBackup(XmlSerializer out) throws IOException, XmlPullParserException {
|
||||
synchronized (this) {
|
||||
out.startTag(null, TAG_LIST);
|
||||
out.attribute(null, ATT_VERSION, String.valueOf(DB_VERSION));
|
||||
|
||||
// Don't do anything with changes from the backup, because there shouldn't be any.
|
||||
DirtyTracker tracker = obj -> { };
|
||||
if (mHandler.hasMessages(H.MSG_PERSIST)) {
|
||||
mHandler.removeMessages(H.MSG_PERSIST);
|
||||
handlePersist();
|
||||
}
|
||||
for (String file : new File(mSliceDir.getAbsolutePath()).list()) {
|
||||
if (file.isEmpty()) continue;
|
||||
try (ParserHolder parser = getParser(file)) {
|
||||
Persistable p;
|
||||
while (parser.parser.getEventType() != XmlPullParser.START_TAG) {
|
||||
parser.parser.next();
|
||||
}
|
||||
if (SliceClientPermissions.TAG_CLIENT.equals(parser.parser.getName())) {
|
||||
p = SliceClientPermissions.createFrom(parser.parser, tracker);
|
||||
} else {
|
||||
p = SliceProviderPermissions.createFrom(parser.parser, tracker);
|
||||
}
|
||||
p.writeTo(out);
|
||||
}
|
||||
}
|
||||
|
||||
out.endTag(null, TAG_LIST);
|
||||
}
|
||||
}
|
||||
|
||||
public void readRestore(XmlPullParser parser) throws IOException, XmlPullParserException {
|
||||
synchronized (this) {
|
||||
while ((parser.getEventType() != XmlPullParser.START_TAG
|
||||
|| !TAG_LIST.equals(parser.getName()))
|
||||
&& parser.getEventType() != XmlPullParser.END_DOCUMENT) {
|
||||
parser.next();
|
||||
}
|
||||
int xmlVersion = XmlUtils.readIntAttribute(parser, ATT_VERSION, 0);
|
||||
if (xmlVersion < DB_VERSION) {
|
||||
// No conversion support right now.
|
||||
return;
|
||||
}
|
||||
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
|
||||
if (parser.getEventType() == XmlPullParser.START_TAG) {
|
||||
if (SliceClientPermissions.TAG_CLIENT.equals(parser.getName())) {
|
||||
SliceClientPermissions client = SliceClientPermissions.createFrom(parser,
|
||||
this);
|
||||
synchronized (mCachedClients) {
|
||||
mCachedClients.put(client.getPkg(), client);
|
||||
}
|
||||
onPersistableDirty(client);
|
||||
mHandler.sendMessageDelayed(
|
||||
mHandler.obtainMessage(H.MSG_CLEAR_CLIENT, client.getPkg()),
|
||||
PERMISSION_CACHE_PERIOD);
|
||||
} else if (SliceProviderPermissions.TAG_PROVIDER.equals(parser.getName())) {
|
||||
SliceProviderPermissions provider = SliceProviderPermissions.createFrom(
|
||||
parser, this);
|
||||
synchronized (mCachedProviders) {
|
||||
mCachedProviders.put(provider.getPkg(), provider);
|
||||
}
|
||||
onPersistableDirty(provider);
|
||||
mHandler.sendMessageDelayed(
|
||||
mHandler.obtainMessage(H.MSG_CLEAR_PROVIDER, provider.getPkg()),
|
||||
PERMISSION_CACHE_PERIOD);
|
||||
} else {
|
||||
parser.next();
|
||||
}
|
||||
} else {
|
||||
parser.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SliceClientPermissions getClient(PkgUser pkgUser) {
|
||||
SliceClientPermissions client;
|
||||
synchronized (mCachedClients) {
|
||||
client = mCachedClients.get(pkgUser);
|
||||
}
|
||||
if (client == null) {
|
||||
try (ParserHolder parser = getParser(SliceClientPermissions.getFileName(pkgUser))) {
|
||||
client = SliceClientPermissions.createFrom(parser.parser, this);
|
||||
synchronized (mCachedClients) {
|
||||
mCachedClients.put(pkgUser, client);
|
||||
}
|
||||
mHandler.sendMessageDelayed(mHandler.obtainMessage(H.MSG_CLEAR_CLIENT, pkgUser),
|
||||
PERMISSION_CACHE_PERIOD);
|
||||
return client;
|
||||
} catch (FileNotFoundException e) {
|
||||
// No client exists yet.
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Can't read client", e);
|
||||
} catch (XmlPullParserException e) {
|
||||
Log.e(TAG, "Can't read client", e);
|
||||
}
|
||||
// Can't read or no permissions exist, create a clean object.
|
||||
client = new SliceClientPermissions(pkgUser, this);
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
private SliceProviderPermissions getProvider(PkgUser pkgUser) {
|
||||
SliceProviderPermissions provider;
|
||||
synchronized (mCachedProviders) {
|
||||
provider = mCachedProviders.get(pkgUser);
|
||||
}
|
||||
if (provider == null) {
|
||||
try (ParserHolder parser = getParser(SliceProviderPermissions.getFileName(pkgUser))) {
|
||||
provider = SliceProviderPermissions.createFrom(parser.parser, this);
|
||||
synchronized (mCachedProviders) {
|
||||
mCachedProviders.put(pkgUser, provider);
|
||||
}
|
||||
mHandler.sendMessageDelayed(mHandler.obtainMessage(H.MSG_CLEAR_PROVIDER, pkgUser),
|
||||
PERMISSION_CACHE_PERIOD);
|
||||
return provider;
|
||||
} catch (FileNotFoundException e) {
|
||||
// No provider exists yet.
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Can't read provider", e);
|
||||
} catch (XmlPullParserException e) {
|
||||
Log.e(TAG, "Can't read provider", e);
|
||||
}
|
||||
// Can't read or no permissions exist, create a clean object.
|
||||
provider = new SliceProviderPermissions(pkgUser, this);
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
private ParserHolder getParser(String fileName)
|
||||
throws FileNotFoundException, XmlPullParserException {
|
||||
AtomicFile file = getFile(fileName);
|
||||
ParserHolder holder = new ParserHolder();
|
||||
holder.input = file.openRead();
|
||||
holder.parser = XmlPullParserFactory.newInstance().newPullParser();
|
||||
holder.parser.setInput(holder.input, Encoding.UTF_8.name());
|
||||
return holder;
|
||||
}
|
||||
|
||||
private AtomicFile getFile(String fileName) {
|
||||
if (!mSliceDir.exists()) {
|
||||
mSliceDir.mkdir();
|
||||
}
|
||||
return new AtomicFile(new File(mSliceDir, fileName));
|
||||
}
|
||||
|
||||
private void handlePersist() {
|
||||
synchronized (this) {
|
||||
for (Persistable persistable : mDirty) {
|
||||
AtomicFile file = getFile(persistable.getFileName());
|
||||
final FileOutputStream stream;
|
||||
try {
|
||||
stream = file.startWrite();
|
||||
} catch (IOException e) {
|
||||
Slog.w(TAG, "Failed to save access file", e);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
|
||||
out.setOutput(stream, Encoding.UTF_8.name());
|
||||
|
||||
persistable.writeTo(out);
|
||||
|
||||
out.flush();
|
||||
file.finishWrite(stream);
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
Slog.w(TAG, "Failed to save access file, restoring backup", e);
|
||||
file.failWrite(stream);
|
||||
}
|
||||
}
|
||||
mDirty.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRemove(PkgUser pkgUser) {
|
||||
getFile(SliceClientPermissions.getFileName(pkgUser)).delete();
|
||||
getFile(SliceProviderPermissions.getFileName(pkgUser)).delete();
|
||||
mDirty.remove(mCachedClients.remove(pkgUser));
|
||||
mDirty.remove(mCachedProviders.remove(pkgUser));
|
||||
}
|
||||
|
||||
private final class H extends Handler {
|
||||
private static final int MSG_ADD_DIRTY = 1;
|
||||
private static final int MSG_PERSIST = 2;
|
||||
private static final int MSG_REMOVE = 3;
|
||||
private static final int MSG_CLEAR_CLIENT = 4;
|
||||
private static final int MSG_CLEAR_PROVIDER = 5;
|
||||
|
||||
public H(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_ADD_DIRTY:
|
||||
mDirty.add((Persistable) msg.obj);
|
||||
break;
|
||||
case MSG_PERSIST:
|
||||
handlePersist();
|
||||
break;
|
||||
case MSG_REMOVE:
|
||||
handleRemove((PkgUser) msg.obj);
|
||||
break;
|
||||
case MSG_CLEAR_CLIENT:
|
||||
synchronized (mCachedClients) {
|
||||
mCachedClients.remove(msg.obj);
|
||||
}
|
||||
break;
|
||||
case MSG_CLEAR_PROVIDER:
|
||||
synchronized (mCachedProviders) {
|
||||
mCachedProviders.remove(msg.obj);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class PkgUser {
|
||||
private static final String SEPARATOR = "@";
|
||||
private static final String FORMAT = "%s" + SEPARATOR + "%d";
|
||||
private final String mPkg;
|
||||
private final int mUserId;
|
||||
|
||||
public PkgUser(String pkg, int userId) {
|
||||
mPkg = pkg;
|
||||
mUserId = userId;
|
||||
}
|
||||
|
||||
public PkgUser(String pkgUserStr) throws IllegalArgumentException {
|
||||
try {
|
||||
String[] vals = pkgUserStr.split(SEPARATOR, 2);
|
||||
mPkg = vals[0];
|
||||
mUserId = Integer.parseInt(vals[1]);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getPkg() {
|
||||
return mPkg;
|
||||
}
|
||||
|
||||
public int getUserId() {
|
||||
return mUserId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mPkg.hashCode() + mUserId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!getClass().equals(obj != null ? obj.getClass() : null)) return false;
|
||||
PkgUser other = (PkgUser) obj;
|
||||
return Objects.equals(other.mPkg, mPkg) && other.mUserId == mUserId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(FORMAT, mPkg, mUserId);
|
||||
}
|
||||
}
|
||||
|
||||
private class ParserHolder implements AutoCloseable {
|
||||
|
||||
private InputStream input;
|
||||
private XmlPullParser parser;
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.slice;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.server.slice.DirtyTracker.Persistable;
|
||||
import com.android.server.slice.SlicePermissionManager.PkgUser;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
public class SliceProviderPermissions implements DirtyTracker, Persistable {
|
||||
|
||||
private static final String TAG = "SliceProviderPermissions";
|
||||
|
||||
static final String TAG_PROVIDER = "provider";
|
||||
private static final String TAG_AUTHORITY = "authority";
|
||||
private static final String TAG_PKG = "pkg";
|
||||
private static final String NAMESPACE = null;
|
||||
|
||||
private static final String ATTR_PKG = "pkg";
|
||||
private static final String ATTR_AUTHORITY = "authority";
|
||||
|
||||
private final PkgUser mPkg;
|
||||
private final ArrayMap<String, SliceAuthority> mAuths = new ArrayMap<>();
|
||||
private final DirtyTracker mTracker;
|
||||
|
||||
public SliceProviderPermissions(@NonNull PkgUser pkg, @NonNull DirtyTracker tracker) {
|
||||
mPkg = pkg;
|
||||
mTracker = tracker;
|
||||
}
|
||||
|
||||
public PkgUser getPkg() {
|
||||
return mPkg;
|
||||
}
|
||||
|
||||
public synchronized Collection<SliceAuthority> getAuthorities() {
|
||||
return new ArrayList<>(mAuths.values());
|
||||
}
|
||||
|
||||
public synchronized SliceAuthority getOrCreateAuthority(String authority) {
|
||||
SliceAuthority ret = mAuths.get(authority);
|
||||
if (ret == null) {
|
||||
ret = new SliceAuthority(authority, this);
|
||||
mAuths.put(authority, ret);
|
||||
onPersistableDirty(ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPersistableDirty(Persistable obj) {
|
||||
mTracker.onPersistableDirty(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return getFileName(mPkg);
|
||||
}
|
||||
|
||||
public synchronized void writeTo(XmlSerializer out) throws IOException {
|
||||
out.startTag(NAMESPACE, TAG_PROVIDER);
|
||||
out.attribute(NAMESPACE, ATTR_PKG, mPkg.toString());
|
||||
|
||||
final int N = mAuths.size();
|
||||
for (int i = 0; i < N; i++) {
|
||||
out.startTag(NAMESPACE, TAG_AUTHORITY);
|
||||
out.attribute(NAMESPACE, ATTR_AUTHORITY, mAuths.valueAt(i).mAuthority);
|
||||
|
||||
mAuths.valueAt(i).writeTo(out);
|
||||
|
||||
out.endTag(NAMESPACE, TAG_AUTHORITY);
|
||||
}
|
||||
|
||||
out.endTag(NAMESPACE, TAG_PROVIDER);
|
||||
}
|
||||
|
||||
public static SliceProviderPermissions createFrom(XmlPullParser parser, DirtyTracker tracker)
|
||||
throws XmlPullParserException, IOException {
|
||||
// Get to the beginning of the provider.
|
||||
while (parser.getEventType() != XmlPullParser.START_TAG
|
||||
|| !TAG_PROVIDER.equals(parser.getName())) {
|
||||
parser.next();
|
||||
}
|
||||
int depth = parser.getDepth();
|
||||
PkgUser pkgUser = new PkgUser(parser.getAttributeValue(NAMESPACE, ATTR_PKG));
|
||||
SliceProviderPermissions provider = new SliceProviderPermissions(pkgUser, tracker);
|
||||
parser.next();
|
||||
|
||||
while (parser.getDepth() > depth) {
|
||||
if (parser.getEventType() == XmlPullParser.START_TAG
|
||||
&& TAG_AUTHORITY.equals(parser.getName())) {
|
||||
try {
|
||||
SliceAuthority authority = new SliceAuthority(
|
||||
parser.getAttributeValue(NAMESPACE, ATTR_AUTHORITY), provider);
|
||||
authority.readFrom(parser);
|
||||
provider.mAuths.put(authority.getAuthority(), authority);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Slog.e(TAG, "Couldn't read PkgUser", e);
|
||||
}
|
||||
}
|
||||
|
||||
parser.next();
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
public static String getFileName(PkgUser pkg) {
|
||||
return String.format("provider_%s", pkg.toString());
|
||||
}
|
||||
|
||||
public static class SliceAuthority implements Persistable {
|
||||
private final String mAuthority;
|
||||
private final DirtyTracker mTracker;
|
||||
private final ArraySet<PkgUser> mPkgs = new ArraySet<>();
|
||||
|
||||
public SliceAuthority(String authority, DirtyTracker tracker) {
|
||||
mAuthority = authority;
|
||||
mTracker = tracker;
|
||||
}
|
||||
|
||||
public String getAuthority() {
|
||||
return mAuthority;
|
||||
}
|
||||
|
||||
public synchronized void addPkg(PkgUser pkg) {
|
||||
if (mPkgs.add(pkg)) {
|
||||
mTracker.onPersistableDirty(this);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void removePkg(PkgUser pkg) {
|
||||
if (mPkgs.remove(pkg)) {
|
||||
mTracker.onPersistableDirty(this);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized Collection<PkgUser> getPkgs() {
|
||||
return new ArraySet<>(mPkgs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public synchronized void writeTo(XmlSerializer out) throws IOException {
|
||||
final int N = mPkgs.size();
|
||||
for (int i = 0; i < N; i++) {
|
||||
out.startTag(NAMESPACE, TAG_PKG);
|
||||
out.text(mPkgs.valueAt(i).toString());
|
||||
out.endTag(NAMESPACE, TAG_PKG);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void readFrom(XmlPullParser parser)
|
||||
throws IOException, XmlPullParserException {
|
||||
parser.next();
|
||||
int depth = parser.getDepth();
|
||||
while (parser.getDepth() >= depth) {
|
||||
if (parser.getEventType() == XmlPullParser.START_TAG
|
||||
&& TAG_PKG.equals(parser.getName())) {
|
||||
mPkgs.add(new PkgUser(parser.nextText()));
|
||||
}
|
||||
parser.next();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!getClass().equals(obj != null ? obj.getClass() : null)) return false;
|
||||
SliceAuthority other = (SliceAuthority) obj;
|
||||
return Objects.equals(mAuthority, other.mAuthority)
|
||||
&& Objects.equals(mPkgs, other.mPkgs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("(%s: %s)", mAuthority, mPkgs.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.slice;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.net.Uri;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper.RunWithLooper;
|
||||
import android.util.Xml.Encoding;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
import com.android.server.slice.SlicePermissionManager.PkgUser;
|
||||
import com.android.server.slice.SliceClientPermissions.SliceAuthority;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@RunWithLooper
|
||||
public class SliceClientPermissionsTest extends UiServiceTestCase {
|
||||
|
||||
@Test
|
||||
public void testRemoveBasic() {
|
||||
PkgUser pkg = new PkgUser("com.android.pkg", 0);
|
||||
DirtyTracker tracker = mock(DirtyTracker.class);
|
||||
SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
|
||||
Uri base = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority("com.android.pkg.slices").build();
|
||||
|
||||
PkgUser testPkg = new PkgUser("other", 2);
|
||||
|
||||
client.grantUri(base.buildUpon()
|
||||
.appendPath("first")
|
||||
.build(), testPkg);
|
||||
client.revokeUri(base.buildUpon()
|
||||
.appendPath("first")
|
||||
.build(), testPkg);
|
||||
|
||||
assertFalse(client.hasPermission(base.buildUpon()
|
||||
.appendPath("first")
|
||||
.appendPath("third")
|
||||
.build(), testPkg.getUserId()));
|
||||
|
||||
ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities());
|
||||
assertEquals(0, authorities.get(0).getPaths().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveSubtrees() {
|
||||
PkgUser pkg = new PkgUser("com.android.pkg", 0);
|
||||
DirtyTracker tracker = mock(DirtyTracker.class);
|
||||
SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
|
||||
Uri base = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority("com.android.pkg.slices").build();
|
||||
|
||||
PkgUser testPkg = new PkgUser("other", 2);
|
||||
|
||||
client.grantUri(base.buildUpon()
|
||||
.appendPath("first")
|
||||
.appendPath("second")
|
||||
.build(), testPkg);
|
||||
client.grantUri(base.buildUpon()
|
||||
.appendPath("first")
|
||||
.appendPath("third")
|
||||
.build(), testPkg);
|
||||
client.revokeUri(base.buildUpon()
|
||||
.appendPath("first")
|
||||
.build(), testPkg);
|
||||
|
||||
assertFalse(client.hasPermission(base.buildUpon()
|
||||
.appendPath("first")
|
||||
.appendPath("fourth")
|
||||
.build(), testPkg.getUserId()));
|
||||
|
||||
ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities());
|
||||
assertEquals(0, authorities.get(0).getPaths().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddConsolidate_addFirst() {
|
||||
PkgUser pkg = new PkgUser("com.android.pkg", 0);
|
||||
DirtyTracker tracker = mock(DirtyTracker.class);
|
||||
SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
|
||||
Uri base = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority("com.android.pkg.slices").build();
|
||||
|
||||
PkgUser testPkg = new PkgUser("other", 2);
|
||||
|
||||
client.grantUri(base.buildUpon()
|
||||
.appendPath("first")
|
||||
.build(), testPkg);
|
||||
client.grantUri(base.buildUpon()
|
||||
.appendPath("first")
|
||||
.appendPath("second")
|
||||
.build(), testPkg);
|
||||
|
||||
assertTrue(client.hasPermission(base.buildUpon()
|
||||
.appendPath("first")
|
||||
.appendPath("third")
|
||||
.build(), testPkg.getUserId()));
|
||||
|
||||
ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities());
|
||||
assertEquals(1, authorities.get(0).getPaths().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddConsolidate_addSecond() {
|
||||
PkgUser pkg = new PkgUser("com.android.pkg", 0);
|
||||
DirtyTracker tracker = mock(DirtyTracker.class);
|
||||
SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
|
||||
Uri base = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority("com.android.pkg.slices").build();
|
||||
|
||||
PkgUser testPkg = new PkgUser("other", 2);
|
||||
|
||||
client.grantUri(base.buildUpon()
|
||||
.appendPath("first")
|
||||
.appendPath("second")
|
||||
.build(), testPkg);
|
||||
client.grantUri(base.buildUpon()
|
||||
.appendPath("first")
|
||||
.build(), testPkg);
|
||||
|
||||
assertTrue(client.hasPermission(base.buildUpon()
|
||||
.appendPath("first")
|
||||
.appendPath("third")
|
||||
.build(), testPkg.getUserId()));
|
||||
|
||||
ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities());
|
||||
assertEquals(1, authorities.get(0).getPaths().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirty_addAuthority() {
|
||||
PkgUser pkg = new PkgUser("com.android.pkg", 0);
|
||||
DirtyTracker tracker = mock(DirtyTracker.class);
|
||||
SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
|
||||
|
||||
client.getOrCreateAuthority(new PkgUser("some_auth", 2), new PkgUser("com.pkg", 2));
|
||||
|
||||
verify(tracker).onPersistableDirty(eq(client));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirty_addPkg() {
|
||||
PkgUser pkg = new PkgUser("com.android.pkg", 0);
|
||||
DirtyTracker tracker = mock(DirtyTracker.class);
|
||||
SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
|
||||
|
||||
SliceAuthority auth = client.getOrCreateAuthority(
|
||||
new PkgUser("some_auth", 2),
|
||||
new PkgUser("com.pkg", 2));
|
||||
clearInvocations(tracker);
|
||||
|
||||
auth.addPath(Arrays.asList("/something/"));
|
||||
|
||||
verify(tracker).onPersistableDirty(eq(client));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreation() {
|
||||
SliceClientPermissions client = createClient();
|
||||
ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities());
|
||||
authorities.sort(Comparator.comparing(SliceAuthority::getAuthority));
|
||||
|
||||
assertEquals(2, authorities.size());
|
||||
assertEquals("com.android.pkg", authorities.get(0).getAuthority());
|
||||
assertEquals("com.android.pkg.slices", authorities.get(1).getAuthority());
|
||||
|
||||
assertEquals(1, authorities.get(0).getPaths().size());
|
||||
assertEquals(2, authorities.get(1).getPaths().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerialization() throws XmlPullParserException, IOException {
|
||||
SliceClientPermissions client = createClient();
|
||||
client.setHasFullAccess(true);
|
||||
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer();
|
||||
serializer.setOutput(output, Encoding.UTF_8.name());
|
||||
|
||||
client.writeTo(serializer);
|
||||
serializer.flush();
|
||||
|
||||
ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
|
||||
XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
|
||||
parser.setInput(input, Encoding.UTF_8.name());
|
||||
|
||||
SliceClientPermissions deser = SliceClientPermissions.createFrom(parser,
|
||||
mock(DirtyTracker.class));
|
||||
|
||||
assertEquivalent(client, deser);
|
||||
}
|
||||
|
||||
private void assertEquivalent(SliceClientPermissions o1, SliceClientPermissions o2) {
|
||||
assertEquals(o1.getPkg(), o2.getPkg());
|
||||
ArrayList<SliceAuthority> a1 = new ArrayList<>(o1.getAuthorities());
|
||||
ArrayList<SliceAuthority> a2 = new ArrayList<>(o2.getAuthorities());
|
||||
a1.sort(Comparator.comparing(SliceAuthority::getAuthority));
|
||||
a2.sort(Comparator.comparing(SliceAuthority::getAuthority));
|
||||
assertEquals(a1, a2);
|
||||
}
|
||||
|
||||
private static SliceClientPermissions createClient() {
|
||||
PkgUser pkg = new PkgUser("com.android.pkg", 2);
|
||||
DirtyTracker tracker = mock(DirtyTracker.class);
|
||||
SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
|
||||
|
||||
SliceAuthority auth = client.getOrCreateAuthority(
|
||||
new PkgUser("com.android.pkg.slices", 3),
|
||||
new PkgUser("com.android.pkg", 3));
|
||||
auth.addPath(Arrays.asList("/something/"));
|
||||
auth.addPath(Arrays.asList("/something/else"));
|
||||
|
||||
auth = client.getOrCreateAuthority(
|
||||
new PkgUser("com.android.pkg", 3),
|
||||
new PkgUser("com.pkg", 1));
|
||||
auth.addPath(Arrays.asList("/somewhere"));
|
||||
return client;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.slice;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentResolver;
|
||||
import android.net.Uri;
|
||||
import android.net.Uri.Builder;
|
||||
import android.os.FileUtils;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.testing.TestableLooper.RunWithLooper;
|
||||
import android.util.Log;
|
||||
import android.util.Xml.Encoding;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@RunWithLooper
|
||||
public class SlicePermissionManagerTest extends UiServiceTestCase {
|
||||
|
||||
@Test
|
||||
public void testBackup() throws XmlPullParserException, IOException {
|
||||
File sliceDir = new File(mContext.getDataDir(), "system/slices");
|
||||
Uri uri = new Builder().scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority("authority")
|
||||
.path("something").build();
|
||||
SlicePermissionManager permissions = new SlicePermissionManager(mContext,
|
||||
TestableLooper.get(this).getLooper(), sliceDir);
|
||||
|
||||
permissions.grantFullAccess("com.android.mypkg", 10);
|
||||
permissions.grantSliceAccess("com.android.otherpkg", 0, "com.android.lastpkg", 1, uri);
|
||||
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer();
|
||||
serializer.setOutput(output, Encoding.UTF_8.name());
|
||||
|
||||
|
||||
TestableLooper.get(this).processAllMessages();
|
||||
permissions.writeBackup(serializer);
|
||||
serializer.flush();
|
||||
|
||||
ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
|
||||
XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
|
||||
parser.setInput(input, Encoding.UTF_8.name());
|
||||
|
||||
permissions = new SlicePermissionManager(mContext,
|
||||
TestableLooper.get(this).getLooper());
|
||||
permissions.readRestore(parser);
|
||||
|
||||
assertTrue(permissions.hasFullAccess("com.android.mypkg", 10));
|
||||
assertTrue(permissions.hasPermission("com.android.otherpkg", 0,
|
||||
ContentProvider.maybeAddUserId(uri, 1)));
|
||||
permissions.removePkg("com.android.lastpkg", 1);
|
||||
assertFalse(permissions.hasPermission("com.android.otherpkg", 0,
|
||||
ContentProvider.maybeAddUserId(uri, 1)));
|
||||
|
||||
// Cleanup.
|
||||
assertTrue(FileUtils.deleteContentsAndDir(sliceDir));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.slice;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper.RunWithLooper;
|
||||
import android.util.Xml.Encoding;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
import com.android.server.slice.SlicePermissionManager.PkgUser;
|
||||
import com.android.server.slice.SliceProviderPermissions.SliceAuthority;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@RunWithLooper
|
||||
public class SliceProviderPermissionsTest extends UiServiceTestCase {
|
||||
|
||||
@Test
|
||||
public void testDirty_addAuthority() {
|
||||
PkgUser pkg = new PkgUser("com.android.pkg", 0);
|
||||
DirtyTracker tracker = mock(DirtyTracker.class);
|
||||
SliceProviderPermissions provider = new SliceProviderPermissions(pkg, tracker);
|
||||
|
||||
provider.getOrCreateAuthority("some_auth");
|
||||
|
||||
verify(tracker).onPersistableDirty(eq(provider));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirty_addPkg() {
|
||||
PkgUser pkg = new PkgUser("com.android.pkg", 0);
|
||||
DirtyTracker tracker = mock(DirtyTracker.class);
|
||||
SliceProviderPermissions provider = new SliceProviderPermissions(pkg, tracker);
|
||||
|
||||
SliceAuthority auth = provider.getOrCreateAuthority("some_auth");
|
||||
clearInvocations(tracker);
|
||||
|
||||
auth.addPkg(new PkgUser("pkg", 0));
|
||||
|
||||
verify(tracker).onPersistableDirty(eq(provider));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreation() {
|
||||
SliceProviderPermissions provider = createProvider();
|
||||
ArrayList<SliceAuthority> authorities = new ArrayList<>(provider.getAuthorities());
|
||||
authorities.sort(Comparator.comparing(SliceAuthority::getAuthority));
|
||||
|
||||
assertEquals(2, authorities.size());
|
||||
assertEquals("com.android.pkg", authorities.get(0).getAuthority());
|
||||
assertEquals("com.android.pkg.slices", authorities.get(1).getAuthority());
|
||||
|
||||
assertEquals(1, authorities.get(0).getPkgs().size());
|
||||
assertEquals(2, authorities.get(1).getPkgs().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerialization() throws XmlPullParserException, IOException {
|
||||
SliceProviderPermissions provider = createProvider();
|
||||
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer();
|
||||
serializer.setOutput(output, Encoding.UTF_8.name());
|
||||
|
||||
provider.writeTo(serializer);
|
||||
serializer.flush();
|
||||
|
||||
ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
|
||||
XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
|
||||
parser.setInput(input, Encoding.UTF_8.name());
|
||||
|
||||
SliceProviderPermissions deser = SliceProviderPermissions.createFrom(parser,
|
||||
mock(DirtyTracker.class));
|
||||
|
||||
assertEquivalent(provider, deser);
|
||||
}
|
||||
|
||||
private void assertEquivalent(SliceProviderPermissions o1, SliceProviderPermissions o2) {
|
||||
assertEquals(o1.getPkg(), o2.getPkg());
|
||||
assertEquals(o1.getAuthorities(), o2.getAuthorities());
|
||||
}
|
||||
|
||||
private static SliceProviderPermissions createProvider() {
|
||||
PkgUser pkg = new PkgUser("com.android.pkg", 2);
|
||||
DirtyTracker tracker = mock(DirtyTracker.class);
|
||||
SliceProviderPermissions provider = new SliceProviderPermissions(pkg, tracker);
|
||||
|
||||
SliceAuthority auth = provider.getOrCreateAuthority("com.android.pkg.slices");
|
||||
auth.addPkg(new PkgUser("com.example.pkg", 0));
|
||||
auth.addPkg(new PkgUser("example.pkg.com", 10));
|
||||
|
||||
auth = provider.getOrCreateAuthority("com.android.pkg");
|
||||
auth.addPkg(new PkgUser("com.example.pkg", 2));
|
||||
return provider;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user