Merge "Allow read-only sharing of Uri in voicemail content provider."
diff --git a/src/com/android/providers/contacts/VoicemailContentProvider.java b/src/com/android/providers/contacts/VoicemailContentProvider.java
index c55c11c..d6ddd92 100644
--- a/src/com/android/providers/contacts/VoicemailContentProvider.java
+++ b/src/com/android/providers/contacts/VoicemailContentProvider.java
@@ -26,6 +26,8 @@
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Binder;
@@ -91,7 +93,7 @@
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
- UriData uriData = checkPermissionsAndCreateUriData(uri);
+ UriData uriData = checkPermissionsAndCreateUriDataForReadOperation(uri);
SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
selectionBuilder.addClause(getPackageRestrictionClause());
return getTableDelegate(uriData).query(uriData, projection, selectionBuilder.build(),
@@ -117,7 +119,12 @@
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
- UriData uriData = checkPermissionsAndCreateUriData(uri);
+ UriData uriData = null;
+ if (mode.equals("r")) {
+ uriData = checkPermissionsAndCreateUriDataForReadOperation(uri);
+ } else {
+ uriData = checkPermissionsAndCreateUriData(uri);
+ }
// openFileHelper() relies on "_data" column to be populated with the file path.
return getTableDelegate(uriData).openFile(uriData, mode);
}
@@ -261,6 +268,20 @@
* Performs necessary voicemail permission checks common to all operations and returns
* the structured representation, {@link UriData}, of the supplied uri.
*/
+ private UriData checkPermissionsAndCreateUriDataForReadOperation(Uri uri) {
+ // If the caller has been explicitly granted read permission to this URI then no need to
+ // check further.
+ if (context().checkCallingUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ == PackageManager.PERMISSION_GRANTED) {
+ return UriData.createUriData(uri);
+ }
+ return checkPermissionsAndCreateUriData(uri);
+ }
+
+ /**
+ * Performs necessary voicemail permission checks common to all operations and returns
+ * the structured representation, {@link UriData}, of the supplied uri.
+ */
private UriData checkPermissionsAndCreateUriData(Uri uri) {
mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
UriData uriData = UriData.createUriData(uri);
diff --git a/tests/src/com/android/providers/contacts/ContactsActor.java b/tests/src/com/android/providers/contacts/ContactsActor.java
index 865c956..29d8eed 100644
--- a/tests/src/com/android/providers/contacts/ContactsActor.java
+++ b/tests/src/com/android/providers/contacts/ContactsActor.java
@@ -56,8 +56,6 @@
import android.test.RenamingDelegatingContext;
import android.test.mock.MockContentResolver;
import android.test.mock.MockContext;
-import android.test.mock.MockResources;
-import android.util.TypedValue;
import java.io.File;
import java.io.IOException;
@@ -88,6 +86,7 @@
private Account[] mAccounts = new Account[0];
private Set<String> mGrantedPermissions = Sets.newHashSet();
+ private final Set<Uri> mGrantedUriPermissions = Sets.newHashSet();
private CountryDetector mMockCountryDetector = new CountryDetector(null){
@Override
@@ -145,7 +144,7 @@
Class<? extends ContentProvider> providerClass, String authority) throws Exception {
resolver = new MockContentResolver();
context = new RestrictionMockContext(overallContext, packageName, resolver,
- mGrantedPermissions);
+ mGrantedPermissions, mGrantedUriPermissions);
this.packageName = packageName;
RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext(context,
@@ -196,6 +195,14 @@
mGrantedPermissions.removeAll(Arrays.asList(permissions));
}
+ public void addUriPermissions(Uri... uris) {
+ mGrantedUriPermissions.addAll(Arrays.asList(uris));
+ }
+
+ public void removeUriPermissions(Uri... uris) {
+ mGrantedUriPermissions.removeAll(Arrays.asList(uris));
+ }
+
/**
* Mock {@link Context} that reports specific well-known values for testing
* data protection. The creator can override the owner package name, and
@@ -213,16 +220,19 @@
private final ContentResolver mResolver;
private final Resources mRes;
private final Set<String> mGrantedPermissions;
+ private final Set<Uri> mGrantedUriPermissions;
/**
* Create a {@link Context} under the given package name.
*/
public RestrictionMockContext(Context overallContext, String reportedPackageName,
- ContentResolver resolver, Set<String> grantedPermissions) {
+ ContentResolver resolver, Set<String> grantedPermissions,
+ Set<Uri> grantedUriPermissions) {
mOverallContext = overallContext;
mReportedPackageName = reportedPackageName;
mResolver = resolver;
mGrantedPermissions = grantedPermissions;
+ mGrantedUriPermissions = grantedUriPermissions;
mPackageManager = new ContactsMockPackageManager();
mPackageManager.addPackage(1000, PACKAGE_GREY);
@@ -281,6 +291,20 @@
}
@Override
+ public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
+ return checkCallingUriPermission(uri, modeFlags);
+ }
+
+ @Override
+ public int checkCallingUriPermission(Uri uri, int modeFlags) {
+ if (mGrantedUriPermissions.contains(uri)) {
+ return PackageManager.PERMISSION_GRANTED;
+ } else {
+ return PackageManager.PERMISSION_DENIED;
+ }
+ }
+
+ @Override
public int checkCallingOrSelfPermission(String permission) {
return checkCallingPermission(permission);
}
diff --git a/tests/src/com/android/providers/contacts/VoicemailProviderTest.java b/tests/src/com/android/providers/contacts/VoicemailProviderTest.java
index 119d8b3..702b55b 100644
--- a/tests/src/com/android/providers/contacts/VoicemailProviderTest.java
+++ b/tests/src/com/android/providers/contacts/VoicemailProviderTest.java
@@ -16,10 +16,13 @@
package com.android.providers.contacts;
+import com.android.common.io.MoreCloseables;
+
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
+import android.os.ParcelFileDescriptor;
import android.provider.CallLog.Calls;
import android.provider.VoicemailContract;
import android.provider.VoicemailContract.Status;
@@ -27,6 +30,7 @@
import android.test.MoreAsserts;
import java.io.FileNotFoundException;
+import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
@@ -225,6 +229,107 @@
.build();
}
+ public void testUriPermissions() {
+ setUpForFullPermission();
+ final Uri uri1 = insertVoicemail();
+ final Uri uri2 = insertVoicemail();
+ // Give away all permissions before querying. Access to both uris should be denied.
+ setUpForNoPermission();
+ checkHasNoAccessToUri(uri1);
+ checkHasNoAccessToUri(uri2);
+
+ // Just grant permission to uri1. uri1 should pass but uri2 should still fail.
+ mActor.addUriPermissions(uri1);
+ checkHasReadOnlyAccessToUri(uri1);
+ checkHasNoAccessToUri(uri2);
+
+ // Cleanup.
+ mActor.removeUriPermissions(uri1);
+ }
+
+ private void checkHasNoAccessToUri(final Uri uri) {
+ checkHasNoReadAccessToUri(uri);
+ checkHasNoWriteAccessToUri(uri);
+ }
+
+ private void checkHasReadOnlyAccessToUri(final Uri uri) {
+ checkHasReadAccessToUri(uri);
+ checkHasNoWriteAccessToUri(uri);
+ }
+
+ private void checkHasReadAccessToUri(final Uri uri) {
+ Cursor cursor = null;
+ try {
+ cursor = mResolver.query(uri, null, null ,null, null);
+ assertEquals(1, cursor.getCount());
+ try {
+ ParcelFileDescriptor fd = mResolver.openFileDescriptor(uri, "r");
+ assertNotNull(fd);
+ fd.close();
+ } catch (FileNotFoundException e) {
+ fail(e.getMessage());
+ } catch (IOException e) {
+ fail(e.getMessage());
+ }
+ } finally {
+ MoreCloseables.closeQuietly(cursor);
+ }
+ }
+
+ private void checkHasNoReadAccessToUri(final Uri uri) {
+ EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
+ @Override
+ public void run() {
+ mResolver.query(uri, null, null ,null, null);
+ }
+ });
+ EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mResolver.openFileDescriptor(uri, "r");
+ } catch (FileNotFoundException e) {
+ fail(e.getMessage());
+ }
+ }
+ });
+ }
+
+ private void checkHasNoWriteAccessToUri(final Uri uri) {
+ EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
+ @Override
+ public void run() {
+ mResolver.update(uri, getTestVoicemailValues(), null, null);
+ }
+ });
+ EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
+ @Override
+ public void run() {
+ mResolver.delete(uri, null, null);
+ }
+ });
+ EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mResolver.openFileDescriptor(uri, "w");
+ } catch (FileNotFoundException e) {
+ fail(e.getMessage());
+ }
+ }
+ });
+ EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mResolver.openFileDescriptor(uri, "rw");
+ } catch (FileNotFoundException e) {
+ fail(e.getMessage());
+ }
+ }
+ });
+ }
+
// Test to ensure that all operations fail when no voicemail permission is granted.
public void testNoPermissions() {
setUpForNoPermission();