diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyRepository.java index 4fb17f818..b5e1cc71d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyRepository.java @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.daos; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import android.content.ContentResolver; @@ -27,6 +28,7 @@ import android.content.Context; import android.database.Cursor; import android.support.annotation.WorkerThread; +import com.squareup.sqldelight.RowMapper; import com.squareup.sqldelight.SqlDelightQuery; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.sufficientlysecure.keychain.KeychainDatabase; @@ -129,13 +131,27 @@ public class KeyRepository extends AbstractDao { public List getAllMasterKeyIds() { SqlDelightQuery query = KeyRingPublic.FACTORY.selectAllMasterKeyIds(); - return mapAllRows(query, KeyRingPublic.FACTORY.selectAllMasterKeyIdsMapper()); + ArrayList result = new ArrayList<>(); + try (Cursor cursor = getReadableDb().query(query)) { + while (cursor.moveToNext()) { + Long item = KeyRingPublic.FACTORY.selectAllMasterKeyIdsMapper().map(cursor); + result.add(item); + } + } + return result; } public List getMasterKeyIdsBySigner(List signerMasterKeyIds) { long[] signerKeyIds = getLongListAsArray(signerMasterKeyIds); SqlDelightQuery query = KeySignature.FACTORY.selectMasterKeyIdsBySigner(signerKeyIds); - return mapAllRows(query, KeySignature.FACTORY.selectMasterKeyIdsBySignerMapper()); + ArrayList result = new ArrayList<>(); + try (Cursor cursor = getReadableDb().query(query)) { + while (cursor.moveToNext()) { + Long item = KeySignature.FACTORY.selectMasterKeyIdsBySignerMapper().map(cursor); + result.add(item); + } + } + return result; } public Long getMasterKeyIdBySubkeyId(long subKeyId) { @@ -150,38 +166,88 @@ public class KeyRepository extends AbstractDao { public List getUnifiedKeyInfo(long... masterKeyIds) { SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoByMasterKeyIds(masterKeyIds); - return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER); + ArrayList result = new ArrayList<>(); + try (Cursor cursor = getReadableDb().query(query)) { + while (cursor.moveToNext()) { + UnifiedKeyInfo item = SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor); + result.add(item); + } + } + return result; } public List getUnifiedKeyInfosByMailAddress(String mailAddress) { SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoSearchMailAddress('%' + mailAddress + '%'); - return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER); + ArrayList result = new ArrayList<>(); + try (Cursor cursor = getReadableDb().query(query)) { + while (cursor.moveToNext()) { + UnifiedKeyInfo item = SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor); + result.add(item); + } + } + return result; } public List getAllUnifiedKeyInfo() { SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfo(); - return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER); + ArrayList result = new ArrayList<>(); + try (Cursor cursor = getReadableDb().query(query)) { + while (cursor.moveToNext()) { + UnifiedKeyInfo item = SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor); + result.add(item); + } + } + return result; } public List getAllUnifiedKeyInfoWithSecret() { SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfoWithSecret(); - return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER); + ArrayList result = new ArrayList<>(); + try (Cursor cursor = getReadableDb().query(query)) { + while (cursor.moveToNext()) { + UnifiedKeyInfo item = SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor); + result.add(item); + } + } + return result; } public List getUserIds(long... masterKeyIds) { SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyId(masterKeyIds); - return mapAllRows(query, UserPacket.USER_ID_MAPPER); + ArrayList result = new ArrayList<>(); + try (Cursor cursor = getReadableDb().query(query)) { + while (cursor.moveToNext()) { + UserId item = UserPacket.USER_ID_MAPPER.map(cursor); + result.add(item); + } + } + return result; } public List getConfirmedUserIds(long masterKeyId) { SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyIdAndVerification( Certification.FACTORY, masterKeyId, VerificationStatus.VERIFIED_SECRET); - return mapAllRows(query, (cursor) -> UserPacket.USER_ID_MAPPER.map(cursor).user_id()); + ArrayList result = new ArrayList<>(); + try (Cursor cursor1 = getReadableDb().query(query)) { + while (cursor1.moveToNext()) { + String item = ((RowMapper) (cursor) -> UserPacket.USER_ID_MAPPER.map(cursor).user_id()) + .map(cursor1); + result.add(item); + } + } + return result; } public List getSubKeysByMasterKeyId(long masterKeyId) { SqlDelightQuery query = SubKey.FACTORY.selectSubkeysByMasterKeyId(masterKeyId); - return mapAllRows(query, SubKey.SUBKEY_MAPPER); + ArrayList result = new ArrayList<>(); + try (Cursor cursor = getReadableDb().query(query)) { + while (cursor.moveToNext()) { + SubKey item = SubKey.SUBKEY_MAPPER.map(cursor); + result.add(item); + } + } + return result; } public SecretKeyType getSecretKeyType(long keyId) throws NotFoundException { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/UserIdDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/UserIdDao.java index 5706f6765..3a7bb8863 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/UserIdDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/UserIdDao.java @@ -1,7 +1,11 @@ package org.sufficientlysecure.keychain.daos; -import java.util.List; +import java.util.HashMap; +import java.util.Map; + +import android.content.Context; +import android.database.Cursor; import com.squareup.sqldelight.SqlDelightQuery; import org.sufficientlysecure.keychain.KeychainDatabase; @@ -10,17 +14,31 @@ import org.sufficientlysecure.keychain.model.UserPacket.UidStatus; public class UserIdDao extends AbstractDao { - public UserIdDao(KeychainDatabase db, DatabaseNotifyManager databaseNotifyManager) { + public static UserIdDao getInstance(Context context) { + KeychainDatabase keychainDatabase = KeychainDatabase.getInstance(context); + DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); + + return new UserIdDao(keychainDatabase, databaseNotifyManager); + } + + private UserIdDao(KeychainDatabase db, DatabaseNotifyManager databaseNotifyManager) { super(db, databaseNotifyManager); } - public List getUidStatusByEmailLike(String emailLike) { + public UidStatus getUidStatusByEmailLike(String emailLike) { SqlDelightQuery query = UserPacket.FACTORY.selectUserIdStatusByEmailLike(emailLike); - return mapAllRows(query, UserPacket.UID_STATUS_MAPPER); + return mapSingleRow(query, UserPacket.UID_STATUS_MAPPER); } - public List getUidStatusByEmail(String... emails) { + public Map getUidStatusByEmail(String... emails) { SqlDelightQuery query = UserPacket.FACTORY.selectUserIdStatusByEmail(emails); - return mapAllRows(query, UserPacket.UID_STATUS_MAPPER); + Map result = new HashMap<>(); + try (Cursor cursor = getReadableDb().query(query)) { + while (cursor.moveToNext()) { + UidStatus item = UserPacket.UID_STATUS_MAPPER.map(cursor); + result.put(item.email(), item); + } + } + return result; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java index 4802b667e..d0a3058a5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java @@ -38,19 +38,6 @@ public class KeychainExternalContract { public static final int KEY_STATUS_UNVERIFIED = 1; public static final int KEY_STATUS_VERIFIED = 2; - public static class EmailStatus implements BaseColumns { - public static final String EMAIL_ADDRESS = "email_address"; - public static final String USER_ID = "user_id"; - public static final String USER_ID_STATUS = "email_status"; - public static final String MASTER_KEY_ID = "master_key_id"; - - public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon() - .appendPath(BASE_EMAIL_STATUS).build(); - - public static final String CONTENT_TYPE - = "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.email_status"; - } - public static class AutocryptStatus implements BaseColumns { public static final String ADDRESS = "address"; @@ -72,9 +59,6 @@ public class KeychainExternalContract { public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon() .appendPath(BASE_AUTOCRYPT_STATUS).build(); - - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.email_status"; } private KeychainExternalContract() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java index 6dfa1e143..db6aaa94c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java @@ -3,8 +3,11 @@ package org.sufficientlysecure.keychain.remote; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import android.content.Context; import android.support.annotation.Nullable; @@ -146,15 +149,15 @@ public class AutocryptInteractor { return uncachedKeyRing; } - public List determineAutocryptRecommendations(String... autocryptIds) { - List result = new ArrayList<>(autocryptIds.length); + public Map determineAutocryptRecommendations(String... autocryptIds) { + Map result = new HashMap<>(autocryptIds.length); for (AutocryptKeyStatus autocryptKeyStatus : autocryptPeerDao.getAutocryptKeyStatus(packageName, autocryptIds)) { AutocryptRecommendationResult peerResult = determineAutocryptRecommendation(autocryptKeyStatus); - result.add(peerResult); + result.put(peerResult.peerId, peerResult); } - return result; + return Collections.unmodifiableMap(result); } /** Determines Autocrypt "ui-recommendation", according to spec. diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java index 8b8788f57..f3a6fca2e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -20,36 +20,28 @@ package org.sufficientlysecure.keychain.remote; import java.security.AccessControlException; import java.util.Arrays; -import java.util.HashMap; +import java.util.Collections; import java.util.List; +import java.util.Map; -import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteQueryBuilder; +import android.database.MatrixCursor; import android.net.Uri; import android.support.annotation.NonNull; -import android.text.TextUtils; +import android.widget.Toast; import org.sufficientlysecure.keychain.BuildConfig; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.KeychainDatabase; -import org.sufficientlysecure.keychain.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.daos.ApiAppDao; import org.sufficientlysecure.keychain.daos.DatabaseNotifyManager; import org.sufficientlysecure.keychain.daos.UserIdDao; import org.sufficientlysecure.keychain.model.UserPacket.UidStatus; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; -import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.provider.KeychainExternalContract; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus; -import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus; import org.sufficientlysecure.keychain.remote.AutocryptInteractor.AutocryptRecommendationResult; import org.sufficientlysecure.keychain.remote.AutocryptInteractor.AutocryptState; import timber.log.Timber; @@ -61,9 +53,6 @@ public class KeychainExternalProvider extends ContentProvider { private static final int AUTOCRYPT_STATUS = 201; private static final int AUTOCRYPT_STATUS_INTERNAL = 202; - public static final String TEMP_TABLE_QUERIED_ADDRESSES = "queried_addresses"; - public static final String TEMP_TABLE_COLUMN_ADDRES = "address"; - private UriMatcher uriMatcher; private ApiPermissionHelper apiPermissionHelper; @@ -81,7 +70,6 @@ public class KeychainExternalProvider extends ContentProvider { return matcher; } - /** {@inheritDoc} */ @Override public boolean onCreate() { uriMatcher = buildUriMatcher(); @@ -95,105 +83,22 @@ public class KeychainExternalProvider extends ContentProvider { return true; } - /** - * {@inheritDoc} - */ - @Override - public String getType(@NonNull Uri uri) { - final int match = uriMatcher.match(uri); - switch (match) { - case EMAIL_STATUS: - return EmailStatus.CONTENT_TYPE; - default: - throw new UnsupportedOperationException("Unknown uri: " + uri); - } - } - - /** - * {@inheritDoc} - */ @Override public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Timber.v("query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")"); - long startTime = System.currentTimeMillis(); - - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - - int match = uriMatcher.match(uri); - - String groupBy = null; - - KeychainDatabase temporaryDb = KeychainDatabase.getTemporaryInstance(getContext()); - SupportSQLiteDatabase db = temporaryDb.getReadableDatabase(); + Context context = getContext(); + if (context == null) { + throw new IllegalStateException(); + } String callingPackageName = apiPermissionHelper.getCurrentCallingPackage(); + int match = uriMatcher.match(uri); switch (match) { case EMAIL_STATUS: { - boolean callerIsAllowed = apiPermissionHelper.isAllowedIgnoreErrors(); - if (!callerIsAllowed) { - throw new AccessControlException("An application must register before use of KeychainExternalProvider!"); - } - - db.execSQL("CREATE TEMPORARY TABLE " + TEMP_TABLE_QUERIED_ADDRESSES + " (" + TEMP_TABLE_COLUMN_ADDRES + " TEXT);"); - ContentValues cv = new ContentValues(); - for (String address : selectionArgs) { - cv.put(TEMP_TABLE_COLUMN_ADDRES, address); - db.insert(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_FAIL, cv); - } - - HashMap projectionMap = new HashMap<>(); - projectionMap.put(EmailStatus._ID, "email AS _id"); - projectionMap.put(EmailStatus.EMAIL_ADDRESS, // this is actually the queried address - TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES + " AS " + EmailStatus.EMAIL_ADDRESS); - projectionMap.put(EmailStatus.USER_ID, - Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.USER_ID); - // we take the minimum (>0) here, where "1" is "verified by known secret key", "2" is "self-certified" - projectionMap.put(EmailStatus.USER_ID_STATUS, "CASE ( MIN (" + Certs.VERIFIED + " ) ) " - // remap to keep this provider contract independent from our internal representation - + " WHEN " + Certs.VERIFIED_SELF + " THEN " + KeychainExternalContract.KEY_STATUS_UNVERIFIED - + " WHEN " + Certs.VERIFIED_SECRET + " THEN " + KeychainExternalContract.KEY_STATUS_VERIFIED - + " WHEN NULL THEN " + KeychainExternalContract.KEY_STATUS_UNVERIFIED - + " END AS " + EmailStatus.USER_ID_STATUS); - projectionMap.put(EmailStatus.MASTER_KEY_ID, - Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " AS " + EmailStatus.MASTER_KEY_ID); - qb.setProjectionMap(projectionMap); - - if (projection == null) { - throw new IllegalArgumentException("Please provide a projection!"); - } - - qb.setTables( - TEMP_TABLE_QUERIED_ADDRESSES - + " LEFT JOIN " + Tables.USER_PACKETS + " ON (" - + Tables.USER_PACKETS + "." + UserPackets.USER_ID + " IS NOT NULL" - + " AND " + Tables.USER_PACKETS + "." + UserPackets.EMAIL + " LIKE " + TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES - + ")" - + " LEFT JOIN " + Tables.CERTS + " ON (" - + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = " + Tables.CERTS + "." + Certs.MASTER_KEY_ID - + " AND " + Tables.USER_PACKETS + "." + UserPackets.RANK + " = " + Tables.CERTS + "." + Certs.RANK - + ")" - ); - // in case there are multiple verifying certificates - groupBy = TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES; - List plist = Arrays.asList(projection); - if (plist.contains(EmailStatus.USER_ID)) { - groupBy += ", " + Tables.USER_PACKETS + "." + UserPackets.USER_ID; - } - - // verified == 0 has no self-cert, which is basically an error case. never return that! - // verified == null is fine, because it means there was no join partner - qb.appendWhere(Tables.CERTS + "." + Certs.VERIFIED + " IS NULL OR " + Tables.CERTS + "." + Certs.VERIFIED + " > 0"); - - if (TextUtils.isEmpty(sortOrder)) { - sortOrder = EmailStatus.EMAIL_ADDRESS; - } - - // uri to watch is all /key_rings/ - uri = DatabaseNotifyManager.getNotifyUriAllKeys(); - - break; + Toast.makeText(context, "This API is no longer supported by OpenKeychain!", Toast.LENGTH_SHORT).show(); + return new MatrixCursor(projection); } case AUTOCRYPT_STATUS_INTERNAL: @@ -214,22 +119,6 @@ public class KeychainExternalProvider extends ContentProvider { throw new IllegalArgumentException("Please provide a projection!"); } - db.execSQL("CREATE TEMPORARY TABLE " + TEMP_TABLE_QUERIED_ADDRESSES + " (" + - TEMP_TABLE_COLUMN_ADDRES + " TEXT NOT NULL PRIMARY KEY, " + - AutocryptStatus.UID_KEY_STATUS + " INT, " + - AutocryptStatus.UID_ADDRESS + " TEXT, " + - AutocryptStatus.UID_MASTER_KEY_ID + " INT, " + - AutocryptStatus.UID_CANDIDATES + " INT, " + - AutocryptStatus.AUTOCRYPT_PEER_STATE + " INT DEFAULT " + AutocryptStatus.AUTOCRYPT_PEER_DISABLED + ", " + - AutocryptStatus.AUTOCRYPT_KEY_STATUS + " INT, " + - AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID + " INT" + - ");"); - ContentValues cv = new ContentValues(); - for (String address : selectionArgs) { - cv.put(TEMP_TABLE_COLUMN_ADDRES, address); - db.insert(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_FAIL, cv); - } - List plist = Arrays.asList(projection); boolean isWildcardSelector = selectionArgs.length == 1 && selectionArgs[0].contains("%"); boolean queriesUidResult = plist.contains(AutocryptStatus.UID_KEY_STATUS) || @@ -243,116 +132,116 @@ public class KeychainExternalProvider extends ContentProvider { throw new UnsupportedOperationException("Cannot wildcard-query autocrypt results!"); } - UserIdDao userIdDao = new UserIdDao(temporaryDb, DatabaseNotifyManager.create(getContext())); + Map uidStatuses = queriesUidResult ? + loadUidStatusMap(selectionArgs, isWildcardSelector) : Collections.emptyMap(); + Map autocryptStates = queriesAutocryptResult ? + loadAutocryptRecommendationMap(selectionArgs, callingPackageName) : Collections.emptyMap(); - if (queriesUidResult) { - List uidStatuses; - if (isWildcardSelector) { - uidStatuses = userIdDao.getUidStatusByEmailLike(selectionArgs[0]); - } else { - uidStatuses = userIdDao.getUidStatusByEmail(selectionArgs); - } - fillTempTableWithUidResult(db, uidStatuses, isWildcardSelector ? selectionArgs[0] : null); - } + MatrixCursor cursor = + mapResultsToProjectedMatrixCursor(projection, selectionArgs, uidStatuses, autocryptStates); - if (queriesAutocryptResult) { - AutocryptInteractor autocryptInteractor = - AutocryptInteractor.getInstance(getContext(), callingPackageName); - List autocryptStates = - autocryptInteractor.determineAutocryptRecommendations(selectionArgs); - - fillTempTableWithAutocryptRecommendations(db, autocryptStates); - } - - HashMap projectionMap = new HashMap<>(); - projectionMap.put(AutocryptStatus._ID, AutocryptStatus._ID); - projectionMap.put(AutocryptStatus.ADDRESS, AutocryptStatus.ADDRESS); - projectionMap.put(AutocryptStatus.UID_KEY_STATUS, AutocryptStatus.UID_KEY_STATUS); - projectionMap.put(AutocryptStatus.UID_ADDRESS, AutocryptStatus.UID_ADDRESS); - projectionMap.put(AutocryptStatus.UID_MASTER_KEY_ID, AutocryptStatus.UID_MASTER_KEY_ID); - projectionMap.put(AutocryptStatus.UID_CANDIDATES, AutocryptStatus.UID_CANDIDATES); - projectionMap.put(AutocryptStatus.AUTOCRYPT_PEER_STATE, AutocryptStatus.AUTOCRYPT_PEER_STATE); - projectionMap.put(AutocryptStatus.AUTOCRYPT_KEY_STATUS, AutocryptStatus.AUTOCRYPT_KEY_STATUS); - projectionMap.put(AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID, AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID); - qb.setProjectionMap(projectionMap); - qb.setTables(TEMP_TABLE_QUERIED_ADDRESSES); - - if (TextUtils.isEmpty(sortOrder)) { - sortOrder = AutocryptStatus.ADDRESS; - } - - // uri to watch is all /key_rings/ uri = DatabaseNotifyManager.getNotifyUriAllKeys(); - break; + cursor.setNotificationUri(context.getContentResolver(), uri); + + return cursor; } default: { throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); } - } + } - // If no sort order is specified use the default - String orderBy; - if (TextUtils.isEmpty(sortOrder)) { - orderBy = null; - } else { - orderBy = sortOrder; - } + @NonNull + private MatrixCursor mapResultsToProjectedMatrixCursor(String[] projection, String[] selectionArgs, + Map uidStatuses, Map autocryptStates) { + MatrixCursor cursor = new MatrixCursor(projection); + for (String selectionArg : selectionArgs) { + AutocryptRecommendationResult autocryptResult = autocryptStates.get(selectionArg); + UidStatus uidStatus = uidStatuses.get(selectionArg); - qb.setStrict(true); - String query = qb.buildQuery(projection, null, groupBy, null, orderBy, null); - Cursor cursor = db.query(query); - if (cursor != null) { - // Tell the cursor what uri to watch, so it knows when its source data changes - cursor.setNotificationUri(getContext().getContentResolver(), uri); - if (Constants.DEBUG_LOG_DB_QUERIES) { - DatabaseUtils.dumpCursor(cursor); + Object[] row = new Object[projection.length]; + for (int i = 0; i < projection.length; i++) { + if (AutocryptStatus.ADDRESS.equals(projection[i]) || AutocryptStatus._ID.equals(projection[i])) { + row[i] = selectionArg; + } else { + row[i] = columnNameToRowContent(projection[i], autocryptResult, uidStatus); + } } + cursor.addRow(row); } - - Timber.d("Query: " + qb.buildQuery(projection, selection, groupBy, null, orderBy, null)); - Timber.d(Constants.TAG, "Query took %s ms", (System.currentTimeMillis() - startTime)); - return cursor; } - private void fillTempTableWithAutocryptRecommendations(SupportSQLiteDatabase db, - List autocryptRecommendations) { - ContentValues cv = new ContentValues(); - for (AutocryptRecommendationResult peerResult : autocryptRecommendations) { - cv.clear(); - - cv.put(AutocryptStatus.AUTOCRYPT_PEER_STATE, getPeerStateValue(peerResult.autocryptState)); - if (peerResult.masterKeyId != null) { - cv.put(AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID, peerResult.masterKeyId); - cv.put(AutocryptStatus.AUTOCRYPT_KEY_STATUS, peerResult.isVerified ? + private Object columnNameToRowContent( + String columnName, AutocryptRecommendationResult autocryptResult, UidStatus uidStatus) { + switch (columnName) { + case AutocryptStatus.UID_KEY_STATUS: { + if (uidStatus == null) { + return null; + } + return uidStatus.keyStatus() == VerificationStatus.VERIFIED_SECRET ? KeychainExternalContract.KEY_STATUS_VERIFIED : - KeychainExternalContract.KEY_STATUS_UNVERIFIED); + KeychainExternalContract.KEY_STATUS_UNVERIFIED; } + case AutocryptStatus.UID_ADDRESS: + if (uidStatus == null) { + return null; + } + return uidStatus.user_id(); - db.update(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_IGNORE, cv,TEMP_TABLE_COLUMN_ADDRES + "=?", - new String[] { peerResult.peerId }); + case AutocryptStatus.UID_MASTER_KEY_ID: + if (uidStatus == null) { + return null; + } + return uidStatus.master_key_id(); + + case AutocryptStatus.UID_CANDIDATES: + if (uidStatus == null) { + return null; + } + return uidStatus.candidates(); + + case AutocryptStatus.AUTOCRYPT_PEER_STATE: + if (autocryptResult == null) { + return null; + } + return getPeerStateValue(autocryptResult.autocryptState); + + case AutocryptStatus.AUTOCRYPT_KEY_STATUS: + if (autocryptResult == null) { + return null; + } + return autocryptResult.isVerified ? + KeychainExternalContract.KEY_STATUS_VERIFIED : KeychainExternalContract.KEY_STATUS_UNVERIFIED; + + case AutocryptStatus.AUTOCRYPT_MASTER_KEY_ID: + if (autocryptResult == null) { + return null; + } + return autocryptResult.masterKeyId; + + default: + throw new IllegalArgumentException("Unhandled case " + columnName); } } - private void fillTempTableWithUidResult(SupportSQLiteDatabase db, List uidStatuses, String key) { - ContentValues cv = new ContentValues(); - for (UidStatus uidStatus : uidStatuses) { - int keyStatus = uidStatus.keyStatus() == VerificationStatus.VERIFIED_SECRET ? - KeychainExternalContract.KEY_STATUS_VERIFIED : KeychainExternalContract.KEY_STATUS_UNVERIFIED; - - cv.put(AutocryptStatus.UID_ADDRESS, uidStatus.user_id()); - cv.put(AutocryptStatus.UID_MASTER_KEY_ID, uidStatus.master_key_id()); - cv.put(AutocryptStatus.UID_KEY_STATUS, keyStatus); - cv.put(AutocryptStatus.UID_CANDIDATES, uidStatus.candidates()); - - db.update(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_IGNORE, cv, - TEMP_TABLE_COLUMN_ADDRES + "= ?", - new String[] { key != null ? key : uidStatus.email() }); + private Map loadUidStatusMap(String[] selectionArgs, boolean isWildcardSelector) { + UserIdDao userIdDao = UserIdDao.getInstance(getContext()); + if (isWildcardSelector) { + UidStatus uidStatus = userIdDao.getUidStatusByEmailLike(selectionArgs[0]); + return Collections.singletonMap(selectionArgs[0], uidStatus); + } else { + return userIdDao.getUidStatusByEmail(selectionArgs); } } + private Map loadAutocryptRecommendationMap( + String[] selectionArgs, String callingPackageName) { + AutocryptInteractor autocryptInteractor = AutocryptInteractor.getInstance(getContext(), callingPackageName); + return autocryptInteractor.determineAutocryptRecommendations(selectionArgs); + } + private int getPeerStateValue(AutocryptState autocryptState) { switch (autocryptState) { case DISABLE: return AutocryptStatus.AUTOCRYPT_PEER_DISABLED; @@ -364,6 +253,11 @@ public class KeychainExternalProvider extends ContentProvider { throw new IllegalStateException("Unhandled case!"); } + @Override + public String getType(@NonNull Uri uri) { + throw new UnsupportedOperationException("Unknown uri: " + uri); + } + @Override public Uri insert(@NonNull Uri uri, ContentValues values) { throw new UnsupportedOperationException(); diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java index 27ffb3ffd..327145607 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java @@ -18,19 +18,18 @@ import org.robolectric.shadows.ShadowBinder; import org.robolectric.shadows.ShadowLog; import org.robolectric.shadows.ShadowPackageManager; import org.sufficientlysecure.keychain.KeychainTestRunner; +import org.sufficientlysecure.keychain.daos.ApiAppDao; +import org.sufficientlysecure.keychain.daos.AutocryptPeerDao; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.model.ApiApp; import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin; import org.sufficientlysecure.keychain.operations.CertifyOperation; import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; -import org.sufficientlysecure.keychain.daos.ApiAppDao; -import org.sufficientlysecure.keychain.daos.AutocryptPeerDao; import org.sufficientlysecure.keychain.provider.KeyRepositorySaveTest; -import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.provider.KeychainExternalContract; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus; -import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -49,10 +48,7 @@ public class KeychainExternalProviderTest { static final String PACKAGE_NAME = "test.package"; static final byte[] PACKAGE_SIGNATURE = new byte[] { 1, 2, 3 }; static final String MAIL_ADDRESS_1 = "twi@openkeychain.org"; - static final String MAIL_ADDRESS_2 = "pink@openkeychain.org"; - static final String MAIL_ADDRESS_SEC_1 = "twi-sec@openkeychain.org"; static final String USER_ID_1 = "twi "; - static final String USER_ID_SEC_1 = "twi "; static final long KEY_ID_SECRET = 0x5D4DA4423C39122FL; static final long KEY_ID_PUBLIC = 0x9A282CE2AB44A382L; public static final String AUTOCRYPT_PEER = "tid"; @@ -92,8 +88,8 @@ public class KeychainExternalProviderTest { apiAppDao.deleteApiApp(PACKAGE_NAME); contentResolver.query( - EmailStatus.CONTENT_URI, - new String[] { EmailStatus.EMAIL_ADDRESS, EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID }, + AutocryptStatus.CONTENT_URI, + new String[] { AutocryptStatus.ADDRESS }, null, new String [] { }, null ); } @@ -104,106 +100,12 @@ public class KeychainExternalProviderTest { apiAppDao.insertApiApp(ApiApp.create(PACKAGE_NAME, new byte[] { 1, 2, 4 })); contentResolver.query( - EmailStatus.CONTENT_URI, - new String[] { EmailStatus.EMAIL_ADDRESS, EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID }, + AutocryptStatus.CONTENT_URI, + new String[] { AutocryptStatus.ADDRESS }, null, new String [] { }, null ); } - @Test - public void testEmailStatus_withNonExistentAddress() throws Exception { - Cursor cursor = contentResolver.query( - EmailStatus.CONTENT_URI, new String[] { - EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID }, - null, new String [] { MAIL_ADDRESS_1 }, null - ); - - assertNotNull(cursor); - assertTrue(cursor.moveToFirst()); - assertEquals(MAIL_ADDRESS_1, cursor.getString(0)); - assertEquals(KeychainExternalContract.KEY_STATUS_UNAVAILABLE, cursor.getInt(1)); - assertTrue(cursor.isNull(2)); - } - - @Test - public void testEmailStatus() throws Exception { - insertPublicKeyringFrom("/test-keys/testring.pub"); - - Cursor cursor = contentResolver.query( - EmailStatus.CONTENT_URI, new String[] { - EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID }, - null, new String [] { MAIL_ADDRESS_1 }, null - ); - - assertNotNull(cursor); - assertTrue(cursor.moveToFirst()); - assertEquals(MAIL_ADDRESS_1, cursor.getString(0)); - assertEquals(KeychainExternalContract.KEY_STATUS_UNVERIFIED, cursor.getInt(1)); - assertEquals("twi ", cursor.getString(2)); - assertFalse(cursor.moveToNext()); - } - - @Test - public void testEmailStatus_multiple() throws Exception { - insertPublicKeyringFrom("/test-keys/testring.pub"); - - Cursor cursor = contentResolver.query( - EmailStatus.CONTENT_URI, new String[] { - EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID }, - null, new String [] { MAIL_ADDRESS_1, MAIL_ADDRESS_2 }, null - ); - - assertNotNull(cursor); - assertTrue(cursor.moveToNext()); - assertEquals(MAIL_ADDRESS_2, cursor.getString(0)); - assertEquals(KeychainExternalContract.KEY_STATUS_UNAVAILABLE, cursor.getInt(1)); - assertTrue(cursor.isNull(2)); - assertTrue(cursor.moveToNext()); - assertEquals(MAIL_ADDRESS_1, cursor.getString(0)); - assertEquals(KeychainExternalContract.KEY_STATUS_UNVERIFIED, cursor.getInt(1)); - assertEquals("twi ", cursor.getString(2)); - assertFalse(cursor.moveToNext()); - } - - @Test - public void testEmailStatus_withSecretKey() throws Exception { - insertSecretKeyringFrom("/test-keys/testring.sec"); - - Cursor cursor = contentResolver.query( - EmailStatus.CONTENT_URI, new String[] { - EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID_STATUS, EmailStatus.USER_ID }, - null, new String [] { MAIL_ADDRESS_SEC_1 }, null - ); - - assertNotNull(cursor); - assertTrue(cursor.moveToFirst()); - assertEquals(MAIL_ADDRESS_SEC_1, cursor.getString(0)); - assertEquals(USER_ID_SEC_1, cursor.getString(2)); - assertEquals(2, cursor.getInt(1)); - assertFalse(cursor.moveToNext()); - } - - @Test - public void testEmailStatus_withConfirmedKey() throws Exception { - insertSecretKeyringFrom("/test-keys/testring.sec"); - insertPublicKeyringFrom("/test-keys/testring.pub"); - - certifyKey(KEY_ID_SECRET, KEY_ID_PUBLIC, USER_ID_1); - - Cursor cursor = contentResolver.query( - EmailStatus.CONTENT_URI, new String[] { - EmailStatus.EMAIL_ADDRESS, EmailStatus.USER_ID, EmailStatus.USER_ID_STATUS}, - null, new String [] { MAIL_ADDRESS_1 }, null - ); - - assertNotNull(cursor); - assertTrue(cursor.moveToFirst()); - assertEquals(MAIL_ADDRESS_1, cursor.getString(0)); - assertEquals(USER_ID_1, cursor.getString(1)); - assertEquals(KeychainExternalContract.KEY_STATUS_VERIFIED, cursor.getInt(2)); - assertFalse(cursor.moveToNext()); - } - @Test public void testAutocryptStatus_autocryptPeer_withUnconfirmedKey() throws Exception { insertSecretKeyringFrom("/test-keys/testring.sec");