diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java index f42b46b43..421ece8fb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java @@ -51,7 +51,7 @@ import timber.log.Timber; */ public class KeychainDatabase { private static final String DATABASE_NAME = "openkeychain.db"; - private static final int DATABASE_VERSION = 29; + private static final int DATABASE_VERSION = 30; private final SupportSQLiteOpenHelper supportSQLiteOpenHelper; private static KeychainDatabase sInstance; @@ -133,6 +133,8 @@ public class KeychainDatabase { db.execSQL(AutocryptPeersModel.CREATE_TABLE); db.execSQL(ApiAllowedKeysModel.CREATE_TABLE); db.execSQL(KeysModel.UNIFIEDKEYVIEW); + db.execSQL(KeysModel.VALIDKEYSVIEW); + db.execSQL(UserPacketsModel.UIDSTATUS); db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ", " + KeysColumns.MASTER_KEY_ID + ");"); db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", " @@ -356,18 +358,28 @@ public class KeychainDatabase { renameApiAutocryptPeersTable(db); case 28: - recreateUnifiedKeyView(db); // drop old table from version 20 db.execSQL("DROP TABLE IF EXISTS api_accounts"); + + case 29: + recreateUnifiedKeyView(db); } } private void recreateUnifiedKeyView(SupportSQLiteDatabase db) { try { db.beginTransaction(); + // noinspection deprecation db.execSQL("DROP VIEW IF EXISTS " + KeysModel.UNIFIEDKEYVIEW_VIEW_NAME); db.execSQL(KeysModel.UNIFIEDKEYVIEW); + // noinspection deprecation + db.execSQL("DROP VIEW IF EXISTS " + KeysModel.VALIDMASTERKEYS_VIEW_NAME); + db.execSQL(KeysModel.VALIDKEYSVIEW); + // noinspection deprecation + db.execSQL("DROP VIEW IF EXISTS " + UserPacketsModel.UIDSTATUS_VIEW_NAME); + db.execSQL(UserPacketsModel.UIDSTATUS); + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/UserPacket.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/UserPacket.java index 0fa57a462..6bd833a81 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/UserPacket.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/UserPacket.java @@ -16,6 +16,8 @@ public abstract class UserPacket implements UserPacketsModel { FACTORY.selectUserIdsByMasterKeyIdMapper(AutoValue_UserPacket_UserId::new); public static final SelectUserAttributesByTypeAndMasterKeyIdMapper USER_ATTRIBUTE_MAPPER = FACTORY.selectUserAttributesByTypeAndMasterKeyIdMapper(AutoValue_UserPacket_UserAttribute::new); + public static final UidStatusMapper UID_STATUS_MAPPER = + FACTORY.selectUserIdStatusByEmailMapper(AutoValue_UserPacket_UidStatus::new); public static UserPacket create(long masterKeyId, int rank, Long type, String userId, String name, String email, String comment, byte[] attribute_data, boolean isPrimary, boolean isRevoked) { @@ -55,4 +57,11 @@ public abstract class UserPacket implements UserPacketsModel { return CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER.decode(verified_int()); } } + + @AutoValue + public static abstract class UidStatus implements UidStatusModel { + public VerificationStatus keyStatus() { + return CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER.decode(key_status_int()); + } + } } 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 b8c4fb106..fd8418dc9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -36,12 +36,16 @@ import android.net.Uri; import android.support.annotation.NonNull; import android.text.TextUtils; +import com.squareup.sqldelight.SqlDelightQuery; 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.model.UserPacket; +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.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; @@ -248,7 +252,7 @@ public class KeychainExternalProvider extends ContentProvider { plist.contains(AutocryptStatus.UID_MASTER_KEY_ID) || plist.contains(AutocryptStatus.UID_CANDIDATES); if (queriesUidResult) { - fillTempTableWithUidResult(db, isWildcardSelector); + fillTempTableWithUidResult(db, isWildcardSelector, selectionArgs); } boolean queriesAutocryptResult = plist.contains(AutocryptStatus.AUTOCRYPT_PEER_STATE) || @@ -343,44 +347,32 @@ public class KeychainExternalProvider extends ContentProvider { } } - private void fillTempTableWithUidResult(SupportSQLiteDatabase db, boolean isWildcardSelector) { - String cmpOperator = isWildcardSelector ? " LIKE " : " = "; - long unixSeconds = System.currentTimeMillis() / 1000; - db.execSQL("REPLACE INTO " + TEMP_TABLE_QUERIED_ADDRESSES + - "(" + TEMP_TABLE_COLUMN_ADDRES + ", " + AutocryptStatus.UID_KEY_STATUS + ", " + - AutocryptStatus.UID_ADDRESS + ", " + AutocryptStatus.UID_MASTER_KEY_ID - + ", " + AutocryptStatus.UID_CANDIDATES + ")" + - " SELECT " + TEMP_TABLE_COLUMN_ADDRES + ", " + - "CASE ( MIN (" + Tables.CERTS + "." + 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 - + " END AS " + AutocryptStatus.UID_KEY_STATUS - + ", " + Tables.USER_PACKETS + "." + UserPackets.USER_ID - + ", " + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID - + ", COUNT(DISTINCT " + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + ")" - + " FROM " + TEMP_TABLE_QUERIED_ADDRESSES - + " LEFT JOIN " + Tables.USER_PACKETS + " ON (" - + Tables.USER_PACKETS + "." + UserPackets.EMAIL + cmpOperator + - TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES - + ")" - + " LEFT JOIN " + Tables.CERTS + " ON (" - + Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = " + Tables.USER_PACKETS + "." + - UserPackets.MASTER_KEY_ID - + " AND " + Tables.CERTS + "." + Certs.RANK + " = " + Tables.USER_PACKETS + "." + - UserPackets.RANK - + " AND " + Tables.CERTS + "." + Certs.VERIFIED + " > 0" - + ")" - + " WHERE (EXISTS (SELECT 1 FROM " + Tables.KEYS + " WHERE " - + Tables.KEYS + "." + Keys.KEY_ID + " = " + Tables.USER_PACKETS + "." + - UserPackets.MASTER_KEY_ID - + " AND " + Tables.KEYS + "." + Keys.RANK + " = 0" - + " AND " + Tables.KEYS + "." + Keys.IS_REVOKED + " = 0" - + " AND NOT " + "(" + Tables.KEYS + "." + Keys.EXPIRY + " IS NOT NULL AND " + Tables.KEYS + - "." + Keys.EXPIRY - + " < " + unixSeconds + ")" - + ")) OR " + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " IS NULL" - + " GROUP BY " + TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES); + private void fillTempTableWithUidResult(SupportSQLiteDatabase db, + boolean isWildcardSelector, String[] selectionArgs) { + SqlDelightQuery query; + if (isWildcardSelector) { + query = UserPacket.FACTORY.selectUserIdStatusByEmailLike(selectionArgs[0]); + } else { + query = UserPacket.FACTORY.selectUserIdStatusByEmail(selectionArgs); + } + + try (Cursor cursor = db.query(query)) { + ContentValues cv = new ContentValues(); + while (cursor.moveToNext()) { + UidStatus uidStatus = UserPacket.UID_STATUS_MAPPER.map(cursor); + 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[] { isWildcardSelector ? selectionArgs[0] : uidStatus.email() }); + } + } } private int getPeerStateValue(AutocryptState autocryptState) { diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq index 702c90472..8b11bb483 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq @@ -36,6 +36,12 @@ UPDATE keys SET has_secret = ?2 WHERE key_id = ?1; +validKeysView: +CREATE VIEW validMasterKeys AS +SELECT * + FROM keys + WHERE rank = 0 AND is_revoked = 0 AND is_secure = 1 AND (expiry IS NULL OR expiry >= strftime('%s', 'now')); + unifiedKeyView: CREATE VIEW unifiedKeyView AS SELECT keys.master_key_id, keys.fingerprint, MIN(user_packets.rank), user_packets.user_id, user_packets.name, user_packets.email, user_packets.comment, keys.creation, keys.expiry, keys.is_revoked, keys.is_secure, keys.can_certify, certs.verified, diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq index ec0b48463..e152d7fed 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq @@ -49,3 +49,23 @@ SELECT user_packets.master_key_id, user_packets.rank, attribute_data, is_primary LEFT JOIN certs ON ( user_packets.master_key_id = certs.master_key_id AND user_packets.rank = certs.rank AND certs.verified > 0 ) WHERE user_packets.type = ? AND user_packets.master_key_id = ? AND user_packets.rank = ? GROUP BY user_packets.master_key_id, user_packets.rank; + + +uidStatus: +CREATE VIEW uidStatus AS + SELECT user_packets.email, MIN(certs.verified) AS key_status_int, user_packets.user_id, user_packets.master_key_id, COUNT(DISTINCT user_packets.master_key_id) AS candidates + FROM user_packets + JOIN validMasterKeys USING (master_key_id) + LEFT JOIN certs ON (certs.master_key_id = user_packets.master_key_id AND certs.rank = user_packets.rank AND certs.verified > 0) + WHERE user_packets.email IS NOT NULL + GROUP BY user_packets.email; + +selectUserIdStatusByEmail: +SELECT * +FROM uidStatus + WHERE email IN ?; + +selectUserIdStatusByEmailLike: +SELECT * +FROM uidStatus + WHERE email LIKE ?;