return KeychainExternalProvider as MatrixCursor

This commit is contained in:
Vincent Breitmoser 2018-07-12 14:57:39 +02:00
parent 3150d2d3f9
commit acb9544195
6 changed files with 214 additions and 347 deletions

View file

@ -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<Long> getAllMasterKeyIds() {
SqlDelightQuery query = KeyRingPublic.FACTORY.selectAllMasterKeyIds();
return mapAllRows(query, KeyRingPublic.FACTORY.selectAllMasterKeyIdsMapper());
ArrayList<Long> 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<Long> getMasterKeyIdsBySigner(List<Long> signerMasterKeyIds) {
long[] signerKeyIds = getLongListAsArray(signerMasterKeyIds);
SqlDelightQuery query = KeySignature.FACTORY.selectMasterKeyIdsBySigner(signerKeyIds);
return mapAllRows(query, KeySignature.FACTORY.selectMasterKeyIdsBySignerMapper());
ArrayList<Long> 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<UnifiedKeyInfo> getUnifiedKeyInfo(long... masterKeyIds) {
SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoByMasterKeyIds(masterKeyIds);
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER);
ArrayList<UnifiedKeyInfo> 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<UnifiedKeyInfo> getUnifiedKeyInfosByMailAddress(String mailAddress) {
SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoSearchMailAddress('%' + mailAddress + '%');
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER);
ArrayList<UnifiedKeyInfo> 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<UnifiedKeyInfo> getAllUnifiedKeyInfo() {
SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfo();
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER);
ArrayList<UnifiedKeyInfo> 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<UnifiedKeyInfo> getAllUnifiedKeyInfoWithSecret() {
SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfoWithSecret();
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER);
ArrayList<UnifiedKeyInfo> 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<UserId> getUserIds(long... masterKeyIds) {
SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyId(masterKeyIds);
return mapAllRows(query, UserPacket.USER_ID_MAPPER);
ArrayList<UserId> 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<String> 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<String> result = new ArrayList<>();
try (Cursor cursor1 = getReadableDb().query(query)) {
while (cursor1.moveToNext()) {
String item = ((RowMapper<String>) (cursor) -> UserPacket.USER_ID_MAPPER.map(cursor).user_id())
.map(cursor1);
result.add(item);
}
}
return result;
}
public List<SubKey> getSubKeysByMasterKeyId(long masterKeyId) {
SqlDelightQuery query = SubKey.FACTORY.selectSubkeysByMasterKeyId(masterKeyId);
return mapAllRows(query, SubKey.SUBKEY_MAPPER);
ArrayList<SubKey> 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 {

View file

@ -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<UidStatus> 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<UidStatus> getUidStatusByEmail(String... emails) {
public Map<String,UidStatus> getUidStatusByEmail(String... emails) {
SqlDelightQuery query = UserPacket.FACTORY.selectUserIdStatusByEmail(emails);
return mapAllRows(query, UserPacket.UID_STATUS_MAPPER);
Map<String,UidStatus> 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;
}
}

View file

@ -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() {

View file

@ -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<AutocryptRecommendationResult> determineAutocryptRecommendations(String... autocryptIds) {
List<AutocryptRecommendationResult> result = new ArrayList<>(autocryptIds.length);
public Map<String,AutocryptRecommendationResult> determineAutocryptRecommendations(String... autocryptIds) {
Map<String,AutocryptRecommendationResult> 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.

View file

@ -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<String, String> 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<String> 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<String> 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<String, UidStatus> uidStatuses = queriesUidResult ?
loadUidStatusMap(selectionArgs, isWildcardSelector) : Collections.emptyMap();
Map<String, AutocryptRecommendationResult> autocryptStates = queriesAutocryptResult ?
loadAutocryptRecommendationMap(selectionArgs, callingPackageName) : Collections.emptyMap();
if (queriesUidResult) {
List<UidStatus> 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<AutocryptRecommendationResult> autocryptStates =
autocryptInteractor.determineAutocryptRecommendations(selectionArgs);
fillTempTableWithAutocryptRecommendations(db, autocryptStates);
}
HashMap<String, String> 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<String, UidStatus> uidStatuses, Map<String, AutocryptRecommendationResult> 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<AutocryptRecommendationResult> 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<UidStatus> 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<String, UidStatus> 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<String, AutocryptRecommendationResult> 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();

View file

@ -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 <twi@openkeychain.org>";
static final String USER_ID_SEC_1 = "twi <twi-sec@openkeychain.org>";
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 <twi@openkeychain.org>", 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 <twi@openkeychain.org>", 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");