diff options
author | Brian Attwell <brianattwell@google.com> | 2015-02-18 18:14:08 -0800 |
---|---|---|
committer | Brian Attwell <brianattwell@google.com> | 2015-02-19 10:39:30 -0800 |
commit | 148374fdbe3d723c7485e149afcfed314b2814e0 (patch) | |
tree | d24ef0366febc8fc3448eb115ad80b72c1165127 | |
parent | 30165add8f1eef3bcf14dba37fe8e78d6e83bfd7 (diff) | |
download | packages_providers_ContactsProvider-148374fdbe3d723c7485e149afcfed314b2814e0.zip packages_providers_ContactsProvider-148374fdbe3d723c7485e149afcfed314b2814e0.tar.gz packages_providers_ContactsProvider-148374fdbe3d723c7485e149afcfed314b2814e0.tar.bz2 |
Persist pre-authorized URIs
When exposing the Authority API, reviewers requested the
API be implemented properly instead of storing the pre-authorized
values in memory.
Bug: 18777272
Change-Id: I31e719b10803344f579bb89c8269f0a597a83c3c
3 files changed, 162 insertions, 31 deletions
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java index ed907f8..e4688ee 100644 --- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java +++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java @@ -1,5 +1,5 @@ /* -T * Copyright (C) 2009 The Android Open Source Project + * Copyright (C) 2009 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. @@ -88,7 +88,6 @@ import com.android.providers.contacts.util.NeededForTesting; import com.google.android.collect.Sets; import com.google.common.annotations.VisibleForTesting; -import java.util.HashMap; import java.util.Locale; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -120,7 +119,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { * 1000-1100 M * </pre> */ - static final int DATABASE_VERSION = 1001; + static final int DATABASE_VERSION = 1002; public interface Tables { public static final String CONTACTS = "contacts"; @@ -150,6 +149,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { public static final String SEARCH_INDEX = "search_index"; public static final String VOICEMAIL_STATUS = "voicemail_status"; public static final String METADATA_SYNC = "metadata_sync"; + public static final String PRE_AUTHORIZED_URIS = "pre_authorized_uris"; // This list of tables contains auto-incremented sequences. public static final String[] SEQUENCE_TABLES = new String[] { @@ -683,6 +683,12 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { public static final String TOKENS = "tokens"; } + public interface PreAuthorizedUris { + public static final String _ID = BaseColumns._ID; + public static final String URI = "uri"; + public static final String EXPIRATION = "expiration"; + } + /** * Private table for calculating per-contact-method ranking. */ @@ -1585,6 +1591,11 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { MetadataSyncColumns.RAW_CONTACT_BACKUP_ID + ", " + MetadataSyncColumns.ACCOUNT_ID +");"); + db.execSQL("CREATE TABLE " + Tables.PRE_AUTHORIZED_URIS + " ("+ + PreAuthorizedUris._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + PreAuthorizedUris.URI + " STRING NOT NULL, " + + PreAuthorizedUris.EXPIRATION + " INTEGER NOT NULL DEFAULT 0);"); + // When adding new tables, be sure to also add size-estimates in updateSqliteStats createContactsViews(db); createGroupsView(db); @@ -2856,6 +2867,12 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { oldVersion = 1001; } + if (oldVersion < 1002) { + rebuildSqliteStats = true; + upgradeToVersion1002(db); + oldVersion = 1002; + } + if (upgradeViewsAndTriggers) { createContactsViews(db); createGroupsView(db); @@ -2961,12 +2978,12 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { // For each Contact, find the RawContact that contributed the display name db.execSQL( "UPDATE " + Tables.CONTACTS + - " SET " + Contacts.NAME_RAW_CONTACT_ID + "=(" + + " SET " + Contacts.NAME_RAW_CONTACT_ID + "=(" + " SELECT " + RawContacts._ID + " FROM " + Tables.RAW_CONTACTS + " WHERE " + RawContacts.CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + " AND " + RawContactsColumns.CONCRETE_DISPLAY_NAME + "=" + - Tables.CONTACTS + "." + Contacts.DISPLAY_NAME + + Tables.CONTACTS + "." + Contacts.DISPLAY_NAME + " ORDER BY " + RawContacts._ID + " LIMIT 1)" ); @@ -3077,11 +3094,11 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { SQLiteStatement structuredNameUpdate = db.compileStatement( "UPDATE " + Tables.DATA + - " SET " + + " SET " + StructuredName.FULL_NAME_STYLE + "=?," + StructuredName.DISPLAY_NAME + "=?," + StructuredName.PHONETIC_NAME_STYLE + "=?" + - " WHERE " + Data._ID + "=?"); + " WHERE " + Data._ID + "=?"); NameSplitter.Name name = new NameSplitter.Name(); Cursor cursor = db.query(StructName205Query.TABLE, @@ -3545,7 +3562,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { private void insertNicknameLookup(SQLiteDatabase db, SQLiteStatement nameLookupInsert) { final long mimeTypeId = lookupMimeTypeId(db, Nickname.CONTENT_ITEM_TYPE); Cursor cursor = db.query(NicknameQuery.TABLE, NicknameQuery.COLUMNS, - NicknameQuery.SELECTION, new String[] {String.valueOf(mimeTypeId)}, + NicknameQuery.SELECTION, new String[]{String.valueOf(mimeTypeId)}, null, null, null); try { while (cursor.moveToNext()) { @@ -4309,6 +4326,15 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { "raw_contact_backup_id, account_id);"); } + @VisibleForTesting + public void upgradeToVersion1002(SQLiteDatabase db) { + db.execSQL("DROP TABLE IF EXISTS pre_authorized_uris;"); + db.execSQL("CREATE TABLE pre_authorized_uris ("+ + "_id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "uri STRING NOT NULL, " + + "expiration INTEGER NOT NULL DEFAULT 0);"); + } + public String extractHandleFromEmailAddress(String email) { Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(email); if (tokens.length == 0) { @@ -4479,6 +4505,9 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { updateIndexStats(db, Tables.ACCOUNTS, null, "3"); + updateIndexStats(db, Tables.PRE_AUTHORIZED_URIS, + null, "1"); + updateIndexStats(db, Tables.VISIBLE_CONTACTS, null, "2000"); diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java index 06d198e..e853833 100644 --- a/src/com/android/providers/contacts/ContactsProvider2.java +++ b/src/com/android/providers/contacts/ContactsProvider2.java @@ -129,6 +129,7 @@ import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupColumns; import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupType; import com.android.providers.contacts.ContactsDatabaseHelper.PhoneLookupColumns; import com.android.providers.contacts.ContactsDatabaseHelper.PhotoFilesColumns; +import com.android.providers.contacts.ContactsDatabaseHelper.PreAuthorizedUris; import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns; import com.android.providers.contacts.ContactsDatabaseHelper.Projections; import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns; @@ -1345,9 +1346,6 @@ public class ContactsProvider2 extends AbstractContactsProvider private final ThreadLocal<TransactionContext> mTransactionContext = new ThreadLocal<TransactionContext>(); - // Map of single-use pre-authorized URIs to expiration times. - private final Map<Uri, Long> mPreAuthorizedUris = Maps.newHashMap(); - // Random number generator. private final SecureRandom mRandom = new SecureRandom(); @@ -2214,8 +2212,13 @@ public class ContactsProvider2 extends AbstractContactsProvider Uri authUri = uri.buildUpon() .appendQueryParameter(PREAUTHORIZED_URI_TOKEN, token) .build(); - long expiration = SystemClock.elapsedRealtime() + mPreAuthorizedUriDuration; - mPreAuthorizedUris.put(authUri, expiration); + long expiration = Clock.getInstance().currentTimeMillis() + mPreAuthorizedUriDuration; + + final SQLiteDatabase db = mDbHelper.get().getWritableDatabase(); + final ContentValues values = new ContentValues(); + values.put(PreAuthorizedUris.EXPIRATION, expiration); + values.put(PreAuthorizedUris.URI, authUri.toString()); + db.insert(Tables.PRE_AUTHORIZED_URIS, null, values); return authUri; } @@ -2229,22 +2232,27 @@ public class ContactsProvider2 extends AbstractContactsProvider public boolean isValidPreAuthorizedUri(Uri uri) { // Only proceed if the URI has a permission token parameter. if (uri.getQueryParameter(PREAUTHORIZED_URI_TOKEN) != null) { - // First expire any pre-authorization URIs that are no longer valid. - long now = SystemClock.elapsedRealtime(); - Set<Uri> expiredUris = Sets.newHashSet(); - for (Uri preAuthUri : mPreAuthorizedUris.keySet()) { - if (mPreAuthorizedUris.get(preAuthUri) < now) { - expiredUris.add(preAuthUri); - } - } - for (Uri expiredUri : expiredUris) { - mPreAuthorizedUris.remove(expiredUri); - } + final long now = Clock.getInstance().currentTimeMillis(); + final SQLiteDatabase db = mDbHelper.get().getWritableDatabase(); + db.beginTransaction(); + try { + // First delete any pre-authorization URIs that are no longer valid. Unfortunately, + // this operation will grab a write lock for readonly queries. Since this only + // affects readonly queries that use PREAUTHORIZED_URI_TOKEN, it isn't worth moving + // this deletion into a BACKGROUND_TASK. + db.delete(Tables.PRE_AUTHORIZED_URIS, PreAuthorizedUris.EXPIRATION + " < ?1", + new String[]{String.valueOf(now)}); + + // Now check to see if the pre-authorized URI map contains the URI. + final Cursor c = db.query(Tables.PRE_AUTHORIZED_URIS, null, + PreAuthorizedUris.URI + "=?1", + new String[]{uri.toString()}, null, null, null); + final boolean isValid = c.getCount() != 0; - // Now check to see if the pre-authorized URI map contains the URI. - if (mPreAuthorizedUris.containsKey(uri)) { - // Unexpired token - skip the permission check. - return true; + db.setTransactionSuccessful(); + return isValid; + } finally { + db.endTransaction(); } } return false; diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java index d8dc737..94997c6 100644 --- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java +++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java @@ -19,7 +19,6 @@ package com.android.providers.contacts; import static com.android.providers.contacts.TestUtils.cv; import android.accounts.Account; -import android.content.ContentProvider; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentResolver; @@ -27,14 +26,13 @@ import android.content.ContentUris; import android.content.ContentValues; import android.content.Entity; import android.content.EntityIterator; -import android.content.pm.UserInfo; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.MatrixCursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.os.AsyncTask; -import android.os.UserManager; +import android.os.Bundle; import android.provider.CallLog.Calls; import android.provider.CallLog; import android.provider.ContactsContract; @@ -8614,6 +8612,102 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { * End pinning support tests ******************************************************/ + public void testAuthorization_authorize() throws Exception { + // Setup + ContentValues values = new ContentValues(); + long id1 = createContact(values, "Noah", "Tever", "18004664411", + "email@email.com", StatusUpdates.OFFLINE, 0, 0, 0, 0); + Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, id1); + + // Execute: pre authorize the contact + Uri authorizedUri = getPreAuthorizedUri(contactUri); + + // Sanity check: URIs are different + assertNotSame(authorizedUri, contactUri); + + // Verify: the URI is pre authorized + final ContactsProvider2 cp = (ContactsProvider2) getProvider(); + assertTrue(cp.isValidPreAuthorizedUri(authorizedUri)); + } + + public void testAuthorization_unauthorized() throws Exception { + // Setup + ContentValues values = new ContentValues(); + long id1 = createContact(values, "Noah", "Tever", "18004664411", + "email@email.com", StatusUpdates.OFFLINE, 0, 0, 0, 0); + Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, id1); + + // Verify: the URI is *not* pre authorized + final ContactsProvider2 cp = (ContactsProvider2) getProvider(); + assertFalse(cp.isValidPreAuthorizedUri(contactUri)); + } + + public void testAuthorization_invalidAuthorization() throws Exception { + // Setup + ContentValues values = new ContentValues(); + long id1 = createContact(values, "Noah", "Tever", "18004664411", + "email@email.com", StatusUpdates.OFFLINE, 0, 0, 0, 0); + Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, id1); + + // Execute: pre authorize the contact and then modify the resulting URI slightly + Uri authorizedUri = getPreAuthorizedUri(contactUri); + Uri almostAuthorizedUri = Uri.parse(authorizedUri.toString() + "2"); + + // Verify: the URI is not pre authorized + final ContactsProvider2 cp = (ContactsProvider2) getProvider(); + assertFalse(cp.isValidPreAuthorizedUri(almostAuthorizedUri)); + } + + public void testAuthorization_expired() throws Exception { + // Setup + ContentValues values = new ContentValues(); + long id1 = createContact(values, "Noah", "Tever", "18004664411", + "email@email.com", StatusUpdates.OFFLINE, 0, 0, 0, 0); + Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, id1); + sMockClock.install(); + + // Execute: pre authorize the contact + Uri authorizedUri = getPreAuthorizedUri(contactUri); + sMockClock.setCurrentTimeMillis(sMockClock.currentTimeMillis() + 1000000); + + // Verify: the authorization for the URI expired + final ContactsProvider2 cp = (ContactsProvider2) getProvider(); + assertFalse(cp.isValidPreAuthorizedUri(authorizedUri)); + } + + public void testAuthorization_contactUpgrade() throws Exception { + ContactsDatabaseHelper helper = + ((ContactsDatabaseHelper) ((ContactsProvider2) getProvider()).getDatabaseHelper()); + SQLiteDatabase db = helper.getWritableDatabase(); + + // Perform the unit tests against an upgraded version of the database, instead of a freshly + // created version of the database. + helper.upgradeToVersion1002(db); + testAuthorization_authorize(); + helper.upgradeToVersion1002(db); + testAuthorization_expired(); + helper.upgradeToVersion1002(db); + testAuthorization_expired(); + helper.upgradeToVersion1002(db); + testAuthorization_invalidAuthorization(); + } + + private Uri getPreAuthorizedUri(Uri uri) { + final Bundle uriBundle = new Bundle(); + uriBundle.putParcelable(ContactsContract.Authorization.KEY_URI_TO_AUTHORIZE, uri); + final Bundle authResponse = mResolver.call( + ContactsContract.AUTHORITY_URI, + ContactsContract.Authorization.AUTHORIZATION_METHOD, + null, + uriBundle); + return (Uri) authResponse.getParcelable( + ContactsContract.Authorization.KEY_AUTHORIZED_URI); + } + + /** + * End Authorization Tests + ******************************************************/ + private Cursor queryGroupMemberships(Account account) { Cursor c = mResolver.query(TestUtil.maybeAddAccountQueryParameters(Data.CONTENT_URI, account), |