mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-20 08:39:22 +01:00
Add a write-through cache to the identity store.
This commit is contained in:
@@ -26,8 +26,9 @@ import androidx.annotation.Nullable;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.database.identity.IdentityRecordList;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityStoreRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
@@ -39,9 +40,6 @@ import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class IdentityDatabase extends Database {
|
||||
|
||||
@@ -91,43 +89,6 @@ public class IdentityDatabase extends Database {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
public Cursor getIdentities() {
|
||||
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
|
||||
return database.query(TABLE_NAME, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public @Nullable IdentityReader readerFor(@Nullable Cursor cursor) {
|
||||
if (cursor == null) return null;
|
||||
return new IdentityReader(cursor);
|
||||
}
|
||||
|
||||
public Optional<IdentityRecord> getIdentity(@NonNull String addressName) {
|
||||
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
|
||||
String query = ADDRESS + " = ?";
|
||||
String[] args = SqlUtil.buildArgs(addressName);
|
||||
|
||||
try (Cursor cursor = database.query(TABLE_NAME, null, query, args, null, null, null)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
return Optional.of(getIdentityRecord(cursor));
|
||||
}
|
||||
} catch (InvalidKeyException | IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
public Optional<IdentityRecord> getIdentity(@NonNull RecipientId recipientId) {
|
||||
Recipient recipient = Recipient.resolved(recipientId);
|
||||
|
||||
if (recipient.hasServiceIdentifier()) {
|
||||
return getIdentity(recipient.requireServiceId());
|
||||
} else {
|
||||
Log.w(TAG, "Recipient has no service identifier!");
|
||||
return Optional.absent();
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable IdentityStoreRecord getIdentityStoreRecord(@NonNull String addressName) {
|
||||
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
|
||||
String query = ADDRESS + " = ?";
|
||||
@@ -170,34 +131,6 @@ public class IdentityDatabase extends Database {
|
||||
return null;
|
||||
}
|
||||
|
||||
public @NonNull IdentityRecordList getIdentities(@NonNull List<Recipient> recipients) {
|
||||
List<String> addressNames = recipients.stream()
|
||||
.filter(Recipient::hasServiceIdentifier)
|
||||
.map(Recipient::requireServiceId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (addressNames.isEmpty()) {
|
||||
return IdentityRecordList.EMPTY;
|
||||
}
|
||||
|
||||
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
|
||||
SqlUtil.Query query = SqlUtil.buildCollectionQuery(ADDRESS, addressNames);
|
||||
|
||||
List<IdentityRecord> records = new LinkedList<>();
|
||||
|
||||
try (Cursor cursor = database.query(TABLE_NAME, null, query.getWhere(), query.getWhereArgs(), null, null, null)) {
|
||||
while (cursor.moveToNext()) {
|
||||
try {
|
||||
records.add(getIdentityRecord(cursor));
|
||||
} catch (InvalidKeyException | IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new IdentityRecordList(records);
|
||||
}
|
||||
|
||||
public void saveIdentity(@NonNull String addressName,
|
||||
@NonNull RecipientId recipientId,
|
||||
IdentityKey identityKey,
|
||||
@@ -206,25 +139,10 @@ public class IdentityDatabase extends Database {
|
||||
long timestamp,
|
||||
boolean nonBlockingApproval)
|
||||
{
|
||||
saveIdentityInternal(addressName, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval);
|
||||
saveIdentityInternal(addressName, recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval);
|
||||
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(recipientId);
|
||||
}
|
||||
|
||||
public void saveIdentity(@NonNull RecipientId recipientId,
|
||||
IdentityKey identityKey,
|
||||
VerifiedStatus verifiedStatus,
|
||||
boolean firstUse,
|
||||
long timestamp,
|
||||
boolean nonBlockingApproval)
|
||||
{
|
||||
saveIdentityInternal(Recipient.resolved(recipientId).requireServiceId(), identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval);
|
||||
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(recipientId);
|
||||
}
|
||||
|
||||
public void setApproval(@NonNull RecipientId recipientId, boolean nonBlockingApproval) {
|
||||
setApproval(Recipient.resolved(recipientId).requireServiceId(), recipientId, nonBlockingApproval);
|
||||
}
|
||||
|
||||
public void setApproval(@NonNull String addressName, @NonNull RecipientId recipientId, boolean nonBlockingApproval) {
|
||||
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
|
||||
|
||||
@@ -236,11 +154,11 @@ public class IdentityDatabase extends Database {
|
||||
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(recipientId);
|
||||
}
|
||||
|
||||
public void setVerified(@NonNull RecipientId recipientId, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
|
||||
public void setVerified(@NonNull String addressName, @NonNull RecipientId recipientId, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
|
||||
SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
|
||||
|
||||
String query = ADDRESS + " = ? AND " + IDENTITY_KEY + " = ?";
|
||||
String[] args = SqlUtil.buildArgs(Recipient.resolved(recipientId).requireServiceId(), Base64.encodeBytes(identityKey.serialize()));
|
||||
String[] args = SqlUtil.buildArgs(addressName, Base64.encodeBytes(identityKey.serialize()));
|
||||
|
||||
ContentValues contentValues = new ContentValues(1);
|
||||
contentValues.put(VERIFIED, verifiedStatus.toInt());
|
||||
@@ -248,25 +166,27 @@ public class IdentityDatabase extends Database {
|
||||
int updated = database.update(TABLE_NAME, contentValues, query, args);
|
||||
|
||||
if (updated > 0) {
|
||||
Optional<IdentityRecord> record = getIdentity(recipientId);
|
||||
Optional<IdentityRecord> record = getIdentityRecord(addressName);
|
||||
if (record.isPresent()) EventBus.getDefault().post(record.get());
|
||||
DatabaseFactory.getRecipientDatabase(context).markNeedsSync(recipientId);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateIdentityAfterSync(@NonNull String addressName, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
|
||||
boolean hadEntry = getIdentity(addressName).isPresent();
|
||||
public void updateIdentityAfterSync(@NonNull String addressName, @NonNull RecipientId recipientId, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
|
||||
boolean hadEntry = getIdentityRecord(addressName).isPresent();
|
||||
boolean keyMatches = hasMatchingKey(addressName, identityKey);
|
||||
boolean statusMatches = keyMatches && hasMatchingStatus(addressName, identityKey, verifiedStatus);
|
||||
|
||||
if (!keyMatches || !statusMatches) {
|
||||
saveIdentityInternal(addressName, identityKey, verifiedStatus, !hadEntry, System.currentTimeMillis(), true);
|
||||
saveIdentityInternal(addressName, recipientId, identityKey, verifiedStatus, !hadEntry, System.currentTimeMillis(), true);
|
||||
|
||||
Optional<IdentityRecord> record = getIdentity(addressName);
|
||||
Optional<IdentityRecord> record = getIdentityRecord(addressName);
|
||||
|
||||
if (record.isPresent()) {
|
||||
EventBus.getDefault().post(record.get());
|
||||
}
|
||||
|
||||
ApplicationDependencies.getIdentityStore().invalidate(addressName);
|
||||
}
|
||||
|
||||
if (hadEntry && !keyMatches) {
|
||||
@@ -274,6 +194,26 @@ public class IdentityDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(@NonNull String addressName) {
|
||||
databaseHelper.getSignalWritableDatabase().delete(IdentityDatabase.TABLE_NAME, IdentityDatabase.ADDRESS + " = ?", SqlUtil.buildArgs(addressName));
|
||||
}
|
||||
|
||||
private Optional<IdentityRecord> getIdentityRecord(@NonNull String addressName) {
|
||||
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
|
||||
String query = ADDRESS + " = ?";
|
||||
String[] args = SqlUtil.buildArgs(addressName);
|
||||
|
||||
try (Cursor cursor = database.query(TABLE_NAME, null, query, args, null, null, null)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
return Optional.of(getIdentityRecord(cursor));
|
||||
}
|
||||
} catch (InvalidKeyException | IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
private boolean hasMatchingKey(@NonNull String addressName, IdentityKey identityKey) {
|
||||
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
|
||||
String query = ADDRESS + " = ? AND " + IDENTITY_KEY + " = ?";
|
||||
@@ -307,6 +247,7 @@ public class IdentityDatabase extends Database {
|
||||
}
|
||||
|
||||
private void saveIdentityInternal(@NonNull String addressName,
|
||||
@NonNull RecipientId recipientId,
|
||||
IdentityKey identityKey,
|
||||
VerifiedStatus verifiedStatus,
|
||||
boolean firstUse,
|
||||
@@ -326,86 +267,6 @@ public class IdentityDatabase extends Database {
|
||||
|
||||
database.replace(TABLE_NAME, null, contentValues);
|
||||
|
||||
EventBus.getDefault().post(new IdentityRecord(RecipientId.fromExternalPush(addressName), identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval));
|
||||
EventBus.getDefault().post(new IdentityRecord(recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval));
|
||||
}
|
||||
|
||||
public static class IdentityRecord {
|
||||
|
||||
private final RecipientId recipientId;
|
||||
private final IdentityKey identitykey;
|
||||
private final VerifiedStatus verifiedStatus;
|
||||
private final boolean firstUse;
|
||||
private final long timestamp;
|
||||
private final boolean nonblockingApproval;
|
||||
|
||||
private IdentityRecord(@NonNull RecipientId recipientId,
|
||||
IdentityKey identitykey,
|
||||
VerifiedStatus verifiedStatus,
|
||||
boolean firstUse,
|
||||
long timestamp,
|
||||
boolean nonblockingApproval)
|
||||
{
|
||||
this.recipientId = recipientId;
|
||||
this.identitykey = identitykey;
|
||||
this.verifiedStatus = verifiedStatus;
|
||||
this.firstUse = firstUse;
|
||||
this.timestamp = timestamp;
|
||||
this.nonblockingApproval = nonblockingApproval;
|
||||
}
|
||||
|
||||
public RecipientId getRecipientId() {
|
||||
return recipientId;
|
||||
}
|
||||
|
||||
public IdentityKey getIdentityKey() {
|
||||
return identitykey;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public VerifiedStatus getVerifiedStatus() {
|
||||
return verifiedStatus;
|
||||
}
|
||||
|
||||
public boolean isApprovedNonBlocking() {
|
||||
return nonblockingApproval;
|
||||
}
|
||||
|
||||
public boolean isFirstUse() {
|
||||
return firstUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String toString() {
|
||||
return "{recipientId: " + recipientId + ", identityKey: " + identitykey + ", verifiedStatus: " + verifiedStatus + ", firstUse: " + firstUse + "}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class IdentityReader {
|
||||
private final Cursor cursor;
|
||||
|
||||
IdentityReader(@NonNull Cursor cursor) {
|
||||
this.cursor = cursor;
|
||||
}
|
||||
|
||||
public @Nullable IdentityRecord getNext() {
|
||||
if (cursor.moveToNext()) {
|
||||
try {
|
||||
return getIdentityRecord(cursor);
|
||||
} catch (IOException | InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors;
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColorsMapper;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
@@ -802,7 +803,7 @@ public class RecipientDatabase extends Database {
|
||||
try {
|
||||
IdentityKey identityKey = new IdentityKey(insert.getIdentityKey().get(), 0);
|
||||
|
||||
DatabaseFactory.getIdentityDatabase(context).updateIdentityAfterSync(insert.getAddress().getIdentifier(), identityKey, StorageSyncModels.remoteToLocalIdentityStatus(insert.getIdentityState()));
|
||||
DatabaseFactory.getIdentityDatabase(context).updateIdentityAfterSync(insert.getAddress().getIdentifier(), recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(insert.getIdentityState()));
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.w(TAG, "Failed to process identity key during insert! Skipping.", e);
|
||||
}
|
||||
@@ -812,9 +813,9 @@ public class RecipientDatabase extends Database {
|
||||
}
|
||||
|
||||
public void applyStorageSyncContactUpdate(@NonNull StorageRecordUpdate<SignalContactRecord> update) {
|
||||
SQLiteDatabase db = databaseHelper.getSignalWritableDatabase();
|
||||
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
|
||||
ContentValues values = getValuesForStorageContact(update.getNew(), false);
|
||||
SQLiteDatabase db = databaseHelper.getSignalWritableDatabase();
|
||||
TextSecureIdentityKeyStore identityStore = ApplicationDependencies.getIdentityStore();
|
||||
ContentValues values = getValuesForStorageContact(update.getNew(), false);
|
||||
|
||||
try {
|
||||
int updateCount = db.update(TABLE_NAME, values, STORAGE_SERVICE_ID + " = ?", new String[]{Base64.encodeBytes(update.getOld().getId().getRaw())});
|
||||
@@ -842,14 +843,14 @@ public class RecipientDatabase extends Database {
|
||||
}
|
||||
|
||||
try {
|
||||
Optional<IdentityRecord> oldIdentityRecord = identityDatabase.getIdentity(recipientId);
|
||||
Optional<IdentityRecord> oldIdentityRecord = identityStore.getIdentityRecord(recipientId);
|
||||
|
||||
if (update.getNew().getIdentityKey().isPresent() && update.getNew().getAddress().hasValidUuid()) {
|
||||
IdentityKey identityKey = new IdentityKey(update.getNew().getIdentityKey().get(), 0);
|
||||
DatabaseFactory.getIdentityDatabase(context).updateIdentityAfterSync(update.getNew().getAddress().getIdentifier(), identityKey, StorageSyncModels.remoteToLocalIdentityStatus(update.getNew().getIdentityState()));
|
||||
DatabaseFactory.getIdentityDatabase(context).updateIdentityAfterSync(update.getNew().getAddress().getIdentifier(), recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(update.getNew().getIdentityState()));
|
||||
}
|
||||
|
||||
Optional<IdentityRecord> newIdentityRecord = identityDatabase.getIdentity(recipientId);
|
||||
Optional<IdentityRecord> newIdentityRecord = identityStore.getIdentityRecord(recipientId);
|
||||
|
||||
if ((newIdentityRecord.isPresent() && newIdentityRecord.get().getVerifiedStatus() == VerifiedStatus.VERIFIED) &&
|
||||
(!oldIdentityRecord.isPresent() || oldIdentityRecord.get().getVerifiedStatus() != VerifiedStatus.VERIFIED))
|
||||
@@ -2901,7 +2902,7 @@ public class RecipientDatabase extends Database {
|
||||
db.update(TABLE_NAME, uuidValues, ID_WHERE, SqlUtil.buildArgs(byUuid));
|
||||
|
||||
// Identities
|
||||
db.delete(IdentityDatabase.TABLE_NAME, IdentityDatabase.ADDRESS + " = ?", SqlUtil.buildArgs(byE164));
|
||||
ApplicationDependencies.getIdentityStore().delete(e164Settings.e164);
|
||||
|
||||
// Group Receipts
|
||||
ContentValues groupReceiptValues = new ContentValues();
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.database.identity;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.thoughtcrime.securesms.database.model
|
||||
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.libsignal.IdentityKey
|
||||
|
||||
data class IdentityRecord(
|
||||
val recipientId: RecipientId,
|
||||
val identityKey: IdentityKey,
|
||||
val verifiedStatus: IdentityDatabase.VerifiedStatus,
|
||||
@get:JvmName("isFirstUse")
|
||||
val firstUse: Boolean,
|
||||
val timestamp: Long,
|
||||
@get:JvmName("isApprovedNonBlocking")
|
||||
val nonblockingApproval: Boolean
|
||||
)
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.database.model
|
||||
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.libsignal.IdentityKey
|
||||
|
||||
data class IdentityStoreRecord(
|
||||
@@ -10,4 +11,15 @@ data class IdentityStoreRecord(
|
||||
val firstUse: Boolean,
|
||||
val timestamp: Long,
|
||||
val nonblockingApproval: Boolean
|
||||
)
|
||||
) {
|
||||
fun toIdentityRecord(recipientId: RecipientId): IdentityRecord {
|
||||
return IdentityRecord(
|
||||
recipientId = recipientId,
|
||||
identityKey = identityKey,
|
||||
verifiedStatus = verifiedStatus,
|
||||
firstUse = firstUse,
|
||||
timestamp = timestamp,
|
||||
nonblockingApproval = nonblockingApproval
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user