summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@google.com>2020-04-17 17:57:16 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2020-04-17 17:57:16 +0000
commit64bd9c7be84bbb1ff2a94de4ba917fdf5af75a93 (patch)
tree7120869cdc9ca34431a00b8b92cc10af3ef370de
parent26e55eb8ab8a8a476ca63d81e5567c16443c056b (diff)
parent1d194f92379711308c7eb1ce3bf07896391ea045 (diff)
Merge "Uri permission grants improvements, tests, fixes." into rvc-dev
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java9
-rw-r--r--services/core/java/com/android/server/uri/GrantUri.java13
-rw-r--r--services/core/java/com/android/server/uri/NeededUriGrants.java11
-rw-r--r--services/core/java/com/android/server/uri/TEST_MAPPING30
-rw-r--r--services/core/java/com/android/server/uri/UriGrantsManagerInternal.java1
-rw-r--r--services/core/java/com/android/server/uri/UriGrantsManagerService.java394
-rw-r--r--services/core/java/com/android/server/uri/UriPermission.java10
-rw-r--r--services/core/java/com/android/server/uri/UriPermissionOwner.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java355
-rw-r--r--services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java207
-rw-r--r--services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java170
11 files changed, 972 insertions, 229 deletions
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2a9f503602ac..89aee78677c6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2683,7 +2683,6 @@ public class ActivityManagerService extends IActivityManager.Stub
Slog.d("AppOps", "AppOpsService published");
LocalServices.addService(ActivityManagerInternal.class, mInternal);
mActivityTaskManager.onActivityManagerInternalAdded();
- mUgmInternal.onActivityManagerInternalAdded();
mPendingIntentController.onActivityManagerInternalAdded();
// Wait for the synchronized block started in mProcessCpuThread,
// so that any other access to mProcessCpuTracker from main thread
@@ -6412,7 +6411,7 @@ public class ActivityManagerService extends IActivityManager.Stub
if (pid == MY_PID) {
return PackageManager.PERMISSION_GRANTED;
}
- return mUgmInternal.checkUriPermission(new GrantUri(userId, uri, false), uid, modeFlags)
+ return mUgmInternal.checkUriPermission(new GrantUri(userId, uri, modeFlags), uid, modeFlags)
? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
}
@@ -6424,7 +6423,7 @@ public class ActivityManagerService extends IActivityManager.Stub
public void grantUriPermission(IApplicationThread caller, String targetPkg, Uri uri,
final int modeFlags, int userId) {
enforceNotIsolatedCaller("grantUriPermission");
- GrantUri grantUri = new GrantUri(userId, uri, false);
+ GrantUri grantUri = new GrantUri(userId, uri, modeFlags);
synchronized(this) {
final ProcessRecord r = getRecordForAppLocked(caller);
if (r == null) {
@@ -6482,8 +6481,8 @@ public class ActivityManagerService extends IActivityManager.Stub
return;
}
- mUgmInternal.revokeUriPermission(targetPackage, r.uid, new GrantUri(userId, uri, false),
- modeFlags);
+ mUgmInternal.revokeUriPermission(targetPackage, r.uid,
+ new GrantUri(userId, uri, modeFlags), modeFlags);
}
}
diff --git a/services/core/java/com/android/server/uri/GrantUri.java b/services/core/java/com/android/server/uri/GrantUri.java
index 15715757d6e0..f9b6a7a81dee 100644
--- a/services/core/java/com/android/server/uri/GrantUri.java
+++ b/services/core/java/com/android/server/uri/GrantUri.java
@@ -18,6 +18,7 @@ package com.android.server.uri;
import android.content.ContentProvider;
import android.content.ContentResolver;
+import android.content.Intent;
import android.net.Uri;
import android.util.proto.ProtoOutputStream;
@@ -27,12 +28,12 @@ import com.android.server.am.GrantUriProto;
public class GrantUri {
public final int sourceUserId;
public final Uri uri;
- public boolean prefix;
+ public final boolean prefix;
- public GrantUri(int sourceUserId, Uri uri, boolean prefix) {
+ public GrantUri(int sourceUserId, Uri uri, int modeFlags) {
this.sourceUserId = sourceUserId;
this.uri = uri;
- this.prefix = prefix;
+ this.prefix = (modeFlags & Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) != 0;
}
@Override
@@ -74,12 +75,12 @@ public class GrantUri {
proto.end(token);
}
- public static GrantUri resolve(int defaultSourceUserHandle, Uri uri) {
+ public static GrantUri resolve(int defaultSourceUserHandle, Uri uri, int modeFlags) {
if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
return new GrantUri(ContentProvider.getUserIdFromUri(uri, defaultSourceUserHandle),
- ContentProvider.getUriWithoutUserId(uri), false);
+ ContentProvider.getUriWithoutUserId(uri), modeFlags);
} else {
- return new GrantUri(defaultSourceUserHandle, uri, false);
+ return new GrantUri(defaultSourceUserHandle, uri, modeFlags);
}
}
}
diff --git a/services/core/java/com/android/server/uri/NeededUriGrants.java b/services/core/java/com/android/server/uri/NeededUriGrants.java
index 012039484dc9..8c8f55304fbb 100644
--- a/services/core/java/com/android/server/uri/NeededUriGrants.java
+++ b/services/core/java/com/android/server/uri/NeededUriGrants.java
@@ -16,22 +16,23 @@
package com.android.server.uri;
+import android.util.ArraySet;
import android.util.proto.ProtoOutputStream;
import com.android.server.am.NeededUriGrantsProto;
-import java.util.ArrayList;
-
/** List of {@link GrantUri} a process needs. */
-public class NeededUriGrants extends ArrayList<GrantUri> {
+public class NeededUriGrants {
final String targetPkg;
final int targetUid;
final int flags;
+ final ArraySet<GrantUri> uris;
public NeededUriGrants(String targetPkg, int targetUid, int flags) {
this.targetPkg = targetPkg;
this.targetUid = targetUid;
this.flags = flags;
+ this.uris = new ArraySet<>();
}
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
@@ -40,9 +41,9 @@ public class NeededUriGrants extends ArrayList<GrantUri> {
proto.write(NeededUriGrantsProto.TARGET_UID, targetUid);
proto.write(NeededUriGrantsProto.FLAGS, flags);
- final int N = this.size();
+ final int N = uris.size();
for (int i = 0; i < N; i++) {
- this.get(i).dumpDebug(proto, NeededUriGrantsProto.GRANTS);
+ uris.valueAt(i).dumpDebug(proto, NeededUriGrantsProto.GRANTS);
}
proto.end(token);
}
diff --git a/services/core/java/com/android/server/uri/TEST_MAPPING b/services/core/java/com/android/server/uri/TEST_MAPPING
new file mode 100644
index 000000000000..e5cda03be25d
--- /dev/null
+++ b/services/core/java/com/android/server/uri/TEST_MAPPING
@@ -0,0 +1,30 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.uri."
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsAppSecurityHostTestCases",
+ "options": [
+ {
+ "include-filter": "android.appsecurity.cts.AppSecurityTests#testPermissionDiffCert"
+ }
+ ]
+ },
+ {
+ "name": "CtsWindowManagerDeviceTestCases",
+ "options": [
+ {
+ "include-filter": "android.server.wm.CrossAppDragAndDropTests"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
index 2f50fcb08c02..8afb87f3020d 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
@@ -30,7 +30,6 @@ import java.io.PrintWriter;
*/
public interface UriGrantsManagerInternal {
void onSystemReady();
- void onActivityManagerInternalAdded();
void removeUriPermissionIfNeeded(UriPermission perm);
void grantUriPermission(int callingUid, String targetPkg, GrantUri grantUri,
final int modeFlags, UriPermissionOwner owner, int targetUserId);
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index fe34e86a27ae..b8c2f90cf047 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -21,6 +21,9 @@ import static android.Manifest.permission.FORCE_PERSISTABLE_URI_PERMISSIONS;
import static android.Manifest.permission.GET_APP_GRANTED_URI_PERMISSIONS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
+import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
+import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
@@ -53,7 +56,6 @@ import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
@@ -76,6 +78,8 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.Preconditions;
import com.android.server.IoThread;
@@ -83,11 +87,11 @@ import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
+import libcore.io.IoUtils;
+
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
-import libcore.io.IoUtils;
-
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -114,7 +118,6 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
private static final boolean ENABLE_DYNAMIC_PERMISSIONS = false;
private final Object mLock = new Object();
- private final Context mContext;
private final H mH;
ActivityManagerInternal mAmInternal;
PackageManagerInternal mPmInternal;
@@ -143,19 +146,30 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
private final SparseArray<ArrayMap<GrantUri, UriPermission>>
mGrantedUriPermissions = new SparseArray<>();
- private UriGrantsManagerService(Context context) {
- mContext = context;
+ private UriGrantsManagerService() {
+ this(SystemServiceManager.ensureSystemDir());
+ }
+
+ private UriGrantsManagerService(File systemDir) {
mH = new H(IoThread.get().getLooper());
- final File systemDir = SystemServiceManager.ensureSystemDir();
mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml"), "uri-grants");
}
- private void start() {
- LocalServices.addService(UriGrantsManagerInternal.class, new LocalService());
+ @VisibleForTesting
+ static UriGrantsManagerService createForTest(File systemDir) {
+ final UriGrantsManagerService service = new UriGrantsManagerService(systemDir);
+ service.mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
+ service.mPmInternal = LocalServices.getService(PackageManagerInternal.class);
+ return service;
}
- void onActivityManagerInternalAdded() {
- mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
+ @VisibleForTesting
+ UriGrantsManagerInternal getLocalService() {
+ return new LocalService();
+ }
+
+ private void start() {
+ LocalServices.addService(UriGrantsManagerInternal.class, new LocalService());
}
public static final class Lifecycle extends SystemService {
@@ -163,7 +177,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
public Lifecycle(Context context) {
super(context);
- mService = new UriGrantsManagerService(context);
+ mService = new UriGrantsManagerService();
}
@Override
@@ -172,11 +186,27 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
mService.start();
}
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ mService.mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mService.mPmInternal = LocalServices.getService(PackageManagerInternal.class);
+ }
+ }
+
public UriGrantsManagerService getService() {
return mService;
}
}
+ private int checkUidPermission(String permission, int uid) {
+ try {
+ return AppGlobals.getPackageManager().checkUidPermission(permission, uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* @param uri This uri must NOT contain an embedded userId.
* @param sourceUserId The userId in which the uri is to be resolved.
@@ -207,7 +237,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
throw new IllegalArgumentException("null uri");
}
- grantUriPermission(fromUid, targetPkg, new GrantUri(sourceUserId, uri, false),
+ grantUriPermission(fromUid, targetPkg, new GrantUri(sourceUserId, uri, modeFlags),
modeFlags, owner, targetUserId);
}
}
@@ -220,16 +250,12 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
final int callingUid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(callingUid);
- final IPackageManager pm = AppGlobals.getPackageManager();
- try {
- final int packageUid = pm.getPackageUid(packageName,
- MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, callingUserId);
- if (packageUid != callingUid) {
- throw new SecurityException(
- "Package " + packageName + " does not belong to calling UID " + callingUid);
- }
- } catch (RemoteException e) {
- throw new SecurityException("Failed to verify package name ownership");
+ final PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
+ final int packageUid = pm.getPackageUidInternal(packageName,
+ MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, callingUserId);
+ if (packageUid != callingUid) {
+ throw new SecurityException(
+ "Package " + packageName + " does not belong to calling UID " + callingUid);
}
final ArrayList<android.content.UriPermission> result = Lists.newArrayList();
@@ -303,7 +329,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
if (toPackage != null) {
mAmInternal.enforceCallingPermission(FORCE_PERSISTABLE_URI_PERMISSIONS,
"takePersistableUriPermission");
- uid = getPmInternal().getPackageUid(toPackage, 0, userId);
+ uid = mPmInternal.getPackageUidInternal(toPackage, 0, userId);
} else {
enforceNotIsolatedCaller("takePersistableUriPermission");
uid = Binder.getCallingUid();
@@ -314,11 +340,11 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
synchronized (mLock) {
boolean persistChanged = false;
- GrantUri grantUri = new GrantUri(userId, uri, false);
- UriPermission exactPerm = findUriPermissionLocked(uid, grantUri);
+ UriPermission exactPerm = findUriPermissionLocked(uid,
+ new GrantUri(userId, uri, 0));
UriPermission prefixPerm = findUriPermissionLocked(uid,
- new GrantUri(userId, uri, true));
+ new GrantUri(userId, uri, FLAG_GRANT_PREFIX_URI_PERMISSION));
final boolean exactValid = (exactPerm != null)
&& ((modeFlags & exactPerm.persistableModeFlags) == modeFlags);
@@ -327,7 +353,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
if (!(exactValid || prefixValid)) {
throw new SecurityException("No persistable permission grants found for UID "
- + uid + " and Uri " + grantUri.toSafeString());
+ + uid + " and Uri " + uri.toSafeString());
}
if (exactValid) {
@@ -368,7 +394,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
if (toPackage != null) {
mAmInternal.enforceCallingPermission(FORCE_PERSISTABLE_URI_PERMISSIONS,
"releasePersistableUriPermission");
- uid = getPmInternal().getPackageUid(toPackage, 0, userId);
+ uid = mPmInternal.getPackageUidInternal(toPackage, 0, userId);
} else {
enforceNotIsolatedCaller("releasePersistableUriPermission");
uid = Binder.getCallingUid();
@@ -381,9 +407,9 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
boolean persistChanged = false;
UriPermission exactPerm = findUriPermissionLocked(uid,
- new GrantUri(userId, uri, false));
+ new GrantUri(userId, uri, 0));
UriPermission prefixPerm = findUriPermissionLocked(uid,
- new GrantUri(userId, uri, true));
+ new GrantUri(userId, uri, FLAG_GRANT_PREFIX_URI_PERMISSION));
if (exactPerm == null && prefixPerm == null && toPackage == null) {
throw new SecurityException("No permission grants found for UID " + uid
+ " and Uri " + uri.toSafeString());
@@ -559,16 +585,12 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
if (contentUserHint == UserHandle.USER_CURRENT) {
contentUserHint = UserHandle.getUserId(callingUid);
}
- final IPackageManager pm = AppGlobals.getPackageManager();
int targetUid;
if (needed != null) {
targetUid = needed.targetUid;
} else {
- try {
- targetUid = pm.getPackageUid(targetPkg, MATCH_DEBUG_TRIAGED_MISSING, targetUserId);
- } catch (RemoteException ex) {
- return null;
- }
+ targetUid = mPmInternal.getPackageUidInternal(targetPkg, MATCH_DEBUG_TRIAGED_MISSING,
+ targetUserId);
if (targetUid < 0) {
if (DEBUG) Slog.v(TAG, "Can't grant URI permission no uid for: " + targetPkg
+ " on user " + targetUserId);
@@ -576,27 +598,27 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
}
}
if (data != null) {
- GrantUri grantUri = GrantUri.resolve(contentUserHint, data);
+ GrantUri grantUri = GrantUri.resolve(contentUserHint, data, mode);
targetUid = checkGrantUriPermission(callingUid, targetPkg, grantUri, mode, targetUid);
if (targetUid > 0) {
if (needed == null) {
needed = new NeededUriGrants(targetPkg, targetUid, mode);
}
- needed.add(grantUri);
+ needed.uris.add(grantUri);
}
}
if (clip != null) {
for (int i=0; i<clip.getItemCount(); i++) {
Uri uri = clip.getItemAt(i).getUri();
if (uri != null) {
- GrantUri grantUri = GrantUri.resolve(contentUserHint, uri);
+ GrantUri grantUri = GrantUri.resolve(contentUserHint, uri, mode);
targetUid = checkGrantUriPermission(callingUid, targetPkg,
grantUri, mode, targetUid);
if (targetUid > 0) {
if (needed == null) {
needed = new NeededUriGrants(targetPkg, targetUid, mode);
}
- needed.add(grantUri);
+ needed.uris.add(grantUri);
}
} else {
Intent clipIntent = clip.getItemAt(i).getIntent();
@@ -666,16 +688,13 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
final ProviderInfo pi = getProviderInfo(uri.getAuthority(), sourceUserId,
MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE);
if (pi != null && sourcePkg.equals(pi.packageName)) {
- int targetUid = -1;
- try {
- targetUid = AppGlobals.getPackageManager().getPackageUid(
+ int targetUid = mPmInternal.getPackageUidInternal(
targetPkg, MATCH_UNINSTALLED_PACKAGES, targetUserId);
- } catch (RemoteException e) {
- }
if (targetUid != -1) {
+ final GrantUri grantUri = new GrantUri(sourceUserId, uri,
+ prefix ? Intent.FLAG_GRANT_PREFIX_URI_PERMISSION : 0);
final UriPermission perm = findOrCreateUriPermission(
- sourcePkg, targetPkg, targetUid,
- new GrantUri(sourceUserId, uri, prefix));
+ sourcePkg, targetPkg, targetUid, grantUri);
perm.initPersistedModes(modeFlags, createdTime);
}
} else {
@@ -733,13 +752,10 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
return;
}
- if ((modeFlags & Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) != 0) {
- grantUri.prefix = true;
- }
final UriPermission perm = findOrCreateUriPermission(
pi.packageName, targetPkg, targetUid, grantUri);
perm.grantModes(modeFlags, owner);
- getPmInternal().grantImplicitAccess(UserHandle.getUserId(targetUid), null,
+ mPmInternal.grantImplicitAccess(UserHandle.getUserId(targetUid), null,
UserHandle.getAppId(targetUid), pi.applicationInfo.uid, false /*direct*/);
}
@@ -748,10 +764,10 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
if (needed == null) {
return;
}
- for (int i=0; i<needed.size(); i++) {
- GrantUri grantUri = needed.get(i);
+ final int N = needed.uris.size();
+ for (int i = 0; i < N; i++) {
grantUriPermissionUnchecked(needed.targetUid, needed.targetPkg,
- grantUri, needed.flags, owner);
+ needed.uris.valueAt(i), needed.flags, owner);
}
}
@@ -760,13 +776,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
if (targetPkg == null) {
throw new NullPointerException("targetPkg");
}
- int targetUid;
- final IPackageManager pm = AppGlobals.getPackageManager();
- try {
- targetUid = pm.getPackageUid(targetPkg, MATCH_DEBUG_TRIAGED_MISSING, targetUserId);
- } catch (RemoteException ex) {
- return;
- }
+ int targetUid = mPmInternal.getPackageUidInternal(targetPkg, MATCH_DEBUG_TRIAGED_MISSING,
+ targetUserId);
targetUid = checkGrantUriPermission(callingUid, targetPkg, grantUri, modeFlags, targetUid);
if (targetUid < 0) {
@@ -780,7 +791,6 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
final int modeFlags) {
if (DEBUG) Slog.v(TAG, "Revoking all granted permissions to " + grantUri);
- final IPackageManager pm = AppGlobals.getPackageManager();
final String authority = grantUri.uri.getAuthority();
final ProviderInfo pi = getProviderInfo(authority, grantUri.sourceUserId,
MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE);
@@ -791,7 +801,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
}
// Does the caller have this permission on the URI?
- if (!checkHoldingPermissions(pm, pi, grantUri, callingUid, modeFlags)) {
+ if (!checkHoldingPermissions(pi, grantUri, callingUid, modeFlags)) {
// If they don't have direct access to the URI, then revoke any
// ownerless URI permissions that have been granted to them.
final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
@@ -864,7 +874,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
* in {@link ContentProvider}.
*/
private boolean checkHoldingPermissions(
- IPackageManager pm, ProviderInfo pi, GrantUri grantUri, int uid, final int modeFlags) {
+ ProviderInfo pi, GrantUri grantUri, int uid, final int modeFlags) {
if (DEBUG) Slog.v(TAG, "checkHoldingPermissions: uri=" + grantUri + " uid=" + uid);
if (UserHandle.getUserId(uid) != grantUri.sourceUserId) {
if (ActivityManager.checkComponentPermission(INTERACT_ACROSS_USERS, uid, -1, true)
@@ -872,10 +882,10 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
return false;
}
}
- return checkHoldingPermissionsInternal(pm, pi, grantUri, uid, modeFlags, true);
+ return checkHoldingPermissionsInternal(pi, grantUri, uid, modeFlags, true);
}
- private boolean checkHoldingPermissionsInternal(IPackageManager pm, ProviderInfo pi,
+ private boolean checkHoldingPermissionsInternal(ProviderInfo pi,
GrantUri grantUri, int uid, final int modeFlags, boolean considerUidPermissions) {
if (pi.applicationInfo.uid == uid) {
return true;
@@ -885,74 +895,70 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
boolean readMet = (modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0;
boolean writeMet = (modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0;
- try {
- // check if target holds top-level <provider> permissions
- if (!readMet && pi.readPermission != null && considerUidPermissions
- && (pm.checkUidPermission(pi.readPermission, uid) == PERMISSION_GRANTED)) {
- readMet = true;
- }
- if (!writeMet && pi.writePermission != null && considerUidPermissions
- && (pm.checkUidPermission(pi.writePermission, uid) == PERMISSION_GRANTED)) {
- writeMet = true;
- }
- // track if unprotected read/write is allowed; any denied
- // <path-permission> below removes this ability
- boolean allowDefaultRead = pi.readPermission == null;
- boolean allowDefaultWrite = pi.writePermission == null;
-
- // check if target holds any <path-permission> that match uri
- final PathPermission[] pps = pi.pathPermissions;
- if (pps != null) {
- final String path = grantUri.uri.getPath();
- int i = pps.length;
- while (i > 0 && (!readMet || !writeMet)) {
- i--;
- PathPermission pp = pps[i];
- if (pp.match(path)) {
- if (!readMet) {
- final String pprperm = pp.getReadPermission();
- if (DEBUG) Slog.v(TAG,
- "Checking read perm for " + pprperm + " for " + pp.getPath()
- + ": match=" + pp.match(path)
- + " check=" + pm.checkUidPermission(pprperm, uid));
- if (pprperm != null) {
- if (considerUidPermissions && pm.checkUidPermission(pprperm, uid)
- == PERMISSION_GRANTED) {
- readMet = true;
- } else {
- allowDefaultRead = false;
- }
+ // check if target holds top-level <provider> permissions
+ if (!readMet && pi.readPermission != null && considerUidPermissions
+ && (checkUidPermission(pi.readPermission, uid) == PERMISSION_GRANTED)) {
+ readMet = true;
+ }
+ if (!writeMet && pi.writePermission != null && considerUidPermissions
+ && (checkUidPermission(pi.writePermission, uid) == PERMISSION_GRANTED)) {
+ writeMet = true;
+ }
+
+ // track if unprotected read/write is allowed; any denied
+ // <path-permission> below removes this ability
+ boolean allowDefaultRead = pi.readPermission == null;
+ boolean allowDefaultWrite = pi.writePermission == null;
+
+ // check if target holds any <path-permission> that match uri
+ final PathPermission[] pps = pi.pathPermissions;
+ if (pps != null) {
+ final String path = grantUri.uri.getPath();
+ int i = pps.length;
+ while (i > 0 && (!readMet || !writeMet)) {
+ i--;
+ PathPermission pp = pps[i];
+ if (pp.match(path)) {
+ if (!readMet) {
+ final String pprperm = pp.getReadPermission();
+ if (DEBUG) Slog.v(TAG,
+ "Checking read perm for " + pprperm + " for " + pp.getPath()
+ + ": match=" + pp.match(path)
+ + " check=" + checkUidPermission(pprperm, uid));
+ if (pprperm != null) {
+ if (considerUidPermissions && checkUidPermission(pprperm, uid)
+ == PERMISSION_GRANTED) {
+ readMet = true;
+ } else {
+ allowDefaultRead = false;
}
}
- if (!writeMet) {
- final String ppwperm = pp.getWritePermission();
- if (DEBUG) Slog.v(TAG,
- "Checking write perm " + ppwperm + " for " + pp.getPath()
- + ": match=" + pp.match(path)
- + " check=" + pm.checkUidPermission(ppwperm, uid));
- if (ppwperm != null) {
- if (considerUidPermissions && pm.checkUidPermission(ppwperm, uid)
- == PERMISSION_GRANTED) {
- writeMet = true;
- } else {
- allowDefaultWrite = false;
- }
+ }
+ if (!writeMet) {
+ final String ppwperm = pp.getWritePermission();
+ if (DEBUG) Slog.v(TAG,
+ "Checking write perm " + ppwperm + " for " + pp.getPath()
+ + ": match=" + pp.match(path)
+ + " check=" + checkUidPermission(ppwperm, uid));
+ if (ppwperm != null) {
+ if (considerUidPermissions && checkUidPermission(ppwperm, uid)
+ == PERMISSION_GRANTED) {
+ writeMet = true;
+ } else {
+ allowDefaultWrite = false;
}
}
}
}
}
-
- // grant unprotected <provider> read/write, if not blocked by
- // <path-permission> above
- if (allowDefaultRead) readMet = true;
- if (allowDefaultWrite) writeMet = true;
-
- } catch (RemoteException e) {
- return false;
}
+ // grant unprotected <provider> read/write, if not blocked by
+ // <path-permission> above
+ if (allowDefaultRead) readMet = true;
+ if (allowDefaultWrite) writeMet = true;
+
// If this provider says that grants are always required, we need to
// consult it directly to determine if the UID has permission
final boolean forceMet;
@@ -1013,14 +1019,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
}
private ProviderInfo getProviderInfo(String authority, int userHandle, int pmFlags) {
- ProviderInfo pi = null;
- try {
- pi = AppGlobals.getPackageManager().resolveContentProvider(
- authority, PackageManager.GET_URI_PERMISSION_PATTERNS | pmFlags,
- userHandle);
- } catch (RemoteException ex) {
- }
- return pi;
+ return mPmInternal.resolveContentProvider(authority,
+ PackageManager.GET_URI_PERMISSION_PATTERNS | pmFlags, userHandle);
}
/**
@@ -1033,7 +1033,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
* lastTargetUid else set that to -1.
*/
int checkGrantUriPermission(int callingUid, String targetPkg, GrantUri grantUri,
- final int modeFlags, int lastTargetUid) {
+ int modeFlags, int lastTargetUid) {
if (!Intent.isAccessUriMode(modeFlags)) {
return -1;
}
@@ -1042,8 +1042,6 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
if (DEBUG) Slog.v(TAG, "Checking grant " + targetPkg + " permission to " + grantUri);
}
- final IPackageManager pm = AppGlobals.getPackageManager();
-
// If this is not a content: uri, we can't do anything with it.
if (!ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme())) {
if (DEBUG) Slog.v(TAG, "Can't grant URI permission for non-content URI: " + grantUri);
@@ -1079,39 +1077,22 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
int targetUid = lastTargetUid;
if (targetUid < 0 && targetPkg != null) {
- try {
- targetUid = pm.getPackageUid(targetPkg, MATCH_DEBUG_TRIAGED_MISSING,
- UserHandle.getUserId(callingUid));
- if (targetUid < 0) {
- if (DEBUG) Slog.v(TAG, "Can't grant URI permission no uid for: " + targetPkg);
- return -1;
- }
- } catch (RemoteException ex) {
+ targetUid = mPmInternal.getPackageUidInternal(targetPkg, MATCH_DEBUG_TRIAGED_MISSING,
+ UserHandle.getUserId(callingUid));
+ if (targetUid < 0) {
+ if (DEBUG) Slog.v(TAG, "Can't grant URI permission no uid for: " + targetPkg);
return -1;
}
}
- // Figure out the value returned when access is allowed
- final int allowedResult;
- if ((modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0
- || pi.forceUriPermissions) {
- // If we're extending a persistable grant or need to force, then we need to return
- // "targetUid" so that we always create a grant data structure to
- // support take/release APIs
- allowedResult = targetUid;
- } else {
- // Otherwise, we can return "-1" to indicate that no grant data
- // structures need to be created
- allowedResult = -1;
- }
-
+ boolean targetHoldsPermission = false;
if (targetUid >= 0) {
// First... does the target actually need this permission?
- if (checkHoldingPermissions(pm, pi, grantUri, targetUid, modeFlags)) {
+ if (checkHoldingPermissions(pi, grantUri, targetUid, modeFlags)) {
// No need to grant the target this permission.
if (DEBUG) Slog.v(TAG,
"Target " + targetPkg + " already has full permission to " + grantUri);
- return allowedResult;
+ targetHoldsPermission = true;
}
} else {
// First... there is no target package, so can anyone access it?
@@ -1145,11 +1126,27 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
}
}
}
+ if (pi.forceUriPermissions) {
+ // When provider requires dynamic permission checks, the only
+ // way to be safe is to issue permission grants for each item by
+ // assuming no generic access
+ allowed = false;
+ }
if (allowed) {
- return allowedResult;
+ targetHoldsPermission = true;
}
}
+ final boolean basicGrant = (modeFlags & ~(FLAG_GRANT_READ_URI_PERMISSION
+ | FLAG_GRANT_WRITE_URI_PERMISSION)) == 0;
+ if (basicGrant && targetHoldsPermission) {
+ // When caller holds permission, and this is a simple permission
+ // grant, we can skip generating any bookkeeping; when any advanced
+ // features have been requested, we proceed below to make sure the
+ // provider supports granting permissions
+ return -1;
+ }
+
/* There is a special cross user grant if:
* - The target is on another user.
* - Apps on the current user can access the uri without any uid permissions.
@@ -1158,38 +1155,42 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
*/
boolean specialCrossUserGrant = targetUid >= 0
&& UserHandle.getUserId(targetUid) != grantUri.sourceUserId
- && checkHoldingPermissionsInternal(pm, pi, grantUri, callingUid,
+ && checkHoldingPermissionsInternal(pi, grantUri, callingUid,
modeFlags, false /*without considering the uid permissions*/);
// Second... is the provider allowing granting of URI permissions?
- if (!specialCrossUserGrant) {
- if (!pi.grantUriPermissions) {
- throw new SecurityException("Provider " + pi.packageName
- + "/" + pi.name
- + " does not allow granting of Uri permissions (uri "
- + grantUri + ")");
- }
- if (pi.uriPermissionPatterns != null) {
- final int N = pi.uriPermissionPatterns.length;
- boolean allowed = false;
- for (int i=0; i<N; i++) {
- if (pi.uriPermissionPatterns[i] != null
- && pi.uriPermissionPatterns[i].match(grantUri.uri.getPath())) {
- allowed = true;
- break;
- }
+ boolean grantAllowed = pi.grantUriPermissions;
+ if (!ArrayUtils.isEmpty(pi.uriPermissionPatterns)) {
+ final int N = pi.uriPermissionPatterns.length;
+ grantAllowed = false;
+ for (int i = 0; i < N; i++) {
+ if (pi.uriPermissionPatterns[i] != null
+ && pi.uriPermissionPatterns[i].match(grantUri.uri.getPath())) {
+ grantAllowed = true;
+ break;
}
- if (!allowed) {
+ }
+ }
+ if (!grantAllowed) {
+ if (specialCrossUserGrant) {
+ // We're only okay issuing basic grant access across user
+ // boundaries; advanced flags are blocked here
+ if (!basicGrant) {
throw new SecurityException("Provider " + pi.packageName
+ "/" + pi.name
- + " does not allow granting of permission to path of Uri "
- + grantUri);
+ + " does not allow granting of advanced Uri permissions (uri "
+ + grantUri + ")");
}
+ } else {
+ throw new SecurityException("Provider " + pi.packageName
+ + "/" + pi.name
+ + " does not allow granting of Uri permissions (uri "
+ + grantUri + ")");
}
}
// Third... does the caller itself have permission to access this uri?
- if (!checkHoldingPermissions(pm, pi, grantUri, callingUid, modeFlags)) {
+ if (!checkHoldingPermissions(pi, grantUri, callingUid, modeFlags)) {
// Require they hold a strong enough Uri permission
if (!checkUriPermission(grantUri, callingUid, modeFlags)) {
if (android.Manifest.permission.MANAGE_DOCUMENTS.equals(pi.readPermission)) {
@@ -1203,6 +1204,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
}
}
}
+
return targetUid;
}
@@ -1212,7 +1214,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
int checkGrantUriPermission(int callingUid, String targetPkg, Uri uri, int modeFlags,
int userId) {
return checkGrantUriPermission(callingUid, targetPkg,
- new GrantUri(userId, uri, false), modeFlags, -1);
+ new GrantUri(userId, uri, modeFlags), modeFlags, -1);
}
boolean checkUriPermission(GrantUri grantUri, int uid, final int modeFlags) {
@@ -1297,14 +1299,6 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
}
}
- private PackageManagerInternal getPmInternal() {
- // Don't need to synchonize; worst-case scenario LocalServices will be called twice.
- if (mPmInternal == null) {
- mPmInternal = LocalServices.getService(PackageManagerInternal.class);
- }
- return mPmInternal;
- }
-
final class H extends Handler {
static final int PERSIST_URI_GRANTS_MSG = 1;
@@ -1324,7 +1318,6 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
}
final class LocalService implements UriGrantsManagerInternal {
-
@Override
public void removeUriPermissionIfNeeded(UriPermission perm) {
synchronized (mLock) {
@@ -1419,13 +1412,6 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
}
@Override
- public void onActivityManagerInternalAdded() {
- synchronized (mLock) {
- UriGrantsManagerService.this.onActivityManagerInternalAdded();
- }
- }
-
- @Override
public IBinder newUriPermissionOwner(String name) {
enforceNotIsolatedCaller("newUriPermissionOwner");
synchronized(mLock) {
@@ -1454,8 +1440,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
if (uri == null) {
owner.removeUriPermissions(mode);
} else {
- final boolean prefix = (mode & Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) != 0;
- owner.removeUriPermission(new GrantUri(userId, uri, prefix), mode);
+ owner.removeUriPermission(new GrantUri(userId, uri, mode), mode);
}
}
}
@@ -1478,12 +1463,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
boolean printed = false;
int dumpUid = -2;
if (dumpPackage != null) {
- try {
- dumpUid = mContext.getPackageManager().getPackageUidAsUser(dumpPackage,
- MATCH_ANY_USER, 0);
- } catch (PackageManager.NameNotFoundException e) {
- dumpUid = -1;
- }
+ dumpUid = mPmInternal.getPackageUidInternal(dumpPackage, MATCH_ANY_USER, 0);
}
for (int i = 0; i < mGrantedUriPermissions.size(); i++) {
int uid = mGrantedUriPermissions.keyAt(i);
diff --git a/services/core/java/com/android/server/uri/UriPermission.java b/services/core/java/com/android/server/uri/UriPermission.java
index bd6348a5661b..6db781ac419a 100644
--- a/services/core/java/com/android/server/uri/UriPermission.java
+++ b/services/core/java/com/android/server/uri/UriPermission.java
@@ -16,6 +16,7 @@
package com.android.server.uri;
+import android.annotation.Nullable;
import android.app.GrantedUriPermission;
import android.content.Intent;
import android.os.Binder;
@@ -79,7 +80,7 @@ final class UriPermission {
*/
long persistedCreateTime = INVALID_TIME;
- private static final long INVALID_TIME = Long.MIN_VALUE;
+ static final long INVALID_TIME = Long.MIN_VALUE;
private ArraySet<UriPermissionOwner> mReadOwners;
private ArraySet<UriPermissionOwner> mWriteOwners;
@@ -96,7 +97,7 @@ final class UriPermission {
private void updateModeFlags() {
final int oldModeFlags = modeFlags;
- modeFlags = ownedModeFlags | globalModeFlags | persistableModeFlags | persistedModeFlags;
+ modeFlags = ownedModeFlags | globalModeFlags | persistedModeFlags;
if (Log.isLoggable(TAG, Log.VERBOSE) && (modeFlags != oldModeFlags)) {
Slog.d(TAG,
@@ -123,7 +124,7 @@ final class UriPermission {
updateModeFlags();
}
- void grantModes(int modeFlags, UriPermissionOwner owner) {
+ boolean grantModes(int modeFlags, @Nullable UriPermissionOwner owner) {
final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
@@ -144,6 +145,7 @@ final class UriPermission {
}
updateModeFlags();
+ return false;
}
/**
@@ -176,8 +178,6 @@ final class UriPermission {
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
final int before = persistedModeFlags;
-
- persistableModeFlags &= ~modeFlags;
persistedModeFlags &= ~modeFlags;
if (persistedModeFlags == 0) {
diff --git a/services/core/java/com/android/server/uri/UriPermissionOwner.java b/services/core/java/com/android/server/uri/UriPermissionOwner.java
index f2c06cd696b2..2b404a43a338 100644
--- a/services/core/java/com/android/server/uri/UriPermissionOwner.java
+++ b/services/core/java/com/android/server/uri/UriPermissionOwner.java
@@ -25,6 +25,7 @@ import android.util.ArraySet;
import android.util.proto.ProtoOutputStream;
import com.android.server.am.UriPermissionOwnerProto;
+
import com.google.android.collect.Sets;
import java.io.PrintWriter;
diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
new file mode 100644
index 000000000000..cf1978ec47a1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2020 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.uri;
+
+import static com.android.server.uri.UriGrantsMockContext.FLAG_PERSISTABLE;
+import static com.android.server.uri.UriGrantsMockContext.FLAG_PREFIX;
+import static com.android.server.uri.UriGrantsMockContext.FLAG_READ;
+import static com.android.server.uri.UriGrantsMockContext.PKG_CAMERA;
+import static com.android.server.uri.UriGrantsMockContext.PKG_COMPLEX;
+import static com.android.server.uri.UriGrantsMockContext.PKG_SOCIAL;
+import static com.android.server.uri.UriGrantsMockContext.UID_PRIMARY_CAMERA;
+import static com.android.server.uri.UriGrantsMockContext.UID_PRIMARY_COMPLEX;
+import static com.android.server.uri.UriGrantsMockContext.UID_PRIMARY_PRIVATE;
+import static com.android.server.uri.UriGrantsMockContext.UID_PRIMARY_PUBLIC;
+import static com.android.server.uri.UriGrantsMockContext.UID_PRIMARY_SOCIAL;
+import static com.android.server.uri.UriGrantsMockContext.UID_SECONDARY_CAMERA;
+import static com.android.server.uri.UriGrantsMockContext.UID_SECONDARY_SOCIAL;
+import static com.android.server.uri.UriGrantsMockContext.URI_PHOTO_1;
+import static com.android.server.uri.UriGrantsMockContext.URI_PRIVATE;
+import static com.android.server.uri.UriGrantsMockContext.URI_PUBLIC;
+import static com.android.server.uri.UriGrantsMockContext.USER_PRIMARY;
+import static com.android.server.uri.UriGrantsMockContext.USER_SECONDARY;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ClipData;
+import android.content.Intent;
+import android.content.pm.ProviderInfo;
+import android.net.Uri;
+import android.util.ArraySet;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Set;
+
+public class UriGrantsManagerServiceTest {
+ private UriGrantsMockContext mContext;
+ private UriGrantsManagerService mService;
+ private UriGrantsManagerInternal mLocalService;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = new UriGrantsMockContext(InstrumentationRegistry.getContext());
+ mService = UriGrantsManagerService.createForTest(mContext.getFilesDir());
+ mLocalService = mService.getLocalService();
+ }
+
+ @Test
+ public void testNeeded_normal() {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, URI_PHOTO_1).addFlags(FLAG_READ);
+ final GrantUri expectedGrant = new GrantUri(USER_PRIMARY, URI_PHOTO_1, FLAG_READ);
+
+ final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent(
+ UID_PRIMARY_CAMERA, PKG_SOCIAL, intent, intent.getFlags(), null,
+ USER_PRIMARY);
+ assertEquals(PKG_SOCIAL, needed.targetPkg);
+ assertEquals(UID_PRIMARY_SOCIAL, needed.targetUid);
+ assertEquals(FLAG_READ, needed.flags);
+ assertEquals(asSet(expectedGrant), needed.uris);
+ }
+
+ /**
+ * No need to issue grants for public authorities.
+ */
+ @Test
+ public void testNeeded_public() {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, URI_PUBLIC).addFlags(FLAG_READ);
+ final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent(
+ UID_PRIMARY_PUBLIC, PKG_SOCIAL, intent, intent.getFlags(), null,
+ USER_PRIMARY);
+ assertNull(needed);
+ }
+
+ /**
+ * But we're willing to issue grants to public authorities when crossing
+ * user boundaries.
+ */
+ @Test
+ public void testNeeded_public_differentUser() {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, URI_PUBLIC).addFlags(FLAG_READ);
+ final GrantUri expectedGrant = new GrantUri(USER_PRIMARY, URI_PUBLIC, FLAG_READ);
+
+ final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent(
+ UID_PRIMARY_PUBLIC, PKG_SOCIAL, intent, intent.getFlags(), null, USER_SECONDARY);
+ assertEquals(PKG_SOCIAL, needed.targetPkg);
+ assertEquals(UID_SECONDARY_SOCIAL, needed.targetUid);
+ assertEquals(FLAG_READ, needed.flags);
+ assertEquals(asSet(expectedGrant), needed.uris);
+ }
+
+ /**
+ * Refuse to issue grants for private authorities.
+ */
+ @Test
+ public void testNeeded_private() {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, URI_PRIVATE).addFlags(FLAG_READ);
+ try {
+ mService.checkGrantUriPermissionFromIntent(
+ UID_PRIMARY_PRIVATE, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY);
+ fail();
+ } catch (SecurityException expected) {
+ }
+ }
+
+ /**
+ * Verify that we can't grant permissions to top level of a provider with
+ * complex permission model.
+ */
+ @Test
+ public void testNeeded_complex_top() {
+ final Uri uri = Uri.parse("content://" + PKG_COMPLEX + "/");
+ {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
+ .addFlags(FLAG_READ);
+ assertNull(mService.checkGrantUriPermissionFromIntent(UID_PRIMARY_COMPLEX, PKG_SOCIAL,
+ intent, intent.getFlags(), null, USER_PRIMARY));
+ }
+ {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
+ .addFlags(FLAG_READ | FLAG_PREFIX);
+ try {
+ mService.checkGrantUriPermissionFromIntent(UID_PRIMARY_COMPLEX, PKG_SOCIAL,
+ intent, intent.getFlags(), null, USER_PRIMARY);
+ fail();
+ } catch (SecurityException expected) {
+ }
+ }
+ {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
+ .addFlags(FLAG_READ | FLAG_PERSISTABLE);
+ try {
+ mService.checkGrantUriPermissionFromIntent(UID_PRIMARY_COMPLEX, PKG_SOCIAL,
+ intent, intent.getFlags(), null, USER_PRIMARY);
+ fail();
+ } catch (SecurityException expected) {
+ }
+ }
+ }
+
+ /**
+ * Verify that we allow special cross-user grants to top level of a provider
+ * that normally wouldn't allow it. Only basic permission modes are allowed;
+ * advanced modes throw.
+ */
+ @Test
+ public void testNeeded_complex_top_differentUser() {
+ final Uri uri = Uri.parse("content://" + PKG_COMPLEX + "/");
+ {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
+ .addFlags(FLAG_READ);
+ final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent(
+ UID_PRIMARY_COMPLEX, PKG_SOCIAL, intent, intent.getFlags(), null,
+ USER_SECONDARY);
+ assertEquals(FLAG_READ, needed.flags);
+ }
+ {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
+ .addFlags(FLAG_READ | FLAG_PREFIX);
+ try {
+ mService.checkGrantUriPermissionFromIntent(
+ UID_PRIMARY_COMPLEX, PKG_SOCIAL, intent, intent.getFlags(), null,
+ USER_SECONDARY);
+ fail();
+ } catch (SecurityException expected) {
+ }
+ }
+ {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
+ .addFlags(FLAG_READ | FLAG_PERSISTABLE);
+ try {
+ mService.checkGrantUriPermissionFromIntent(
+ UID_PRIMARY_COMPLEX, PKG_SOCIAL, intent, intent.getFlags(), null,
+ USER_SECONDARY);
+ fail();
+ } catch (SecurityException expected) {
+ }
+ }
+ }
+
+ /**
+ * Verify that we can grant permissions to middle level of a provider with
+ * complex permission model.
+ */
+ @Test
+ public void testNeeded_complex_middle() {
+ final Uri uri = Uri.parse("content://" + PKG_COMPLEX + "/secure/12");
+ {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
+ .addFlags(FLAG_READ);
+ final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent(
+ UID_PRIMARY_COMPLEX, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY);
+ assertEquals(asSet(new GrantUri(USER_PRIMARY, uri, 0)), needed.uris);
+ }
+ {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
+ .addFlags(FLAG_READ | FLAG_PREFIX);
+ final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent(
+ UID_PRIMARY_COMPLEX, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY);
+ assertEquals(asSet(new GrantUri(USER_PRIMARY, uri, FLAG_PREFIX)), needed.uris);
+ }
+ {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri)
+ .addFlags(FLAG_READ | FLAG_PERSISTABLE);
+ final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent(
+ UID_PRIMARY_COMPLEX, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY);
+ assertEquals(asSet(new GrantUri(USER_PRIMARY, uri, 0)), needed.uris);
+ }
+ }
+
+ /**
+ * Verify that when we try sending a list of mixed items that the actual
+ * grants are verified based on the capabilities of the caller.
+ */
+ @Test
+ public void testNeeded_mixedPersistable() {
+ final ClipData clip = ClipData.newRawUri("test", URI_PHOTO_1);
+ clip.addItem(new ClipData.Item(URI_PUBLIC));
+
+ final Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.addFlags(FLAG_READ | FLAG_PERSISTABLE);
+ intent.setClipData(clip);
+
+ {
+ // When granting towards primary, persistable can't be honored so
+ // the entire grant fails
+ try {
+ mService.checkGrantUriPermissionFromIntent(UID_PRIMARY_CAMERA, PKG_SOCIAL, intent,
+ intent.getFlags(), null, USER_PRIMARY);
+ fail();
+ } catch (SecurityException expected) {
+ }
+ }
+ {
+ // When granting towards secondary, persistable can't be honored so
+ // the entire grant fails
+ try {
+ mService.checkGrantUriPermissionFromIntent(UID_PRIMARY_CAMERA, PKG_SOCIAL, intent,
+ intent.getFlags(), null, USER_SECONDARY);
+ fail();
+ } catch (SecurityException expected) {
+ }
+ }
+ }
+
+ /**
+ * Verify that two overlapping owners require separate grants and that they
+ * don't interfere with each other.
+ */
+ @Test
+ public void testGrant_overlap() {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, URI_PHOTO_1).addFlags(FLAG_READ);
+
+ final UriPermissionOwner activity = new UriPermissionOwner(mLocalService, "activity");
+ final UriPermissionOwner service = new UriPermissionOwner(mLocalService, "service");
+
+ final GrantUri expectedGrant = new GrantUri(USER_PRIMARY, URI_PHOTO_1, FLAG_READ);
+
+ // Grant read via activity and write via service
+ mService.grantUriPermissionUncheckedFromIntent(mService.checkGrantUriPermissionFromIntent(
+ UID_PRIMARY_CAMERA, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY),
+ activity);
+ mService.grantUriPermissionUncheckedFromIntent(mService.checkGrantUriPermissionFromIntent(
+ UID_PRIMARY_CAMERA, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY),
+ service);
+
+ // Verify that everything is good with the world
+ assertTrue(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ));
+
+ // Finish activity; service should hold permission
+ activity.removeUriPermissions();
+ assertTrue(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ));
+
+ // And finishing service should wrap things up
+ service.removeUriPermissions();
+ assertFalse(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ));
+ }
+
+ @Test
+ public void testCheckAuthorityGrants() {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, URI_PHOTO_1).addFlags(FLAG_READ);
+ final UriPermissionOwner owner = new UriPermissionOwner(mLocalService, "primary");
+
+ final ProviderInfo cameraInfo = mContext.mPmInternal.resolveContentProvider(
+ PKG_CAMERA, 0, USER_PRIMARY);
+
+ // By default no social can see any camera
+ assertFalse(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL,
+ cameraInfo, USER_PRIMARY, true));
+ assertFalse(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL,
+ cameraInfo, USER_SECONDARY, true));
+ assertFalse(mService.checkAuthorityGrants(UID_SECONDARY_SOCIAL,
+ cameraInfo, USER_PRIMARY, true));
+ assertFalse(mService.checkAuthorityGrants(UID_SECONDARY_SOCIAL,
+ cameraInfo, USER_SECONDARY, true));
+
+ // Granting primary camera to primary social
+ mService.grantUriPermissionUncheckedFromIntent(mService.checkGrantUriPermissionFromIntent(
+ UID_PRIMARY_CAMERA, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY),
+ owner);
+ assertTrue(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL,
+ cameraInfo, USER_PRIMARY, true));
+ assertFalse(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL,
+ cameraInfo, USER_SECONDARY, true));
+ assertFalse(mService.checkAuthorityGrants(UID_SECONDARY_SOCIAL,
+ cameraInfo, USER_PRIMARY, true));
+ assertFalse(mService.checkAuthorityGrants(UID_SECONDARY_SOCIAL,
+ cameraInfo, USER_SECONDARY, true));
+
+ // Granting secondary camera to primary social
+ mService.grantUriPermissionUncheckedFromIntent(mService.checkGrantUriPermissionFromIntent(
+ UID_SECONDARY_CAMERA, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY),
+ owner);
+ assertTrue(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL,
+ cameraInfo, USER_PRIMARY, true));
+ assertTrue(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL,
+ cameraInfo, USER_SECONDARY, true));
+ assertFalse(mService.checkAuthorityGrants(UID_SECONDARY_SOCIAL,
+ cameraInfo, USER_PRIMARY, true));
+ assertFalse(mService.checkAuthorityGrants(UID_SECONDARY_SOCIAL,
+ cameraInfo, USER_SECONDARY, true));
+
+ // And releasing the grant means we lose access
+ owner.removeUriPermissions();
+ assertFalse(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL,
+ cameraInfo, USER_PRIMARY, true));
+ assertFalse(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL,
+ cameraInfo, USER_SECONDARY, true));
+ }
+
+ private static <T> Set<T> asSet(T... values) {
+ return new ArraySet<T>(Arrays.asList(values));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java
new file mode 100644
index 000000000000..989928dbbcc4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2020 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.uri;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.app.ActivityManagerInternal;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PathPermission;
+import android.content.pm.ProviderInfo;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.os.PatternMatcher;
+import android.os.UserHandle;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockPackageManager;
+
+import com.android.server.LocalServices;
+
+import java.io.File;
+
+public class UriGrantsMockContext extends ContextWrapper {
+ static final String TAG = "UriGrants";
+
+ static final int FLAG_READ = Intent.FLAG_GRANT_READ_URI_PERMISSION;
+ static final int FLAG_WRITE = Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+ static final int FLAG_PERSISTABLE = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
+ static final int FLAG_PREFIX = Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+
+ static final int USER_PRIMARY = 10;
+ static final int USER_SECONDARY = 11;
+
+ /** Typical social network app */
+ static final String PKG_SOCIAL = "com.example.social";
+ /** Typical camera app that allows grants */
+ static final String PKG_CAMERA = "com.example.camera";
+ /** Completely private app/provider that offers no grants */
+ static final String PKG_PRIVATE = "com.example.private";
+ /** Completely public app/provider that needs no grants */
+ static final String PKG_PUBLIC = "com.example.public";
+ /** Complex provider that offers nested grants */
+ static final String PKG_COMPLEX = "com.example.complex";
+
+ private static final int UID_SOCIAL = android.os.Process.LAST_APPLICATION_UID - 1;
+ private static final int UID_CAMERA = android.os.Process.LAST_APPLICATION_UID - 2;
+ private static final int UID_PRIVATE = android.os.Process.LAST_APPLICATION_UID - 3;
+ private static final int UID_PUBLIC = android.os.Process.LAST_APPLICATION_UID - 4;
+ private static final int UID_COMPLEX = android.os.Process.LAST_APPLICATION_UID - 5;
+
+ static final int UID_PRIMARY_SOCIAL = UserHandle.getUid(USER_PRIMARY, UID_SOCIAL);
+ static final int UID_PRIMARY_CAMERA = UserHandle.getUid(USER_PRIMARY, UID_CAMERA);
+ static final int UID_PRIMARY_PRIVATE = UserHandle.getUid(USER_PRIMARY, UID_PRIVATE);
+ static final int UID_PRIMARY_PUBLIC = UserHandle.getUid(USER_PRIMARY, UID_PUBLIC);
+ static final int UID_PRIMARY_COMPLEX = UserHandle.getUid(USER_PRIMARY, UID_COMPLEX);
+
+ static final int UID_SECONDARY_SOCIAL = UserHandle.getUid(USER_SECONDARY, UID_SOCIAL);
+ static final int UID_SECONDARY_CAMERA = UserHandle.getUid(USER_SECONDARY, UID_CAMERA);
+ static final int UID_SECONDARY_PRIVATE = UserHandle.getUid(USER_SECONDARY, UID_PRIVATE);
+ static final int UID_SECONDARY_PUBLIC = UserHandle.getUid(USER_SECONDARY, UID_PUBLIC);
+ static final int UID_SECONDARY_COMPLEX = UserHandle.getUid(USER_PRIMARY, UID_COMPLEX);
+
+ static final Uri URI_PHOTO_1 = Uri.parse("content://" + PKG_CAMERA + "/1");
+ static final Uri URI_PHOTO_2 = Uri.parse("content://" + PKG_CAMERA + "/2");
+ static final Uri URI_PRIVATE = Uri.parse("content://" + PKG_PRIVATE + "/42");
+ static final Uri URI_PUBLIC = Uri.parse("content://" + PKG_PUBLIC + "/42");
+
+ private final File mDir;
+
+ private final MockPackageManager mPackage;
+ private final MockContentResolver mResolver;
+
+ final ActivityManagerInternal mAmInternal;
+ final PackageManagerInternal mPmInternal;
+
+ public UriGrantsMockContext(@NonNull Context base) {
+ super(base);
+ mDir = new File(base.getFilesDir(), TAG);
+ mDir.mkdirs();
+ FileUtils.deleteContents(mDir);
+
+ mPackage = new MockPackageManager();
+ mResolver = new MockContentResolver(this);
+
+ mAmInternal = mock(ActivityManagerInternal.class);
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.addService(ActivityManagerInternal.class, mAmInternal);
+
+ mPmInternal = mock(PackageManagerInternal.class);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPmInternal);
+
+ for (int userId : new int[] { USER_PRIMARY, USER_SECONDARY }) {
+ when(mPmInternal.getPackageUidInternal(eq(PKG_SOCIAL), anyInt(), eq(userId)))
+ .thenReturn(UserHandle.getUid(userId, UID_SOCIAL));
+ when(mPmInternal.getPackageUidInternal(eq(PKG_CAMERA), anyInt(), eq(userId)))
+ .thenReturn(UserHandle.getUid(userId, UID_CAMERA));
+ when(mPmInternal.getPackageUidInternal(eq(PKG_PRIVATE), anyInt(), eq(userId)))
+ .thenReturn(UserHandle.getUid(userId, UID_PRIVATE));
+ when(mPmInternal.getPackageUidInternal(eq(PKG_PUBLIC), anyInt(), eq(userId)))
+ .thenReturn(UserHandle.getUid(userId, UID_PUBLIC));
+ when(mPmInternal.getPackageUidInternal(eq(PKG_COMPLEX), anyInt(), eq(userId)))
+ .thenReturn(UserHandle.getUid(userId, UID_COMPLEX));
+
+ when(mPmInternal.resolveContentProvider(eq(PKG_CAMERA), anyInt(), eq(userId)))
+ .thenReturn(buildCameraProvider(userId));
+ when(mPmInternal.resolveContentProvider(eq(PKG_PRIVATE), anyInt(), eq(userId)))
+ .thenReturn(buildPrivateProvider(userId));
+ when(mPmInternal.resolveContentProvider(eq(PKG_PUBLIC), anyInt(), eq(userId)))
+ .thenReturn(buildPublicProvider(userId));
+ when(mPmInternal.resolveContentProvider(eq(PKG_COMPLEX), anyInt(), eq(userId)))
+ .thenReturn(buildComplexProvider(userId));
+ }
+ }
+
+ private static ProviderInfo buildCameraProvider(int userId) {
+ final ProviderInfo pi = new ProviderInfo();
+ pi.packageName = PKG_CAMERA;
+ pi.authority = PKG_CAMERA;
+ pi.readPermission = android.Manifest.permission.READ_EXTERNAL_STORAGE;
+ pi.writePermission = android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
+ pi.grantUriPermissions = true;
+ pi.applicationInfo = new ApplicationInfo();
+ pi.applicationInfo.uid = UserHandle.getUid(userId, UID_CAMERA);
+ return pi;
+ }
+
+ private static ProviderInfo buildPrivateProvider(int userId) {
+ final ProviderInfo pi = new ProviderInfo();
+ pi.packageName = PKG_PRIVATE;
+ pi.authority = PKG_PRIVATE;
+ pi.exported = false;
+ pi.grantUriPermissions = false;
+ pi.applicationInfo = new ApplicationInfo();
+ pi.applicationInfo.uid = UserHandle.getUid(userId, UID_PRIVATE);
+ return pi;
+ }
+
+ private static ProviderInfo buildPublicProvider(int userId) {
+ final ProviderInfo pi = new ProviderInfo();
+ pi.packageName = PKG_PUBLIC;
+ pi.authority = PKG_PUBLIC;
+ pi.exported = true;
+ pi.grantUriPermissions = false;
+ pi.applicationInfo = new ApplicationInfo();
+ pi.applicationInfo.uid = UserHandle.getUid(userId, UID_PUBLIC);
+ return pi;
+ }
+
+ private static ProviderInfo buildComplexProvider(int userId) {
+ final ProviderInfo pi = new ProviderInfo();
+ pi.packageName = PKG_COMPLEX;
+ pi.authority = PKG_COMPLEX;
+ pi.exported = true;
+ pi.grantUriPermissions = true;
+ pi.applicationInfo = new ApplicationInfo();
+ pi.applicationInfo.uid = UserHandle.getUid(userId, UID_COMPLEX);
+ pi.pathPermissions = new PathPermission[] {
+ new PathPermission("/secure", PathPermission.PATTERN_PREFIX,
+ android.Manifest.permission.READ_EXTERNAL_STORAGE,
+ android.Manifest.permission.WRITE_EXTERNAL_STORAGE),
+ };
+ pi.uriPermissionPatterns = new PatternMatcher[] {
+ new PatternMatcher("/secure", PathPermission.PATTERN_PREFIX),
+ new PatternMatcher("/insecure", PathPermission.PATTERN_PREFIX),
+ };
+ return pi;
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mPackage;
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mResolver;
+ }
+
+ @Override
+ public File getFilesDir() {
+ return mDir;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java b/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java
new file mode 100644
index 000000000000..07005a9902d7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2020 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.uri;
+
+import static com.android.server.uri.UriGrantsMockContext.FLAG_PERSISTABLE;
+import static com.android.server.uri.UriGrantsMockContext.FLAG_READ;
+import static com.android.server.uri.UriGrantsMockContext.FLAG_WRITE;
+import static com.android.server.uri.UriGrantsMockContext.PKG_CAMERA;
+import static com.android.server.uri.UriGrantsMockContext.PKG_SOCIAL;
+import static com.android.server.uri.UriGrantsMockContext.UID_PRIMARY_SOCIAL;
+import static com.android.server.uri.UriGrantsMockContext.URI_PHOTO_1;
+import static com.android.server.uri.UriGrantsMockContext.URI_PHOTO_2;
+import static com.android.server.uri.UriGrantsMockContext.USER_PRIMARY;
+import static com.android.server.uri.UriPermission.INVALID_TIME;
+import static com.android.server.uri.UriPermission.STRENGTH_GLOBAL;
+import static com.android.server.uri.UriPermission.STRENGTH_NONE;
+import static com.android.server.uri.UriPermission.STRENGTH_OWNED;
+import static com.android.server.uri.UriPermission.STRENGTH_PERSISTABLE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class UriPermissionTest {
+ @Mock
+ private UriGrantsManagerInternal mService;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testNone() {
+ final GrantUri grant = new GrantUri(USER_PRIMARY, URI_PHOTO_1, FLAG_READ);
+ final UriPermission perm = new UriPermission(PKG_CAMERA,
+ PKG_SOCIAL, UID_PRIMARY_SOCIAL, grant);
+ assertEquals(STRENGTH_NONE, perm.getStrength(FLAG_READ));
+ }
+
+ @Test
+ public void testGlobal() {
+ final GrantUri grant = new GrantUri(USER_PRIMARY, URI_PHOTO_1, FLAG_READ);
+ final UriPermission perm = new UriPermission(PKG_CAMERA,
+ PKG_SOCIAL, UID_PRIMARY_SOCIAL, grant);
+
+ assertFalse(perm.grantModes(FLAG_READ, null));
+ assertEquals(STRENGTH_GLOBAL, perm.getStrength(FLAG_READ));
+
+ assertFalse(perm.revokeModes(FLAG_READ, true));
+ assertEquals(STRENGTH_NONE, perm.getStrength(FLAG_READ));
+ }
+
+ @Test
+ public void testOwned() {
+ final GrantUri grant = new GrantUri(USER_PRIMARY, URI_PHOTO_1, FLAG_READ);
+ final UriPermission perm = new UriPermission(PKG_CAMERA,
+ PKG_SOCIAL, UID_PRIMARY_SOCIAL, grant);
+ final UriPermissionOwner owner = new UriPermissionOwner(mService, "test");
+
+ assertFalse(perm.grantModes(FLAG_READ, owner));
+ assertEquals(STRENGTH_OWNED, perm.getStrength(FLAG_READ));
+
+ assertFalse(perm.revokeModes(FLAG_READ, false));
+ assertEquals(STRENGTH_OWNED, perm.getStrength(FLAG_READ));
+
+ assertFalse(perm.revokeModes(FLAG_READ, true));
+ assertEquals(STRENGTH_NONE, perm.getStrength(FLAG_READ));
+ }
+
+ @Test
+ public void testOverlap() {
+ final GrantUri grant1 = new GrantUri(USER_PRIMARY, URI_PHOTO_1, FLAG_READ);
+ final GrantUri grant2 = new GrantUri(USER_PRIMARY, URI_PHOTO_2, FLAG_READ);
+
+ final UriPermission photo1 = new UriPermission(PKG_CAMERA,
+ PKG_SOCIAL, UID_PRIMARY_SOCIAL, grant1);
+ final UriPermission photo2 = new UriPermission(PKG_CAMERA,
+ PKG_SOCIAL, UID_PRIMARY_SOCIAL, grant2);
+
+ // Verify behavior when we have multiple owners within the same app
+ final UriPermissionOwner activity = new UriPermissionOwner(mService, "activity");
+ final UriPermissionOwner service = new UriPermissionOwner(mService, "service");
+
+ photo1.grantModes(FLAG_READ | FLAG_WRITE, activity);
+ photo1.grantModes(FLAG_READ, service);
+ photo2.grantModes(FLAG_READ, activity);
+ photo2.grantModes(FLAG_WRITE, null);
+
+ assertEquals(FLAG_READ | FLAG_WRITE, photo1.modeFlags);
+ assertEquals(FLAG_READ | FLAG_WRITE, photo2.modeFlags);
+
+ // Shutting down activity should only trim away write access
+ activity.removeUriPermissions();
+ assertEquals(FLAG_READ, photo1.modeFlags);
+ assertEquals(FLAG_WRITE, photo2.modeFlags);
+
+ // Shutting down service should bring everything else down
+ service.removeUriPermissions();
+ assertEquals(0, photo1.modeFlags);
+ assertEquals(FLAG_WRITE, photo2.modeFlags);
+ }
+
+ @Test
+ public void testPersist() {
+ final GrantUri grant = new GrantUri(USER_PRIMARY, URI_PHOTO_1, FLAG_READ);
+ final UriPermission perm = new UriPermission(PKG_CAMERA,
+ PKG_SOCIAL, UID_PRIMARY_SOCIAL, grant);
+
+ assertFalse(perm.grantModes(FLAG_READ, null));
+ assertFalse(perm.grantModes(FLAG_WRITE | FLAG_PERSISTABLE, null));
+ assertEquals(STRENGTH_GLOBAL, perm.getStrength(FLAG_READ));
+ assertEquals(STRENGTH_PERSISTABLE, perm.getStrength(FLAG_WRITE));
+
+ // Verify behavior of non-persistable mode; nothing happens
+ {
+ assertFalse(perm.takePersistableModes(FLAG_READ));
+ assertEquals(0, perm.persistedModeFlags);
+
+ assertFalse(perm.releasePersistableModes(FLAG_READ));
+ assertEquals(0, perm.persistedModeFlags);
+ }
+
+ // Verify behavior of persistable mode
+ {
+ assertEquals(FLAG_WRITE, perm.persistableModeFlags);
+ assertEquals(0, perm.persistedModeFlags);
+ assertTrue(perm.takePersistableModes(FLAG_WRITE));
+ assertEquals(FLAG_WRITE, perm.persistableModeFlags);
+ assertEquals(FLAG_WRITE, perm.persistedModeFlags);
+
+ // Attempting to take a second time should be a no-op
+ final long createTime = perm.persistedCreateTime;
+ assertFalse(perm.takePersistableModes(FLAG_WRITE));
+ assertEquals(createTime, perm.persistedCreateTime);
+
+ assertTrue(perm.releasePersistableModes(FLAG_WRITE));
+ assertEquals(FLAG_WRITE, perm.persistableModeFlags);
+ assertEquals(0, perm.persistedModeFlags);
+ assertEquals(INVALID_TIME, perm.persistedCreateTime);
+
+ // Attempting to release a second time should be a no-op
+ assertFalse(perm.releasePersistableModes(FLAG_WRITE));
+
+ // We should still be able to take again
+ assertTrue(perm.takePersistableModes(FLAG_WRITE));
+ assertEquals(FLAG_WRITE, perm.persistableModeFlags);
+ assertEquals(FLAG_WRITE, perm.persistedModeFlags);
+ }
+ }
+}