Add a write-through cache to the identity store.
This commit is contained in:
parent
50dfe7bc25
commit
7ac83625d3
32 changed files with 469 additions and 388 deletions
|
@ -55,12 +55,10 @@ import android.widget.Toast;
|
|||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.core.view.OneShotPreDrawListener;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||
import androidx.transition.TransitionManager;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
|
@ -70,9 +68,9 @@ import org.thoughtcrime.securesms.components.camera.CameraView;
|
|||
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceVerifiedUpdateJob;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
|
@ -119,7 +117,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement
|
|||
private final VerifyScanFragment scanFragment = new VerifyScanFragment();
|
||||
|
||||
public static Intent newIntent(@NonNull Context context,
|
||||
@NonNull IdentityDatabase.IdentityRecord identityRecord)
|
||||
@NonNull IdentityRecord identityRecord)
|
||||
{
|
||||
return newIntent(context,
|
||||
identityRecord.getRecipientId(),
|
||||
|
@ -128,7 +126,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement
|
|||
}
|
||||
|
||||
public static Intent newIntent(@NonNull Context context,
|
||||
@NonNull IdentityDatabase.IdentityRecord identityRecord,
|
||||
@NonNull IdentityRecord identityRecord,
|
||||
boolean verified)
|
||||
{
|
||||
return newIntent(context,
|
||||
|
@ -642,16 +640,15 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement
|
|||
try (SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) {
|
||||
if (verified) {
|
||||
Log.i(TAG, "Saving identity: " + recipientId);
|
||||
DatabaseFactory.getIdentityDatabase(getActivity())
|
||||
.saveIdentity(recipientId,
|
||||
remoteIdentity,
|
||||
VerifiedStatus.VERIFIED, false,
|
||||
System.currentTimeMillis(), true);
|
||||
ApplicationDependencies.getIdentityStore()
|
||||
.saveIdentityWithoutSideEffects(recipientId,
|
||||
remoteIdentity,
|
||||
VerifiedStatus.VERIFIED,
|
||||
false,
|
||||
System.currentTimeMillis(),
|
||||
true);
|
||||
} else {
|
||||
DatabaseFactory.getIdentityDatabase(getActivity())
|
||||
.setVerified(recipientId,
|
||||
remoteIdentity,
|
||||
VerifiedStatus.DEFAULT);
|
||||
ApplicationDependencies.getIdentityStore().setVerified(recipientId, remoteIdentity, VerifiedStatus.DEFAULT);
|
||||
}
|
||||
|
||||
ApplicationDependencies.getJobManager()
|
||||
|
|
|
@ -9,9 +9,11 @@ import androidx.appcompat.app.AlertDialog;
|
|||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.whispersystems.signalservice.api.SignalSessionLock;
|
||||
|
||||
|
@ -40,12 +42,12 @@ public class UntrustedSendDialog extends AlertDialog.Builder implements DialogIn
|
|||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
final IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(getContext());
|
||||
final TextSecureIdentityKeyStore identityStore = ApplicationDependencies.getIdentityStore();
|
||||
|
||||
SimpleTask.run(() -> {
|
||||
try(SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) {
|
||||
for (IdentityRecord identityRecord : untrustedRecords) {
|
||||
identityDatabase.setApproval(identityRecord.getRecipientId(), true);
|
||||
identityStore.setApproval(identityRecord.getRecipientId(), true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import androidx.annotation.RequiresApi;
|
|||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
|
@ -11,7 +11,9 @@ import org.thoughtcrime.securesms.R;
|
|||
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.whispersystems.signalservice.api.SignalSessionLock;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -39,27 +41,16 @@ public class UnverifiedSendDialog extends AlertDialog.Builder implements DialogI
|
|||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
final IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(getContext());
|
||||
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
try(SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) {
|
||||
for (IdentityRecord identityRecord : untrustedRecords) {
|
||||
identityDatabase.setVerified(identityRecord.getRecipientId(),
|
||||
identityRecord.getIdentityKey(),
|
||||
IdentityDatabase.VerifiedStatus.DEFAULT);
|
||||
}
|
||||
SimpleTask.run(() -> {
|
||||
try(SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) {
|
||||
for (IdentityRecord identityRecord : untrustedRecords) {
|
||||
ApplicationDependencies.getIdentityStore().setVerified(identityRecord.getRecipientId(),
|
||||
identityRecord.getIdentityKey(),
|
||||
IdentityDatabase.VerifiedStatus.DEFAULT);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
resendListener.onResendMessage();
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
return null;
|
||||
}, nothing -> resendListener.onResendMessage());
|
||||
}
|
||||
|
||||
public interface ResendListener {
|
||||
|
|
|
@ -11,8 +11,8 @@ import org.signal.storageservice.protos.groups.local.DecryptedPendingMember
|
|||
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase
|
||||
import org.thoughtcrime.securesms.database.MediaDatabase
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.groups.GroupManager
|
||||
|
@ -64,13 +64,9 @@ class ConversationSettingsRepository(
|
|||
SignalExecutors.BOUNDED.execute { consumer(DatabaseFactory.getGroupDatabase(context).activeGroupCount > 0) }
|
||||
}
|
||||
|
||||
fun getIdentity(recipientId: RecipientId, consumer: (IdentityDatabase.IdentityRecord?) -> Unit) {
|
||||
fun getIdentity(recipientId: RecipientId, consumer: (IdentityRecord?) -> Unit) {
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
consumer(
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
.getIdentity(recipientId)
|
||||
.orNull()
|
||||
)
|
||||
consumer(ApplicationDependencies.getIdentityStore().getIdentityRecord(recipientId).orNull())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.components.settings.conversation
|
|||
import android.database.Cursor
|
||||
import org.thoughtcrime.securesms.components.settings.conversation.preferences.ButtonStripPreference
|
||||
import org.thoughtcrime.securesms.components.settings.conversation.preferences.LegacyGroupPreference
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
@ -43,7 +43,7 @@ sealed class SpecificSettingsState {
|
|||
abstract val isLoaded: Boolean
|
||||
|
||||
data class RecipientSettingsState(
|
||||
val identityRecord: IdentityDatabase.IdentityRecord? = null,
|
||||
val identityRecord: IdentityRecord? = null,
|
||||
val allGroupsInCommon: List<Recipient> = listOf(),
|
||||
val groupsInCommon: List<Recipient> = listOf(),
|
||||
val selfHasGroups: Boolean = false,
|
||||
|
|
|
@ -42,8 +42,7 @@ class WebRtcCallRepository {
|
|||
@WorkerThread
|
||||
void getIdentityRecords(@NonNull Recipient recipient, @NonNull Consumer<IdentityRecordList> consumer) {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
|
||||
List<Recipient> recipients;
|
||||
List<Recipient> recipients;
|
||||
|
||||
if (recipient.isGroup()) {
|
||||
recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.requireGroupId(), GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF);
|
||||
|
@ -51,7 +50,7 @@ class WebRtcCallRepository {
|
|||
recipients = Collections.singletonList(recipient);
|
||||
}
|
||||
|
||||
consumer.accept(identityDatabase.getIdentities(recipients));
|
||||
consumer.accept(ApplicationDependencies.getIdentityStore().getIdentityRecords(recipients));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.signal.core.util.ThreadUtil;
|
|||
import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor;
|
||||
import org.thoughtcrime.securesms.components.sensors.Orientation;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||
import org.thoughtcrime.securesms.events.CallParticipantId;
|
||||
|
@ -440,7 +441,7 @@ public class WebRtcCallViewModel extends ViewModel {
|
|||
if (recipient.isGroup()) {
|
||||
repository.getIdentityRecords(recipient, identityRecords -> {
|
||||
if (identityRecords.isUntrusted(false) || identityRecords.isUnverified(false)) {
|
||||
List<IdentityDatabase.IdentityRecord> records = identityRecords.getUnverifiedRecords();
|
||||
List<IdentityRecord> records = identityRecords.getUnverifiedRecords();
|
||||
records.addAll(identityRecords.getUntrustedRecords());
|
||||
events.postValue(new Event.ShowGroupCallSafetyNumberChange(records));
|
||||
} else {
|
||||
|
@ -475,13 +476,13 @@ public class WebRtcCallViewModel extends ViewModel {
|
|||
}
|
||||
|
||||
public static class ShowGroupCallSafetyNumberChange extends Event {
|
||||
private final List<IdentityDatabase.IdentityRecord> identityRecords;
|
||||
private final List<IdentityRecord> identityRecords;
|
||||
|
||||
public ShowGroupCallSafetyNumberChange(@NonNull List<IdentityDatabase.IdentityRecord> identityRecords) {
|
||||
public ShowGroupCallSafetyNumberChange(@NonNull List<IdentityRecord> identityRecords) {
|
||||
this.identityRecords = identityRecords;
|
||||
}
|
||||
|
||||
public @NonNull List<IdentityDatabase.IdentityRecord> getIdentityRecords() {
|
||||
public @NonNull List<IdentityRecord> getIdentityRecords() {
|
||||
return identityRecords;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -159,7 +159,7 @@ import org.thoughtcrime.securesms.database.DraftDatabase.Draft;
|
|||
import org.thoughtcrime.securesms.database.DraftDatabase.Drafts;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
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.database.MentionUtil;
|
||||
import org.thoughtcrime.securesms.database.MentionUtil.UpdatedBodyAndMentions;
|
||||
|
@ -1957,8 +1957,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
|||
new AsyncTask<Recipient, Void, Pair<IdentityRecordList, String>>() {
|
||||
@Override
|
||||
protected @NonNull Pair<IdentityRecordList, String> doInBackground(Recipient... params) {
|
||||
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(ConversationActivity.this);
|
||||
List<Recipient> recipients;
|
||||
List<Recipient> recipients;
|
||||
|
||||
if (params[0].isGroup()) {
|
||||
recipients = DatabaseFactory.getGroupDatabase(ConversationActivity.this)
|
||||
|
@ -1968,7 +1967,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
|||
}
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
IdentityRecordList identityRecordList = identityDatabase.getIdentities(recipients);
|
||||
IdentityRecordList identityRecordList = ApplicationDependencies.getIdentityStore().getIdentityRecords(recipients);
|
||||
|
||||
Log.i(TAG, String.format(Locale.US, "Loaded %d identities in %d ms", recipients.size(), System.currentTimeMillis() - startTime));
|
||||
|
||||
|
@ -3954,27 +3953,16 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
|||
private class UnverifiedDismissedListener implements UnverifiedBannerView.DismissListener {
|
||||
@Override
|
||||
public void onDismissed(final List<IdentityRecord> unverifiedIdentities) {
|
||||
final IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(ConversationActivity.this);
|
||||
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
try (SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) {
|
||||
for (IdentityRecord identityRecord : unverifiedIdentities) {
|
||||
identityDatabase.setVerified(identityRecord.getRecipientId(),
|
||||
identityRecord.getIdentityKey(),
|
||||
VerifiedStatus.DEFAULT);
|
||||
}
|
||||
SimpleTask.run(() -> {
|
||||
try (SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) {
|
||||
for (IdentityRecord identityRecord : unverifiedIdentities) {
|
||||
ApplicationDependencies.getIdentityStore().setVerified(identityRecord.getRecipientId(),
|
||||
identityRecord.getIdentityKey(),
|
||||
VerifiedStatus.DEFAULT);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
initializeIdentityRecords();
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
return null;
|
||||
}, nothing -> initializeIdentityRecords());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1654,7 +1654,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
|||
.setView(R.layout.safety_number_changed_learn_more_dialog)
|
||||
.setPositiveButton(R.string.ConversationFragment_verify, (d, w) -> {
|
||||
SimpleTask.run(getLifecycle(), () -> {
|
||||
return DatabaseFactory.getIdentityDatabase(requireContext()).getIdentity(recipient.getId());
|
||||
return ApplicationDependencies.getIdentityStore().getIdentityRecord(recipient.getId());
|
||||
}, identityRecord -> {
|
||||
if (identityRecord.isPresent()) {
|
||||
startActivity(VerifyIdentityActivity.newIntent(requireContext(), identityRecord.get()));
|
||||
|
|
|
@ -30,7 +30,7 @@ import org.thoughtcrime.securesms.conversation.colors.Colorizer;
|
|||
import org.thoughtcrime.securesms.conversation.mutiselect.Multiselect;
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart;
|
||||
import org.thoughtcrime.securesms.conversation.ui.error.EnableCallNotificationSettingsDialog;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.model.GroupCallUpdateDetailsUtil;
|
||||
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.LiveUpdateMessage;
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.thoughtcrime.securesms.components.ContactFilterView
|
|||
import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader
|
||||
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord
|
||||
import org.thoughtcrime.securesms.keyboard.findListener
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
@ -215,7 +215,7 @@ class MultiselectForwardFragment :
|
|||
.show()
|
||||
}
|
||||
|
||||
private fun displaySafetyNumberConfirmation(identityRecords: List<IdentityDatabase.IdentityRecord>) {
|
||||
private fun displaySafetyNumberConfirmation(identityRecords: List<IdentityRecord>) {
|
||||
SafetyNumberChangeDialog.show(childFragmentManager, identityRecords)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,9 +5,10 @@ import androidx.core.util.Consumer
|
|||
import io.reactivex.rxjava3.core.Single
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||
import org.thoughtcrime.securesms.database.identity.IdentityRecordList
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.sharing.MultiShareArgs
|
||||
|
@ -26,11 +27,10 @@ class MultiselectForwardRepository(context: Context) {
|
|||
val onAllMessagesFailed: () -> Unit
|
||||
)
|
||||
|
||||
fun checkForBadIdentityRecords(shareContacts: List<ShareContact>, consumer: Consumer<List<IdentityDatabase.IdentityRecord>>) {
|
||||
fun checkForBadIdentityRecords(shareContacts: List<ShareContact>, consumer: Consumer<List<IdentityRecord>>) {
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
val identityDatabase: IdentityDatabase = DatabaseFactory.getIdentityDatabase(context)
|
||||
val recipients: List<Recipient> = shareContacts.map { Recipient.resolved(it.recipientId.get()) }
|
||||
val identityRecordList: IdentityRecordList = identityDatabase.getIdentities(recipients)
|
||||
val identityRecordList: IdentityRecordList = ApplicationDependencies.getIdentityStore().getIdentityRecords(recipients)
|
||||
|
||||
consumer.accept(identityRecordList.untrustedRecords)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package org.thoughtcrime.securesms.conversation.mutiselect.forward
|
||||
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord
|
||||
import org.thoughtcrime.securesms.sharing.ShareContact
|
||||
|
||||
data class MultiselectForwardState(
|
||||
|
@ -11,7 +11,7 @@ data class MultiselectForwardState(
|
|||
object Selection : Stage()
|
||||
object FirstConfirmation : Stage()
|
||||
object LoadingIdentities : Stage()
|
||||
data class SafetyConfirmation(val identities: List<IdentityDatabase.IdentityRecord>) : Stage()
|
||||
data class SafetyConfirmation(val identities: List<IdentityRecord>) : Stage()
|
||||
object SendPending : Stage()
|
||||
object SomeFailed : Stage()
|
||||
object AllFailed : Stage()
|
||||
|
|
|
@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.conversation.ui.error;
|
|||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.R;
|
|||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.components.FromTextView;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.adapter.AlwaysChangedDiffUtil;
|
||||
|
||||
|
@ -80,6 +81,6 @@ final class SafetyNumberChangeAdapter extends ListAdapter<ChangedRecipient, Safe
|
|||
}
|
||||
|
||||
interface Callbacks {
|
||||
void onViewIdentityRecord(@NonNull IdentityDatabase.IdentityRecord identityRecord);
|
||||
void onViewIdentityRecord(@NonNull IdentityRecord identityRecord);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.R;
|
|||
import org.thoughtcrime.securesms.VerifyIdentityActivity;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
||||
|
@ -62,9 +63,9 @@ public final class SafetyNumberChangeDialog extends DialogFragment implements Sa
|
|||
fragment.show(fragmentManager, SAFETY_NUMBER_DIALOG);
|
||||
}
|
||||
|
||||
public static void show(@NonNull FragmentManager fragmentManager, @NonNull List<IdentityDatabase.IdentityRecord> identityRecords) {
|
||||
public static void show(@NonNull FragmentManager fragmentManager, @NonNull List<IdentityRecord> identityRecords) {
|
||||
List<String> ids = Stream.of(identityRecords)
|
||||
.filterNot(IdentityDatabase.IdentityRecord::isFirstUse)
|
||||
.filterNot(IdentityRecord::isFirstUse)
|
||||
.map(record -> record.getRecipientId().serialize())
|
||||
.distinct()
|
||||
.toList();
|
||||
|
@ -102,9 +103,9 @@ public final class SafetyNumberChangeDialog extends DialogFragment implements Sa
|
|||
fragment.show(fragmentManager, SAFETY_NUMBER_DIALOG);
|
||||
}
|
||||
|
||||
public static void showForGroupCall(@NonNull FragmentManager fragmentManager, @NonNull List<IdentityDatabase.IdentityRecord> identityRecords) {
|
||||
public static void showForGroupCall(@NonNull FragmentManager fragmentManager, @NonNull List<IdentityRecord> identityRecords) {
|
||||
List<String> ids = Stream.of(identityRecords)
|
||||
.filterNot(IdentityDatabase.IdentityRecord::isFirstUse)
|
||||
.filterNot(IdentityRecord::isFirstUse)
|
||||
.map(record -> record.getRecipientId().serialize())
|
||||
.distinct()
|
||||
.toList();
|
||||
|
@ -255,7 +256,7 @@ public final class SafetyNumberChangeDialog extends DialogFragment implements Sa
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onViewIdentityRecord(@NonNull IdentityDatabase.IdentityRecord identityRecord) {
|
||||
public void onViewIdentityRecord(@NonNull IdentityRecord identityRecord) {
|
||||
startActivity(VerifyIdentityActivity.newIntent(requireContext(), identityRecord));
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.crypto.ReentrantSessionLock;
|
|||
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.MessageDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
|
@ -68,7 +68,7 @@ final class SafetyNumberChangeRepository {
|
|||
|
||||
List<Recipient> recipients = Stream.of(recipientIds).map(Recipient::resolved).toList();
|
||||
|
||||
List<ChangedRecipient> changedRecipients = Stream.of(DatabaseFactory.getIdentityDatabase(context).getIdentities(recipients).getIdentityRecords())
|
||||
List<ChangedRecipient> changedRecipients = Stream.of(ApplicationDependencies.getIdentityStore().getIdentityRecords(recipients).getIdentityRecords())
|
||||
.map(record -> new ChangedRecipient(Recipient.resolved(record.getRecipientId()), record))
|
||||
.toList();
|
||||
|
||||
|
@ -96,7 +96,7 @@ final class SafetyNumberChangeRepository {
|
|||
|
||||
@WorkerThread
|
||||
private TrustAndVerifyResult trustOrVerifyChangedRecipientsInternal(@NonNull List<ChangedRecipient> changedRecipients) {
|
||||
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
|
||||
TextSecureIdentityKeyStore identityStore = ApplicationDependencies.getIdentityStore();
|
||||
|
||||
try(SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) {
|
||||
for (ChangedRecipient changedRecipient : changedRecipients) {
|
||||
|
@ -104,12 +104,12 @@ final class SafetyNumberChangeRepository {
|
|||
|
||||
if (changedRecipient.isUnverified()) {
|
||||
Log.d(TAG, "Setting " + identityRecord.getRecipientId() + " as verified");
|
||||
identityDatabase.setVerified(identityRecord.getRecipientId(),
|
||||
identityRecord.getIdentityKey(),
|
||||
IdentityDatabase.VerifiedStatus.DEFAULT);
|
||||
ApplicationDependencies.getIdentityStore().setVerified(identityRecord.getRecipientId(),
|
||||
identityRecord.getIdentityKey(),
|
||||
IdentityDatabase.VerifiedStatus.DEFAULT);
|
||||
} else {
|
||||
Log.d(TAG, "Setting " + identityRecord.getRecipientId() + " as approved");
|
||||
identityDatabase.setApproval(identityRecord.getRecipientId(), true);
|
||||
identityStore.setApproval(identityRecord.getRecipientId(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,17 +3,23 @@ package org.thoughtcrime.securesms.crypto.storage;
|
|||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SessionUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
|
||||
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.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||
import org.thoughtcrime.securesms.util.LRUCache;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
|
@ -21,19 +27,29 @@ import org.whispersystems.libsignal.SignalProtocolAddress;
|
|||
import org.whispersystems.libsignal.state.IdentityKeyStore;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class TextSecureIdentityKeyStore implements IdentityKeyStore {
|
||||
|
||||
private static final int TIMESTAMP_THRESHOLD_SECONDS = 5;
|
||||
|
||||
private static final String TAG = Log.tag(TextSecureIdentityKeyStore.class);
|
||||
private static final Object LOCK = new Object();
|
||||
|
||||
private static final Object LOCK = new Object();
|
||||
private static final int TIMESTAMP_THRESHOLD_SECONDS = 5;
|
||||
|
||||
private final Context context;
|
||||
private final Cache cache;
|
||||
|
||||
public TextSecureIdentityKeyStore(Context context) {
|
||||
this(context, DatabaseFactory.getIdentityDatabase(context));
|
||||
}
|
||||
|
||||
TextSecureIdentityKeyStore(@NonNull Context context, @NonNull IdentityDatabase identityDatabase) {
|
||||
this.context = context;
|
||||
this.cache = new Cache(identityDatabase);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -46,40 +62,44 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
|
|||
return TextSecurePreferences.getLocalRegistrationId(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
|
||||
return saveIdentity(address, identityKey, false) == SaveResult.UPDATE;
|
||||
}
|
||||
|
||||
public @NonNull SaveResult saveIdentity(SignalProtocolAddress address, IdentityKey identityKey, boolean nonBlockingApproval) {
|
||||
synchronized (LOCK) {
|
||||
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
|
||||
RecipientId recipientId = RecipientId.fromExternalPush(address.getName());
|
||||
Optional<IdentityRecord> identityRecord = identityDatabase.getIdentity(recipientId);
|
||||
IdentityStoreRecord identityRecord = cache.get(address.getName());
|
||||
RecipientId recipientId = RecipientId.fromExternalPush(address.getName());
|
||||
|
||||
if (!identityRecord.isPresent()) {
|
||||
if (identityRecord == null) {
|
||||
Log.i(TAG, "Saving new identity...");
|
||||
identityDatabase.saveIdentity(recipientId, identityKey, VerifiedStatus.DEFAULT, true, System.currentTimeMillis(), nonBlockingApproval);
|
||||
cache.save(address.getName(), recipientId, identityKey, VerifiedStatus.DEFAULT, true, System.currentTimeMillis(), nonBlockingApproval);
|
||||
return SaveResult.NEW;
|
||||
}
|
||||
|
||||
if (!identityRecord.get().getIdentityKey().equals(identityKey)) {
|
||||
Log.i(TAG, "Replacing existing identity... Existing: " + identityRecord.get().getIdentityKey().hashCode() + " New: " + identityKey.hashCode());
|
||||
if (!identityRecord.getIdentityKey().equals(identityKey)) {
|
||||
Log.i(TAG, "Replacing existing identity... Existing: " + identityRecord.getIdentityKey().hashCode() + " New: " + identityKey.hashCode());
|
||||
VerifiedStatus verifiedStatus;
|
||||
|
||||
if (identityRecord.get().getVerifiedStatus() == VerifiedStatus.VERIFIED ||
|
||||
identityRecord.get().getVerifiedStatus() == VerifiedStatus.UNVERIFIED)
|
||||
if (identityRecord.getVerifiedStatus() == VerifiedStatus.VERIFIED ||
|
||||
identityRecord.getVerifiedStatus() == VerifiedStatus.UNVERIFIED)
|
||||
{
|
||||
verifiedStatus = VerifiedStatus.UNVERIFIED;
|
||||
} else {
|
||||
verifiedStatus = VerifiedStatus.DEFAULT;
|
||||
}
|
||||
|
||||
identityDatabase.saveIdentity(recipientId, identityKey, verifiedStatus, false, System.currentTimeMillis(), nonBlockingApproval);
|
||||
cache.save(address.getName(), recipientId, identityKey, verifiedStatus, false, System.currentTimeMillis(), nonBlockingApproval);
|
||||
IdentityUtil.markIdentityUpdate(context, recipientId);
|
||||
SessionUtil.archiveSiblingSessions(address);
|
||||
DatabaseFactory.getSenderKeySharedDatabase(context).deleteAllFor(recipientId);
|
||||
return SaveResult.UPDATE;
|
||||
}
|
||||
|
||||
if (isNonBlockingApprovalRequired(identityRecord.get())) {
|
||||
if (isNonBlockingApprovalRequired(identityRecord)) {
|
||||
Log.i(TAG, "Setting approval status...");
|
||||
identityDatabase.setApproval(recipientId, nonBlockingApproval);
|
||||
cache.setApproval(address.getName(), recipientId, identityRecord, nonBlockingApproval);
|
||||
return SaveResult.NON_BLOCKING_APPROVAL_REQUIRED;
|
||||
}
|
||||
|
||||
|
@ -87,73 +107,132 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
|
||||
return saveIdentity(address, identityKey, false) == SaveResult.UPDATE;
|
||||
public void saveIdentityWithoutSideEffects(@NonNull RecipientId recipientId,
|
||||
IdentityKey identityKey,
|
||||
VerifiedStatus verifiedStatus,
|
||||
boolean firstUse,
|
||||
long timestamp,
|
||||
boolean nonBlockingApproval)
|
||||
{
|
||||
Recipient recipient = Recipient.resolved(recipientId);
|
||||
if (recipient.hasServiceIdentifier()) {
|
||||
cache.save(recipient.requireServiceId(), recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval);
|
||||
} else {
|
||||
Log.w(TAG, "[saveIdentity] No serviceId for " + recipient.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
|
||||
synchronized (LOCK) {
|
||||
if (DatabaseFactory.getRecipientDatabase(context).containsPhoneOrUuid(address.getName())) {
|
||||
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
|
||||
RecipientId ourRecipientId = Recipient.self().getId();
|
||||
RecipientId theirRecipientId = RecipientId.fromExternalPush(address.getName());
|
||||
boolean isSelf = address.getName().equals(TextSecurePreferences.getLocalUuid(context).toString()) ||
|
||||
address.getName().equals(TextSecurePreferences.getLocalNumber(context));
|
||||
|
||||
if (ourRecipientId.equals(theirRecipientId)) {
|
||||
return identityKey.equals(IdentityKeyUtil.getIdentityKey(context));
|
||||
}
|
||||
if (isSelf) {
|
||||
return identityKey.equals(IdentityKeyUtil.getIdentityKey(context));
|
||||
}
|
||||
|
||||
switch (direction) {
|
||||
case SENDING: return isTrustedForSending(identityKey, identityDatabase.getIdentity(theirRecipientId));
|
||||
case RECEIVING: return true;
|
||||
default: throw new AssertionError("Unknown direction: " + direction);
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "Tried to check if identity is trusted for " + address.getName() + ", but no matching recipient existed!");
|
||||
switch (direction) {
|
||||
case SENDING: return false;
|
||||
case RECEIVING: return true;
|
||||
default: throw new AssertionError("Unknown direction: " + direction);
|
||||
}
|
||||
}
|
||||
IdentityStoreRecord record = cache.get(address.getName());
|
||||
|
||||
switch (direction) {
|
||||
case SENDING:
|
||||
return isTrustedForSending(identityKey, record);
|
||||
case RECEIVING:
|
||||
return true;
|
||||
default:
|
||||
throw new AssertionError("Unknown direction: " + direction);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKey getIdentity(SignalProtocolAddress address) {
|
||||
if (DatabaseFactory.getRecipientDatabase(context).containsPhoneOrUuid(address.getName())) {
|
||||
RecipientId recipientId = RecipientId.fromExternalPush(address.getName());
|
||||
Optional<IdentityRecord> record = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipientId);
|
||||
IdentityStoreRecord record = cache.get(address.getName());
|
||||
return record != null ? record.getIdentityKey() : null;
|
||||
}
|
||||
|
||||
if (record.isPresent()) {
|
||||
return record.get().getIdentityKey();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
public @NonNull Optional<IdentityRecord> getIdentityRecord(@NonNull RecipientId recipientId) {
|
||||
Recipient recipient = Recipient.resolved(recipientId);
|
||||
|
||||
if (recipient.hasServiceIdentifier()) {
|
||||
IdentityStoreRecord record = cache.get(recipient.requireServiceId());
|
||||
return Optional.fromNullable(record).transform(r -> r.toIdentityRecord(recipientId));
|
||||
} else {
|
||||
Log.w(TAG, "Tried to get identity for " + address.getName() + ", but no matching recipient existed!");
|
||||
return null;
|
||||
Log.w(TAG, "[getIdentityRecord] No serviceId for " + recipient.getId());
|
||||
return Optional.absent();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isTrustedForSending(IdentityKey identityKey, Optional<IdentityRecord> identityRecord) {
|
||||
if (!identityRecord.isPresent()) {
|
||||
public @NonNull IdentityRecordList getIdentityRecords(@NonNull List<Recipient> recipients) {
|
||||
List<String> addressNames = recipients.stream()
|
||||
.filter(Recipient::hasServiceIdentifier)
|
||||
.map(Recipient::requireServiceId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (addressNames.isEmpty()) {
|
||||
return IdentityRecordList.EMPTY;
|
||||
}
|
||||
|
||||
List<IdentityRecord> records = new ArrayList<>(recipients.size());
|
||||
|
||||
for (Recipient recipient : recipients) {
|
||||
if (recipient.hasServiceIdentifier()) {
|
||||
IdentityStoreRecord record = cache.get(recipient.requireServiceId());
|
||||
|
||||
if (record != null) {
|
||||
records.add(record.toIdentityRecord(recipient.getId()));
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "[getIdentityRecords] No serviceId for " + recipient.getId());
|
||||
}
|
||||
}
|
||||
|
||||
return new IdentityRecordList(records);
|
||||
}
|
||||
|
||||
public void setApproval(@NonNull RecipientId recipientId, boolean nonBlockingApproval) {
|
||||
Recipient recipient = Recipient.resolved(recipientId);
|
||||
|
||||
if (recipient.hasServiceIdentifier()) {
|
||||
cache.setApproval(recipient.requireServiceId(), recipientId, nonBlockingApproval);
|
||||
} else {
|
||||
Log.w(TAG, "[setApproval] No serviceId for " + recipient.getId());
|
||||
}
|
||||
}
|
||||
|
||||
public void setVerified(@NonNull RecipientId recipientId, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
|
||||
Recipient recipient = Recipient.resolved(recipientId);
|
||||
|
||||
if (recipient.hasServiceIdentifier()) {
|
||||
cache.setVerified(recipient.requireServiceId(), recipientId, identityKey, verifiedStatus);
|
||||
} else {
|
||||
Log.w(TAG, "[setVerified] No serviceId for " + recipient.getId());
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(@NonNull String addressName) {
|
||||
cache.delete(addressName);
|
||||
}
|
||||
|
||||
public void invalidate(@NonNull String addressName) {
|
||||
cache.invalidate(addressName);
|
||||
}
|
||||
|
||||
private boolean isTrustedForSending(@NonNull IdentityKey identityKey, @Nullable IdentityStoreRecord identityRecord) {
|
||||
if (identityRecord == null) {
|
||||
Log.w(TAG, "Nothing here, returning true...");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!identityKey.equals(identityRecord.get().getIdentityKey())) {
|
||||
Log.w(TAG, "Identity keys don't match... service: " + identityKey.hashCode() + " database: " + identityRecord.get().getIdentityKey().hashCode());
|
||||
if (!identityKey.equals(identityRecord.getIdentityKey())) {
|
||||
Log.w(TAG, "Identity keys don't match... service: " + identityKey.hashCode() + " database: " + identityRecord.getIdentityKey().hashCode());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (identityRecord.get().getVerifiedStatus() == VerifiedStatus.UNVERIFIED) {
|
||||
if (identityRecord.getVerifiedStatus() == VerifiedStatus.UNVERIFIED) {
|
||||
Log.w(TAG, "Needs unverified approval!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isNonBlockingApprovalRequired(identityRecord.get())) {
|
||||
if (isNonBlockingApprovalRequired(identityRecord)) {
|
||||
Log.w(TAG, "Needs non-blocking approval!");
|
||||
return false;
|
||||
}
|
||||
|
@ -161,10 +240,78 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
|
|||
return true;
|
||||
}
|
||||
|
||||
private boolean isNonBlockingApprovalRequired(IdentityRecord identityRecord) {
|
||||
return !identityRecord.isFirstUse() &&
|
||||
System.currentTimeMillis() - identityRecord.getTimestamp() < TimeUnit.SECONDS.toMillis(TIMESTAMP_THRESHOLD_SECONDS) &&
|
||||
!identityRecord.isApprovedNonBlocking();
|
||||
private boolean isNonBlockingApprovalRequired(IdentityStoreRecord record) {
|
||||
return !record.getFirstUse() &&
|
||||
!record.getNonblockingApproval() &&
|
||||
System.currentTimeMillis() - record.getTimestamp() < TimeUnit.SECONDS.toMillis(TIMESTAMP_THRESHOLD_SECONDS);
|
||||
}
|
||||
|
||||
private static final class Cache {
|
||||
|
||||
private final Map<String, IdentityStoreRecord> cache;
|
||||
private final IdentityDatabase identityDatabase;
|
||||
|
||||
Cache(@NonNull IdentityDatabase identityDatabase) {
|
||||
this.identityDatabase = identityDatabase;
|
||||
this.cache = new LRUCache<>(200);
|
||||
}
|
||||
|
||||
public synchronized @Nullable IdentityStoreRecord get(@NonNull String addressName) {
|
||||
if (cache.containsKey(addressName)) {
|
||||
return cache.get(addressName);
|
||||
} else {
|
||||
IdentityStoreRecord record = identityDatabase.getIdentityStoreRecord(addressName);
|
||||
cache.put(addressName, record);
|
||||
return record;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void save(@NonNull String addressName, @NonNull RecipientId recipientId, @NonNull IdentityKey identityKey, @NonNull VerifiedStatus verifiedStatus, boolean firstUse, long timestamp, boolean nonBlockingApproval) {
|
||||
identityDatabase.saveIdentity(addressName, recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval);
|
||||
cache.put(addressName, new IdentityStoreRecord(addressName, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval));
|
||||
}
|
||||
|
||||
public synchronized void setApproval(@NonNull String addressName, @NonNull RecipientId recipientId, boolean nonblockingApproval) {
|
||||
setApproval(addressName, recipientId, cache.get(addressName), nonblockingApproval);
|
||||
}
|
||||
|
||||
public synchronized void setApproval(@NonNull String addressName, @NonNull RecipientId recipientId, @Nullable IdentityStoreRecord record, boolean nonblockingApproval) {
|
||||
identityDatabase.setApproval(addressName, recipientId, nonblockingApproval);
|
||||
|
||||
if (record != null) {
|
||||
cache.put(record.getAddressName(),
|
||||
new IdentityStoreRecord(record.getAddressName(),
|
||||
record.getIdentityKey(),
|
||||
record.getVerifiedStatus(),
|
||||
record.getFirstUse(),
|
||||
record.getTimestamp(),
|
||||
nonblockingApproval));
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void setVerified(@NonNull String addressName, @NonNull RecipientId recipientId, @NonNull IdentityKey identityKey, @NonNull VerifiedStatus verifiedStatus) {
|
||||
identityDatabase.setVerified(addressName, recipientId, identityKey, verifiedStatus);
|
||||
|
||||
IdentityStoreRecord record = cache.get(addressName);
|
||||
if (record != null) {
|
||||
cache.put(addressName,
|
||||
new IdentityStoreRecord(record.getAddressName(),
|
||||
record.getIdentityKey(),
|
||||
verifiedStatus,
|
||||
record.getFirstUse(),
|
||||
record.getTimestamp(),
|
||||
record.getNonblockingApproval()));
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void delete(@NonNull String addressName) {
|
||||
identityDatabase.delete(addressName);
|
||||
cache.remove(addressName);
|
||||
}
|
||||
|
||||
public synchronized void invalidate(@NonNull String addressName) {
|
||||
cache.remove(addressName);
|
||||
}
|
||||
}
|
||||
|
||||
public enum SaveResult {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
|||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
|
@ -144,10 +145,10 @@ public class MultiDeviceContactUpdateJob extends BaseJob {
|
|||
return;
|
||||
}
|
||||
|
||||
Optional<IdentityDatabase.IdentityRecord> identityRecord = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipient.getId());
|
||||
Optional<VerifiedMessage> verifiedMessage = getVerifiedMessage(recipient, identityRecord);
|
||||
Map<RecipientId, Integer> inboxPositions = DatabaseFactory.getThreadDatabase(context).getInboxPositions();
|
||||
Set<RecipientId> archived = DatabaseFactory.getThreadDatabase(context).getArchivedRecipients();
|
||||
Optional<IdentityRecord> identityRecord = ApplicationDependencies.getIdentityStore().getIdentityRecord(recipient.getId());
|
||||
Optional<VerifiedMessage> verifiedMessage = getVerifiedMessage(recipient, identityRecord);
|
||||
Map<RecipientId, Integer> inboxPositions = DatabaseFactory.getThreadDatabase(context).getInboxPositions();
|
||||
Set<RecipientId> archived = DatabaseFactory.getThreadDatabase(context).getArchivedRecipients();
|
||||
|
||||
out.write(new DeviceContact(RecipientUtil.toSignalServiceAddress(context, recipient),
|
||||
Optional.fromNullable(recipient.isGroup() || recipient.isSystemContact() ? recipient.getDisplayName(context) : null),
|
||||
|
@ -203,13 +204,13 @@ public class MultiDeviceContactUpdateJob extends BaseJob {
|
|||
Set<RecipientId> archived = DatabaseFactory.getThreadDatabase(context).getArchivedRecipients();
|
||||
|
||||
for (Recipient recipient : recipients) {
|
||||
Optional<IdentityDatabase.IdentityRecord> identity = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipient.getId());
|
||||
Optional<VerifiedMessage> verified = getVerifiedMessage(recipient, identity);
|
||||
Optional<String> name = Optional.fromNullable(recipient.isSystemContact() ? recipient.getDisplayName(context) : recipient.getGroupName(context));
|
||||
Optional<ProfileKey> profileKey = ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey());
|
||||
boolean blocked = recipient.isBlocked();
|
||||
Optional<Integer> expireTimer = recipient.getExpiresInSeconds() > 0 ? Optional.of(recipient.getExpiresInSeconds()) : Optional.absent();
|
||||
Optional<Integer> inboxPosition = Optional.fromNullable(inboxPositions.get(recipient.getId()));
|
||||
Optional<IdentityRecord> identity = ApplicationDependencies.getIdentityStore().getIdentityRecord(recipient.getId());
|
||||
Optional<VerifiedMessage> verified = getVerifiedMessage(recipient, identity);
|
||||
Optional<String> name = Optional.fromNullable(recipient.isSystemContact() ? recipient.getDisplayName(context) : recipient.getGroupName(context));
|
||||
Optional<ProfileKey> profileKey = ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey());
|
||||
boolean blocked = recipient.isBlocked();
|
||||
Optional<Integer> expireTimer = recipient.getExpiresInSeconds() > 0 ? Optional.of(recipient.getExpiresInSeconds()) : Optional.absent();
|
||||
Optional<Integer> inboxPosition = Optional.fromNullable(inboxPositions.get(recipient.getId()));
|
||||
|
||||
out.write(new DeviceContact(RecipientUtil.toSignalServiceAddress(context, recipient),
|
||||
name,
|
||||
|
@ -386,7 +387,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob {
|
|||
}
|
||||
}
|
||||
|
||||
private Optional<VerifiedMessage> getVerifiedMessage(Recipient recipient, Optional<IdentityDatabase.IdentityRecord> identity)
|
||||
private Optional<VerifiedMessage> getVerifiedMessage(Recipient recipient, Optional<IdentityRecord> identity)
|
||||
throws InvalidNumberException, IOException
|
||||
{
|
||||
if (!identity.isPresent()) return Optional.absent();
|
||||
|
|
|
@ -365,10 +365,7 @@ public class RetrieveProfileJob extends BaseJob {
|
|||
|
||||
IdentityKey identityKey = new IdentityKey(Base64.decode(identityKeyValue), 0);
|
||||
|
||||
if (!DatabaseFactory.getIdentityDatabase(context)
|
||||
.getIdentity(recipient.getId())
|
||||
.isPresent())
|
||||
{
|
||||
if (!ApplicationDependencies.getIdentityStore().getIdentityRecord(recipient.getId()).isPresent()) {
|
||||
Log.w(TAG, "Still first use...");
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -552,7 +552,7 @@ public final class MessageContentProcessor {
|
|||
database.markAsMissedCall(smsMessageId.get(), message.getType() == OfferMessage.Type.VIDEO_CALL);
|
||||
} else {
|
||||
RemotePeer remotePeer = new RemotePeer(senderRecipient.getId());
|
||||
byte[] remoteIdentityKey = DatabaseFactory.getIdentityDatabase(context).getIdentity(senderRecipient.getId()).transform(record -> record.getIdentityKey().serialize()).orNull();
|
||||
byte[] remoteIdentityKey = ApplicationDependencies.getIdentityStore().getIdentityRecord(senderRecipient.getId()).transform(record -> record.getIdentityKey().serialize()).orNull();
|
||||
|
||||
ApplicationDependencies.getSignalCallManager()
|
||||
.receivedOffer(new WebRtcData.CallMetadata(remotePeer, new CallId(message.getId()), content.getSenderDevice()),
|
||||
|
@ -570,7 +570,7 @@ public final class MessageContentProcessor {
|
|||
{
|
||||
log(String.valueOf(content), "handleCallAnswerMessage...");
|
||||
RemotePeer remotePeer = new RemotePeer(senderRecipient.getId());
|
||||
byte[] remoteIdentityKey = DatabaseFactory.getIdentityDatabase(context).getIdentity(senderRecipient.getId()).transform(record -> record.getIdentityKey().serialize()).orNull();
|
||||
byte[] remoteIdentityKey = ApplicationDependencies.getIdentityStore().getIdentityRecord(senderRecipient.getId()).transform(record -> record.getIdentityKey().serialize()).orNull();
|
||||
|
||||
ApplicationDependencies.getSignalCallManager()
|
||||
.receivedAnswer(new WebRtcData.CallMetadata(remotePeer, new CallId(message.getId()), content.getSenderDevice()),
|
||||
|
|
|
@ -12,6 +12,8 @@ import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
|||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeException;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.GroupManager;
|
||||
|
@ -51,11 +53,9 @@ final class RecipientDialogRepository {
|
|||
return groupId;
|
||||
}
|
||||
|
||||
void getIdentity(@NonNull Consumer<IdentityDatabase.IdentityRecord> callback) {
|
||||
void getIdentity(@NonNull Consumer<IdentityRecord> callback) {
|
||||
SignalExecutors.BOUNDED.execute(
|
||||
() -> callback.accept(DatabaseFactory.getIdentityDatabase(context)
|
||||
.getIdentity(recipientId)
|
||||
.orNull()));
|
||||
() -> callback.accept(ApplicationDependencies.getIdentityStore().getIdentityRecord(recipientId).orNull()));
|
||||
}
|
||||
|
||||
void getRecipient(@NonNull RecipientCallback recipientCallback) {
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.VerifyIdentityActivity;
|
|||
import org.thoughtcrime.securesms.components.settings.conversation.ConversationSettingsActivity;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.LiveGroup;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
|
||||
|
@ -37,13 +38,13 @@ import java.util.Objects;
|
|||
|
||||
final class RecipientDialogViewModel extends ViewModel {
|
||||
|
||||
private final Context context;
|
||||
private final RecipientDialogRepository recipientDialogRepository;
|
||||
private final LiveData<Recipient> recipient;
|
||||
private final MutableLiveData<IdentityDatabase.IdentityRecord> identity;
|
||||
private final LiveData<AdminActionStatus> adminActionStatus;
|
||||
private final LiveData<Boolean> canAddToAGroup;
|
||||
private final MutableLiveData<Boolean> adminActionBusy;
|
||||
private final Context context;
|
||||
private final RecipientDialogRepository recipientDialogRepository;
|
||||
private final LiveData<Recipient> recipient;
|
||||
private final MutableLiveData<IdentityRecord> identity;
|
||||
private final LiveData<AdminActionStatus> adminActionStatus;
|
||||
private final LiveData<Boolean> canAddToAGroup;
|
||||
private final MutableLiveData<Boolean> adminActionBusy;
|
||||
|
||||
private RecipientDialogViewModel(@NonNull Context context,
|
||||
@NonNull RecipientDialogRepository recipientDialogRepository)
|
||||
|
@ -101,7 +102,7 @@ final class RecipientDialogViewModel extends ViewModel {
|
|||
return adminActionStatus;
|
||||
}
|
||||
|
||||
LiveData<IdentityDatabase.IdentityRecord> getIdentity() {
|
||||
LiveData<IdentityRecord> getIdentity() {
|
||||
return identity;
|
||||
}
|
||||
|
||||
|
@ -133,7 +134,7 @@ final class RecipientDialogViewModel extends ViewModel {
|
|||
recipientDialogRepository.getRecipient(recipient -> BlockUnblockDialog.showUnblockFor(activity, activity.getLifecycle(), recipient, () -> RecipientUtil.unblock(context, recipient)));
|
||||
}
|
||||
|
||||
void onViewSafetyNumberClicked(@NonNull Activity activity, @NonNull IdentityDatabase.IdentityRecord identityRecord) {
|
||||
void onViewSafetyNumberClicked(@NonNull Activity activity, @NonNull IdentityRecord identityRecord) {
|
||||
activity.startActivity(VerifyIdentityActivity.newIntent(activity, identityRecord));
|
||||
}
|
||||
|
||||
|
|
|
@ -157,10 +157,13 @@ public final class RegistrationRepository {
|
|||
TextSecurePreferences.setFcmDisabled(context, registrationData.isNotFcm());
|
||||
TextSecurePreferences.setWebsocketRegistered(context, true);
|
||||
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
.saveIdentity(selfId,
|
||||
identityKey.getPublicKey(), IdentityDatabase.VerifiedStatus.VERIFIED,
|
||||
true, System.currentTimeMillis(), true);
|
||||
ApplicationDependencies.getIdentityStore()
|
||||
.saveIdentityWithoutSideEffects(selfId,
|
||||
identityKey.getPublicKey(),
|
||||
IdentityDatabase.VerifiedStatus.VERIFIED,
|
||||
true,
|
||||
System.currentTimeMillis(),
|
||||
true);
|
||||
|
||||
TextSecurePreferences.setPushRegistered(context, true);
|
||||
TextSecurePreferences.setPushServerPassword(context, registrationData.getPassword());
|
||||
|
|
|
@ -10,11 +10,12 @@ import org.signal.core.util.concurrent.SignalExecutors;
|
|||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.MessageDatabase;
|
||||
import org.thoughtcrime.securesms.database.MessageDatabase.InsertResult;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
|
@ -52,8 +53,7 @@ public final class IdentityUtil {
|
|||
final RecipientId recipientId = recipient.getId();
|
||||
|
||||
SimpleTask.run(SignalExecutors.BOUNDED,
|
||||
() -> DatabaseFactory.getIdentityDatabase(context)
|
||||
.getIdentity(recipientId),
|
||||
() -> ApplicationDependencies.getIdentityStore().getIdentityRecord(recipientId),
|
||||
future::set);
|
||||
|
||||
return future;
|
||||
|
@ -161,9 +161,9 @@ public final class IdentityUtil {
|
|||
|
||||
public static void processVerifiedMessage(Context context, VerifiedMessage verifiedMessage) {
|
||||
try(SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) {
|
||||
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
|
||||
Recipient recipient = Recipient.externalPush(context, verifiedMessage.getDestination());
|
||||
Optional<IdentityRecord> identityRecord = identityDatabase.getIdentity(recipient.getId());
|
||||
TextSecureIdentityKeyStore identityStore = ApplicationDependencies.getIdentityStore();
|
||||
Recipient recipient = Recipient.externalPush(context, verifiedMessage.getDestination());
|
||||
Optional<IdentityRecord> identityRecord = identityStore.getIdentityRecord(recipient.getId());
|
||||
|
||||
if (!identityRecord.isPresent() && verifiedMessage.getVerified() == VerifiedMessage.VerifiedState.DEFAULT) {
|
||||
Log.w(TAG, "No existing record for default status");
|
||||
|
@ -175,7 +175,7 @@ public final class IdentityUtil {
|
|||
identityRecord.get().getIdentityKey().equals(verifiedMessage.getIdentityKey()) &&
|
||||
identityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.DEFAULT)
|
||||
{
|
||||
identityDatabase.setVerified(recipient.getId(), identityRecord.get().getIdentityKey(), IdentityDatabase.VerifiedStatus.DEFAULT);
|
||||
identityStore.setVerified(recipient.getId(), identityRecord.get().getIdentityKey(), IdentityDatabase.VerifiedStatus.DEFAULT);
|
||||
markIdentityVerified(context, recipient, false, true);
|
||||
}
|
||||
|
||||
|
@ -185,7 +185,7 @@ public final class IdentityUtil {
|
|||
(identityRecord.isPresent() && identityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.VERIFIED)))
|
||||
{
|
||||
saveIdentity(verifiedMessage.getDestination().getIdentifier(), verifiedMessage.getIdentityKey());
|
||||
identityDatabase.setVerified(recipient.getId(), verifiedMessage.getIdentityKey(), IdentityDatabase.VerifiedStatus.VERIFIED);
|
||||
identityStore.setVerified(recipient.getId(), verifiedMessage.getIdentityKey(), IdentityDatabase.VerifiedStatus.VERIFIED);
|
||||
markIdentityVerified(context, recipient, true, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
package org.thoughtcrime.securesms.crypto.storage
|
||||
|
||||
import android.content.Context
|
||||
import junit.framework.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.times
|
||||
import org.mockito.Mockito.verify
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase
|
||||
import org.thoughtcrime.securesms.database.model.IdentityStoreRecord
|
||||
import org.whispersystems.libsignal.IdentityKey
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress
|
||||
import org.whispersystems.libsignal.ecc.ECPublicKey
|
||||
|
||||
class TextSecureIdentityKeyStoreTest {
|
||||
|
||||
companion object {
|
||||
private const val ADDRESS = "address1"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getIdentity() hits disk on first retrieve but not the second`() {
|
||||
val mockDb = mock(IdentityDatabase::class.java)
|
||||
val subject = TextSecureIdentityKeyStore(mock(Context::class.java), mockDb)
|
||||
val identityKey = IdentityKey(ECPublicKey.fromPublicKeyBytes(ByteArray(32)))
|
||||
val record = mockRecord(ADDRESS, identityKey)
|
||||
|
||||
`when`(mockDb.getIdentityStoreRecord(ADDRESS)).thenReturn(record)
|
||||
|
||||
assertEquals(identityKey, subject.getIdentity(SignalProtocolAddress(ADDRESS, 1)))
|
||||
verify(mockDb, times(1)).getIdentityStoreRecord(ADDRESS)
|
||||
|
||||
assertEquals(identityKey, subject.getIdentity(SignalProtocolAddress(ADDRESS, 1)))
|
||||
verify(mockDb, times(1)).getIdentityStoreRecord(ADDRESS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invalidate() evicts cache entry`() {
|
||||
val mockDb = mock(IdentityDatabase::class.java)
|
||||
val subject = TextSecureIdentityKeyStore(mock(Context::class.java), mockDb)
|
||||
val identityKey = IdentityKey(ECPublicKey.fromPublicKeyBytes(ByteArray(32)))
|
||||
val record = mockRecord(ADDRESS, identityKey)
|
||||
|
||||
`when`(mockDb.getIdentityStoreRecord(ADDRESS)).thenReturn(record)
|
||||
|
||||
assertEquals(identityKey, subject.getIdentity(SignalProtocolAddress(ADDRESS, 1)))
|
||||
verify(mockDb, times(1)).getIdentityStoreRecord(ADDRESS)
|
||||
|
||||
subject.invalidate(ADDRESS)
|
||||
|
||||
assertEquals(identityKey, subject.getIdentity(SignalProtocolAddress(ADDRESS, 1)))
|
||||
verify(mockDb, times(2)).getIdentityStoreRecord(ADDRESS)
|
||||
}
|
||||
|
||||
private fun mockRecord(addressName: String, identityKey: IdentityKey): IdentityStoreRecord {
|
||||
return IdentityStoreRecord(
|
||||
addressName = addressName,
|
||||
identityKey = identityKey,
|
||||
verifiedStatus = IdentityDatabase.VerifiedStatus.DEFAULT,
|
||||
firstUse = false,
|
||||
timestamp = 1,
|
||||
nonblockingApproval = true
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue