aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave Santoro <dsantoro@google.com>2011-10-03 18:26:42 -0700
committerDave Santoro <dsantoro@google.com>2011-10-04 11:39:31 -0700
commit36612112760df799ef89f7e324e5dfabd5ca0d2b (patch)
treed036469cecdaddc25c6ae0edb1fa9dab8a9e5926
parentc990980ab4beb7b81c3337526f1bdcd5d1a14730 (diff)
downloadContactsProvider-36612112760df799ef89f7e324e5dfabd5ca0d2b.tar.gz
Implement new social stream permissions.
Reading/writing social streams now requires READ_SOCIAL_STREAM or WRITE_SOCIAL_STREAM permission. The special stream item insertion that occurs on status update insertion is exempt from this requirement. Bug 5406886 Change-Id: I6a711d7f33b501e2c331c71684b2eb3a6bfd5ec5
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java64
-rw-r--r--tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java2
-rw-r--r--tests/src/com/android/providers/contacts/ContactsProvider2Test.java318
3 files changed, 275 insertions, 109 deletions
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 8a4df445..c21d7b8c 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -372,6 +372,22 @@ public class ContactsProvider2 extends AbstractContactsProvider
INSERT_URI_ID_VALUE_MAP.put(STREAM_ITEMS_ID_PHOTOS, StreamItemPhotos.STREAM_ITEM_ID);
}
+ // Any interactions that involve these URIs will also require the calling package to have either
+ // android.permission.READ_SOCIAL_STREAM permission or android.permission.WRITE_SOCIAL_STREAM
+ // permission, depending on the type of operation being performed.
+ private static final List<Integer> SOCIAL_STREAM_URIS = Lists.newArrayList(
+ CONTACTS_ID_STREAM_ITEMS,
+ CONTACTS_LOOKUP_STREAM_ITEMS,
+ CONTACTS_LOOKUP_ID_STREAM_ITEMS,
+ RAW_CONTACTS_ID_STREAM_ITEMS,
+ RAW_CONTACTS_ID_STREAM_ITEMS_ID,
+ STREAM_ITEMS,
+ STREAM_ITEMS_PHOTOS,
+ STREAM_ITEMS_ID,
+ STREAM_ITEMS_ID_PHOTOS,
+ STREAM_ITEMS_ID_PHOTOS_ID
+ );
+
private static final String SELECTION_FAVORITES_GROUPS_BY_RAW_CONTACT_ID =
RawContactsColumns.CONCRETE_ID + "=? AND "
+ GroupsColumns.CONCRETE_ACCOUNT_NAME
@@ -2026,6 +2042,10 @@ public class ContactsProvider2 extends AbstractContactsProvider
@Override
public Uri insert(Uri uri, ContentValues values) {
waitForAccess(mWriteAccessLatch);
+
+ // Enforce stream items access check if applicable.
+ enforceSocialStreamWritePermission(uri);
+
if (mapsToProfileDbWithInsertedValues(uri, values)) {
switchToProfileMode();
return mProfileProvider.insert(uri, values);
@@ -2053,6 +2073,10 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
}
waitForAccess(mWriteAccessLatch);
+
+ // Enforce stream items access check if applicable.
+ enforceSocialStreamWritePermission(uri);
+
if (mapsToProfileDb(uri)) {
switchToProfileMode();
return mProfileProvider.update(uri, values, selection, selectionArgs);
@@ -2065,6 +2089,10 @@ public class ContactsProvider2 extends AbstractContactsProvider
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
waitForAccess(mWriteAccessLatch);
+
+ // Enforce stream items access check if applicable.
+ enforceSocialStreamWritePermission(uri);
+
if (mapsToProfileDb(uri)) {
switchToProfileMode();
return mProfileProvider.delete(uri, selection, selectionArgs);
@@ -2734,6 +2762,30 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
/**
+ * If the given URI is reading stream items or stream photos, this will run a permission check
+ * for the android.permission.READ_SOCIAL_STREAM permission - otherwise it will do nothing.
+ * @param uri The URI to check.
+ */
+ private void enforceSocialStreamReadPermission(Uri uri) {
+ if (SOCIAL_STREAM_URIS.contains(sUriMatcher.match(uri))) {
+ getContext().enforceCallingOrSelfPermission(
+ "android.permission.READ_SOCIAL_STREAM", null);
+ }
+ }
+
+ /**
+ * If the given URI is modifying stream items or stream photos, this will run a permission check
+ * for the android.permission.WRITE_SOCIAL_STREAM permission - otherwise it will do nothing.
+ * @param uri The URI to check.
+ */
+ private void enforceSocialStreamWritePermission(Uri uri) {
+ if (SOCIAL_STREAM_URIS.contains(sUriMatcher.match(uri))) {
+ getContext().enforceCallingOrSelfPermission(
+ "android.permission.WRITE_SOCIAL_STREAM", null);
+ }
+ }
+
+ /**
* Checks whether the given raw contact ID is owned by the given account.
* If the resolved account is null, this will return true iff the raw contact
* is also associated with the "null" account.
@@ -3256,16 +3308,17 @@ public class ContactsProvider2 extends AbstractContactsProvider
// Check for an existing stream item from this source, and insert or update.
Uri streamUri = StreamItems.CONTENT_URI;
- Cursor c = query(streamUri, new String[]{StreamItems._ID},
+ Cursor c = queryLocal(streamUri, new String[]{StreamItems._ID},
StreamItems.RAW_CONTACT_ID + "=?",
- new String[]{String.valueOf(rawContactId)}, null);
+ new String[]{String.valueOf(rawContactId)},
+ null, -1 /* directory ID */);
try {
if (c.getCount() > 0) {
c.moveToFirst();
- update(ContentUris.withAppendedId(streamUri, c.getLong(0)),
+ updateInTransaction(ContentUris.withAppendedId(streamUri, c.getLong(0)),
streamItemValues, null, null);
} else {
- insert(streamUri, streamItemValues);
+ insertInTransaction(streamUri, streamItemValues);
}
} finally {
c.close();
@@ -4618,6 +4671,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
waitForAccess(mReadAccessLatch);
+ // Enforce stream items access check if applicable.
+ enforceSocialStreamReadPermission(uri);
+
// Query the profile DB if appropriate.
if (mapsToProfileDb(uri)) {
switchToProfileMode();
diff --git a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
index c3fcea65..14bf3257 100644
--- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
@@ -104,6 +104,8 @@ public abstract class BaseContactsProvider2Test extends PhotoLoadingTestCase {
mActor.addPermissions(
"android.permission.READ_CONTACTS",
"android.permission.WRITE_CONTACTS",
+ "android.permission.READ_SOCIAL_STREAM",
+ "android.permission.WRITE_SOCIAL_STREAM",
"android.permission.READ_PROFILE",
"android.permission.WRITE_PROFILE");
}
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index 10c9e11b..8d2b9796 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -1664,48 +1664,42 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
c.close();
}
- public void testQueryProfileRequiresReadPermission() {
- mActor.removePermissions("android.permission.READ_PROFILE");
-
- createBasicProfileContact(new ContentValues());
-
- // Queries for the profile should fail.
+ private void expectSecurityException(String failureMessage, Uri uri, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder) {
Cursor c = null;
-
- // Case 1: Retrieving profile contact.
try {
- c = mResolver.query(Profile.CONTENT_URI, null, null, null, Contacts._ID);
- fail("Querying for the profile without READ_PROFILE access should fail.");
+ c = mResolver.query(uri, projection, selection, selectionArgs, sortOrder);
+ fail(failureMessage);
} catch (SecurityException expected) {
+ // The security exception is expected to occur because we're missing a permission.
} finally {
if (c != null) {
c.close();
}
}
+ }
+
+ public void testQueryProfileRequiresReadPermission() {
+ mActor.removePermissions("android.permission.READ_PROFILE");
+
+ createBasicProfileContact(new ContentValues());
+
+ // Case 1: Retrieving profile contact.
+ expectSecurityException(
+ "Querying for the profile without READ_PROFILE access should fail.",
+ Profile.CONTENT_URI, null, null, null, Contacts._ID);
// Case 2: Retrieving profile data.
- try {
- c = mResolver.query(Profile.CONTENT_URI.buildUpon().appendPath("data").build(),
- null, null, null, Contacts._ID);
- fail("Querying for the profile data without READ_PROFILE access should fail.");
- } catch (SecurityException expected) {
- } finally {
- if (c != null) {
- c.close();
- }
- }
+ expectSecurityException(
+ "Querying for the profile data without READ_PROFILE access should fail.",
+ Profile.CONTENT_URI.buildUpon().appendPath("data").build(),
+ null, null, null, Contacts._ID);
// Case 3: Retrieving profile entities.
- try {
- c = mResolver.query(Profile.CONTENT_URI.buildUpon()
- .appendPath("entities").build(), null, null, null, Contacts._ID);
- fail("Querying for the profile entities without READ_PROFILE access should fail.");
- } catch (SecurityException expected) {
- } finally {
- if (c != null) {
- c.close();
- }
- }
+ expectSecurityException(
+ "Querying for the profile entities without READ_PROFILE access should fail.",
+ Profile.CONTENT_URI.buildUpon()
+ .appendPath("entities").build(), null, null, null, Contacts._ID);
}
public void testQueryProfileByContactIdRequiresReadPermission() {
@@ -1715,17 +1709,10 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
mActor.removePermissions("android.permission.READ_PROFILE");
// A query for the profile contact by ID should fail.
- Cursor c = null;
- try {
- c = mResolver.query(ContentUris.withAppendedId(Contacts.CONTENT_URI, profileContactId),
- null, null, null, Contacts._ID);
- fail("Querying for the profile by contact ID without READ_PROFILE access should fail.");
- } catch (SecurityException expected) {
- } finally {
- if (c != null) {
- c.close();
- }
- }
+ expectSecurityException(
+ "Querying for the profile by contact ID without READ_PROFILE access should fail.",
+ ContentUris.withAppendedId(Contacts.CONTENT_URI, profileContactId),
+ null, null, null, Contacts._ID);
}
public void testQueryProfileByRawContactIdRequiresReadPermission() {
@@ -1733,17 +1720,10 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// Remove profile read permission and attempt to retrieve the raw contact.
mActor.removePermissions("android.permission.READ_PROFILE");
- Cursor c = null;
- try {
- c = mResolver.query(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
- profileRawContactId), null, null, null, RawContacts._ID);
- fail("Querying for the raw contact profile without READ_PROFILE access should fail.");
- } catch (SecurityException expected) {
- } finally {
- if (c != null) {
- c.close();
- }
- }
+ expectSecurityException(
+ "Querying for the raw contact profile without READ_PROFILE access should fail.",
+ ContentUris.withAppendedId(RawContacts.CONTENT_URI,
+ profileRawContactId), null, null, null, RawContacts._ID);
}
public void testQueryProfileRawContactRequiresReadPermission() {
@@ -1751,44 +1731,25 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// Remove profile read permission and attempt to retrieve the profile's raw contact data.
mActor.removePermissions("android.permission.READ_PROFILE");
- Cursor c = null;
// Case 1: Retrieve the overall raw contact set for the profile.
- try {
- c = mResolver.query(Profile.CONTENT_RAW_CONTACTS_URI, null, null, null, null);
- fail("Querying for the raw contact profile without READ_PROFILE access should fail.");
- } catch (SecurityException expected) {
- } finally {
- if (c != null) {
- c.close();
- }
- }
+ expectSecurityException(
+ "Querying for the raw contact profile without READ_PROFILE access should fail.",
+ Profile.CONTENT_RAW_CONTACTS_URI, null, null, null, null);
// Case 2: Retrieve the raw contact profile data for the inserted raw contact ID.
- try {
- c = mResolver.query(ContentUris.withAppendedId(
- Profile.CONTENT_RAW_CONTACTS_URI, profileRawContactId).buildUpon()
- .appendPath("data").build(), null, null, null, null);
- fail("Querying for the raw profile data without READ_PROFILE access should fail.");
- } catch (SecurityException expected) {
- } finally {
- if (c != null) {
- c.close();
- }
- }
+ expectSecurityException(
+ "Querying for the raw profile data without READ_PROFILE access should fail.",
+ ContentUris.withAppendedId(
+ Profile.CONTENT_RAW_CONTACTS_URI, profileRawContactId).buildUpon()
+ .appendPath("data").build(), null, null, null, null);
// Case 3: Retrieve the raw contact profile entity for the inserted raw contact ID.
- try {
- c = mResolver.query(ContentUris.withAppendedId(
- Profile.CONTENT_RAW_CONTACTS_URI, profileRawContactId).buildUpon()
- .appendPath("entity").build(), null, null, null, null);
- fail("Querying for the raw profile entities without READ_PROFILE access should fail.");
- } catch (SecurityException expected) {
- } finally {
- if (c != null) {
- c.close();
- }
- }
+ expectSecurityException(
+ "Querying for the raw profile entities without READ_PROFILE access should fail.",
+ ContentUris.withAppendedId(
+ Profile.CONTENT_RAW_CONTACTS_URI, profileRawContactId).buildUpon()
+ .appendPath("entity").build(), null, null, null, null);
}
public void testQueryProfileDataByDataIdRequiresReadPermission() {
@@ -1802,16 +1763,10 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// Remove profile read permission and attempt to retrieve the data
mActor.removePermissions("android.permission.READ_PROFILE");
- try {
- c = mResolver.query(ContentUris.withAppendedId(Data.CONTENT_URI, profileDataId),
- null, null, null, null);
- fail("Querying for the data in the profile without READ_PROFILE access should fail.");
- } catch (SecurityException expected) {
- } finally {
- if (c != null) {
- c.close();
- }
- }
+ expectSecurityException(
+ "Querying for the data in the profile without READ_PROFILE access should fail.",
+ ContentUris.withAppendedId(Data.CONTENT_URI, profileDataId),
+ null, null, null, null);
}
public void testQueryProfileDataRequiresReadPermission() {
@@ -1819,17 +1774,10 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// Remove profile read permission and attempt to retrieve all profile data.
mActor.removePermissions("android.permission.READ_PROFILE");
- Cursor c = null;
- try {
- c = mResolver.query(Profile.CONTENT_URI.buildUpon().appendPath("data").build(),
- null, null, null, null);
- fail("Querying for the data in the profile without READ_PROFILE access should fail.");
- } catch (SecurityException expected) {
- } finally {
- if (c != null) {
- c.close();
- }
- }
+ expectSecurityException(
+ "Querying for the data in the profile without READ_PROFILE access should fail.",
+ Profile.CONTENT_URI.buildUpon().appendPath("data").build(),
+ null, null, null, null);
}
public void testInsertProfileRequiresWritePermission() {
@@ -3845,6 +3793,166 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
expectedValues);
}
+ public void testStreamItemReadRequiresReadSocialStreamPermission() {
+ long rawContactId = createRawContact();
+ long contactId = queryContactId(rawContactId);
+ String lookupKey = queryLookupKey(contactId);
+ long streamItemId = ContentUris.parseId(
+ insertStreamItem(rawContactId, buildGenericStreamItemValues(), null));
+ mActor.removePermissions("android.permission.READ_SOCIAL_STREAM");
+
+ // Try selecting the stream item in various ways.
+ expectSecurityException(
+ "Querying stream items by contact ID requires social stream read permission",
+ Uri.withAppendedPath(
+ ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
+ Contacts.StreamItems.CONTENT_DIRECTORY), null, null, null, null);
+
+ expectSecurityException(
+ "Querying stream items by lookup key requires social stream read permission",
+ Contacts.CONTENT_LOOKUP_URI.buildUpon().appendPath(lookupKey)
+ .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
+ null, null, null, null);
+
+ expectSecurityException(
+ "Querying stream items by lookup key and ID requires social stream read permission",
+ Uri.withAppendedPath(Contacts.getLookupUri(contactId, lookupKey),
+ Contacts.StreamItems.CONTENT_DIRECTORY),
+ null, null, null, null);
+
+ expectSecurityException(
+ "Querying stream items by raw contact ID requires social stream read permission",
+ Uri.withAppendedPath(
+ ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
+ RawContacts.StreamItems.CONTENT_DIRECTORY), null, null, null, null);
+
+ expectSecurityException(
+ "Querying stream items by raw contact ID and stream item ID requires social " +
+ "stream read permission",
+ ContentUris.withAppendedId(
+ Uri.withAppendedPath(
+ ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
+ RawContacts.StreamItems.CONTENT_DIRECTORY),
+ streamItemId), null, null, null, null);
+
+ expectSecurityException(
+ "Querying all stream items requires social stream read permission",
+ StreamItems.CONTENT_URI, null, null, null, null);
+
+ expectSecurityException(
+ "Querying stream item by ID requires social stream read permission",
+ ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId),
+ null, null, null, null);
+ }
+
+ public void testStreamItemPhotoReadRequiresReadSocialStreamPermission() {
+ long rawContactId = createRawContact();
+ long streamItemId = ContentUris.parseId(
+ insertStreamItem(rawContactId, buildGenericStreamItemValues(), null));
+ long streamItemPhotoId = ContentUris.parseId(
+ insertStreamItemPhoto(streamItemId, buildGenericStreamItemPhotoValues(0), null));
+ mActor.removePermissions("android.permission.READ_SOCIAL_STREAM");
+
+ // Try selecting the stream item photo in various ways.
+ expectSecurityException(
+ "Querying all stream item photos requires social stream read permission",
+ StreamItems.CONTENT_URI.buildUpon()
+ .appendPath(StreamItems.StreamItemPhotos.CONTENT_DIRECTORY).build(),
+ null, null, null, null);
+
+ expectSecurityException(
+ "Querying all stream item photos requires social stream read permission",
+ StreamItems.CONTENT_URI.buildUpon()
+ .appendPath(String.valueOf(streamItemId))
+ .appendPath(StreamItems.StreamItemPhotos.CONTENT_DIRECTORY)
+ .appendPath(String.valueOf(streamItemPhotoId)).build(),
+ null, null, null, null);
+ }
+
+ public void testStreamItemModificationRequiresWriteSocialStreamPermission() {
+ long rawContactId = createRawContact();
+ long streamItemId = ContentUris.parseId(
+ insertStreamItem(rawContactId, buildGenericStreamItemValues(), null));
+ mActor.removePermissions("android.permission.WRITE_SOCIAL_STREAM");
+
+ try {
+ insertStreamItem(rawContactId, buildGenericStreamItemValues(), null);
+ fail("Should not be able to insert to stream without write social stream permission");
+ } catch (SecurityException expected) {
+ }
+
+ try {
+ ContentValues values = new ContentValues();
+ values.put(StreamItems.TEXT, "Goodbye world");
+ mResolver.update(ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId),
+ values, null, null);
+ fail("Should not be able to update stream without write social stream permission");
+ } catch (SecurityException expected) {
+ }
+
+ try {
+ mResolver.delete(ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId),
+ null, null);
+ fail("Should not be able to delete from stream without write social stream permission");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ public void testStreamItemPhotoModificationRequiresWriteSocialStreamPermission() {
+ long rawContactId = createRawContact();
+ long streamItemId = ContentUris.parseId(
+ insertStreamItem(rawContactId, buildGenericStreamItemValues(), null));
+ long streamItemPhotoId = ContentUris.parseId(
+ insertStreamItemPhoto(streamItemId, buildGenericStreamItemPhotoValues(0), null));
+ mActor.removePermissions("android.permission.WRITE_SOCIAL_STREAM");
+
+ Uri photoUri = StreamItems.CONTENT_URI.buildUpon()
+ .appendPath(String.valueOf(streamItemId))
+ .appendPath(StreamItems.StreamItemPhotos.CONTENT_DIRECTORY)
+ .appendPath(String.valueOf(streamItemPhotoId)).build();
+
+ try {
+ insertStreamItemPhoto(streamItemId, buildGenericStreamItemPhotoValues(1), null);
+ fail("Should not be able to insert photos without write social stream permission");
+ } catch (SecurityException expected) {
+ }
+
+ try {
+ ContentValues values = new ContentValues();
+ values.put(StreamItemPhotos.PHOTO, loadPhotoFromResource(R.drawable.galaxy,
+ PhotoSize.ORIGINAL));
+ mResolver.update(photoUri, values, null, null);
+ fail("Should not be able to update photos without write social stream permission");
+ } catch (SecurityException expected) {
+ }
+
+ try {
+ mResolver.delete(photoUri, null, null);
+ fail("Should not be able to delete photos without write social stream permission");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ public void testStatusUpdateDoesNotRequireReadOrWriteSocialStreamPermission() {
+ int protocol1 = Im.PROTOCOL_GOOGLE_TALK;
+ String handle1 = "test@gmail.com";
+ long rawContactId = createRawContact();
+ insertImHandle(rawContactId, protocol1, null, handle1);
+ mActor.removePermissions("android.permission.READ_SOCIAL_STREAM");
+ mActor.removePermissions("android.permission.WRITE_SOCIAL_STREAM");
+
+ insertStatusUpdate(protocol1, null, handle1, StatusUpdates.AVAILABLE, "Green",
+ StatusUpdates.CAPABILITY_HAS_CAMERA);
+
+ mActor.addPermissions("android.permission.READ_SOCIAL_STREAM");
+
+ ContentValues expectedValues = new ContentValues();
+ expectedValues.put(StreamItems.TEXT, "Green");
+ assertStoredValues(Uri.withAppendedPath(
+ ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
+ RawContacts.StreamItems.CONTENT_DIRECTORY), expectedValues);
+ }
+
private ContentValues buildGenericStreamItemValues() {
ContentValues values = new ContentValues();
values.put(StreamItems.TEXT, "Hello world");