diff options
author | Jeff Sharkey <jsharkey@android.com> | 2009-10-12 20:38:46 -0700 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2009-10-12 20:38:59 -0700 |
commit | 1d9c0e17216ff6df5f73fbc5e784b5965c5026bd (patch) | |
tree | b385ec32f235cfb2966a4dac510b879771335a1e | |
parent | fda634f3eeff6aed8e8dddca92fc07aa44befedd (diff) | |
download | ContactsProvider-1d9c0e17216ff6df5f73fbc5e784b5965c5026bd.tar.gz |
Unit tests to verify IS_RESTRICTED security mechanisms.
Partially fixes http://b/2148997
-rw-r--r-- | tests/src/com/android/providers/contacts/ContactsActor.java | 119 | ||||
-rw-r--r-- | tests/src/com/android/providers/contacts/RestrictionExceptionsTest.java | 466 |
2 files changed, 339 insertions, 246 deletions
diff --git a/tests/src/com/android/providers/contacts/ContactsActor.java b/tests/src/com/android/providers/contacts/ContactsActor.java index d84f0e10..9b4f4bed 100644 --- a/tests/src/com/android/providers/contacts/ContactsActor.java +++ b/tests/src/com/android/providers/contacts/ContactsActor.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.content.res.Resources.NotFoundException; import android.database.Cursor; import android.net.Uri; import android.os.Binder; @@ -33,12 +34,17 @@ import android.provider.ContactsContract.CommonDataKinds; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.RawContacts; +import android.provider.ContactsContract.StatusUpdates; +import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.test.IsolatedContext; import android.test.RenamingDelegatingContext; import android.test.mock.MockContentResolver; import android.test.mock.MockContext; import android.test.mock.MockPackageManager; +import android.test.mock.MockResources; +import android.util.Log; +import android.util.TypedValue; import java.util.HashMap; @@ -108,6 +114,7 @@ public class ContactsActor { private final String mReportedPackageName; private final RestrictionMockPackageManager mPackageManager; private final ContentResolver mResolver; + private final Resources mRes; /** * Create a {@link Context} under the given package name. @@ -117,11 +124,14 @@ public class ContactsActor { mOverallContext = overallContext; mReportedPackageName = reportedPackageName; mResolver = resolver; + mPackageManager = new RestrictionMockPackageManager(); mPackageManager.addPackage(1000, PACKAGE_GREY); mPackageManager.addPackage(2000, PACKAGE_RED); mPackageManager.addPackage(3000, PACKAGE_GREEN); mPackageManager.addPackage(4000, PACKAGE_BLUE); + + mRes = new RestrictionMockResources(overallContext.getResources()); } @Override @@ -136,7 +146,7 @@ public class ContactsActor { @Override public Resources getResources() { - return mOverallContext.getResources(); + return mRes; } @Override @@ -145,6 +155,56 @@ public class ContactsActor { } } + private static class RestrictionMockResources extends MockResources { + private static final String UNRESTRICTED = "unrestricted_packages"; + private static final int UNRESTRICTED_ID = 1024; + + private static final String[] UNRESTRICTED_LIST = new String[] { + PACKAGE_GREY + }; + + private final Resources mRes; + + public RestrictionMockResources(Resources res) { + mRes = res; + } + + @Override + public int getIdentifier(String name, String defType, String defPackage) { + if (UNRESTRICTED.equals(name)) { + return UNRESTRICTED_ID; + } else { + return mRes.getIdentifier(name, defType, defPackage); + } + } + + @Override + public String[] getStringArray(int id) throws NotFoundException { + if (id == UNRESTRICTED_ID) { + return UNRESTRICTED_LIST; + } else { + return mRes.getStringArray(id); + } + } + + @Override + public void getValue(int id, TypedValue outValue, boolean resolveRefs) + throws NotFoundException { + mRes.getValue(id, outValue, resolveRefs); + } + + @Override + public String getString(int id) throws NotFoundException { + return mRes.getString(id); + } + } + + private static String sCallingPackage = null; + + void ensureCallingPackage() { + sCallingPackage = this.packageName; + } + /** * Mock {@link PackageManager} that knows about a specific set of packages * to help test security models. Because {@link Binder#getCallingUid()} @@ -155,6 +215,9 @@ public class ContactsActor { private final HashMap<Integer, String> mForward = new HashMap<Integer, String>(); private final HashMap<String, Integer> mReverse = new HashMap<String, Integer>(); + public RestrictionMockPackageManager() { + } + /** * Add a UID-to-package mapping, which is then stored internally. */ @@ -165,7 +228,7 @@ public class ContactsActor { @Override public String[] getPackagesForUid(int uid) { - return new String[] { mForward.get(uid) }; + return new String[] { sCallingPackage }; } @Override @@ -178,12 +241,14 @@ public class ContactsActor { } public long createContact(boolean isRestricted, String name) { + ensureCallingPackage(); long contactId = createContact(isRestricted); createName(contactId, name); return contactId; } public long createContact(boolean isRestricted) { + ensureCallingPackage(); final ContentValues values = new ContentValues(); if (isRestricted) { values.put(RawContacts.IS_RESTRICTED, 1); @@ -193,7 +258,16 @@ public class ContactsActor { return ContentUris.parseId(contactUri); } + public long createContactWithStatus(boolean isRestricted, String name, String address, + String status) { + final long rawContactId = createContact(isRestricted, name); + final long dataId = createEmail(rawContactId, address); + createStatus(dataId, status); + return rawContactId; + } + public long createName(long contactId, String name) { + ensureCallingPackage(); final ContentValues values = new ContentValues(); values.put(Data.RAW_CONTACT_ID, contactId); values.put(Data.IS_PRIMARY, 1); @@ -207,6 +281,7 @@ public class ContactsActor { } public long createPhone(long contactId, String phoneNumber) { + ensureCallingPackage(); final ContentValues values = new ContentValues(); values.put(Data.RAW_CONTACT_ID, contactId); values.put(Data.IS_PRIMARY, 1); @@ -221,11 +296,36 @@ public class ContactsActor { return ContentUris.parseId(dataUri); } + public long createEmail(long contactId, String address) { + ensureCallingPackage(); + final ContentValues values = new ContentValues(); + values.put(Data.RAW_CONTACT_ID, contactId); + values.put(Data.IS_PRIMARY, 1); + values.put(Data.IS_SUPER_PRIMARY, 1); + values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); + values.put(Email.TYPE, Email.TYPE_HOME); + values.put(Email.DATA, address); + Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, + contactId), RawContacts.Data.CONTENT_DIRECTORY); + Uri dataUri = resolver.insert(insertUri, values); + return ContentUris.parseId(dataUri); + } + + public long createStatus(long dataId, String status) { + ensureCallingPackage(); + final ContentValues values = new ContentValues(); + values.put(StatusUpdates.DATA_ID, dataId); + values.put(StatusUpdates.STATUS, status); + Uri dataUri = resolver.insert(StatusUpdates.CONTENT_URI, values); + return ContentUris.parseId(dataUri); + } + public void updateException(String packageProvider, String packageClient, boolean allowAccess) { throw new UnsupportedOperationException("RestrictionExceptions are hard-coded"); } public long getContactForRawContact(long rawContactId) { + ensureCallingPackage(); Uri contactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); final Cursor cursor = resolver.query(contactUri, Projections.PROJ_RAW_CONTACTS, null, null, null); @@ -239,6 +339,7 @@ public class ContactsActor { } public int getDataCountForContact(long contactId) { + ensureCallingPackage(); Uri contactUri = Uri.withAppendedPath(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.Data.CONTENT_DIRECTORY); final Cursor cursor = resolver.query(contactUri, Projections.PROJ_ID, null, null, @@ -248,7 +349,19 @@ public class ContactsActor { return count; } + public int getDataCountForRawContact(long rawContactId) { + ensureCallingPackage(); + Uri contactUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, + rawContactId), Contacts.Data.CONTENT_DIRECTORY); + final Cursor cursor = resolver.query(contactUri, Projections.PROJ_ID, null, null, + null); + final int count = cursor.getCount(); + cursor.close(); + return count; + } + public void setSuperPrimaryPhone(long dataId) { + ensureCallingPackage(); final ContentValues values = new ContentValues(); values.put(Data.IS_PRIMARY, 1); values.put(Data.IS_SUPER_PRIMARY, 1); @@ -257,6 +370,7 @@ public class ContactsActor { } public long createGroup(String groupName) { + ensureCallingPackage(); final ContentValues values = new ContentValues(); values.put(ContactsContract.Groups.RES_PACKAGE, packageName); values.put(ContactsContract.Groups.TITLE, groupName); @@ -265,6 +379,7 @@ public class ContactsActor { } public long createGroupMembership(long rawContactId, long groupId) { + ensureCallingPackage(); final ContentValues values = new ContentValues(); values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(Data.MIMETYPE, CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE); diff --git a/tests/src/com/android/providers/contacts/RestrictionExceptionsTest.java b/tests/src/com/android/providers/contacts/RestrictionExceptionsTest.java index c1e897f7..7928d224 100644 --- a/tests/src/com/android/providers/contacts/RestrictionExceptionsTest.java +++ b/tests/src/com/android/providers/contacts/RestrictionExceptionsTest.java @@ -16,46 +16,43 @@ package com.android.providers.contacts; -import static com.android.providers.contacts.ContactsActor.PACKAGE_BLUE; -import static com.android.providers.contacts.ContactsActor.PACKAGE_GREEN; import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY; import static com.android.providers.contacts.ContactsActor.PACKAGE_RED; import android.content.ContentUris; +import android.content.ContentValues; import android.content.Context; +import android.content.Entity; +import android.content.EntityIterator; +import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.net.Uri; -import android.provider.BaseColumns; import android.provider.ContactsContract; +import android.provider.LiveFolders; import android.provider.ContactsContract.Contacts; -import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.Data; +import android.provider.ContactsContract.RawContacts; +import android.provider.ContactsContract.CommonDataKinds.Email; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; +import java.io.InputStream; + /** - * Unit tests for {@link RestrictionExceptions}. - * - * Run the test like this: - * <code> - * adb shell am instrument -e class com.android.providers.contacts.RestrictionExceptionsTest -w \ - * com.android.providers.contacts.tests/android.test.InstrumentationTestRunner - * </code> + * Unit tests for {@link RawContacts#IS_RESTRICTED}. */ @LargeTest public class RestrictionExceptionsTest extends AndroidTestCase { - private static final String TAG = "RestrictionExceptionsTest"; - - private static ContactsActor mGrey; - private static ContactsActor mRed; - private static ContactsActor mGreen; - private static ContactsActor mBlue; + private ContactsActor mGrey; + private ContactsActor mRed; private static final String PHONE_GREY = "555-1111"; private static final String PHONE_RED = "555-2222"; - private static final String PHONE_GREEN = "555-3333"; - private static final String PHONE_BLUE = "555-4444"; + private static final String EMAIL_GREY = "user@example.com"; + private static final String EMAIL_RED = "user@example.org"; + + private static final String GENERIC_STATUS = "Status update"; private static final String GENERIC_NAME = "Smith"; public RestrictionExceptionsTest() { @@ -72,275 +69,256 @@ public class RestrictionExceptionsTest extends AndroidTestCase { SynchronousContactsProvider2.class, ContactsContract.AUTHORITY); mRed = new ContactsActor(overallContext, PACKAGE_RED, SynchronousContactsProvider2.class, ContactsContract.AUTHORITY); - mGreen = new ContactsActor(overallContext, PACKAGE_GREEN, - SynchronousContactsProvider2.class, ContactsContract.AUTHORITY); - mBlue = new ContactsActor(overallContext, PACKAGE_BLUE, - SynchronousContactsProvider2.class, ContactsContract.AUTHORITY); // TODO make the provider wipe data automatically ((SynchronousContactsProvider2)mGrey.provider).wipeData(); } /** - * Create various contacts that are both open and restricted, and assert - * that both {@link Contacts#IS_RESTRICTED} and - * {@link RestrictionExceptions} are being applied correctly. + * Assert that {@link Contacts#CONTACT_STATUS} matches the given value, or + * that no rows are returned when null. */ - public void __testDataRestriction() { - - // Grey creates an unprotected contact - long greyContact = mGrey.createContact(false); - long greyData = mGrey.createPhone(greyContact, PHONE_GREY); - long greyAgg = mGrey.getContactForRawContact(greyContact); - - // Assert that both Grey and Blue can read contact - assertTrue("Owner of unrestricted contact unable to read", - (mGrey.getDataCountForContact(greyAgg) == 1)); - assertTrue("Non-owner of unrestricted contact unable to read", - (mBlue.getDataCountForContact(greyAgg) == 1)); - - // Red grants protected access to itself - mRed.updateException(PACKAGE_RED, PACKAGE_RED, true); - - // Red creates a protected contact - long redContact = mRed.createContact(true); - long redData = mRed.createPhone(redContact, PHONE_RED); - long redAgg = mRed.getContactForRawContact(redContact); - - // Assert that only Red can read contact - assertTrue("Owner of restricted contact unable to read", - (mRed.getDataCountForContact(redAgg) == 1)); - assertTrue("Non-owner of restricted contact able to read", - (mBlue.getDataCountForContact(redAgg) == 0)); - assertTrue("Non-owner of restricted contact able to read", - (mGreen.getDataCountForContact(redAgg) == 0)); + void assertStatus(ContactsActor actor, long aggId, String status) { + final Uri aggUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, aggId); + actor.ensureCallingPackage(); + final Cursor cursor = actor.resolver.query(aggUri, + new String[] { Contacts.CONTACT_STATUS }, null, null, null); try { - // Blue tries to grant an exception for Red data, which should throw - // exception. If it somehow worked, fail this test. - mBlue.updateException(PACKAGE_RED, PACKAGE_BLUE, true); - fail("Non-owner able to grant restriction exception"); - - } catch (RuntimeException e) { + if (status == null) { + assertEquals(0, cursor.getCount()); + } else { + while (cursor.moveToNext()) { + final String foundStatus = cursor.getString(0); + assertEquals(status, foundStatus); + } + } + } finally { + cursor.close(); } + } - // Red grants exception to Blue for contact - mRed.updateException(PACKAGE_RED, PACKAGE_BLUE, true); - - // Both Blue and Red can read Red contact, but still not Green - assertTrue("Owner of restricted contact unable to read", - (mRed.getDataCountForContact(redAgg) == 1)); - assertTrue("Non-owner with restriction exception unable to read", - (mBlue.getDataCountForContact(redAgg) == 1)); - assertTrue("Non-owner of restricted contact able to read", - (mGreen.getDataCountForContact(redAgg) == 0)); - - // Red revokes exception to Blue - mRed.updateException(PACKAGE_RED, PACKAGE_BLUE, false); + public void testRestrictedInsertRestrictedQuery() { + // Restricted query can read restricted data + final long rawContact = mGrey.createContact(true, GENERIC_NAME); + final int count = mGrey.getDataCountForRawContact(rawContact); + assertEquals(1, count); + } - // Assert that only Red can read contact - assertTrue("Owner of restricted contact unable to read", - (mRed.getDataCountForContact(redAgg) == 1)); - assertTrue("Non-owner of restricted contact able to read", - (mBlue.getDataCountForContact(redAgg) == 0)); - assertTrue("Non-owner of restricted contact able to read", - (mGreen.getDataCountForContact(redAgg) == 0)); + public void testRestrictedInsertGenericQuery() { + // Generic query is denied restricted data + final long rawContact = mGrey.createContact(true, GENERIC_NAME); + final int count = mRed.getDataCountForRawContact(rawContact); + assertEquals(0, count); + } + public void testGenericInsertRestrictedQuery() { + // Restricted query can read generic data + final long rawContact = mRed.createContact(false, GENERIC_NAME); + final int count = mGrey.getDataCountForRawContact(rawContact); + assertEquals(1, count); } - /** - * Create an aggregate that has multiple contacts with various levels of - * protected data, and ensure that {@link Contacts#CONTENT_URI} - * details don't expose {@link Contacts#IS_RESTRICTED} data. - */ - public void __testAggregateSummary() { + public void testGenericInsertGenericQuery() { + // Generic query can read generic data + final long rawContact = mRed.createContact(false, GENERIC_NAME); + final int count = mRed.getDataCountForRawContact(rawContact); + assertEquals(1, count); + } - // Red grants exceptions to itself and Grey - mRed.updateException(PACKAGE_RED, PACKAGE_RED, true); - mRed.updateException(PACKAGE_RED, PACKAGE_GREY, true); + public void testMixedAggregateRestrictedQuery() { + // Create mixed aggregate with a restricted phone number + final long greyContact = mGrey.createContact(true, GENERIC_NAME); + final long greyPhone = mGrey.createPhone(greyContact, PHONE_GREY); + final long redContact = mRed.createContact(false, GENERIC_NAME); + final long redPhone = mRed.createPhone(redContact, PHONE_RED); - // Red creates a protected contact - long redContact = mRed.createContact(true); - long redName = mRed.createName(redContact, GENERIC_NAME); - long redPhone = mRed.createPhone(redContact, PHONE_RED); + // Make sure both aggregates were joined + final long greyAgg = mGrey.getContactForRawContact(greyContact); + final long redAgg = mRed.getContactForRawContact(redContact); + assertEquals(greyAgg, redAgg); - // Blue grants exceptions to itself and Grey - mBlue.updateException(PACKAGE_BLUE, PACKAGE_BLUE, true); - mBlue.updateException(PACKAGE_BLUE, PACKAGE_GREY, true); + // Restricted reader should have access to both numbers + final int greyCount = mGrey.getDataCountForContact(greyAgg); + assertEquals(4, greyCount); - // Blue creates a protected contact - long blueContact = mBlue.createContact(true); - long blueName = mBlue.createName(blueContact, GENERIC_NAME); - long bluePhone = mBlue.createPhone(blueContact, PHONE_BLUE); + // Generic reader should have limited access + final int redCount = mRed.getDataCountForContact(redAgg); + assertEquals(2, redCount); + } - // Set the super-primary phone number to Red - mRed.setSuperPrimaryPhone(redPhone); + public void testUpdateRestricted() { + // Assert that we can't un-restrict something + final long greyContact = mGrey.createContact(true, GENERIC_NAME); + final long greyPhone = mGrey.createPhone(greyContact, PHONE_GREY); - // Make sure both aggregates were joined - long singleAgg; - { - long redAgg = mRed.getContactForRawContact(redContact); - long blueAgg = mBlue.getContactForRawContact(blueContact); - assertTrue("Two contacts with identical name not aggregated correctly", - (redAgg == blueAgg)); - singleAgg = redAgg; - } + int count = mRed.getDataCountForRawContact(greyContact); + assertEquals(0, count); - /* - // Grey and Red querying summary should see Red phone. Blue shouldn't - // see any summary data, since it's own data is protected and it's not - // the super-primary. Green shouldn't know this aggregate exists. - assertTrue("Participant with restriction exception reading incorrect summary", - (mGrey.getPrimaryPhoneId(singleAgg) == redPhone)); - assertTrue("Participant with super-primary restricted data reading incorrect summary", - (mRed.getPrimaryPhoneId(singleAgg) == redPhone)); - assertTrue("Participant with non-super-primary restricted data reading incorrect summary", - (mBlue.getPrimaryPhoneId(singleAgg) == 0)); - assertTrue("Non-participant able to discover aggregate existance", - (mGreen.getPrimaryPhoneId(singleAgg) == 0)); - - // Add an unprotected Grey contact into the mix - long greyContact = mGrey.createContact(false); - long greyName = mGrey.createName(greyContact, GENERIC_NAME); - long greyPhone = mGrey.createPhone(greyContact, PHONE_GREY); - - // Set the super-primary phone number to Blue - mBlue.setSuperPrimaryPhone(bluePhone); - - // Make sure all three aggregates were joined - { - long redAgg = mRed.getContactForRawContact(redContact); - long blueAgg = mBlue.getContactForRawContact(blueContact); - long greyAgg = mGrey.getContactForRawContact(greyContact); - assertTrue("Three contacts with identical name not aggregated correctly", - (redAgg == blueAgg) && (blueAgg == greyAgg)); - singleAgg = redAgg; - } + // Try un-restricting that contact + final Uri greyUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, greyContact); + final ContentValues values = new ContentValues(); + values.put(RawContacts.IS_RESTRICTED, 0); + mRed.ensureCallingPackage(); + mRed.provider.update(greyUri, values, null, null); - // Grey and Blue querying summary should see Blue phone. Red should see - // the Grey phone in its summary, since it's the unprotected fallback. - // Red doesn't see its own phone number because it's not super-primary, - // and is protected. Again, green shouldn't know this exists. - assertTrue("Participant with restriction exception reading incorrect summary", - (mGrey.getPrimaryPhoneId(singleAgg) == bluePhone)); - assertTrue("Participant with non-super-primary restricted data reading incorrect summary", - (mRed.getPrimaryPhoneId(singleAgg) == greyPhone)); - assertTrue("Participant with super-primary restricted data reading incorrect summary", - (mBlue.getPrimaryPhoneId(singleAgg) == bluePhone)); - assertTrue("Non-participant couldn't find unrestricted primary through summary", - (mGreen.getPrimaryPhoneId(singleAgg) == greyPhone)); - */ + count = mRed.getDataCountForRawContact(greyContact); + assertEquals(0, count); } - /** - * Create a contact that is completely restricted and isolated in its own - * aggregate, and make sure that another actor can't detect its existence. - */ - public void __testRestrictionSilence() { - Cursor cursor; - - // Green grants exception to itself - mGreen.updateException(PACKAGE_GREEN, PACKAGE_GREEN, true); - - // Green creates a protected contact - long greenContact = mGreen.createContact(true); - long greenData = mGreen.createPhone(greenContact, PHONE_GREEN); - long greenAgg = mGreen.getContactForRawContact(greenContact); - - // AGGREGATES - cursor = mRed.resolver - .query(Contacts.CONTENT_URI, Projections.PROJ_ID, null, null, null); - while (cursor.moveToNext()) { - assertTrue("Discovered restricted contact", - (cursor.getLong(Projections.COL_ID) != greenAgg)); - } - cursor.close(); - - // AGGREGATES_ID - cursor = mRed.resolver.query(ContentUris.withAppendedId(Contacts.CONTENT_URI, greenAgg), - Projections.PROJ_ID, null, null, null); - assertTrue("Discovered restricted contact", (cursor.getCount() == 0)); - cursor.close(); + public void testExportVCard() throws Exception { + // Create mixed aggregate with a restricted phone number + final long greyContact = mGrey.createContact(true, GENERIC_NAME); + final long greyPhone = mGrey.createPhone(greyContact, PHONE_GREY); + final long redContact = mRed.createContact(false, GENERIC_NAME); + final long redPhone = mRed.createPhone(redContact, PHONE_RED); - // AGGREGATES_DATA - cursor = mRed.resolver.query(Uri.withAppendedPath(ContentUris.withAppendedId( - Contacts.CONTENT_URI, greenAgg), Contacts.Data.CONTENT_DIRECTORY), - Projections.PROJ_ID, null, null, null); - assertTrue("Discovered restricted contact", (cursor.getCount() == 0)); + // Make sure both aggregates were joined + final long greyAgg = mGrey.getContactForRawContact(greyContact); + final long redAgg = mRed.getContactForRawContact(redContact); + assertEquals(greyAgg, redAgg); + + // Exported vCard shouldn't contain restricted phone + mRed.ensureCallingPackage(); + final Uri aggUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, greyAgg); + final Cursor cursor = mRed.resolver.query(aggUri, + new String[] { Contacts.LOOKUP_KEY }, null, null, null); + assertTrue(cursor.moveToFirst()); + final String lookupKey = cursor.getString(0); cursor.close(); - // AGGREGATES_SUMMARY - cursor = mRed.resolver.query(Contacts.CONTENT_URI, Projections.PROJ_ID, null, - null, null); - while (cursor.moveToNext()) { - assertTrue("Discovered restricted contact", - (cursor.getLong(Projections.COL_ID) != greenAgg)); - } - cursor.close(); + // Read vCard into buffer + final Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey); + final AssetFileDescriptor file = mRed.resolver.openAssetFileDescriptor(shareUri, "r"); + final InputStream in = file.createInputStream(); + final byte[] buf = new byte[in.available()]; + in.read(buf); + in.close(); + final String card = new String(buf); + + // Make sure that only unrestricted phones appear + assertTrue(card.indexOf(PHONE_RED) != -1); + assertTrue(card.indexOf(PHONE_GREY) == -1); + } - // AGGREGATES_SUMMARY_ID - cursor = mRed.resolver.query(ContentUris.withAppendedId(Contacts.CONTENT_URI, - greenAgg), Projections.PROJ_ID, null, null, null); - assertTrue("Discovered restricted contact", (cursor.getCount() == 0)); - cursor.close(); + public void testContactsLiveFolder() { + final long greyContact = mGrey.createContact(true, GENERIC_NAME); + final long greyPhone = mGrey.createPhone(greyContact, PHONE_GREY); - // TODO: AGGREGATES_SUMMARY_FILTER - // TODO: ========================= + // Protected contact should be omitted from live folder + mRed.ensureCallingPackage(); + final Uri folderUri = Uri.withAppendedPath(ContactsContract.AUTHORITY_URI, + "live_folders/contacts_with_phones"); + final Cursor cursor = mRed.resolver.query(folderUri, + new String[] { LiveFolders._ID }, null, null, null); + try { + while (cursor.moveToNext()) { + final long id = cursor.getLong(0); + assertFalse(id == greyContact); + } + } finally { + cursor.close(); + } + } - // TODO: AGGREGATION_SUGGESTIONS - // TODO: ======================= + public void testRestrictedQueryParam() throws Exception { + final long greyContact = mGrey.createContact(true, GENERIC_NAME); + final long greyPhone = mGrey.createPhone(greyContact, PHONE_GREY); + + final Uri greyUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, greyContact); + final Uri redUri = greyUri.buildUpon().appendQueryParameter( + ContactsContract.REQUESTING_PACKAGE_PARAM_KEY, mRed.packageName).build(); + + // When calling normally, we have access to protected + mGrey.ensureCallingPackage(); + EntityIterator iterator = mGrey.resolver.queryEntities(greyUri, null, null, null); + while (iterator.hasNext()) { + final Entity entity = iterator.next(); + final long rawContactId = entity.getEntityValues().getAsLong(RawContacts._ID); + assertTrue(rawContactId == greyContact); + } - // CONTACTS - cursor = mRed.resolver.query(RawContacts.CONTENT_URI, Projections.PROJ_ID, - null, null, null); - while (cursor.moveToNext()) { - assertTrue("Discovered restricted contact", - (cursor.getLong(Projections.COL_ID) != greenContact)); + // When calling on behalf of another package, protected is omitted + mGrey.ensureCallingPackage(); + iterator = mGrey.resolver.queryEntities(redUri, null, null, null); + while (iterator.hasNext()) { + final Entity entity = iterator.next(); + final long rawContactId = entity.getEntityValues().getAsLong(RawContacts._ID); + assertTrue(rawContactId != greyContact); } - cursor.close(); + } - // CONTACTS_ID - cursor = mRed.resolver.query(ContentUris - .withAppendedId(RawContacts.CONTENT_URI, greenContact), Projections.PROJ_ID, null, - null, null); - assertTrue("Discovered restricted contact", (cursor.getCount() == 0)); - cursor.close(); + public void testRestrictedEmailLookupRestricted() { + final long greyContact = mGrey.createContact(true, GENERIC_NAME); + final long greyEmail = mGrey.createEmail(greyContact, EMAIL_GREY); - // CONTACTS_DATA - cursor = mRed.resolver.query(Uri.withAppendedPath(ContentUris.withAppendedId( - RawContacts.CONTENT_URI, greenContact), RawContacts.Data.CONTENT_DIRECTORY), - Projections.PROJ_ID, null, null, null); - assertTrue("Discovered restricted contact", (cursor.getCount() == 0)); - cursor.close(); + // Restricted caller should see protected data + mGrey.ensureCallingPackage(); + final Uri lookupUri = Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, EMAIL_GREY); + final Cursor cursor = mGrey.resolver.query(lookupUri, + new String[] { Data._ID }, null, null, null); + try { + while (cursor.moveToNext()) { + final long dataId = cursor.getLong(0); + assertTrue(dataId == greyEmail); + } + } finally { + cursor.close(); + } + } - // TODO: CONTACTS_FILTER_EMAIL - // TODO: ===================== + public void testRestrictedEmailLookupGeneric() { + final long greyContact = mGrey.createContact(true, GENERIC_NAME); + final long greyEmail = mGrey.createEmail(greyContact, EMAIL_GREY); - // DATA - cursor = mRed.resolver.query(Data.CONTENT_URI, Projections.PROJ_ID, null, null, null); - while (cursor.moveToNext()) { - assertTrue("Discovered restricted contact", - (cursor.getLong(Projections.COL_ID) != greenData)); + // Generic caller should never see protected data + mRed.ensureCallingPackage(); + final Uri lookupUri = Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, EMAIL_GREY); + final Cursor cursor = mRed.resolver.query(lookupUri, + new String[] { Data._ID }, null, null, null); + try { + while (cursor.moveToNext()) { + final long dataId = cursor.getLong(0); + assertFalse(dataId == greyEmail); + } + } finally { + cursor.close(); } - cursor.close(); + } - // DATA_ID - cursor = mRed.resolver.query(ContentUris.withAppendedId(Data.CONTENT_URI, greenData), - Projections.PROJ_ID, null, null, null); - assertTrue("Discovered restricted contact", (cursor.getCount() == 0)); - cursor.close(); + public void testStatusRestrictedInsertRestrictedQuery() { + final long rawContactId = mGrey.createContactWithStatus(true, + GENERIC_NAME, EMAIL_GREY, GENERIC_STATUS); + final long aggId = mGrey.getContactForRawContact(rawContactId); + + // Restricted query can read restricted status + assertStatus(mGrey, aggId, GENERIC_STATUS); + } - // TODO: PHONE_LOOKUP - // TODO: ============ + public void testStatusRestrictedInsertGenericQuery() { + final long rawContactId = mGrey.createContactWithStatus(true, + GENERIC_NAME, EMAIL_GREY, GENERIC_STATUS); + final long aggId = mGrey.getContactForRawContact(rawContactId); + // Generic query is denied restricted status + assertStatus(mRed, aggId, null); } - private interface Projections { - static final String[] PROJ_ID = new String[] { - BaseColumns._ID, - }; + public void testStatusGenericInsertRestrictedQuery() { + final long rawContactId = mRed.createContactWithStatus(false, + GENERIC_NAME, EMAIL_RED, GENERIC_STATUS); + final long aggId = mRed.getContactForRawContact(rawContactId); - static final int COL_ID = 0; + // Restricted query can read generic status + assertStatus(mGrey, aggId, GENERIC_STATUS); } + public void testStatusGenericInsertGenericQuery() { + final long rawContactId = mRed.createContactWithStatus(false, + GENERIC_NAME, EMAIL_RED, GENERIC_STATUS); + final long aggId = mRed.getContactForRawContact(rawContactId); + + // Generic query can read generic status + assertStatus(mRed, aggId, GENERIC_STATUS); + } } |