diff --git a/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java b/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java index 2ca59a9fed..81f0940a65 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java @@ -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() diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/identity/UntrustedSendDialog.java b/app/src/main/java/org/thoughtcrime/securesms/components/identity/UntrustedSendDialog.java index e8019bf664..9c44f69fd9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/identity/UntrustedSendDialog.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/identity/UntrustedSendDialog.java @@ -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); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/identity/UnverifiedBannerView.java b/app/src/main/java/org/thoughtcrime/securesms/components/identity/UnverifiedBannerView.java index b19098d731..5ff794fac3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/identity/UnverifiedBannerView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/identity/UnverifiedBannerView.java @@ -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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/identity/UnverifiedSendDialog.java b/app/src/main/java/org/thoughtcrime/securesms/components/identity/UnverifiedSendDialog.java index 2a182c2c03..f19978a4aa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/identity/UnverifiedSendDialog.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/identity/UnverifiedSendDialog.java @@ -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() { - @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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsRepository.kt index e32a4daa12..1ba0d3be80 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsRepository.kt @@ -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()) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsState.kt index a89738a82a..1b826ea0d3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsState.kt @@ -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 = listOf(), val groupsInCommon: List = listOf(), val selfHasGroups: Boolean = false, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallRepository.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallRepository.java index cdf046dfcf..c099d9bfe7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallRepository.java @@ -42,8 +42,7 @@ class WebRtcCallRepository { @WorkerThread void getIdentityRecords(@NonNull Recipient recipient, @NonNull Consumer consumer) { SignalExecutors.BOUNDED.execute(() -> { - IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context); - List recipients; + List 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)); }); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallViewModel.java index 3ef9e7f338..dcbad0be93 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallViewModel.java @@ -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 records = identityRecords.getUnverifiedRecords(); + List 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 identityRecords; + private final List identityRecords; - public ShowGroupCallSafetyNumberChange(@NonNull List identityRecords) { + public ShowGroupCallSafetyNumberChange(@NonNull List identityRecords) { this.identityRecords = identityRecords; } - public @NonNull List getIdentityRecords() { + public @NonNull List getIdentityRecords() { return identityRecords; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 9f017f1c19..5f19e82002 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -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>() { @Override protected @NonNull Pair doInBackground(Recipient... params) { - IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(ConversationActivity.this); - List recipients; + List 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 unverifiedIdentities) { - final IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(ConversationActivity.this); - - new AsyncTask() { - @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()); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 0fef0c5bdb..7a2814196d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -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())); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java index f0c1be68af..dd338609ab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java @@ -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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt index 6443bb0083..ecfd895056 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt @@ -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) { + private fun displaySafetyNumberConfirmation(identityRecords: List) { SafetyNumberChangeDialog.show(childFragmentManager, identityRecords) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardRepository.kt index 9872614b69..9d26f7cb52 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardRepository.kt @@ -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, consumer: Consumer>) { + fun checkForBadIdentityRecords(shareContacts: List, consumer: Consumer>) { SignalExecutors.BOUNDED.execute { - val identityDatabase: IdentityDatabase = DatabaseFactory.getIdentityDatabase(context) val recipients: List = shareContacts.map { Recipient.resolved(it.recipientId.get()) } - val identityRecordList: IdentityRecordList = identityDatabase.getIdentities(recipients) + val identityRecordList: IdentityRecordList = ApplicationDependencies.getIdentityStore().getIdentityRecords(recipients) consumer.accept(identityRecordList.untrustedRecords) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardState.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardState.kt index 87b0ad106a..a73a335336 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardState.kt @@ -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) : Stage() + data class SafetyConfirmation(val identities: List) : Stage() object SendPending : Stage() object SomeFailed : Stage() object AllFailed : Stage() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/ChangedRecipient.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/ChangedRecipient.java index 1767c37c48..a16c781f35 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/ChangedRecipient.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/ChangedRecipient.java @@ -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; /** diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeAdapter.java index 33afb9b286..f0f834b273 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeAdapter.java @@ -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 identityRecords) { + public static void show(@NonNull FragmentManager fragmentManager, @NonNull List identityRecords) { List 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 identityRecords) { + public static void showForGroupCall(@NonNull FragmentManager fragmentManager, @NonNull List identityRecords) { List 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)); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java index 7d481a6920..7fdf2eb9cb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java @@ -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 recipients = Stream.of(recipientIds).map(Recipient::resolved).toList(); - List changedRecipients = Stream.of(DatabaseFactory.getIdentityDatabase(context).getIdentities(recipients).getIdentityRecords()) + List 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 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); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java index 18550d4117..eb0dc3260b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java @@ -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 = 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 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 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) { - if (!identityRecord.isPresent()) { + public @NonNull IdentityRecordList getIdentityRecords(@NonNull List recipients) { + List addressNames = recipients.stream() + .filter(Recipient::hasServiceIdentifier) + .map(Recipient::requireServiceId) + .collect(Collectors.toList()); + + if (addressNames.isEmpty()) { + return IdentityRecordList.EMPTY; + } + + List 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 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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/IdentityDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/IdentityDatabase.java index c8c94a1185..14e3385397 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/IdentityDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/IdentityDatabase.java @@ -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 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 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 recipients) { - List 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 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 record = getIdentity(recipientId); + Optional 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 record = getIdentity(addressName); + Optional 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 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(); - } - } - } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index ede2f3832d..fd380e3756 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -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 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 oldIdentityRecord = identityDatabase.getIdentity(recipientId); + Optional 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 newIdentityRecord = identityDatabase.getIdentity(recipientId); + Optional 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(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/identity/IdentityRecordList.java b/app/src/main/java/org/thoughtcrime/securesms/database/identity/IdentityRecordList.java index 4da39bbf5e..69d21db2cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/identity/IdentityRecordList.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/identity/IdentityRecordList.java @@ -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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/IdentityRecord.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/IdentityRecord.kt new file mode 100644 index 0000000000..1ab14413d9 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/IdentityRecord.kt @@ -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 +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/IdentityStoreRecord.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/IdentityStoreRecord.kt index dd5c5395ca..c7764fd325 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/IdentityStoreRecord.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/IdentityStoreRecord.kt @@ -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 + ) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index ac61f06cfe..d6271083ee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -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 identityRecord = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipient.getId()); - Optional verifiedMessage = getVerifiedMessage(recipient, identityRecord); - Map inboxPositions = DatabaseFactory.getThreadDatabase(context).getInboxPositions(); - Set archived = DatabaseFactory.getThreadDatabase(context).getArchivedRecipients(); + Optional identityRecord = ApplicationDependencies.getIdentityStore().getIdentityRecord(recipient.getId()); + Optional verifiedMessage = getVerifiedMessage(recipient, identityRecord); + Map inboxPositions = DatabaseFactory.getThreadDatabase(context).getInboxPositions(); + Set 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 archived = DatabaseFactory.getThreadDatabase(context).getArchivedRecipients(); for (Recipient recipient : recipients) { - Optional identity = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipient.getId()); - Optional verified = getVerifiedMessage(recipient, identity); - Optional name = Optional.fromNullable(recipient.isSystemContact() ? recipient.getDisplayName(context) : recipient.getGroupName(context)); - Optional profileKey = ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey()); - boolean blocked = recipient.isBlocked(); - Optional expireTimer = recipient.getExpiresInSeconds() > 0 ? Optional.of(recipient.getExpiresInSeconds()) : Optional.absent(); - Optional inboxPosition = Optional.fromNullable(inboxPositions.get(recipient.getId())); + Optional identity = ApplicationDependencies.getIdentityStore().getIdentityRecord(recipient.getId()); + Optional verified = getVerifiedMessage(recipient, identity); + Optional name = Optional.fromNullable(recipient.isSystemContact() ? recipient.getDisplayName(context) : recipient.getGroupName(context)); + Optional profileKey = ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey()); + boolean blocked = recipient.isBlocked(); + Optional expireTimer = recipient.getExpiresInSeconds() > 0 ? Optional.of(recipient.getExpiresInSeconds()) : Optional.absent(); + Optional 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 getVerifiedMessage(Recipient recipient, Optional identity) + private Optional getVerifiedMessage(Recipient recipient, Optional identity) throws InvalidNumberException, IOException { if (!identity.isPresent()) return Optional.absent(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java index f90eef4fa9..5bb55b82cd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java @@ -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; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java index 14fcf253fa..f71c8e48a7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java @@ -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()), diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogRepository.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogRepository.java index 2a116a27e3..788c097567 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogRepository.java @@ -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 callback) { + void getIdentity(@NonNull Consumer callback) { SignalExecutors.BOUNDED.execute( - () -> callback.accept(DatabaseFactory.getIdentityDatabase(context) - .getIdentity(recipientId) - .orNull())); + () -> callback.accept(ApplicationDependencies.getIdentityStore().getIdentityRecord(recipientId).orNull())); } void getRecipient(@NonNull RecipientCallback recipientCallback) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java index ed7bcea08c..abf684a5e5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java @@ -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; - private final MutableLiveData identity; - private final LiveData adminActionStatus; - private final LiveData canAddToAGroup; - private final MutableLiveData adminActionBusy; + private final Context context; + private final RecipientDialogRepository recipientDialogRepository; + private final LiveData recipient; + private final MutableLiveData identity; + private final LiveData adminActionStatus; + private final LiveData canAddToAGroup; + private final MutableLiveData adminActionBusy; private RecipientDialogViewModel(@NonNull Context context, @NonNull RecipientDialogRepository recipientDialogRepository) @@ -101,7 +102,7 @@ final class RecipientDialogViewModel extends ViewModel { return adminActionStatus; } - LiveData getIdentity() { + LiveData 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)); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java index 02bb1727fc..0bfee1b199 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java @@ -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()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java index 5763416339..260a670e28 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java @@ -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 = identityDatabase.getIdentity(recipient.getId()); + TextSecureIdentityKeyStore identityStore = ApplicationDependencies.getIdentityStore(); + Recipient recipient = Recipient.externalPush(context, verifiedMessage.getDestination()); + Optional 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); } } diff --git a/app/src/test/java/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStoreTest.kt b/app/src/test/java/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStoreTest.kt new file mode 100644 index 0000000000..a4a9d06ede --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStoreTest.kt @@ -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 + ) + } +}