mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-20 19:18:37 +00:00
Only mark username corrupted after repeated failures.
This commit is contained in:
committed by
Cody Henthorne
parent
1aa7175006
commit
8023285b9d
@@ -12,7 +12,6 @@ import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.signal.core.util.AppUtil
|
||||
import org.signal.core.util.Hex
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.concurrent.SimpleTask
|
||||
import org.signal.core.util.logging.Log
|
||||
@@ -717,7 +716,7 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
|
||||
.setTitle("Corrupt your username?")
|
||||
.setMessage("Are you sure? You might not be able to get your original username back.")
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val random = "${Hex.toStringCondensed(Util.getSecretBytes(4))}.${Random.nextInt(1, 100)}"
|
||||
val random = "${(1..5).map { ('a'..'z').random() }.joinToString(separator = "") }.${Random.nextInt(1, 100)}"
|
||||
|
||||
SignalStore.account().username = random
|
||||
SignalDatabase.recipients.setUsername(Recipient.self().id, random)
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.AccountValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.subscription.Subscriber;
|
||||
@@ -267,9 +268,10 @@ public class RefreshOwnProfileJob extends BaseJob {
|
||||
|
||||
if (TextUtils.isEmpty(localUsernameHash) && TextUtils.isEmpty(remoteUsernameHash)) {
|
||||
Log.d(TAG, "Local and remote username hash are both empty. Considering validated.");
|
||||
UsernameRepository.onUsernameConsistencyValidated();
|
||||
} else if (!Objects.equals(localUsernameHash, remoteUsernameHash)) {
|
||||
Log.w(TAG, "Local username hash does not match server username hash. Local hash: " + (TextUtils.isEmpty(localUsername) ? "empty" : "present") + ", Remote hash: " + (TextUtils.isEmpty(remoteUsernameHash) ? "empty" : "present"));
|
||||
SignalStore.account().setUsernameSyncState(AccountValues.UsernameSyncState.USERNAME_AND_LINK_CORRUPTED);
|
||||
UsernameRepository.onUsernameMismatchDetected();
|
||||
return;
|
||||
} else {
|
||||
Log.d(TAG, "Username validated.");
|
||||
@@ -278,7 +280,8 @@ public class RefreshOwnProfileJob extends BaseJob {
|
||||
Log.w(TAG, "Failed perform synchronization check during username phase.", e);
|
||||
} catch (BaseUsernameException e) {
|
||||
Log.w(TAG, "Our local username data is invalid!", e);
|
||||
SignalStore.account().setUsernameSyncState(AccountValues.UsernameSyncState.USERNAME_AND_LINK_CORRUPTED);
|
||||
UsernameRepository.onUsernameMismatchDetected();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -291,9 +294,7 @@ public class RefreshOwnProfileJob extends BaseJob {
|
||||
|
||||
if (!remoteUsername.getUsername().equals(SignalStore.account().getUsername())) {
|
||||
Log.w(TAG, "The remote username decrypted ok, but the decrypted username did not match our local username!");
|
||||
SignalStore.account().setUsernameSyncState(AccountValues.UsernameSyncState.LINK_CORRUPTED);
|
||||
SignalStore.account().setUsernameLink(null);
|
||||
StorageSyncHelper.scheduleSyncForDataChange();
|
||||
UsernameRepository.onUsernameLinkMismatchDetected();
|
||||
} else {
|
||||
Log.d(TAG, "Username link validated.");
|
||||
}
|
||||
@@ -304,13 +305,11 @@ public class RefreshOwnProfileJob extends BaseJob {
|
||||
Log.w(TAG, "Failed perform synchronization check during the username link phase.", e);
|
||||
} catch (BaseUsernameException e) {
|
||||
Log.w(TAG, "Failed to decrypt username link using the remote encrypted username and our local entropy!", e);
|
||||
SignalStore.account().setUsernameSyncState(AccountValues.UsernameSyncState.LINK_CORRUPTED);
|
||||
SignalStore.account().setUsernameLink(null);
|
||||
StorageSyncHelper.scheduleSyncForDataChange();
|
||||
UsernameRepository.onUsernameLinkMismatchDetected();
|
||||
}
|
||||
|
||||
if (validated) {
|
||||
SignalStore.account().setUsernameSyncState(AccountValues.UsernameSyncState.IN_SYNC);
|
||||
UsernameRepository.onUsernameConsistencyValidated();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.content.SharedPreferences
|
||||
import android.preference.PreferenceManager
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.LongSerializer
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.libsignal.protocol.IdentityKey
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair
|
||||
@@ -72,6 +71,7 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
||||
private const val KEY_USERNAME_LINK_ENTROPY = "account.username_link_entropy"
|
||||
private const val KEY_USERNAME_LINK_SERVER_ID = "account.username_link_server_id"
|
||||
private const val KEY_USERNAME_SYNC_STATE = "phoneNumberPrivacy.usernameSyncState"
|
||||
private const val KEY_USERNAME_SYNC_ERROR_COUNT = "phoneNumberPrivacy.usernameErrorCount"
|
||||
|
||||
@VisibleForTesting
|
||||
const val KEY_E164 = "account.e164"
|
||||
@@ -397,17 +397,14 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
||||
* There are some cases where our username may fall out of sync with the service. In particular, we may get a new value for our username from
|
||||
* storage service but then find that it doesn't match what's on the service.
|
||||
*/
|
||||
var usernameSyncState: UsernameSyncState by enumValue(
|
||||
KEY_USERNAME_SYNC_STATE,
|
||||
UsernameSyncState.IN_SYNC,
|
||||
object : LongSerializer<UsernameSyncState> {
|
||||
override fun serialize(data: UsernameSyncState): Long {
|
||||
Log.i(TAG, "Marking username sync state as: $data")
|
||||
return data.serialize()
|
||||
}
|
||||
override fun deserialize(data: Long): UsernameSyncState = UsernameSyncState.deserialize(data)
|
||||
var usernameSyncState: UsernameSyncState
|
||||
get() = UsernameSyncState.deserialize(getLong(KEY_USERNAME_SYNC_STATE, UsernameSyncState.IN_SYNC.serialize()))
|
||||
set(value) {
|
||||
Log.i(TAG, "Marking username sync state as: $value")
|
||||
putLong(KEY_USERNAME_SYNC_STATE, value.serialize())
|
||||
}
|
||||
)
|
||||
|
||||
var usernameSyncErrorCount: Int by integerValue(KEY_USERNAME_SYNC_ERROR_COUNT, 0)
|
||||
|
||||
private fun clearLocalCredentials() {
|
||||
putString(KEY_SERVICE_PASSWORD, Util.getSecret(18))
|
||||
@@ -520,7 +517,7 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
||||
|
||||
companion object {
|
||||
fun deserialize(value: Long): UsernameSyncState {
|
||||
return values().firstOrNull { it.value == value } ?: throw IllegalArgumentException("Invalud value: $value")
|
||||
return values().firstOrNull { it.value == value } ?: throw IllegalArgumentException("Invalid value: $value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,8 @@ object UsernameRepository {
|
||||
|
||||
private val URL_REGEX = """(https://)?signal.me/?#eu/([a-zA-Z0-9+\-_/]+)""".toRegex()
|
||||
|
||||
val BASE_URL = "https://signal.me/#eu/"
|
||||
private const val BASE_URL = "https://signal.me/#eu/"
|
||||
private const val USERNAME_SYNC_ERROR_THRESHOLD = 3
|
||||
|
||||
private val accountManager: SignalServiceAccountManager get() = ApplicationDependencies.getSignalServiceAccountManager()
|
||||
|
||||
@@ -153,6 +154,7 @@ object UsernameRepository {
|
||||
|
||||
if (SignalStore.account().usernameSyncState == AccountValues.UsernameSyncState.LINK_CORRUPTED) {
|
||||
SignalStore.account().usernameSyncState = AccountValues.UsernameSyncState.IN_SYNC
|
||||
SignalStore.account().usernameSyncErrorCount = 0
|
||||
}
|
||||
|
||||
SignalDatabase.recipients.markNeedsSync(Recipient.self().id)
|
||||
@@ -251,6 +253,44 @@ object UsernameRepository {
|
||||
return BASE_URL + base64
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun onUsernameConsistencyValidated() {
|
||||
SignalStore.account().usernameSyncState = AccountValues.UsernameSyncState.IN_SYNC
|
||||
|
||||
if (SignalStore.account().usernameSyncErrorCount > 0) {
|
||||
Log.i(TAG, "Username consistency validated. There were previously ${SignalStore.account().usernameSyncErrorCount} error(s).")
|
||||
SignalStore.account().usernameSyncErrorCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun onUsernameMismatchDetected() {
|
||||
SignalStore.account().usernameSyncErrorCount++
|
||||
|
||||
if (SignalStore.account().usernameSyncErrorCount >= USERNAME_SYNC_ERROR_THRESHOLD) {
|
||||
Log.w(TAG, "We've now seen ${SignalStore.account().usernameSyncErrorCount} mismatches in a row. Marking username and link as corrupted.")
|
||||
SignalStore.account().usernameSyncState = AccountValues.UsernameSyncState.USERNAME_AND_LINK_CORRUPTED
|
||||
SignalStore.account().usernameSyncErrorCount = 0
|
||||
} else {
|
||||
Log.w(TAG, "Username mismatch reported. At ${SignalStore.account().usernameSyncErrorCount} / $USERNAME_SYNC_ERROR_THRESHOLD tries.")
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun onUsernameLinkMismatchDetected() {
|
||||
SignalStore.account().usernameSyncErrorCount++
|
||||
|
||||
if (SignalStore.account().usernameSyncErrorCount >= USERNAME_SYNC_ERROR_THRESHOLD) {
|
||||
Log.w(TAG, "We've now seen ${SignalStore.account().usernameSyncErrorCount} mismatches in a row. Marking link as corrupted.")
|
||||
SignalStore.account().usernameSyncState = AccountValues.UsernameSyncState.LINK_CORRUPTED
|
||||
SignalStore.account().usernameLink = null
|
||||
SignalStore.account().usernameSyncErrorCount = 0
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
} else {
|
||||
Log.w(TAG, "Link mismatch reported. At ${SignalStore.account().usernameSyncErrorCount} / $USERNAME_SYNC_ERROR_THRESHOLD tries.")
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun reserveUsernameInternal(nickname: String): Result<UsernameState.Reserved, UsernameSetResult> {
|
||||
return try {
|
||||
@@ -293,6 +333,7 @@ object UsernameRepository {
|
||||
SignalStore.account().usernameLink = null
|
||||
SignalDatabase.recipients.setUsername(Recipient.self().id, reserved.username)
|
||||
SignalStore.account().usernameSyncState = AccountValues.UsernameSyncState.IN_SYNC
|
||||
SignalStore.account().usernameSyncErrorCount = 0
|
||||
|
||||
SignalDatabase.recipients.markNeedsSync(Recipient.self().id)
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
@@ -345,6 +386,7 @@ object UsernameRepository {
|
||||
SignalStore.account().username = null
|
||||
SignalStore.account().usernameLink = null
|
||||
SignalStore.account().usernameSyncState = AccountValues.UsernameSyncState.IN_SYNC
|
||||
SignalStore.account().usernameSyncErrorCount = 0
|
||||
SignalDatabase.recipients.markNeedsSync(Recipient.self().id)
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
Log.i(TAG, "[deleteUsername] Successfully deleted the username.")
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.database.model.RecipientRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob;
|
||||
import org.thoughtcrime.securesms.jobs.StorageSyncJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.AccountValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.payments.Entropy;
|
||||
@@ -207,7 +208,6 @@ public final class StorageSyncHelper {
|
||||
SignalStore.storyValues().setUserHasViewedOnboardingStory(update.getNew().hasViewedOnboardingStory());
|
||||
SignalStore.storyValues().setFeatureDisabled(update.getNew().isStoriesDisabled());
|
||||
SignalStore.storyValues().setUserHasSeenGroupStoryEducationSheet(update.getNew().hasSeenGroupStoryEducationSheet());
|
||||
SignalStore.account().setUsername(update.getNew().getUsername());
|
||||
|
||||
if (update.getNew().getStoryViewReceiptsState() == OptionalBool.UNSET) {
|
||||
SignalStore.storyValues().setViewedReceiptsEnabled(update.getNew().isReadReceiptsEnabled());
|
||||
@@ -236,6 +236,12 @@ public final class StorageSyncHelper {
|
||||
ApplicationDependencies.getJobManager().add(new RetrieveProfileAvatarJob(self, update.getNew().getAvatarUrlPath().get()));
|
||||
}
|
||||
|
||||
if (!update.getNew().getUsername().equals(update.getOld().getUsername())) {
|
||||
SignalStore.account().setUsername(update.getNew().getUsername());
|
||||
SignalStore.account().setUsernameSyncState(AccountValues.UsernameSyncState.IN_SYNC);
|
||||
SignalStore.account().setUsernameSyncErrorCount(0);
|
||||
}
|
||||
|
||||
if (update.getNew().getUsernameLink() != null) {
|
||||
SignalStore.account().setUsernameLink(
|
||||
new UsernameLinkComponents(
|
||||
|
||||
Reference in New Issue
Block a user