Convert all account based calls to WebSocket.

This commit is contained in:
Cody Henthorne
2025-03-07 15:48:21 -05:00
committed by Greyson Parrelli
parent 6d115a912d
commit 305b380fef
30 changed files with 748 additions and 615 deletions

View File

@@ -47,7 +47,6 @@ import androidx.transition.TransitionManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.signal.core.util.concurrent.JvmRxExtensions;
import org.signal.core.util.concurrent.LifecycleDisposable;
import org.signal.core.util.concurrent.SimpleTask;
import org.signal.core.util.logging.Log;
@@ -721,12 +720,7 @@ public final class ContactSelectionListFragment extends LoggingFragment {
AlertDialog loadingDialog = SimpleProgressDialog.show(requireContext());
SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> {
try {
return JvmRxExtensions.safeBlockingGet(UsernameRepository.fetchAciForUsername(UsernameUtil.sanitizeUsernameFromSearch(username)));
} catch (InterruptedException e) {
Log.w(TAG, "Interrupted?", e);
return UsernameAciFetchResult.NetworkError.INSTANCE;
}
return UsernameRepository.fetchAciForUsername(UsernameUtil.sanitizeUsernameFromSearch(username));
}, result -> {
loadingDialog.dismiss();

View File

@@ -5,22 +5,22 @@ import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.node.ObjectNode
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.net.SignalNetwork
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.util.JsonUtils
import org.whispersystems.signalservice.api.SignalServiceAccountManager
import java.io.IOException
import org.whispersystems.signalservice.api.NetworkResult
class ExportAccountDataRepository(
private val accountManager: SignalServiceAccountManager = AppDependencies.signalServiceAccountManager
) {
class ExportAccountDataRepository {
fun downloadAccountDataReport(exportAsJson: Boolean): Single<ExportedReport> {
return Single.create {
try {
it.onSuccess(generateAccountDataReport(accountManager.accountDataReport, exportAsJson))
} catch (e: IOException) {
it.onError(e)
when (val result = SignalNetwork.account.accountDataReport()) {
is NetworkResult.Success -> {
it.onSuccess(generateAccountDataReport(result.result, exportAsJson))
}
else -> {
it.onError(result.getCause()!!)
}
}
}.subscribeOn(Schedulers.io())
}

View File

@@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
import org.thoughtcrime.securesms.keyvalue.CertificateType
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.net.SignalNetwork
import org.thoughtcrime.securesms.pin.SvrRepository
import org.thoughtcrime.securesms.pin.SvrWrongPinException
import org.thoughtcrime.securesms.recipients.Recipient
@@ -266,7 +267,7 @@ class ChangeNumberRepository(
SignalStore.misc.setPendingChangeNumberMetadata(metadata)
withContext(Dispatchers.IO) {
result = accountManager.registrationApi.changeNumber(request)
result = SignalNetwork.account.changeNumber(request)
}
val possibleError = result.getCause() as? MismatchedDevicesException

View File

@@ -9,12 +9,13 @@ import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.net.SignalNetwork
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.NetworkResultUtil
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException
import java.io.IOException
import java.util.Optional
import java.util.concurrent.ExecutionException
private val TAG = Log.tag(AdvancedPrivacySettingsRepository::class.java)
@@ -24,9 +25,8 @@ class AdvancedPrivacySettingsRepository(private val context: Context) {
fun disablePushMessages(consumer: (DisablePushMessagesResult) -> Unit) {
SignalExecutors.BOUNDED.execute {
val result = try {
val accountManager = AppDependencies.signalServiceAccountManager
try {
accountManager.setGcmId(Optional.empty())
NetworkResultUtil.toBasicLegacy(SignalNetwork.account.clearFcmToken())
} catch (e: AuthorizationFailedException) {
Log.w(TAG, e)
}

View File

@@ -16,7 +16,9 @@ import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.net.SignalNetwork;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.whispersystems.signalservice.api.NetworkResultUtil;
import org.whispersystems.signalservice.internal.EmptyResponse;
import org.whispersystems.signalservice.internal.ServiceResponse;
@@ -103,7 +105,7 @@ class DeleteAccountRepository {
Log.i(TAG, "deleteAccount: attempting to delete account from server...");
try {
AppDependencies.getSignalServiceAccountManager().deleteAccount();
NetworkResultUtil.toBasicLegacy(SignalNetwork.account().deleteAccount());
} catch (IOException e) {
Log.w(TAG, "deleteAccount: failed to delete account from signal service", e);
onDeleteAccountEvent.accept(DeleteAccountEvent.ServerDeletionFailed.INSTANCE);

View File

@@ -42,6 +42,7 @@ import org.whispersystems.signalservice.api.SignalServiceAccountManager
import org.whispersystems.signalservice.api.SignalServiceDataStore
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver
import org.whispersystems.signalservice.api.SignalServiceMessageSender
import org.whispersystems.signalservice.api.account.AccountApi
import org.whispersystems.signalservice.api.archive.ArchiveApi
import org.whispersystems.signalservice.api.attachment.AttachmentApi
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations
@@ -52,6 +53,7 @@ import org.whispersystems.signalservice.api.services.CallLinksService
import org.whispersystems.signalservice.api.services.DonationsService
import org.whispersystems.signalservice.api.services.ProfileService
import org.whispersystems.signalservice.api.storage.StorageServiceApi
import org.whispersystems.signalservice.api.username.UsernameApi
import org.whispersystems.signalservice.api.websocket.SignalWebSocket
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration
@@ -311,6 +313,12 @@ object AppDependencies {
val storageServiceApi: StorageServiceApi
get() = networkModule.storageServiceApi
val accountApi: AccountApi
get() = networkModule.accountApi
val usernameApi: UsernameApi
get() = networkModule.usernameApi
@JvmStatic
val okHttpClient: OkHttpClient
get() = networkModule.okHttpClient
@@ -338,7 +346,7 @@ object AppDependencies {
interface Provider {
fun providePushServiceSocket(signalServiceConfiguration: SignalServiceConfiguration, groupsV2Operations: GroupsV2Operations): PushServiceSocket
fun provideGroupsV2Operations(signalServiceConfiguration: SignalServiceConfiguration): GroupsV2Operations
fun provideSignalServiceAccountManager(pushServiceSocket: PushServiceSocket, groupsV2Operations: GroupsV2Operations): SignalServiceAccountManager
fun provideSignalServiceAccountManager(authWebSocket: AccountApi, pushServiceSocket: PushServiceSocket, groupsV2Operations: GroupsV2Operations): SignalServiceAccountManager
fun provideSignalServiceMessageSender(authWebSocket: SignalWebSocket.AuthenticatedWebSocket, unauthWebSocket: SignalWebSocket.UnauthenticatedWebSocket, protocolStore: SignalServiceDataStore, pushServiceSocket: PushServiceSocket): SignalServiceMessageSender
fun provideSignalServiceMessageReceiver(pushServiceSocket: PushServiceSocket): SignalServiceMessageReceiver
fun provideSignalServiceNetworkAccess(): SignalServiceNetworkAccess
@@ -382,5 +390,7 @@ object AppDependencies {
fun provideStorageServiceApi(pushServiceSocket: PushServiceSocket): StorageServiceApi
fun provideAuthWebSocket(signalServiceConfigurationSupplier: Supplier<SignalServiceConfiguration>, libSignalNetworkSupplier: Supplier<Network>): SignalWebSocket.AuthenticatedWebSocket
fun provideUnauthWebSocket(signalServiceConfigurationSupplier: Supplier<SignalServiceConfiguration>, libSignalNetworkSupplier: Supplier<Network>): SignalWebSocket.UnauthenticatedWebSocket
fun provideAccountApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket): AccountApi
fun provideUsernameApi(unauthWebSocket: SignalWebSocket.UnauthenticatedWebSocket): UsernameApi
}
}

View File

@@ -80,6 +80,7 @@ import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.SignalServiceDataStore;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.account.AccountApi;
import org.whispersystems.signalservice.api.archive.ArchiveApi;
import org.whispersystems.signalservice.api.attachment.AttachmentApi;
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
@@ -93,6 +94,7 @@ import org.whispersystems.signalservice.api.services.CallLinksService;
import org.whispersystems.signalservice.api.services.DonationsService;
import org.whispersystems.signalservice.api.services.ProfileService;
import org.whispersystems.signalservice.api.storage.StorageServiceApi;
import org.whispersystems.signalservice.api.username.UsernameApi;
import org.whispersystems.signalservice.api.util.CredentialsProvider;
import org.whispersystems.signalservice.api.util.SleepTimer;
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
@@ -140,8 +142,8 @@ public class ApplicationDependencyProvider implements AppDependencies.Provider {
}
@Override
public @NonNull SignalServiceAccountManager provideSignalServiceAccountManager(@NonNull PushServiceSocket pushServiceSocket, @NonNull GroupsV2Operations groupsV2Operations) {
return new SignalServiceAccountManager(pushServiceSocket, groupsV2Operations);
public @NonNull SignalServiceAccountManager provideSignalServiceAccountManager(@NonNull AccountApi accountApi, @NonNull PushServiceSocket pushServiceSocket, @NonNull GroupsV2Operations groupsV2Operations) {
return new SignalServiceAccountManager(accountApi, pushServiceSocket, groupsV2Operations);
}
@Override
@@ -492,6 +494,16 @@ public class ApplicationDependencyProvider implements AppDependencies.Provider {
return new StorageServiceApi(pushServiceSocket);
}
@Override
public @NonNull AccountApi provideAccountApi(@NonNull SignalWebSocket.AuthenticatedWebSocket authWebSocket) {
return new AccountApi(authWebSocket);
}
@Override
public @NonNull UsernameApi provideUsernameApi(@NonNull SignalWebSocket.UnauthenticatedWebSocket unauthWebSocket) {
return new UsernameApi(unauthWebSocket);
}
@VisibleForTesting
static class DynamicCredentialsProvider implements CredentialsProvider {

View File

@@ -26,6 +26,7 @@ import org.thoughtcrime.securesms.push.SignalServiceTrustStore
import org.whispersystems.signalservice.api.SignalServiceAccountManager
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver
import org.whispersystems.signalservice.api.SignalServiceMessageSender
import org.whispersystems.signalservice.api.account.AccountApi
import org.whispersystems.signalservice.api.archive.ArchiveApi
import org.whispersystems.signalservice.api.attachment.AttachmentApi
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations
@@ -37,6 +38,7 @@ import org.whispersystems.signalservice.api.services.CallLinksService
import org.whispersystems.signalservice.api.services.DonationsService
import org.whispersystems.signalservice.api.services.ProfileService
import org.whispersystems.signalservice.api.storage.StorageServiceApi
import org.whispersystems.signalservice.api.username.UsernameApi
import org.whispersystems.signalservice.api.util.Tls12SocketFactory
import org.whispersystems.signalservice.api.websocket.SignalWebSocket
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState
@@ -83,7 +85,7 @@ class NetworkDependenciesModule(
}
val signalServiceAccountManager: SignalServiceAccountManager by lazy {
provider.provideSignalServiceAccountManager(pushServiceSocket, groupsV2Operations)
provider.provideSignalServiceAccountManager(accountApi, pushServiceSocket, groupsV2Operations)
}
val libsignalNetwork: Network by lazy {
@@ -157,6 +159,14 @@ class NetworkDependenciesModule(
provider.provideStorageServiceApi(pushServiceSocket)
}
val accountApi: AccountApi by lazy {
provider.provideAccountApi(authWebSocket)
}
val usernameApi: UsernameApi by lazy {
provider.provideUsernameApi(unauthWebSocket)
}
val okHttpClient: OkHttpClient by lazy {
OkHttpClient.Builder()
.addInterceptor(StandardUserAgentInterceptor())

View File

@@ -38,9 +38,11 @@ import org.thoughtcrime.securesms.gcm.FcmUtil;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.net.SignalNetwork;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.notifications.NotificationIds;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.whispersystems.signalservice.api.NetworkResultUtil;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
import java.io.IOException;
@@ -100,7 +102,7 @@ public class FcmRefreshJob extends BaseJob {
Log.i(TAG, "Token didn't change.");
}
AppDependencies.getSignalServiceAccountManager().setGcmId(token);
NetworkResultUtil.toBasicLegacy(SignalNetwork.account().setFcmToken(token.get()));
SignalStore.account().setFcmToken(token.get());
} else {
throw new RetryLaterException(new IOException("Failed to retrieve a token."));

View File

@@ -19,15 +19,16 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.net.SignalNetwork
import org.whispersystems.signalservice.api.NetworkResult
import org.whispersystems.signalservice.api.account.PniKeyDistributionRequest
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity
import org.whispersystems.signalservice.internal.push.KyberPreKeyEntity
import org.whispersystems.signalservice.internal.push.MismatchedDevices
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage
import org.whispersystems.signalservice.internal.push.SyncMessage
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
import org.whispersystems.signalservice.internal.push.exceptions.MismatchedDevicesException
import java.io.IOException
import java.security.SecureRandom
@@ -112,7 +113,6 @@ class PnpInitializeDevicesJob private constructor(parameters: Parameters) : Base
}
private fun initializeDevices(newE164: String): Single<NetworkResult<VerifyAccountResponse>> {
val accountManager = AppDependencies.signalServiceAccountManager
val messageSender = AppDependencies.signalServiceMessageSender
return Single.fromCallable {
@@ -125,15 +125,25 @@ class PnpInitializeDevicesJob private constructor(parameters: Parameters) : Base
newE164 = newE164
)
distributionResponse = accountManager.registrationApi.distributePniKeys(request)
if (distributionResponse is NetworkResult.StatusCodeError &&
distributionResponse.exception is MismatchedDevicesException
) {
messageSender.handleChangeNumberMismatchDevices((distributionResponse.exception as MismatchedDevicesException).mismatchedDevices)
attempts++
} else {
completed = true
distributionResponse = SignalNetwork.account.distributePniKeys(request)
when (val result = distributionResponse) {
is NetworkResult.Success -> completed = true
is NetworkResult.StatusCodeError -> {
when (result.code) {
409 -> {
val mismatchedDevices: MismatchedDevices? = result.parseJsonBody()
if (mismatchedDevices != null) {
messageSender.handleChangeNumberMismatchDevices(mismatchedDevices)
} else {
Log.w(TAG, "Unable to parse mismatched devices", result.exception)
}
attempts++
}
else -> completed = true
}
}
is NetworkResult.NetworkError -> attempts++
is NetworkResult.ApplicationError -> completed = true
}
}

View File

@@ -16,10 +16,12 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberDiscoverabilityMode;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.keyvalue.SvrValues;
import org.thoughtcrime.securesms.net.SignalNetwork;
import org.thoughtcrime.securesms.registration.data.RegistrationRepository;
import org.thoughtcrime.securesms.registration.secondary.DeviceNameCipher;
import org.thoughtcrime.securesms.util.RemoteConfig;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.NetworkResultUtil;
import org.whispersystems.signalservice.api.account.AccountAttributes;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException;
@@ -125,7 +127,7 @@ public class RefreshAttributesJob extends BaseJob {
recoveryPassword
);
AppDependencies.getSignalServiceAccountManager().setAccountAttributes(accountAttributes);
NetworkResultUtil.toBasicLegacy(SignalNetwork.account().setAccountAttributes(accountAttributes));
hasRefreshedThisAppCycle = true;
}

View File

@@ -23,12 +23,14 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.net.SignalNetwork;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.ProfileUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.NetworkResultUtil;
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
@@ -328,7 +330,7 @@ public class RefreshOwnProfileJob extends BaseJob {
UsernameLinkComponents localUsernameLink = SignalStore.account().getUsernameLink();
if (localUsernameLink != null) {
byte[] remoteEncryptedUsername = AppDependencies.getSignalServiceAccountManager().getEncryptedUsernameFromLinkServerId(localUsernameLink.getServerId());
byte[] remoteEncryptedUsername = NetworkResultUtil.toBasicLegacy(SignalNetwork.username().getEncryptedUsernameFromLinkServerId(localUsernameLink.getServerId()));
Username.UsernameLink combinedLink = new Username.UsernameLink(localUsernameLink.getEntropy(), remoteEncryptedUsername);
Username remoteUsername = Username.fromLink(combinedLink);

View File

@@ -6,16 +6,23 @@
package org.thoughtcrime.securesms.net
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.whispersystems.signalservice.api.account.AccountApi
import org.whispersystems.signalservice.api.archive.ArchiveApi
import org.whispersystems.signalservice.api.attachment.AttachmentApi
import org.whispersystems.signalservice.api.keys.KeysApi
import org.whispersystems.signalservice.api.link.LinkDeviceApi
import org.whispersystems.signalservice.api.storage.StorageServiceApi
import org.whispersystems.signalservice.api.username.UsernameApi
/**
* A convenient way to access network operations, similar to [org.thoughtcrime.securesms.database.SignalDatabase] and [org.thoughtcrime.securesms.keyvalue.SignalStore].
*/
object SignalNetwork {
@JvmStatic
@get:JvmName("account")
val account: AccountApi
get() = AppDependencies.accountApi
val archive: ArchiveApi
get() = AppDependencies.archiveApi
@@ -30,4 +37,9 @@ object SignalNetwork {
val storageService: StorageServiceApi
get() = AppDependencies.storageServiceApi
@JvmStatic
@get:JvmName("username")
val username: UsernameApi
get() = AppDependencies.usernameApi
}

View File

@@ -23,8 +23,10 @@ import org.thoughtcrime.securesms.jobs.Svr3MirrorJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType
import org.thoughtcrime.securesms.megaphone.Megaphones
import org.thoughtcrime.securesms.net.SignalNetwork
import org.thoughtcrime.securesms.registration.viewmodel.SvrAuthCredentialSet
import org.thoughtcrime.securesms.registrationv3.ui.restore.StorageServiceRestore
import org.whispersystems.signalservice.api.NetworkResultUtil
import org.whispersystems.signalservice.api.SvrNoDataException
import org.whispersystems.signalservice.api.kbs.MasterKey
import org.whispersystems.signalservice.api.svr.SecureValueRecovery
@@ -360,7 +362,7 @@ object SvrRepository {
check(SignalStore.svr.hasOptedInWithAccess() && !SignalStore.svr.hasOptedOut()) { "Must have a PIN to set a registration lock!" }
Log.i(TAG, "[enableRegistrationLockForUserWithPin] Enabling registration lock.", true)
AppDependencies.signalServiceAccountManager.enableRegistrationLock(SignalStore.svr.masterKey)
NetworkResultUtil.toBasicLegacy(SignalNetwork.account.enableRegistrationLock(SignalStore.svr.masterKey.deriveRegistrationLock()))
SignalStore.svr.isRegistrationLockEnabled = true
Log.i(TAG, "[enableRegistrationLockForUserWithPin] Registration lock successfully enabled.", true)
}
@@ -374,7 +376,7 @@ object SvrRepository {
check(SignalStore.svr.hasOptedInWithAccess() && !SignalStore.svr.hasOptedOut()) { "Must have a PIN to disable registration lock!" }
Log.i(TAG, "[disableRegistrationLockForUserWithPin] Disabling registration lock.", true)
AppDependencies.signalServiceAccountManager.disableRegistrationLock()
NetworkResultUtil.toBasicLegacy(SignalNetwork.account.disableRegistrationLock())
SignalStore.svr.isRegistrationLockEnabled = false
Log.i(TAG, "[disableRegistrationLockForUserWithPin] Registration lock successfully disabled.", true)
}

View File

@@ -15,23 +15,18 @@ import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.AccountValues
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.net.SignalNetwork
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.util.NetworkUtil
import org.thoughtcrime.securesms.util.UsernameUtil
import org.whispersystems.signalservice.api.NetworkResult
import org.whispersystems.signalservice.api.SignalServiceAccountManager
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.UsernameLinkComponents
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException
import org.whispersystems.signalservice.api.push.exceptions.UsernameIsNotAssociatedWithAnAccountException
import org.whispersystems.signalservice.api.push.exceptions.UsernameIsNotReservedException
import org.whispersystems.signalservice.api.push.exceptions.UsernameMalformedException
import org.whispersystems.signalservice.api.push.exceptions.UsernameTakenException
import org.whispersystems.signalservice.api.util.Usernames
import org.whispersystems.signalservice.api.util.UuidUtil
import org.whispersystems.signalservice.api.util.toByteArray
import java.io.IOException
import java.util.UUID
/**
@@ -200,26 +195,30 @@ object UsernameRepository {
return Single
.fromCallable {
try {
SignalStore.account.usernameLink = null
SignalStore.account.usernameLink = null
Log.d(TAG, "[createOrResetUsernameLink] Creating username link...")
val components = accountManager.createUsernameLink(username)
SignalStore.account.usernameLink = components
Log.d(TAG, "[createOrResetUsernameLink] Creating username link...")
if (SignalStore.account.usernameSyncState == AccountValues.UsernameSyncState.LINK_CORRUPTED) {
SignalStore.account.usernameSyncState = AccountValues.UsernameSyncState.IN_SYNC
SignalStore.account.usernameSyncErrorCount = 0
val usernameLink = username.generateLink()
when (val result = SignalNetwork.account.createUsernameLink(usernameLink)) {
is NetworkResult.Success -> {
SignalStore.account.usernameLink = result.result
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)
StorageSyncHelper.scheduleSyncForDataChange()
Log.d(TAG, "[createOrResetUsernameLink] Username link created.")
UsernameLinkResetResult.Success(result.result)
}
else -> {
Log.w(TAG, "[createOrResetUsernameLink] Failed to rotate the username!", result.getCause())
UsernameLinkResetResult.NetworkError
}
SignalDatabase.recipients.markNeedsSync(Recipient.self().id)
StorageSyncHelper.scheduleSyncForDataChange()
Log.d(TAG, "[createOrResetUsernameLink] Username link created.")
UsernameLinkResetResult.Success(components)
} catch (e: IOException) {
Log.w(TAG, "[createOrResetUsernameLink] Failed to rotate the username!", e)
UsernameLinkResetResult.NetworkError
}
}
.subscribeOn(Schedulers.io())
@@ -234,53 +233,72 @@ object UsernameRepository {
return Single
.fromCallable {
var username: Username? = null
val encryptedUsername = when (val result = SignalNetwork.username.getEncryptedUsernameFromLinkServerId(components.serverId)) {
is NetworkResult.Success -> result.result
is NetworkResult.StatusCodeError -> {
return@fromCallable when (result.code) {
404 -> UsernameLinkConversionResult.NotFound(null)
422 -> UsernameLinkConversionResult.Invalid
else -> UsernameLinkConversionResult.NetworkError
}
}
is NetworkResult.NetworkError -> return@fromCallable UsernameLinkConversionResult.NetworkError
is NetworkResult.ApplicationError -> throw result.throwable
}
try {
val encryptedUsername: ByteArray = accountManager.getEncryptedUsernameFromLinkServerId(components.serverId)
val link = Username.UsernameLink(components.entropy, encryptedUsername)
val link = Username.UsernameLink(components.entropy, encryptedUsername)
val username: Username = try {
Username.fromLink(link)
} catch (e: BaseUsernameException) {
Log.w(TAG, "[convertLinkToUsername] Bad username conversion.", e)
return@fromCallable UsernameLinkConversionResult.Invalid
}
username = Username.fromLink(link)
val aci = accountManager.getAciByUsername(username)
UsernameLinkConversionResult.Success(username, aci)
} catch (e: IOException) {
Log.w(TAG, "[convertLinkToUsername] Failed to lookup user.", e)
if (e is NonSuccessfulResponseCodeException) {
when (e.code) {
when (val result = SignalNetwork.username.getAciByUsername(username)) {
is NetworkResult.Success -> UsernameLinkConversionResult.Success(username, result.result)
is NetworkResult.StatusCodeError -> {
Log.w(TAG, "[convertLinkToUsername] Failed to lookup user.", result.exception)
when (result.code) {
404 -> UsernameLinkConversionResult.NotFound(username)
422 -> UsernameLinkConversionResult.Invalid
else -> UsernameLinkConversionResult.NetworkError
}
} else {
}
is NetworkResult.NetworkError -> {
Log.w(TAG, "[convertLinkToUsername] Failed to lookup user.", result.exception)
UsernameLinkConversionResult.NetworkError
}
} catch (e: BaseUsernameException) {
Log.w(TAG, "[convertLinkToUsername] Bad username conversion.", e)
UsernameLinkConversionResult.Invalid
is NetworkResult.ApplicationError -> throw result.throwable
}
}
.subscribeOn(Schedulers.io())
}
@JvmStatic
fun fetchAciForUsername(username: String): Single<UsernameAciFetchResult> {
return Single.fromCallable {
try {
val aci: ACI = AppDependencies.signalServiceAccountManager.getAciByUsername(Username(username))
UsernameAciFetchResult.Success(aci)
} catch (e: UsernameIsNotAssociatedWithAnAccountException) {
Log.w(TAG, "[fetchAciFromUsername] Failed to get ACI for username hash", e)
UsernameAciFetchResult.NotFound
} catch (e: BaseUsernameException) {
Log.w(TAG, "[fetchAciFromUsername] Invalid username", e)
UsernameAciFetchResult.NotFound
} catch (e: IOException) {
Log.w(TAG, "[fetchAciFromUsername] Hit network error while trying to resolve ACI from username", e)
fun fetchAciForUsername(usernameString: String): UsernameAciFetchResult {
val username = try {
Username(usernameString)
} catch (e: BaseUsernameException) {
Log.w(TAG, "[fetchAciFromUsername] Invalid username", e)
return UsernameAciFetchResult.NotFound
}
return when (val result = SignalNetwork.username.getAciByUsername(username)) {
is NetworkResult.Success -> UsernameAciFetchResult.Success(result.result)
is NetworkResult.StatusCodeError -> {
Log.w(TAG, "[fetchAciFromUsername] Failed to get ACI for username hash", result.exception)
when (result.code) {
404 -> UsernameAciFetchResult.NotFound
else -> UsernameAciFetchResult.NetworkError
}
}
is NetworkResult.NetworkError -> {
Log.w(TAG, "[fetchAciFromUsername] Hit network error while trying to resolve ACI from username", result.exception)
UsernameAciFetchResult.NetworkError
}
is NetworkResult.ApplicationError -> throw result.throwable
}
}
@@ -355,41 +373,56 @@ object UsernameRepository {
@WorkerThread
private fun reserveUsernameInternal(nickname: String, discriminator: String?): Result<UsernameState.Reserved, UsernameSetResult> {
return try {
val candidates: List<Username> = if (discriminator == null) {
val candidates: List<Username> = try {
if (discriminator == null) {
Username.candidatesFrom(nickname, UsernameUtil.MIN_NICKNAME_LENGTH, UsernameUtil.MAX_NICKNAME_LENGTH)
} else {
listOf(Username("$nickname${Usernames.DELIMITER}$discriminator"))
}
val hashes: List<String> = candidates
.map { Base64.encodeUrlSafeWithoutPadding(it.hash) }
val response = accountManager.reserveUsername(hashes)
val hashIndex = hashes.indexOf(response.usernameHash)
if (hashIndex == -1) {
Log.w(TAG, "[reserveUsername] The response hash could not be found in our set of hashes.")
return failure(UsernameSetResult.CANDIDATE_GENERATION_ERROR)
}
Log.i(TAG, "[reserveUsername] Successfully reserved username.")
success(UsernameState.Reserved(candidates[hashIndex]))
} catch (e: BaseUsernameException) {
Log.w(TAG, "[reserveUsername] An error occurred while generating candidates.")
failure(UsernameSetResult.CANDIDATE_GENERATION_ERROR)
} catch (e: UsernameTakenException) {
Log.w(TAG, "[reserveUsername] Username taken.")
failure(UsernameSetResult.USERNAME_UNAVAILABLE)
} catch (e: UsernameMalformedException) {
Log.w(TAG, "[reserveUsername] Username malformed.")
failure(UsernameSetResult.USERNAME_INVALID)
} catch (e: RateLimitException) {
Log.w(TAG, "[reserveUsername] Rate limit exceeded.")
failure(UsernameSetResult.RATE_LIMIT_ERROR)
} catch (e: IOException) {
Log.w(TAG, "[reserveUsername] Generic network exception.", e)
failure(UsernameSetResult.NETWORK_ERROR)
return failure(UsernameSetResult.CANDIDATE_GENERATION_ERROR)
}
val hashes: List<String> = candidates
.map { Base64.encodeUrlSafeWithoutPadding(it.hash) }
return when (val result = SignalNetwork.account.reserveUsername(hashes)) {
is NetworkResult.Success -> {
val hashIndex = hashes.indexOf(result.result.usernameHash)
if (hashIndex == -1) {
Log.w(TAG, "[reserveUsername] The response hash could not be found in our set of hashes.")
return failure(UsernameSetResult.CANDIDATE_GENERATION_ERROR)
}
Log.i(TAG, "[reserveUsername] Successfully reserved username.")
success(UsernameState.Reserved(candidates[hashIndex]))
}
is NetworkResult.StatusCodeError -> {
when (result.code) {
409 -> {
Log.w(TAG, "[reserveUsername] Username taken.")
failure(UsernameSetResult.USERNAME_UNAVAILABLE)
}
422 -> {
Log.w(TAG, "[reserveUsername] Username malformed.")
failure(UsernameSetResult.USERNAME_INVALID)
}
429 -> {
Log.w(TAG, "[reserveUsername] Rate limit exceeded.")
failure(UsernameSetResult.RATE_LIMIT_ERROR)
}
else -> {
Log.w(TAG, "[reserveUsername] Generic network exception.", result.exception)
failure(UsernameSetResult.NETWORK_ERROR)
}
}
}
is NetworkResult.NetworkError -> {
Log.w(TAG, "[reserveUsername] Generic network exception.", result.exception)
failure(UsernameSetResult.NETWORK_ERROR)
}
is NetworkResult.ApplicationError -> throw result.throwable
}
}
@@ -402,25 +435,27 @@ object UsernameRepository {
return UsernameSetResult.NETWORK_ERROR
}
return try {
val oldUsernameLink = SignalStore.account.usernameLink ?: return UsernameSetResult.USERNAME_INVALID
val newUsernameLink = updatedUsername.generateLink(oldUsernameLink.entropy)
val usernameLinkComponents = accountManager.updateUsernameLink(newUsernameLink)
val oldUsernameLink = SignalStore.account.usernameLink ?: return UsernameSetResult.USERNAME_INVALID
val newUsernameLink = updatedUsername.generateLink(oldUsernameLink.entropy)
SignalStore.account.username = updatedUsername.username
SignalStore.account.usernameLink = usernameLinkComponents
SignalDatabase.recipients.setUsername(Recipient.self().id, updatedUsername.username)
SignalStore.account.usernameSyncState = AccountValues.UsernameSyncState.IN_SYNC
SignalStore.account.usernameSyncErrorCount = 0
return when (val result = SignalNetwork.account.updateUsernameLink(newUsernameLink)) {
is NetworkResult.Success -> {
SignalStore.account.username = updatedUsername.username
SignalStore.account.usernameLink = result.result
SignalDatabase.recipients.setUsername(Recipient.self().id, updatedUsername.username)
SignalStore.account.usernameSyncState = AccountValues.UsernameSyncState.IN_SYNC
SignalStore.account.usernameSyncErrorCount = 0
SignalDatabase.recipients.markNeedsSync(Recipient.self().id)
StorageSyncHelper.scheduleSyncForDataChange()
Log.i(TAG, "[updateUsernameDisplayForCurrentLink] Successfully updated username.")
SignalDatabase.recipients.markNeedsSync(Recipient.self().id)
StorageSyncHelper.scheduleSyncForDataChange()
Log.i(TAG, "[updateUsernameDisplayForCurrentLink] Successfully updated username.")
UsernameSetResult.SUCCESS
} catch (e: IOException) {
Log.w(TAG, "[updateUsernameDisplayForCurrentLink] Generic network exception.", e)
UsernameSetResult.NETWORK_ERROR
UsernameSetResult.SUCCESS
}
else -> {
Log.w(TAG, "[updateUsernameDisplayForCurrentLink] Generic network exception.", result.getCause())
UsernameSetResult.NETWORK_ERROR
}
}
}
@@ -433,32 +468,55 @@ object UsernameRepository {
return UsernameSetResult.NETWORK_ERROR
}
return try {
val linkComponents: UsernameLinkComponents = accountManager.confirmUsernameAndCreateNewLink(username)
val link = username.generateLink()
SignalStore.account.username = username.username
SignalStore.account.usernameLink = linkComponents
SignalDatabase.recipients.setUsername(Recipient.self().id, username.username)
SignalStore.account.usernameSyncState = AccountValues.UsernameSyncState.IN_SYNC
SignalStore.account.usernameSyncErrorCount = 0
return when (val result = SignalNetwork.account.confirmUsername(username, link)) {
is NetworkResult.Success -> {
SignalStore.account.username = username.username
SignalStore.account.usernameLink = UsernameLinkComponents(link.entropy, result.result)
SignalDatabase.recipients.setUsername(Recipient.self().id, username.username)
SignalStore.account.usernameSyncState = AccountValues.UsernameSyncState.IN_SYNC
SignalStore.account.usernameSyncErrorCount = 0
SignalDatabase.recipients.markNeedsSync(Recipient.self().id)
StorageSyncHelper.scheduleSyncForDataChange()
Log.i(TAG, "[confirmUsernameAndCreateNewLink] Successfully confirmed username.")
SignalDatabase.recipients.markNeedsSync(Recipient.self().id)
StorageSyncHelper.scheduleSyncForDataChange()
Log.i(TAG, "[confirmUsernameAndCreateNewLink] Successfully confirmed username.")
UsernameSetResult.SUCCESS
} catch (e: UsernameTakenException) {
Log.w(TAG, "[confirmUsernameAndCreateNewLink] Username gone.")
UsernameSetResult.USERNAME_UNAVAILABLE
} catch (e: UsernameIsNotReservedException) {
Log.w(TAG, "[confirmUsernameAndCreateNewLink] Username was not reserved.")
UsernameSetResult.USERNAME_INVALID
} catch (e: BaseUsernameException) {
Log.w(TAG, "[confirmUsernameAndCreateNewLink] Username was not reserved.")
UsernameSetResult.USERNAME_INVALID
} catch (e: IOException) {
Log.w(TAG, "[confirmUsernameAndCreateNewLink] Generic network exception.", e)
UsernameSetResult.NETWORK_ERROR
UsernameSetResult.SUCCESS
}
is NetworkResult.StatusCodeError -> {
when (result.code) {
409 -> {
Log.w(TAG, "[confirmUsernameAndCreateNewLink] Username was not reserved.")
UsernameSetResult.USERNAME_INVALID
}
410 -> {
Log.w(TAG, "[confirmUsernameAndCreateNewLink] Username gone.")
UsernameSetResult.USERNAME_UNAVAILABLE
}
else -> {
Log.w(TAG, "[confirmUsernameAndCreateNewLink] Generic network exception.", result.exception)
UsernameSetResult.NETWORK_ERROR
}
}
}
is NetworkResult.NetworkError -> {
Log.w(TAG, "[confirmUsernameAndCreateNewLink] Generic network exception.", result.exception)
UsernameSetResult.NETWORK_ERROR
}
is NetworkResult.ApplicationError -> {
if (result.throwable is BaseUsernameException) {
Log.w(TAG, "[confirmUsernameAndCreateNewLink] Username was not reserved.")
UsernameSetResult.USERNAME_INVALID
} else {
throw result.throwable
}
}
}
}
@@ -469,43 +527,65 @@ object UsernameRepository {
return UsernameDeleteResult.NETWORK_ERROR
}
return try {
accountManager.deleteUsername()
SignalDatabase.recipients.setUsername(Recipient.self().id, null)
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.")
UsernameDeleteResult.SUCCESS
} catch (e: IOException) {
Log.w(TAG, "[deleteUsername] Generic network exception.", e)
UsernameDeleteResult.NETWORK_ERROR
return when (val result = SignalNetwork.account.deleteUsername()) {
is NetworkResult.Success -> {
SignalDatabase.recipients.setUsername(Recipient.self().id, null)
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.")
UsernameDeleteResult.SUCCESS
}
else -> {
Log.w(TAG, "[deleteUsername] Generic network exception.", result.getCause())
UsernameDeleteResult.NETWORK_ERROR
}
}
}
@WorkerThread
@JvmStatic
private fun reclaimUsernameIfNecessaryInternal(username: Username, usernameLinkComponents: UsernameLinkComponents): UsernameReclaimResult {
try {
accountManager.reclaimUsernameAndLink(username, usernameLinkComponents)
} catch (e: UsernameTakenException) {
Log.w(TAG, "[reclaimUsername] Username gone.")
return UsernameReclaimResult.PERMANENT_ERROR
} catch (e: UsernameIsNotReservedException) {
Log.w(TAG, "[reclaimUsername] Username was not reserved.")
return UsernameReclaimResult.PERMANENT_ERROR
} catch (e: BaseUsernameException) {
Log.w(TAG, "[reclaimUsername] Invalid username.")
return UsernameReclaimResult.PERMANENT_ERROR
} catch (e: IOException) {
Log.w(TAG, "[reclaimUsername] Network error.", e)
return UsernameReclaimResult.NETWORK_ERROR
}
val link = username.generateLink(usernameLinkComponents.entropy)
return UsernameReclaimResult.SUCCESS
return when (val result = SignalNetwork.account.confirmUsername(username, link)) {
is NetworkResult.Success -> UsernameReclaimResult.SUCCESS
is NetworkResult.StatusCodeError -> {
when (result.code) {
409 -> {
Log.w(TAG, "[reclaimUsername] Username was not reserved.")
UsernameReclaimResult.PERMANENT_ERROR
}
410 -> {
Log.w(TAG, "[reclaimUsername] Username gone.")
UsernameReclaimResult.PERMANENT_ERROR
}
else -> {
Log.w(TAG, "[reclaimUsername] Network error.", result.exception)
UsernameReclaimResult.NETWORK_ERROR
}
}
}
is NetworkResult.NetworkError -> {
Log.w(TAG, "[reclaimUsername] Network error.", result.exception)
UsernameReclaimResult.NETWORK_ERROR
}
is NetworkResult.ApplicationError -> {
if (result.throwable is BaseUsernameException) {
Log.w(TAG, "[reclaimUsername] Invalid username.")
UsernameReclaimResult.PERMANENT_ERROR
} else {
throw result.throwable
}
}
}
}
enum class UsernameSetResult {

View File

@@ -13,7 +13,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import org.signal.core.util.concurrent.safeBlockingGet
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientRepository
@@ -66,7 +65,7 @@ class FindByViewModel(
return FindByResult.InvalidEntry
}
return when (val result = UsernameRepository.fetchAciForUsername(username = username).safeBlockingGet()) {
return when (val result = UsernameRepository.fetchAciForUsername(usernameString = username)) {
UsernameRepository.UsernameAciFetchResult.NetworkError -> FindByResult.NotFound()
UsernameRepository.UsernameAciFetchResult.NotFound -> FindByResult.NotFound()
is UsernameRepository.UsernameAciFetchResult.Success -> FindByResult.Success(Recipient.externalUsername(result.aci, username).id)