mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-23 11:15:44 +00:00
Start mirroring to SVR2.
This commit is contained in:
committed by
Clark Chen
parent
dfb7304626
commit
e1570e9512
@@ -60,7 +60,7 @@ import org.thoughtcrime.securesms.jobs.PnpInitializeDevicesJob;
|
||||
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob;
|
||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshKbsCredentialsJob;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshSvrCredentialsJob;
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveRemoteAnnouncementsJob;
|
||||
import org.thoughtcrime.securesms.jobs.StoryOnboardingDownloadJob;
|
||||
@@ -198,7 +198,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
.addPostRender(this::initializeExpiringMessageManager)
|
||||
.addPostRender(() -> SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(this)))
|
||||
.addPostRender(this::initializeTrimThreadsByDateManager)
|
||||
.addPostRender(RefreshKbsCredentialsJob::enqueueIfNecessary)
|
||||
.addPostRender(RefreshSvrCredentialsJob::enqueueIfNecessary)
|
||||
.addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this))
|
||||
.addPostRender(EmojiSearchIndexDownloadJob::scheduleIfNecessary)
|
||||
.addPostRender(() -> SignalDatabase.messageLog().trimOldMessages(System.currentTimeMillis(), FeatureFlags.retryRespondMaxAge()))
|
||||
|
||||
@@ -21,7 +21,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferActivity;
|
||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity;
|
||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrationActivity;
|
||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||
import org.thoughtcrime.securesms.pin.PinRestoreActivity;
|
||||
@@ -189,11 +189,11 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
||||
}
|
||||
|
||||
private boolean userMustCreateSignalPin() {
|
||||
return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().lastPinCreateFailed() && !SignalStore.kbsValues().hasOptedOut();
|
||||
return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.svr().hasPin() && !SignalStore.svr().lastPinCreateFailed() && !SignalStore.svr().hasOptedOut();
|
||||
}
|
||||
|
||||
private boolean userHasSkippedOrForgottenPin() {
|
||||
return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().hasOptedOut() && SignalStore.kbsValues().isPinForgottenOrSkipped();
|
||||
return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.svr().hasPin() && !SignalStore.svr().hasOptedOut() && SignalStore.svr().isPinForgottenOrSkipped();
|
||||
}
|
||||
|
||||
private boolean userMustSetProfileName() {
|
||||
@@ -234,7 +234,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
||||
intent = getIntent();
|
||||
}
|
||||
|
||||
return getRoutedIntent(CreateKbsPinActivity.class, intent);
|
||||
return getRoutedIntent(CreateSvrPinActivity.class, intent);
|
||||
}
|
||||
|
||||
private Intent getCreateProfileNameIntent() {
|
||||
|
||||
@@ -5,7 +5,7 @@ import android.app.backup.BackupDataInput
|
||||
import android.app.backup.BackupDataOutput
|
||||
import android.os.ParcelFileDescriptor
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.absbackup.backupables.KbsAuthTokens
|
||||
import org.thoughtcrime.securesms.absbackup.backupables.SvrAuthTokens
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.io.FileInputStream
|
||||
@@ -17,7 +17,7 @@ import java.io.IOException
|
||||
*/
|
||||
class SignalBackupAgent : BackupAgent() {
|
||||
private val items: List<AndroidBackupItem> = listOf(
|
||||
KbsAuthTokens
|
||||
SvrAuthTokens
|
||||
)
|
||||
|
||||
override fun onBackup(oldState: ParcelFileDescriptor?, data: BackupDataOutput, newState: ParcelFileDescriptor) {
|
||||
|
||||
@@ -3,13 +3,13 @@ package org.thoughtcrime.securesms.absbackup.backupables
|
||||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.absbackup.AndroidBackupItem
|
||||
import org.thoughtcrime.securesms.absbackup.protos.KbsAuthToken
|
||||
import org.thoughtcrime.securesms.absbackup.protos.SvrAuthToken
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
|
||||
/**
|
||||
* This backs up the not-secret KBS Auth tokens, which can be combined with a PIN to prove ownership of a phone number in order to complete the registration process.
|
||||
*/
|
||||
object KbsAuthTokens : AndroidBackupItem {
|
||||
object SvrAuthTokens : AndroidBackupItem {
|
||||
private const val TAG = "KbsAuthTokens"
|
||||
|
||||
override fun getKey(): String {
|
||||
@@ -17,19 +17,19 @@ object KbsAuthTokens : AndroidBackupItem {
|
||||
}
|
||||
|
||||
override fun getDataForBackup(): ByteArray {
|
||||
val proto = KbsAuthToken(tokens = SignalStore.kbsValues().kbsAuthTokenList)
|
||||
val proto = SvrAuthToken(tokens = SignalStore.svr().authTokenList)
|
||||
return proto.encode()
|
||||
}
|
||||
|
||||
override fun restoreData(data: ByteArray) {
|
||||
if (SignalStore.kbsValues().kbsAuthTokenList.isNotEmpty()) {
|
||||
if (SignalStore.svr().authTokenList.isNotEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val proto = KbsAuthToken.ADAPTER.decode(data)
|
||||
val proto = SvrAuthToken.ADAPTER.decode(data)
|
||||
|
||||
SignalStore.kbsValues().putAuthTokenList(proto.tokens)
|
||||
SignalStore.svr().putAuthTokenList(proto.tokens)
|
||||
} catch (e: InvalidProtocolBufferException) {
|
||||
Log.w(TAG, "Cannot restore KbsAuthToken from backup service.")
|
||||
}
|
||||
@@ -28,9 +28,9 @@ import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity
|
||||
import org.thoughtcrime.securesms.lock.v2.KbsConstants
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity
|
||||
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType
|
||||
import org.thoughtcrime.securesms.lock.v2.SvrConstants
|
||||
import org.thoughtcrime.securesms.pin.RegistrationLockV2Dialog
|
||||
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity
|
||||
import org.thoughtcrime.securesms.util.PlayStoreUtil
|
||||
@@ -45,7 +45,7 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag
|
||||
lateinit var viewModel: AccountSettingsViewModel
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == CreateKbsPinActivity.REQUEST_NEW_PIN && resultCode == CreateKbsPinActivity.RESULT_OK) {
|
||||
if (requestCode == CreateSvrPinActivity.REQUEST_NEW_PIN && resultCode == CreateSvrPinActivity.RESULT_OK) {
|
||||
Snackbar.make(requireView(), R.string.ConfirmKbsPinFragment__pin_created, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
@@ -73,9 +73,9 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag
|
||||
isEnabled = state.isDeprecatedOrUnregistered(),
|
||||
onClick = {
|
||||
if (state.hasPin) {
|
||||
startActivityForResult(CreateKbsPinActivity.getIntentForPinChangeFromSettings(requireContext()), CreateKbsPinActivity.REQUEST_NEW_PIN)
|
||||
startActivityForResult(CreateSvrPinActivity.getIntentForPinChangeFromSettings(requireContext()), CreateSvrPinActivity.REQUEST_NEW_PIN)
|
||||
} else {
|
||||
startActivityForResult(CreateKbsPinActivity.getIntentForPinCreate(requireContext()), CreateKbsPinActivity.REQUEST_NEW_PIN)
|
||||
startActivityForResult(CreateSvrPinActivity.getIntentForPinCreate(requireContext()), CreateSvrPinActivity.REQUEST_NEW_PIN)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -240,14 +240,14 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag
|
||||
|
||||
pinEditText.addTextChangedListener(object : SimpleTextWatcher() {
|
||||
override fun onTextChanged(text: String) {
|
||||
turnOffButton.isEnabled = text.length >= KbsConstants.MINIMUM_PIN_LENGTH
|
||||
turnOffButton.isEnabled = text.length >= SvrConstants.MINIMUM_PIN_LENGTH
|
||||
}
|
||||
})
|
||||
|
||||
pinEditText.typeface = Typeface.DEFAULT
|
||||
turnOffButton.setOnClickListener {
|
||||
val pin = pinEditText.text.toString()
|
||||
val correct = PinHashUtil.verifyLocalPinHash(SignalStore.kbsValues().localPinHash!!, pin)
|
||||
val correct = PinHashUtil.verifyLocalPinHash(SignalStore.svr().localPinHash!!, pin)
|
||||
if (correct) {
|
||||
SignalStore.pinValues().setPinRemindersEnabled(false)
|
||||
viewModel.refreshState()
|
||||
|
||||
@@ -18,9 +18,9 @@ class AccountSettingsViewModel : ViewModel() {
|
||||
|
||||
private fun getCurrentState(): AccountSettingsState {
|
||||
return AccountSettingsState(
|
||||
hasPin = SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().hasOptedOut(),
|
||||
hasPin = SignalStore.svr().hasPin() && !SignalStore.svr().hasOptedOut(),
|
||||
pinRemindersEnabled = SignalStore.pinValues().arePinRemindersEnabled(),
|
||||
registrationLockEnabled = SignalStore.kbsValues().isV2RegistrationLockEnabled,
|
||||
registrationLockEnabled = SignalStore.svr().isRegistrationLockEnabled,
|
||||
userUnregistered = TextSecurePreferences.isUnauthorizedReceived(ApplicationDependencies.getApplication()),
|
||||
clientDeprecated = SignalStore.misc().isClientDeprecated
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.thoughtcrime.securesms.LoggingFragment
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberUtil.changeNumberSuccess
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity
|
||||
|
||||
class ChangeNumberPinDiffersFragment : LoggingFragment(R.layout.fragment_change_number_pin_differs) {
|
||||
|
||||
@@ -18,13 +18,13 @@ class ChangeNumberPinDiffersFragment : LoggingFragment(R.layout.fragment_change_
|
||||
}
|
||||
|
||||
val changePin = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == CreateKbsPinActivity.RESULT_OK) {
|
||||
if (result.resultCode == CreateSvrPinActivity.RESULT_OK) {
|
||||
changeNumberSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
view.findViewById<View>(R.id.change_number_pin_differs_update_pin).setOnClickListener {
|
||||
changePin.launch(CreateKbsPinActivity.getIntentForPinChangeFromSettings(requireContext()))
|
||||
changePin.launch(CreateSvrPinActivity.getIntentForPinChangeFromSettings(requireContext()))
|
||||
}
|
||||
|
||||
requireActivity().onBackPressedDispatcher.addCallback(
|
||||
|
||||
@@ -42,7 +42,7 @@ class ChangeNumberRegistrationLockFragment : BaseRegistrationLockFragment(R.layo
|
||||
}
|
||||
|
||||
override fun handleSuccessfulPinEntry(pin: String) {
|
||||
val pinsDiffer: Boolean = SignalStore.kbsValues().localPinHash?.let { !PinHashUtil.verifyLocalPinHash(it, pin) } ?: false
|
||||
val pinsDiffer: Boolean = SignalStore.svr().localPinHash?.let { !PinHashUtil.verifyLocalPinHash(it, pin) } ?: false
|
||||
|
||||
pinButton.cancelSpinning()
|
||||
|
||||
|
||||
@@ -22,18 +22,18 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
|
||||
import org.thoughtcrime.securesms.keyvalue.CertificateType
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.pin.KbsRepository
|
||||
import org.thoughtcrime.securesms.pin.KeyBackupSystemWrongPinException
|
||||
import org.thoughtcrime.securesms.pin.TokenData
|
||||
import org.thoughtcrime.securesms.pin.SvrRepository
|
||||
import org.thoughtcrime.securesms.pin.SvrWrongPinException
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponse
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.SvrAuthCredentialSet
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.whispersystems.signalservice.api.KbsPinData
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender
|
||||
import org.whispersystems.signalservice.api.SvrNoDataException
|
||||
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest
|
||||
import org.whispersystems.signalservice.api.account.PreKeyUpload
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey
|
||||
import org.whispersystems.signalservice.api.push.PNI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.push.ServiceIdType
|
||||
@@ -143,7 +143,7 @@ class ChangeNumberRepository(
|
||||
|
||||
VerifyResponse.from(
|
||||
response = changeNumberResponse,
|
||||
kbsData = null,
|
||||
masterKey = null,
|
||||
pin = null,
|
||||
aciPreKeyCollection = null,
|
||||
pniPreKeyCollection = null
|
||||
@@ -156,18 +156,18 @@ class ChangeNumberRepository(
|
||||
sessionId: String,
|
||||
newE164: String,
|
||||
pin: String,
|
||||
tokenData: TokenData
|
||||
svrAuthCredentials: SvrAuthCredentialSet
|
||||
): Single<ServiceResponse<VerifyResponse>> {
|
||||
return Single.fromCallable {
|
||||
val kbsData: KbsPinData
|
||||
val masterKey: MasterKey
|
||||
val registrationLock: String
|
||||
|
||||
try {
|
||||
kbsData = KbsRepository.restoreMasterKey(pin, tokenData.enclave, tokenData.basicAuth, tokenData.tokenResponse)!!
|
||||
registrationLock = kbsData.masterKey.deriveRegistrationLock()
|
||||
} catch (e: KeyBackupSystemWrongPinException) {
|
||||
masterKey = SvrRepository.restoreMasterKeyPreRegistration(svrAuthCredentials, pin)
|
||||
registrationLock = masterKey.deriveRegistrationLock()
|
||||
} catch (e: SvrWrongPinException) {
|
||||
return@fromCallable ServiceResponse.forExecutionError(e)
|
||||
} catch (e: KeyBackupSystemNoDataException) {
|
||||
} catch (e: SvrNoDataException) {
|
||||
return@fromCallable ServiceResponse.forExecutionError(e)
|
||||
} catch (e: IOException) {
|
||||
return@fromCallable ServiceResponse.forExecutionError(e)
|
||||
@@ -199,7 +199,7 @@ class ChangeNumberRepository(
|
||||
|
||||
VerifyResponse.from(
|
||||
response = changeNumberResponse,
|
||||
kbsData = kbsData,
|
||||
masterKey = masterKey,
|
||||
pin = pin,
|
||||
aciPreKeyCollection = null,
|
||||
pniPreKeyCollection = null
|
||||
|
||||
@@ -16,8 +16,6 @@ import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.pin.KbsRepository
|
||||
import org.thoughtcrime.securesms.pin.TokenData
|
||||
import org.thoughtcrime.securesms.registration.RegistrationSessionProcessor
|
||||
import org.thoughtcrime.securesms.registration.SmsRetrieverReceiver
|
||||
import org.thoughtcrime.securesms.registration.VerifyAccountRepository
|
||||
@@ -27,6 +25,7 @@ import org.thoughtcrime.securesms.registration.VerifyResponseWithRegistrationLoc
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponseWithoutKbs
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.BaseRegistrationViewModel
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.NumberViewState
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.SvrAuthCredentialSet
|
||||
import org.thoughtcrime.securesms.util.DefaultValueLiveData
|
||||
import org.whispersystems.signalservice.api.push.PNI
|
||||
import org.whispersystems.signalservice.api.push.exceptions.IncorrectCodeException
|
||||
@@ -41,9 +40,8 @@ class ChangeNumberViewModel(
|
||||
savedState: SavedStateHandle,
|
||||
password: String,
|
||||
verifyAccountRepository: VerifyAccountRepository,
|
||||
kbsRepository: KbsRepository,
|
||||
private val smsRetrieverReceiver: SmsRetrieverReceiver = SmsRetrieverReceiver(ApplicationDependencies.getApplication())
|
||||
) : BaseRegistrationViewModel(savedState, verifyAccountRepository, kbsRepository, password) {
|
||||
) : BaseRegistrationViewModel(savedState, verifyAccountRepository, password) {
|
||||
|
||||
var oldNumberState: NumberViewState = NumberViewState.Builder().build()
|
||||
private set
|
||||
@@ -179,9 +177,9 @@ class ChangeNumberViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
override fun verifyAccountWithRegistrationLock(pin: String, kbsTokenData: TokenData): Single<ServiceResponse<VerifyResponse>> {
|
||||
override fun verifyAccountWithRegistrationLock(pin: String, svrAuthCredentials: SvrAuthCredentialSet): Single<ServiceResponse<VerifyResponse>> {
|
||||
val sessionId = sessionId ?: throw IllegalStateException("No valid registration session")
|
||||
return changeNumberRepository.changeNumber(sessionId, number.e164Number, pin, kbsTokenData)
|
||||
return changeNumberRepository.changeNumber(sessionId, number.e164Number, pin, svrAuthCredentials)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
@@ -199,14 +197,14 @@ class ChangeNumberViewModel(
|
||||
.map { processor }
|
||||
.onErrorReturn { t ->
|
||||
Log.w(TAG, "Error attempting to change local number", t)
|
||||
VerifyResponseWithRegistrationLockProcessor(ServiceResponse.forUnknownError(t), processor.tokenData)
|
||||
VerifyResponseWithRegistrationLockProcessor(ServiceResponse.forUnknownError(t), processor.svrAuthCredentials)
|
||||
}
|
||||
}
|
||||
|
||||
fun changeNumberWithRecoveryPassword(): Single<Boolean> {
|
||||
val recoveryPassword = SignalStore.kbsValues().recoveryPassword
|
||||
val recoveryPassword = SignalStore.svr().recoveryPassword
|
||||
|
||||
return if (SignalStore.kbsValues().hasPin() && recoveryPassword != null) {
|
||||
return if (SignalStore.svr().hasPin() && recoveryPassword != null) {
|
||||
changeNumberRepository.changeNumber(recoveryPassword = recoveryPassword, newE164 = number.e164Number)
|
||||
.map { r -> VerifyResponseWithoutKbs(r) }
|
||||
.flatMap { p ->
|
||||
@@ -233,8 +231,7 @@ class ChangeNumberViewModel(
|
||||
changeNumberRepository = ChangeNumberRepository(),
|
||||
savedState = handle,
|
||||
password = password,
|
||||
verifyAccountRepository = VerifyAccountRepository(context),
|
||||
kbsRepository = KbsRepository()
|
||||
verifyAccountRepository = VerifyAccountRepository(context)
|
||||
)
|
||||
|
||||
return requireNotNull(modelClass.cast(viewModel))
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
@@ -49,9 +50,12 @@ class InternalSvrPlaygroundViewModel : ViewModel() {
|
||||
loading = true
|
||||
)
|
||||
|
||||
disposables += _state.value.selected.toImplementation()
|
||||
.setPin(_state.value.userPin, SignalStore.kbsValues().getOrCreateMasterKey())
|
||||
.execute()
|
||||
disposables += Single
|
||||
.fromCallable {
|
||||
_state.value.selected.toImplementation()
|
||||
.setPin(_state.value.userPin, SignalStore.svr().getOrCreateMasterKey())
|
||||
.execute()
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { response ->
|
||||
@@ -67,8 +71,7 @@ class InternalSvrPlaygroundViewModel : ViewModel() {
|
||||
loading = true
|
||||
)
|
||||
|
||||
disposables += _state.value.selected.toImplementation()
|
||||
.restoreDataPostRegistration(_state.value.userPin)
|
||||
disposables += Single.fromCallable { _state.value.selected.toImplementation().restoreDataPostRegistration(_state.value.userPin) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { response ->
|
||||
@@ -84,8 +87,7 @@ class InternalSvrPlaygroundViewModel : ViewModel() {
|
||||
loading = true
|
||||
)
|
||||
|
||||
disposables += _state.value.selected.toImplementation()
|
||||
.deleteData()
|
||||
disposables += Single.fromCallable { _state.value.selected.toImplementation().deleteData() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { response ->
|
||||
|
||||
@@ -141,7 +141,7 @@ import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||
import org.thoughtcrime.securesms.insights.InsightsLauncher;
|
||||
import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity;
|
||||
import org.thoughtcrime.securesms.main.Material3OnScrollHelperBinder;
|
||||
import org.thoughtcrime.securesms.main.SearchBinder;
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity;
|
||||
@@ -708,7 +708,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
}
|
||||
}
|
||||
|
||||
if (resultCode == RESULT_OK && requestCode == CreateKbsPinActivity.REQUEST_NEW_PIN) {
|
||||
if (resultCode == RESULT_OK && requestCode == CreateSvrPinActivity.REQUEST_NEW_PIN) {
|
||||
Snackbar.make(fab, R.string.ConfirmKbsPinFragment__pin_created, Snackbar.LENGTH_LONG).show();
|
||||
viewModel.onMegaphoneCompleted(Megaphones.Event.PINS_FOR_ALL);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import androidx.annotation.VisibleForTesting;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.BackoffUtil;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -93,6 +95,12 @@ public abstract class Job {
|
||||
this.canceled = true;
|
||||
}
|
||||
|
||||
/** Provides a default exponential backoff given the current run attempt. */
|
||||
protected final long defaultBackoff() {
|
||||
return BackoffUtil.exponentialBackoff(runAttempt + 1, FeatureFlags.getDefaultMaxBackoff());
|
||||
}
|
||||
|
||||
|
||||
@WorkerThread
|
||||
final void onSubmit() {
|
||||
Log.i(TAG, JobLogger.format(this, "onSubmit()"));
|
||||
@@ -196,7 +204,6 @@ public abstract class Job {
|
||||
return new Result(ResultType.FAILURE, runtimeException, null, INVALID_BACKOFF);
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = PACKAGE_PRIVATE)
|
||||
public boolean isSuccess() {
|
||||
return resultType == ResultType.SUCCESS;
|
||||
}
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.KbsEnclave;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.pin.KbsEnclaves;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Clears data from an old KBS enclave.
|
||||
*/
|
||||
public class ClearFallbackKbsEnclaveJob extends BaseJob {
|
||||
|
||||
public static final String KEY = "ClearFallbackKbsEnclaveJob";
|
||||
|
||||
private static final String TAG = Log.tag(ClearFallbackKbsEnclaveJob.class);
|
||||
|
||||
private static final String KEY_ENCLAVE_NAME = "enclaveName";
|
||||
private static final String KEY_SERVICE_ID = "serviceId";
|
||||
private static final String KEY_MR_ENCLAVE = "mrEnclave";
|
||||
|
||||
private final KbsEnclave enclave;
|
||||
|
||||
ClearFallbackKbsEnclaveJob(@NonNull KbsEnclave enclave) {
|
||||
this(new Parameters.Builder()
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(90))
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.setQueue("ClearFallbackKbsEnclaveJob")
|
||||
.build(),
|
||||
enclave);
|
||||
}
|
||||
|
||||
public static void clearAll() {
|
||||
if (KbsEnclaves.fallbacks().isEmpty()) {
|
||||
Log.i(TAG, "No fallbacks!");
|
||||
return;
|
||||
}
|
||||
|
||||
JobManager jobManager = ApplicationDependencies.getJobManager();
|
||||
|
||||
for (KbsEnclave enclave : KbsEnclaves.fallbacks()) {
|
||||
jobManager.add(new ClearFallbackKbsEnclaveJob(enclave));
|
||||
}
|
||||
}
|
||||
|
||||
private ClearFallbackKbsEnclaveJob(@NonNull Parameters parameters, @NonNull KbsEnclave enclave) {
|
||||
super(parameters);
|
||||
this.enclave = enclave;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable byte[] serialize() {
|
||||
return new JsonJobData.Builder().putString(KEY_ENCLAVE_NAME, enclave.getEnclaveName())
|
||||
.putString(KEY_SERVICE_ID, enclave.getServiceId())
|
||||
.putString(KEY_MR_ENCLAVE, enclave.getMrEnclave())
|
||||
.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRun() throws IOException, UnauthenticatedResponseException {
|
||||
Log.i(TAG, "Preparing to delete data from " + enclave.getEnclaveName());
|
||||
ApplicationDependencies.getKeyBackupService(enclave).newPinChangeSession().removePin();
|
||||
Log.i(TAG, "Successfully deleted the data from " + enclave.getEnclaveName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetry(@NonNull Exception e) {
|
||||
if (e instanceof NonSuccessfulResponseCodeException) {
|
||||
switch (((NonSuccessfulResponseCodeException) e).getCode()) {
|
||||
case 404:
|
||||
return getRunAttempt() < 3;
|
||||
case 508:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextRunAttemptBackoff(int pastAttemptCount, @NonNull Exception e) {
|
||||
if (e instanceof NonSuccessfulResponseCodeException && ((NonSuccessfulResponseCodeException) e).getCode() == 404) {
|
||||
return TimeUnit.DAYS.toMillis(1);
|
||||
} else {
|
||||
return super.getNextRunAttemptBackoff(pastAttemptCount, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
Log.w(TAG, "Job failed! It is likely that the old enclave is offline.");
|
||||
}
|
||||
|
||||
public static class Factory implements Job.Factory<ClearFallbackKbsEnclaveJob> {
|
||||
@Override
|
||||
public @NonNull ClearFallbackKbsEnclaveJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) {
|
||||
JsonJobData data = JsonJobData.deserialize(serializedData);
|
||||
|
||||
KbsEnclave enclave = new KbsEnclave(data.getString(KEY_ENCLAVE_NAME),
|
||||
data.getString(KEY_SERVICE_ID),
|
||||
data.getString(KEY_MR_ENCLAVE));
|
||||
|
||||
return new ClearFallbackKbsEnclaveJob(parameters, enclave);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,6 @@ import org.thoughtcrime.securesms.migrations.DecryptionsDrainedMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.DeleteDeprecatedLogsMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.DirectoryRefreshMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.EmojiDownloadMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.KbsEnclaveMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.MigrationCompleteJob;
|
||||
import org.thoughtcrime.securesms.migrations.OptimizeMessageSearchIndexMigrationJob;
|
||||
@@ -65,7 +64,6 @@ import org.thoughtcrime.securesms.migrations.ProfileMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.ProfileSharingUpdateMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.RebuildMessageSearchIndexMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.RecipientSearchMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StickerAdditionMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StickerDayByDayMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StickerLaunchMigrationJob;
|
||||
@@ -75,6 +73,7 @@ import org.thoughtcrime.securesms.migrations.StorageServiceMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StorageServiceSystemNameMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StoryReadStateMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StoryViewedReceiptsStateMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.Svr2MirrorMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.SyncDistributionListsMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.TrimByLengthSettingsMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.UpdateSmsJobsMigrationJob;
|
||||
@@ -101,11 +100,9 @@ public final class JobManagerFactories {
|
||||
put(AvatarGroupsV2DownloadJob.KEY, new AvatarGroupsV2DownloadJob.Factory());
|
||||
put(BoostReceiptRequestResponseJob.KEY, new BoostReceiptRequestResponseJob.Factory());
|
||||
put(CallLinkPeekJob.KEY, new CallLinkPeekJob.Factory());
|
||||
put("CallSyncEventJob", new FailingJob.Factory());
|
||||
put(CallSyncEventJob.KEY, new CallSyncEventJob.Factory());
|
||||
put(CheckServiceReachabilityJob.KEY, new CheckServiceReachabilityJob.Factory());
|
||||
put(CleanPreKeysJob.KEY, new CleanPreKeysJob.Factory());
|
||||
put(ClearFallbackKbsEnclaveJob.KEY, new ClearFallbackKbsEnclaveJob.Factory());
|
||||
put(ConversationShortcutRankingUpdateJob.KEY, new ConversationShortcutRankingUpdateJob.Factory());
|
||||
put(ConversationShortcutUpdateJob.KEY, new ConversationShortcutUpdateJob.Factory());
|
||||
put(CreateReleaseChannelJob.KEY, new CreateReleaseChannelJob.Factory());
|
||||
@@ -126,7 +123,6 @@ public final class JobManagerFactories {
|
||||
put(GroupCallPeekWorkerJob.KEY, new GroupCallPeekWorkerJob.Factory());
|
||||
put(GroupV2UpdateSelfProfileKeyJob.KEY, new GroupV2UpdateSelfProfileKeyJob.Factory());
|
||||
put(IndividualSendJob.KEY, new IndividualSendJob.Factory());
|
||||
put(KbsEnclaveMigrationWorkerJob.KEY, new KbsEnclaveMigrationWorkerJob.Factory());
|
||||
put(LeaveGroupV2Job.KEY, new LeaveGroupV2Job.Factory());
|
||||
put(LeaveGroupV2WorkerJob.KEY, new LeaveGroupV2WorkerJob.Factory());
|
||||
put(LocalBackupJob.KEY, new LocalBackupJob.Factory());
|
||||
@@ -178,7 +174,7 @@ public final class JobManagerFactories {
|
||||
put(RebuildMessageSearchIndexJob.KEY, new RebuildMessageSearchIndexJob.Factory());
|
||||
put(RefreshAttributesJob.KEY, new RefreshAttributesJob.Factory());
|
||||
put(RefreshCallLinkDetailsJob.KEY, new RefreshCallLinkDetailsJob.Factory());
|
||||
put(RefreshKbsCredentialsJob.KEY, new RefreshKbsCredentialsJob.Factory());
|
||||
put(RefreshSvrCredentialsJob.KEY, new RefreshSvrCredentialsJob.Factory());
|
||||
put(RefreshOwnProfileJob.KEY, new RefreshOwnProfileJob.Factory());
|
||||
put(RemoteConfigRefreshJob.KEY, new RemoteConfigRefreshJob.Factory());
|
||||
put(RemoteDeleteSendJob.KEY, new RemoteDeleteSendJob.Factory());
|
||||
@@ -200,6 +196,7 @@ public final class JobManagerFactories {
|
||||
put(SendViewedReceiptJob.KEY, new SendViewedReceiptJob.Factory(application));
|
||||
put(SyncSystemContactLinksJob.KEY, new SyncSystemContactLinksJob.Factory());
|
||||
put(MultiDeviceStorySendSyncJob.KEY, new MultiDeviceStorySendSyncJob.Factory());
|
||||
put(ResetSvrGuessCountJob.KEY, new ResetSvrGuessCountJob.Factory());
|
||||
put(ServiceOutageDetectionJob.KEY, new ServiceOutageDetectionJob.Factory());
|
||||
put(SmsReceiveJob.KEY, new SmsReceiveJob.Factory());
|
||||
put(SmsSendJob.KEY, new SmsSendJob.Factory());
|
||||
@@ -213,6 +210,7 @@ public final class JobManagerFactories {
|
||||
put(SubscriptionReceiptRequestResponseJob.KEY, new SubscriptionReceiptRequestResponseJob.Factory());
|
||||
put(StoryOnboardingDownloadJob.KEY, new StoryOnboardingDownloadJob.Factory());
|
||||
put(SubmitRateLimitPushChallengeJob.KEY, new SubmitRateLimitPushChallengeJob.Factory());
|
||||
put(Svr2MirrorJob.KEY, new Svr2MirrorJob.Factory());
|
||||
put(ThreadUpdateJob.KEY, new ThreadUpdateJob.Factory());
|
||||
put(TrimThreadJob.KEY, new TrimThreadJob.Factory());
|
||||
put(TypingSendJob.KEY, new TypingSendJob.Factory());
|
||||
@@ -236,7 +234,6 @@ public final class JobManagerFactories {
|
||||
put(DeleteDeprecatedLogsMigrationJob.KEY, new DeleteDeprecatedLogsMigrationJob.Factory());
|
||||
put(DirectoryRefreshMigrationJob.KEY, new DirectoryRefreshMigrationJob.Factory());
|
||||
put(EmojiDownloadMigrationJob.KEY, new EmojiDownloadMigrationJob.Factory());
|
||||
put(KbsEnclaveMigrationJob.KEY, new KbsEnclaveMigrationJob.Factory());
|
||||
put(LegacyMigrationJob.KEY, new LegacyMigrationJob.Factory());
|
||||
put(MigrationCompleteJob.KEY, new MigrationCompleteJob.Factory());
|
||||
put(OptimizeMessageSearchIndexMigrationJob.KEY,new OptimizeMessageSearchIndexMigrationJob.Factory());
|
||||
@@ -249,7 +246,6 @@ public final class JobManagerFactories {
|
||||
put(ProfileSharingUpdateMigrationJob.KEY, new ProfileSharingUpdateMigrationJob.Factory());
|
||||
put(RebuildMessageSearchIndexMigrationJob.KEY, new RebuildMessageSearchIndexMigrationJob.Factory());
|
||||
put(RecipientSearchMigrationJob.KEY, new RecipientSearchMigrationJob.Factory());
|
||||
put(RegistrationPinV2MigrationJob.KEY, new RegistrationPinV2MigrationJob.Factory());
|
||||
put(StickerLaunchMigrationJob.KEY, new StickerLaunchMigrationJob.Factory());
|
||||
put(StickerAdditionMigrationJob.KEY, new StickerAdditionMigrationJob.Factory());
|
||||
put(StickerDayByDayMigrationJob.KEY, new StickerDayByDayMigrationJob.Factory());
|
||||
@@ -259,6 +255,7 @@ public final class JobManagerFactories {
|
||||
put(StorageServiceSystemNameMigrationJob.KEY, new StorageServiceSystemNameMigrationJob.Factory());
|
||||
put(StoryReadStateMigrationJob.KEY, new StoryReadStateMigrationJob.Factory());
|
||||
put(StoryViewedReceiptsStateMigrationJob.KEY, new StoryViewedReceiptsStateMigrationJob.Factory());
|
||||
put(Svr2MirrorMigrationJob.KEY, new Svr2MirrorMigrationJob.Factory());
|
||||
put(SyncDistributionListsMigrationJob.KEY, new SyncDistributionListsMigrationJob.Factory());
|
||||
put(TrimByLengthSettingsMigrationJob.KEY, new TrimByLengthSettingsMigrationJob.Factory());
|
||||
put(UpdateSmsJobsMigrationJob.KEY, new UpdateSmsJobsMigrationJob.Factory());
|
||||
@@ -287,6 +284,11 @@ public final class JobManagerFactories {
|
||||
put("PushTextSendJob", new IndividualSendJob.Factory());
|
||||
put("MultiDevicePniIdentityUpdateJob", new FailingJob.Factory());
|
||||
put("MultiDeviceGroupUpdateJob", new FailingJob.Factory());
|
||||
put("CallSyncEventJob", new FailingJob.Factory());
|
||||
put("RegistrationPinV2MigrationJob", new FailingJob.Factory());
|
||||
put("KbsEnclaveMigrationWorkerJob", new FailingJob.Factory());
|
||||
put("KbsEnclaveMigrationJob", new PassingMigrationJob.Factory());
|
||||
put("ClearFallbackKbsEnclaveJob", new FailingJob.Factory());
|
||||
}};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.BackoffUtil;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.migrations.KbsEnclaveMigrationJob;
|
||||
import org.thoughtcrime.securesms.pin.PinState;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Should only be enqueued by {@link KbsEnclaveMigrationJob}. Does the actual work of migrating KBS
|
||||
* data to the new enclave and deleting it from the old enclave(s).
|
||||
*/
|
||||
public class KbsEnclaveMigrationWorkerJob extends BaseJob {
|
||||
|
||||
public static final String KEY = "KbsEnclaveMigrationWorkerJob";
|
||||
|
||||
private static final String TAG = Log.tag(KbsEnclaveMigrationWorkerJob.class);
|
||||
|
||||
public KbsEnclaveMigrationWorkerJob() {
|
||||
this(new Parameters.Builder()
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setLifespan(Parameters.IMMORTAL)
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.setQueue("KbsEnclaveMigrationWorkerJob")
|
||||
.setMaxInstancesForFactory(1)
|
||||
.build());
|
||||
}
|
||||
|
||||
private KbsEnclaveMigrationWorkerJob(@NonNull Parameters parameters) {
|
||||
super(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable byte[] serialize() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRun() throws IOException, UnauthenticatedResponseException {
|
||||
String pin = SignalStore.kbsValues().getPin();
|
||||
|
||||
if (SignalStore.kbsValues().hasOptedOut()) {
|
||||
Log.w(TAG, "Opted out of KBS! Nothing to migrate.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (pin == null) {
|
||||
Log.w(TAG, "No PIN available! Can't migrate!");
|
||||
return;
|
||||
}
|
||||
|
||||
PinState.onMigrateToNewEnclave(pin);
|
||||
Log.i(TAG, "Migration successful!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetry(@NonNull Exception e) {
|
||||
return e instanceof IOException ||
|
||||
e instanceof UnauthenticatedResponseException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextRunAttemptBackoff(int pastAttemptCount, @NonNull Exception exception) {
|
||||
if (exception instanceof NonSuccessfulResponseCodeException) {
|
||||
if (((NonSuccessfulResponseCodeException) exception).is5xx()) {
|
||||
return BackoffUtil.exponentialBackoff(pastAttemptCount, FeatureFlags.getServerErrorMaxBackoff());
|
||||
}
|
||||
}
|
||||
|
||||
return super.getNextRunAttemptBackoff(pastAttemptCount, exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
throw new AssertionError("This job should never fail. " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
public static class Factory implements Job.Factory<KbsEnclaveMigrationWorkerJob> {
|
||||
@Override
|
||||
public @NonNull KbsEnclaveMigrationWorkerJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) {
|
||||
return new KbsEnclaveMigrationWorkerJob(parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,7 +152,7 @@ class PnpInitializeDevicesJob private constructor(parameters: Parameters) : Base
|
||||
|
||||
VerifyResponse.from(
|
||||
response = distributionResponse,
|
||||
kbsData = null,
|
||||
masterKey = null,
|
||||
pin = null,
|
||||
aciPreKeyCollection = null,
|
||||
pniPreKeyCollection = null
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.KbsValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.SvrValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationRepository;
|
||||
import org.thoughtcrime.securesms.registration.secondary.DeviceNameCipher;
|
||||
@@ -88,17 +88,13 @@ public class RefreshAttributesJob extends BaseJob {
|
||||
boolean fetchesMessages = !SignalStore.account().isFcmEnabled() || SignalStore.internalValues().isWebsocketModeForced();
|
||||
byte[] unidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey());
|
||||
boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context);
|
||||
String registrationLockV1 = null;
|
||||
String registrationLockV2 = null;
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
SvrValues svrValues = SignalStore.svr();
|
||||
int pniRegistrationId = new RegistrationRepository(ApplicationDependencies.getApplication()).getPniRegistrationId();
|
||||
String recoveryPassword = kbsValues.getRecoveryPassword();
|
||||
String recoveryPassword = svrValues.getRecoveryPassword();
|
||||
|
||||
if (kbsValues.isV2RegistrationLockEnabled()) {
|
||||
registrationLockV2 = kbsValues.getRegistrationLockToken();
|
||||
} else if (TextSecurePreferences.isV1RegistrationLockEnabled(context)) {
|
||||
//noinspection deprecation Ok to read here as they have not migrated
|
||||
registrationLockV1 = TextSecurePreferences.getDeprecatedV1RegistrationLockPin(context);
|
||||
if (svrValues.isRegistrationLockEnabled()) {
|
||||
registrationLockV2 = svrValues.getRegistrationLockToken();
|
||||
}
|
||||
|
||||
boolean phoneNumberDiscoverable = SignalStore.phoneNumberPrivacy().getPhoneNumberListingMode().isDiscoverable();
|
||||
@@ -106,8 +102,8 @@ public class RefreshAttributesJob extends BaseJob {
|
||||
String deviceName = SignalStore.account().getDeviceName();
|
||||
byte[] encryptedDeviceName = (deviceName == null) ? null : DeviceNameCipher.encryptDeviceName(deviceName.getBytes(StandardCharsets.UTF_8), SignalStore.account().getAciIdentityKey());
|
||||
|
||||
AccountAttributes.Capabilities capabilities = AppCapabilities.getCapabilities(kbsValues.hasPin() && !kbsValues.hasOptedOut());
|
||||
Log.i(TAG, "Calling setAccountAttributes() reglockV1? " + !TextUtils.isEmpty(registrationLockV1) + ", reglockV2? " + !TextUtils.isEmpty(registrationLockV2) + ", pin? " + kbsValues.hasPin() +
|
||||
AccountAttributes.Capabilities capabilities = AppCapabilities.getCapabilities(svrValues.hasPin() && !svrValues.hasOptedOut());
|
||||
Log.i(TAG, "Calling setAccountAttributes() reglockV2? " + !TextUtils.isEmpty(registrationLockV2) + ", pin? " + svrValues.hasPin() +
|
||||
"\n Recovery password? " + !TextUtils.isEmpty(recoveryPassword) +
|
||||
"\n Phone number discoverable : " + phoneNumberDiscoverable +
|
||||
"\n Device Name : " + (encryptedDeviceName != null) +
|
||||
@@ -117,7 +113,6 @@ public class RefreshAttributesJob extends BaseJob {
|
||||
null,
|
||||
registrationId,
|
||||
fetchesMessages,
|
||||
registrationLockV1,
|
||||
registrationLockV2,
|
||||
unidentifiedAccessKey,
|
||||
universalUnidentifiedAccess,
|
||||
|
||||
@@ -17,7 +17,6 @@ import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
@@ -107,7 +106,7 @@ public class RefreshOwnProfileJob extends BaseJob {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().hasOptedOut() && SignalStore.storageService().getLastSyncTime() == 0) {
|
||||
if (SignalStore.svr().hasPin() && !SignalStore.svr().hasOptedOut() && SignalStore.storageService().getLastSyncTime() == 0) {
|
||||
Log.i(TAG, "Registered with PIN but haven't completed storage sync yet.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.pin.KbsRepository
|
||||
import org.thoughtcrime.securesms.pin.SvrRepository
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException
|
||||
import java.io.IOException
|
||||
import kotlin.time.Duration
|
||||
@@ -14,20 +14,20 @@ import kotlin.time.Duration.Companion.days
|
||||
/**
|
||||
* Refresh KBS authentication credentials for talking to KBS during re-registration.
|
||||
*/
|
||||
class RefreshKbsCredentialsJob private constructor(parameters: Parameters) : BaseJob(parameters) {
|
||||
class RefreshSvrCredentialsJob private constructor(parameters: Parameters) : BaseJob(parameters) {
|
||||
|
||||
companion object {
|
||||
const val KEY = "RefreshKbsCredentialsJob"
|
||||
|
||||
private val TAG = Log.tag(RefreshKbsCredentialsJob::class.java)
|
||||
private val TAG = Log.tag(RefreshSvrCredentialsJob::class.java)
|
||||
private val FREQUENCY: Duration = 15.days
|
||||
|
||||
@JvmStatic
|
||||
fun enqueueIfNecessary() {
|
||||
if (SignalStore.kbsValues().hasPin()) {
|
||||
val lastTimestamp = SignalStore.kbsValues().lastRefreshAuthTimestamp
|
||||
if (SignalStore.svr().hasPin()) {
|
||||
val lastTimestamp = SignalStore.svr().lastRefreshAuthTimestamp
|
||||
if (lastTimestamp + FREQUENCY.inWholeMilliseconds < System.currentTimeMillis() || lastTimestamp > System.currentTimeMillis()) {
|
||||
ApplicationDependencies.getJobManager().add(RefreshKbsCredentialsJob())
|
||||
ApplicationDependencies.getJobManager().add(RefreshSvrCredentialsJob())
|
||||
} else {
|
||||
Log.d(TAG, "Do not need to refresh credentials. Last refresh: $lastTimestamp")
|
||||
}
|
||||
@@ -49,7 +49,7 @@ class RefreshKbsCredentialsJob private constructor(parameters: Parameters) : Bas
|
||||
override fun getFactoryKey(): String = KEY
|
||||
|
||||
override fun onRun() {
|
||||
KbsRepository().refreshAuthorization()
|
||||
SvrRepository.refreshAndStoreAuthorization()
|
||||
}
|
||||
|
||||
override fun onShouldRetry(e: Exception): Boolean {
|
||||
@@ -58,9 +58,9 @@ class RefreshKbsCredentialsJob private constructor(parameters: Parameters) : Bas
|
||||
|
||||
override fun onFailure() = Unit
|
||||
|
||||
class Factory : Job.Factory<RefreshKbsCredentialsJob> {
|
||||
override fun create(parameters: Parameters, serializedData: ByteArray?): RefreshKbsCredentialsJob {
|
||||
return RefreshKbsCredentialsJob(parameters)
|
||||
class Factory : Job.Factory<RefreshSvrCredentialsJob> {
|
||||
override fun create(parameters: Parameters, serializedData: ByteArray?): RefreshSvrCredentialsJob {
|
||||
return RefreshSvrCredentialsJob(parameters)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.BuildConfig
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.pin.SvrRepository
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey
|
||||
import org.whispersystems.signalservice.api.svr.SecureValueRecovery
|
||||
import org.whispersystems.signalservice.api.svr.SecureValueRecovery.BackupResponse
|
||||
import org.whispersystems.signalservice.api.svr.SecureValueRecovery.PinChangeSession
|
||||
import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV1
|
||||
import kotlin.concurrent.withLock
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
/**
|
||||
* Attempts to reset the guess on the SVR PIN. Intended to be enqueued after a successful restore.
|
||||
*/
|
||||
class ResetSvrGuessCountJob private constructor(
|
||||
parameters: Parameters,
|
||||
private val serializedChangeSession: String?,
|
||||
private var svr2Complete: Boolean
|
||||
) : Job(parameters) {
|
||||
|
||||
companion object {
|
||||
const val KEY = "ResetSvrGuessCountJob"
|
||||
|
||||
private val TAG = Log.tag(ResetSvrGuessCountJob::class.java)
|
||||
|
||||
private const val KEY_CHANGE_SESSION = "change_session"
|
||||
private const val KEY_SVR2_COMPLETE = "svr2_complete"
|
||||
}
|
||||
|
||||
constructor() : this(
|
||||
Parameters.Builder()
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setLifespan(1.days.inWholeMilliseconds)
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.setQueue("ResetSvrGuessCountJob")
|
||||
.setMaxInstancesForFactory(1)
|
||||
.build(),
|
||||
null,
|
||||
false
|
||||
)
|
||||
|
||||
override fun serialize(): ByteArray? {
|
||||
return JsonJobData.Builder()
|
||||
.putString(KEY_CHANGE_SESSION, serializedChangeSession)
|
||||
.putBoolean(KEY_SVR2_COMPLETE, svr2Complete)
|
||||
.build()
|
||||
.serialize()
|
||||
}
|
||||
|
||||
override fun getFactoryKey(): String = KEY
|
||||
|
||||
override fun run(): Result {
|
||||
SvrRepository.operationLock.withLock {
|
||||
val pin = SignalStore.svr().pin
|
||||
|
||||
if (SignalStore.svr().hasOptedOut()) {
|
||||
Log.w(TAG, "Opted out of SVR! Nothing to migrate.")
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
if (pin == null) {
|
||||
Log.w(TAG, "No PIN available! Can't migrate!")
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
val masterKey: MasterKey = SignalStore.svr().getOrCreateMasterKey()
|
||||
|
||||
val svr2Result = if (!svr2Complete) {
|
||||
resetGuessCount(ApplicationDependencies.getSignalServiceAccountManager().getSecureValueRecoveryV2(BuildConfig.SVR2_MRENCLAVE), pin, masterKey)
|
||||
} else {
|
||||
Log.d(TAG, "Already reset guess count on SVR2. Skipping.")
|
||||
Result.success()
|
||||
}
|
||||
|
||||
if (!svr2Result.isSuccess) {
|
||||
return svr2Result
|
||||
} else {
|
||||
Log.d(TAG, "SVR2 reset complete. Marking as such so we do not retry it if SVR1 fails.")
|
||||
svr2Complete = true
|
||||
}
|
||||
|
||||
return resetGuessCount(SecureValueRecoveryV1(ApplicationDependencies.getKeyBackupService(BuildConfig.KBS_ENCLAVE)), pin, masterKey)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure() = Unit
|
||||
|
||||
private fun resetGuessCount(svr: SecureValueRecovery, pin: String, masterKey: MasterKey): Result {
|
||||
val session: PinChangeSession = if (serializedChangeSession != null) {
|
||||
svr.resumePinChangeSession(pin, SignalStore.svr().getOrCreateMasterKey(), serializedChangeSession)
|
||||
} else {
|
||||
svr.setPin(pin, masterKey)
|
||||
}
|
||||
|
||||
return when (val response: BackupResponse = session.execute()) {
|
||||
is BackupResponse.Success -> {
|
||||
Log.i(TAG, "Successfully reset guess count. $svr")
|
||||
SignalStore.svr().appendAuthTokenToList(response.authorization.asBasic())
|
||||
Result.success()
|
||||
}
|
||||
is BackupResponse.ApplicationError -> {
|
||||
Log.w(TAG, "Hit an application error. Retrying. $svr", response.exception)
|
||||
Result.retry(defaultBackoff())
|
||||
}
|
||||
BackupResponse.EnclaveNotFound -> {
|
||||
Log.w(TAG, "Could not find the enclave. Giving up. $svr")
|
||||
Result.success()
|
||||
}
|
||||
BackupResponse.ExposeFailure -> {
|
||||
Log.w(TAG, "Failed to expose the backup. Giving up. $svr")
|
||||
Result.success()
|
||||
}
|
||||
is BackupResponse.NetworkError -> {
|
||||
Log.w(TAG, "Hit a network error. Retrying. $svr", response.exception)
|
||||
Result.retry(defaultBackoff())
|
||||
}
|
||||
BackupResponse.ServerRejected -> {
|
||||
Log.w(TAG, "Server told us to stop trying. Giving up. $svr")
|
||||
Result.success()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Factory : Job.Factory<ResetSvrGuessCountJob> {
|
||||
override fun create(parameters: Parameters, serializedData: ByteArray?): ResetSvrGuessCountJob {
|
||||
val data = JsonJobData.deserialize(serializedData)
|
||||
|
||||
return ResetSvrGuessCountJob(
|
||||
parameters,
|
||||
data.getString(KEY_CHANGE_SESSION),
|
||||
data.getBoolean(KEY_SVR2_COMPLETE)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.UnknownStorageIdTable;
|
||||
import org.thoughtcrime.securesms.database.model.RecipientRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
@@ -171,7 +170,7 @@ public class StorageSyncJob extends BaseJob {
|
||||
|
||||
@Override
|
||||
protected void onRun() throws IOException, RetryLaterException, UntrustedIdentityException {
|
||||
if (!SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().hasOptedOut()) {
|
||||
if (!SignalStore.svr().hasPin() && !SignalStore.svr().hasOptedOut()) {
|
||||
Log.i(TAG, "Doesn't have a PIN. Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.BuildConfig
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.pin.SvrRepository
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.whispersystems.signalservice.api.svr.SecureValueRecovery.BackupResponse
|
||||
import org.whispersystems.signalservice.api.svr.SecureValueRecovery.PinChangeSession
|
||||
import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV2
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
/**
|
||||
* Ensures a user's SVR data is written to SVR2.
|
||||
*/
|
||||
class Svr2MirrorJob private constructor(parameters: Parameters, private var serializedChangeSession: String?) : Job(parameters) {
|
||||
|
||||
companion object {
|
||||
const val KEY = "Svr2MirrorJob"
|
||||
|
||||
private val TAG = Log.tag(Svr2MirrorJob::class.java)
|
||||
|
||||
private const val KEY_CHANGE_SESSION = "change_session"
|
||||
}
|
||||
|
||||
constructor() : this(
|
||||
Parameters.Builder()
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setLifespan(Parameters.IMMORTAL)
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.setQueue("Svr2MirrorJob")
|
||||
.setMaxInstancesForFactory(1)
|
||||
.build(),
|
||||
null
|
||||
)
|
||||
|
||||
override fun serialize(): ByteArray? {
|
||||
return JsonJobData.Builder()
|
||||
.putString(KEY_CHANGE_SESSION, serializedChangeSession)
|
||||
.build()
|
||||
.serialize()
|
||||
}
|
||||
|
||||
override fun getFactoryKey(): String = KEY
|
||||
|
||||
override fun run(): Result {
|
||||
SvrRepository.operationLock.withLock {
|
||||
val pin = SignalStore.svr().pin
|
||||
|
||||
if (SignalStore.svr().hasOptedOut()) {
|
||||
Log.w(TAG, "Opted out of SVR! Nothing to migrate.")
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
if (pin == null) {
|
||||
Log.w(TAG, "No PIN available! Can't migrate!")
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
if (!FeatureFlags.svr2()) {
|
||||
Log.w(TAG, "SVR2 was disabled! SKipping.")
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
val svr2: SecureValueRecoveryV2 = ApplicationDependencies.getSignalServiceAccountManager().getSecureValueRecoveryV2(BuildConfig.SVR2_MRENCLAVE)
|
||||
|
||||
val session: PinChangeSession = serializedChangeSession?.let { session ->
|
||||
svr2.resumePinChangeSession(pin, SignalStore.svr().getOrCreateMasterKey(), session)
|
||||
} ?: svr2.setPin(pin, SignalStore.svr().getOrCreateMasterKey())
|
||||
|
||||
serializedChangeSession = session.serialize()
|
||||
|
||||
return when (val response: BackupResponse = session.execute()) {
|
||||
is BackupResponse.Success -> {
|
||||
Log.i(TAG, "Successfully migrated to SVR2!")
|
||||
SignalStore.svr().appendAuthTokenToList(response.authorization.asBasic())
|
||||
ApplicationDependencies.getJobManager().add(RefreshAttributesJob())
|
||||
Result.success()
|
||||
}
|
||||
is BackupResponse.ApplicationError -> {
|
||||
Log.w(TAG, "Hit an application error. Retrying.", response.exception)
|
||||
Result.retry(defaultBackoff())
|
||||
}
|
||||
BackupResponse.EnclaveNotFound -> {
|
||||
Log.w(TAG, "Could not find the enclave. Giving up.")
|
||||
Result.success()
|
||||
}
|
||||
BackupResponse.ExposeFailure -> {
|
||||
Log.w(TAG, "Failed to expose the backup. Giving up.")
|
||||
Result.success()
|
||||
}
|
||||
is BackupResponse.NetworkError -> {
|
||||
Log.w(TAG, "Hit a network error. Retrying.", response.exception)
|
||||
Result.retry(defaultBackoff())
|
||||
}
|
||||
BackupResponse.ServerRejected -> {
|
||||
Log.w(TAG, "Server told us to stop trying. Giving up.")
|
||||
Result.success()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure() = Unit
|
||||
|
||||
class Factory : Job.Factory<Svr2MirrorJob> {
|
||||
override fun create(parameters: Parameters, serializedData: ByteArray?): Svr2MirrorJob {
|
||||
return Svr2MirrorJob(parameters, JsonJobData.deserialize(serializedData).getString(KEY_CHANGE_SESSION))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -245,8 +245,8 @@ internal class PaymentsValues internal constructor(store: KeyValueStore) : Signa
|
||||
|
||||
fun showUpdatePinInfoCard(): Boolean {
|
||||
return if (userHasLargeBalance() &&
|
||||
SignalStore.kbsValues().hasPin() &&
|
||||
!SignalStore.kbsValues().hasOptedOut() && SignalStore.pinValues().keyboardType == PinKeyboardType.NUMERIC
|
||||
SignalStore.svr().hasPin() &&
|
||||
!SignalStore.svr().hasOptedOut() && SignalStore.pinValues().keyboardType == PinKeyboardType.NUMERIC
|
||||
) {
|
||||
store.getBoolean(SHOW_CASHING_OUT_INFO_CARD, true)
|
||||
} else {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package org.thoughtcrime.securesms.keyvalue;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
@@ -11,13 +8,11 @@ import org.thoughtcrime.securesms.lock.SignalPinReminders;
|
||||
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Specifically handles just the UI/UX state around PINs. For actual keys, see {@link KbsValues}.
|
||||
* Specifically handles just the UI/UX state around PINs. For actual keys, see {@link SvrValues}.
|
||||
*/
|
||||
public final class PinValues extends SignalStoreValues {
|
||||
|
||||
@@ -50,7 +45,7 @@ public final class PinValues extends SignalStoreValues {
|
||||
.putLong(NEXT_INTERVAL, nextInterval)
|
||||
.apply();
|
||||
|
||||
SignalStore.kbsValues().setPinIfNotPresent(pin);
|
||||
SignalStore.svr().setPinIfNotPresent(pin);
|
||||
}
|
||||
|
||||
public void onEntrySuccessWithWrongGuess(@NonNull String pin) {
|
||||
@@ -62,7 +57,7 @@ public final class PinValues extends SignalStoreValues {
|
||||
.putLong(NEXT_INTERVAL, nextInterval)
|
||||
.apply();
|
||||
|
||||
SignalStore.kbsValues().setPinIfNotPresent(pin);
|
||||
SignalStore.svr().setPinIfNotPresent(pin);
|
||||
}
|
||||
|
||||
public void onEntrySkipWithWrongGuess() {
|
||||
|
||||
@@ -19,7 +19,7 @@ public final class SignalStore {
|
||||
private KeyValueStore store;
|
||||
|
||||
private final AccountValues accountValues;
|
||||
private final KbsValues kbsValues;
|
||||
private final SvrValues svrValues;
|
||||
private final RegistrationValues registrationValues;
|
||||
private final PinValues pinValues;
|
||||
private final RemoteConfigValues remoteConfigValues;
|
||||
@@ -63,7 +63,7 @@ public final class SignalStore {
|
||||
private SignalStore(@NonNull KeyValueStore store) {
|
||||
this.store = store;
|
||||
this.accountValues = new AccountValues(store);
|
||||
this.kbsValues = new KbsValues(store);
|
||||
this.svrValues = new SvrValues(store);
|
||||
this.registrationValues = new RegistrationValues(store);
|
||||
this.pinValues = new PinValues(store);
|
||||
this.remoteConfigValues = new RemoteConfigValues(store);
|
||||
@@ -92,7 +92,7 @@ public final class SignalStore {
|
||||
|
||||
public static void onFirstEverAppLaunch() {
|
||||
account().onFirstEverAppLaunch();
|
||||
kbsValues().onFirstEverAppLaunch();
|
||||
svr().onFirstEverAppLaunch();
|
||||
registrationValues().onFirstEverAppLaunch();
|
||||
pinValues().onFirstEverAppLaunch();
|
||||
remoteConfigValues().onFirstEverAppLaunch();
|
||||
@@ -121,7 +121,7 @@ public final class SignalStore {
|
||||
public static List<String> getKeysToIncludeInBackup() {
|
||||
List<String> keys = new ArrayList<>();
|
||||
keys.addAll(account().getKeysToIncludeInBackup());
|
||||
keys.addAll(kbsValues().getKeysToIncludeInBackup());
|
||||
keys.addAll(svr().getKeysToIncludeInBackup());
|
||||
keys.addAll(registrationValues().getKeysToIncludeInBackup());
|
||||
keys.addAll(pinValues().getKeysToIncludeInBackup());
|
||||
keys.addAll(remoteConfigValues().getKeysToIncludeInBackup());
|
||||
@@ -168,8 +168,8 @@ public final class SignalStore {
|
||||
return getInstance().accountValues;
|
||||
}
|
||||
|
||||
public static @NonNull KbsValues kbsValues() {
|
||||
return getInstance().kbsValues;
|
||||
public static @NonNull SvrValues svr() {
|
||||
return getInstance().svrValues;
|
||||
}
|
||||
|
||||
public static @NonNull RegistrationValues registrationValues() {
|
||||
|
||||
@@ -33,7 +33,7 @@ public class StorageServiceValues extends SignalStoreValues {
|
||||
if (getStore().containsKey(SYNC_STORAGE_KEY)) {
|
||||
return new StorageKey(getBlob(SYNC_STORAGE_KEY, null));
|
||||
}
|
||||
return SignalStore.kbsValues().getOrCreateMasterKey().deriveStorageServiceKey();
|
||||
return SignalStore.svr().getOrCreateMasterKey().deriveStorageServiceKey();
|
||||
}
|
||||
|
||||
public long getLastSyncTime() {
|
||||
|
||||
@@ -5,7 +5,6 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.StringStringSerializer;
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import org.whispersystems.signalservice.api.KbsPinData;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.api.kbs.PinHashUtil;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
@@ -13,14 +12,13 @@ import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class KbsValues extends SignalStoreValues {
|
||||
public final class SvrValues extends SignalStoreValues {
|
||||
|
||||
public static final String V2_LOCK_ENABLED = "kbs.v2_lock_enabled";
|
||||
public static final String REGISTRATION_LOCK_ENABLED = "kbs.v2_lock_enabled";
|
||||
private static final String MASTER_KEY = "kbs.registration_lock_master_key";
|
||||
private static final String TOKEN_RESPONSE = "kbs.token_response";
|
||||
private static final String PIN = "kbs.pin";
|
||||
@@ -28,10 +26,10 @@ public final class KbsValues extends SignalStoreValues {
|
||||
private static final String LAST_CREATE_FAILED_TIMESTAMP = "kbs.last_create_failed_timestamp";
|
||||
public static final String OPTED_OUT = "kbs.opted_out";
|
||||
private static final String PIN_FORGOTTEN_OR_SKIPPED = "kbs.pin.forgotten.or.skipped";
|
||||
private static final String KBS_AUTH_TOKENS = "kbs.kbs_auth_tokens";
|
||||
private static final String KBS_LAST_AUTH_REFRESH_TIMESTAMP = "kbs.kbs_auth_tokens.last_refresh_timestamp";
|
||||
private static final String SVR_AUTH_TOKENS = "kbs.kbs_auth_tokens";
|
||||
private static final String SVR_LAST_AUTH_REFRESH_TIMESTAMP = "kbs.kbs_auth_tokens.last_refresh_timestamp";
|
||||
|
||||
KbsValues(KeyValueStore store) {
|
||||
SvrValues(KeyValueStore store) {
|
||||
super(store);
|
||||
}
|
||||
|
||||
@@ -42,39 +40,27 @@ public final class KbsValues extends SignalStoreValues {
|
||||
@Override
|
||||
@NonNull
|
||||
List<String> getKeysToIncludeInBackup() {
|
||||
return List.of(KBS_AUTH_TOKENS);
|
||||
return List.of(SVR_AUTH_TOKENS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deliberately does not clear the {@link #MASTER_KEY}.
|
||||
*
|
||||
* Should only be called by {@link org.thoughtcrime.securesms.pin.PinState}
|
||||
*/
|
||||
public void clearRegistrationLockAndPin() {
|
||||
getStore().beginWrite()
|
||||
.remove(V2_LOCK_ENABLED)
|
||||
.remove(REGISTRATION_LOCK_ENABLED)
|
||||
.remove(TOKEN_RESPONSE)
|
||||
.remove(LOCK_LOCAL_PIN_HASH)
|
||||
.remove(PIN)
|
||||
.remove(LAST_CREATE_FAILED_TIMESTAMP)
|
||||
.remove(OPTED_OUT)
|
||||
.remove(KBS_AUTH_TOKENS)
|
||||
.remove(KBS_LAST_AUTH_REFRESH_TIMESTAMP)
|
||||
.remove(SVR_AUTH_TOKENS)
|
||||
.remove(SVR_LAST_AUTH_REFRESH_TIMESTAMP)
|
||||
.commit();
|
||||
}
|
||||
|
||||
/** Should only be set by {@link org.thoughtcrime.securesms.pin.PinState}. */
|
||||
public synchronized void setKbsMasterKey(@NonNull KbsPinData pinData, @NonNull String pin) {
|
||||
MasterKey masterKey = pinData.getMasterKey();
|
||||
String tokenResponse;
|
||||
try {
|
||||
tokenResponse = JsonUtils.toJson(pinData.getTokenResponse());
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
public synchronized void setMasterKey(@NonNull MasterKey masterKey, @NonNull String pin) {
|
||||
getStore().beginWrite()
|
||||
.putString(TOKEN_RESPONSE, tokenResponse)
|
||||
.putBlob(MASTER_KEY, masterKey.serialize())
|
||||
.putString(LOCK_LOCAL_PIN_HASH, PinHashUtil.localPinHash(pin))
|
||||
.putString(PIN, pin)
|
||||
@@ -89,19 +75,17 @@ public final class KbsValues extends SignalStoreValues {
|
||||
}
|
||||
}
|
||||
|
||||
/** Should only be set by {@link org.thoughtcrime.securesms.pin.PinState}. */
|
||||
public synchronized void setV2RegistrationLockEnabled(boolean enabled) {
|
||||
putBoolean(V2_LOCK_ENABLED, enabled);
|
||||
public synchronized void setRegistrationLockEnabled(boolean enabled) {
|
||||
putBoolean(REGISTRATION_LOCK_ENABLED, enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not registration lock V2 is enabled.
|
||||
*/
|
||||
public synchronized boolean isV2RegistrationLockEnabled() {
|
||||
return getBoolean(V2_LOCK_ENABLED, false);
|
||||
public synchronized boolean isRegistrationLockEnabled() {
|
||||
return getBoolean(REGISTRATION_LOCK_ENABLED, false);
|
||||
}
|
||||
|
||||
/** Should only be set by {@link org.thoughtcrime.securesms.pin.PinState}. */
|
||||
public synchronized void onPinCreateFailure() {
|
||||
putLong(LAST_CREATE_FAILED_TIMESTAMP, System.currentTimeMillis());
|
||||
}
|
||||
@@ -136,7 +120,7 @@ public final class KbsValues extends SignalStoreValues {
|
||||
* Returns null if master key is not backed up by a pin.
|
||||
*/
|
||||
public synchronized @Nullable MasterKey getPinBackedMasterKey() {
|
||||
if (!isV2RegistrationLockEnabled()) return null;
|
||||
if (!isRegistrationLockEnabled()) return null;
|
||||
return getMasterKey();
|
||||
}
|
||||
|
||||
@@ -184,12 +168,12 @@ public final class KbsValues extends SignalStoreValues {
|
||||
}
|
||||
|
||||
public synchronized void putAuthTokenList(List<String> tokens) {
|
||||
putList(KBS_AUTH_TOKENS, tokens, StringStringSerializer.INSTANCE);
|
||||
putList(SVR_AUTH_TOKENS, tokens, StringStringSerializer.INSTANCE);
|
||||
setLastRefreshAuthTimestamp(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public synchronized List<String> getKbsAuthTokenList() {
|
||||
return getList(KBS_AUTH_TOKENS, StringStringSerializer.INSTANCE);
|
||||
public synchronized List<String> getAuthTokenList() {
|
||||
return getList(SVR_AUTH_TOKENS, StringStringSerializer.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,7 +182,7 @@ public final class KbsValues extends SignalStoreValues {
|
||||
* @return whether the token was added (new) or ignored (already existed)
|
||||
*/
|
||||
public synchronized boolean appendAuthTokenToList(String token) {
|
||||
List<String> tokens = getKbsAuthTokenList();
|
||||
List<String> tokens = getAuthTokenList();
|
||||
if (tokens.contains(token)) {
|
||||
return false;
|
||||
} else {
|
||||
@@ -209,7 +193,7 @@ public final class KbsValues extends SignalStoreValues {
|
||||
}
|
||||
|
||||
public boolean removeAuthTokens(@NonNull List<String> invalid) {
|
||||
List<String> tokens = new ArrayList<>(getKbsAuthTokenList());
|
||||
List<String> tokens = new ArrayList<>(getAuthTokenList());
|
||||
if (tokens.removeAll(invalid)) {
|
||||
putAuthTokenList(tokens);
|
||||
return true;
|
||||
@@ -218,7 +202,6 @@ public final class KbsValues extends SignalStoreValues {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Should only be called by {@link org.thoughtcrime.securesms.pin.PinState}. */
|
||||
public synchronized void optOut() {
|
||||
getStore().beginWrite()
|
||||
.putBoolean(OPTED_OUT, true)
|
||||
@@ -247,10 +230,10 @@ public final class KbsValues extends SignalStoreValues {
|
||||
}
|
||||
|
||||
private void setLastRefreshAuthTimestamp(long timestamp) {
|
||||
putLong(KBS_LAST_AUTH_REFRESH_TIMESTAMP, timestamp);
|
||||
putLong(SVR_LAST_AUTH_REFRESH_TIMESTAMP, timestamp);
|
||||
}
|
||||
|
||||
public long getLastRefreshAuthTimestamp() {
|
||||
return getLong(KBS_LAST_AUTH_REFRESH_TIMESTAMP, 0L);
|
||||
return getLong(SVR_LAST_AUTH_REFRESH_TIMESTAMP, 0L);
|
||||
}
|
||||
}
|
||||
@@ -31,8 +31,8 @@ import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
import org.thoughtcrime.securesms.lock.v2.KbsConstants;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity;
|
||||
import org.thoughtcrime.securesms.lock.v2.SvrConstants;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.whispersystems.signalservice.api.kbs.PinHashUtil;
|
||||
@@ -44,7 +44,7 @@ public final class SignalPinReminderDialog {
|
||||
private static final String TAG = Log.tag(SignalPinReminderDialog.class);
|
||||
|
||||
public static void show(@NonNull Context context, @NonNull Launcher launcher, @NonNull Callback mainCallback) {
|
||||
if (!SignalStore.kbsValues().hasPin()) {
|
||||
if (!SignalStore.svr().hasPin()) {
|
||||
throw new AssertionError("Must have a PIN!");
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ public final class SignalPinReminderDialog {
|
||||
@Override
|
||||
public void onClick(@NonNull View widget) {
|
||||
dialog.dismiss();
|
||||
launcher.launch(CreateKbsPinActivity.getIntentForPinChangeFromForgotPin(context), CreateKbsPinActivity.REQUEST_NEW_PIN);
|
||||
launcher.launch(CreateSvrPinActivity.getIntentForPinChangeFromForgotPin(context), CreateSvrPinActivity.REQUEST_NEW_PIN);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -115,11 +115,11 @@ public final class SignalPinReminderDialog {
|
||||
|
||||
pinEditText.addTextChangedListener(new SimpleTextWatcher() {
|
||||
|
||||
private final String localHash = Objects.requireNonNull(SignalStore.kbsValues().getLocalPinHash());
|
||||
private final String localHash = Objects.requireNonNull(SignalStore.svr().getLocalPinHash());
|
||||
|
||||
@Override
|
||||
public void onTextChanged(String text) {
|
||||
if (text.length() >= KbsConstants.MINIMUM_PIN_LENGTH) {
|
||||
if (text.length() >= SvrConstants.MINIMUM_PIN_LENGTH) {
|
||||
submit.setEnabled(true);
|
||||
|
||||
if (PinHashUtil.verifyLocalPinHash(localHash, text)) {
|
||||
@@ -169,7 +169,7 @@ public final class SignalPinReminderDialog {
|
||||
private final String localPinHash;
|
||||
|
||||
V2PinVerifier() {
|
||||
localPinHash = SignalStore.kbsValues().getLocalPinHash();
|
||||
localPinHash = SignalStore.svr().getLocalPinHash();
|
||||
|
||||
if (localPinHash == null) throw new AssertionError("No local pin hash set at time of reminder");
|
||||
}
|
||||
@@ -179,7 +179,7 @@ public final class SignalPinReminderDialog {
|
||||
if (pin == null) return;
|
||||
if (TextUtils.isEmpty(pin)) return;
|
||||
|
||||
if (pin.length() < KbsConstants.MINIMUM_PIN_LENGTH) return;
|
||||
if (pin.length() < SvrConstants.MINIMUM_PIN_LENGTH) return;
|
||||
|
||||
if (PinHashUtil.verifyLocalPinHash(localPinHash, pin)) {
|
||||
callback.onPinCorrect(pin);
|
||||
|
||||
@@ -14,7 +14,6 @@ import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
@@ -33,7 +32,7 @@ import org.thoughtcrime.securesms.util.text.AfterTextChanged;
|
||||
import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton;
|
||||
import org.thoughtcrime.securesms.util.views.LearnMoreTextView;
|
||||
|
||||
public abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel> extends LoggingFragment {
|
||||
public abstract class BaseSvrPinFragment<ViewModel extends BaseSvrPinViewModel> extends LoggingFragment {
|
||||
|
||||
private TextView title;
|
||||
private LearnMoreTextView description;
|
||||
@@ -62,8 +61,8 @@ public abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel>
|
||||
initializeViews(view);
|
||||
|
||||
viewModel = initializeViewModel();
|
||||
viewModel.getUserEntry().observe(getViewLifecycleOwner(), kbsPin -> {
|
||||
boolean isEntryValid = kbsPin.length() >= KbsConstants.MINIMUM_PIN_LENGTH;
|
||||
viewModel.getUserEntry().observe(getViewLifecycleOwner(), svrPin -> {
|
||||
boolean isEntryValid = svrPin.length() >= SvrConstants.MINIMUM_PIN_LENGTH;
|
||||
|
||||
confirm.setEnabled(isEntryValid);
|
||||
confirm.setAlpha(isEntryValid ? 1f : 0.5f);
|
||||
@@ -100,9 +99,9 @@ public abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel>
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(@NonNull Menu menu) {
|
||||
if (RegistrationLockUtil.userHasRegistrationLock(requireContext()) ||
|
||||
SignalStore.kbsValues().hasPin() ||
|
||||
SignalStore.kbsValues().hasOptedOut())
|
||||
if (SignalStore.svr().isRegistrationLockEnabled() ||
|
||||
SignalStore.svr().hasPin() ||
|
||||
SignalStore.svr().hasOptedOut())
|
||||
{
|
||||
menu.clear();
|
||||
}
|
||||
@@ -3,8 +3,8 @@ package org.thoughtcrime.securesms.lock.v2;
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
interface BaseKbsPinViewModel {
|
||||
LiveData<KbsPin> getUserEntry();
|
||||
interface BaseSvrPinViewModel {
|
||||
LiveData<SvrPin> getUserEntry();
|
||||
|
||||
LiveData<PinKeyboardType> getKeyboard();
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package org.thoughtcrime.securesms.lock.v2;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.pin.PinState;
|
||||
import org.signal.core.util.concurrent.SimpleTask;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
final class ConfirmKbsPinRepository {
|
||||
|
||||
private static final String TAG = Log.tag(ConfirmKbsPinRepository.class);
|
||||
|
||||
void setPin(@NonNull KbsPin kbsPin, @NonNull PinKeyboardType keyboard, @NonNull Consumer<PinSetResult> resultConsumer) {
|
||||
|
||||
Context context = ApplicationDependencies.getApplication();
|
||||
String pinValue = kbsPin.toString();
|
||||
|
||||
SimpleTask.run(() -> {
|
||||
try {
|
||||
Log.i(TAG, "Setting pin on KBS");
|
||||
PinState.onPinChangedOrCreated(context, pinValue, keyboard);
|
||||
Log.i(TAG, "Pin set on KBS");
|
||||
|
||||
return PinSetResult.SUCCESS;
|
||||
} catch (IOException | UnauthenticatedResponseException | InvalidKeyException e) {
|
||||
Log.w(TAG, e);
|
||||
PinState.onPinCreateFailure();
|
||||
return PinSetResult.FAILURE;
|
||||
}
|
||||
}, resultConsumer::accept);
|
||||
}
|
||||
|
||||
enum PinSetResult {
|
||||
SUCCESS,
|
||||
FAILURE
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
package org.thoughtcrime.securesms.lock.v2;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.Transformations;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import org.thoughtcrime.securesms.lock.v2.ConfirmKbsPinRepository.PinSetResult;
|
||||
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
|
||||
|
||||
final class ConfirmKbsPinViewModel extends ViewModel implements BaseKbsPinViewModel {
|
||||
|
||||
private final ConfirmKbsPinRepository repository;
|
||||
|
||||
private final DefaultValueLiveData<KbsPin> userEntry = new DefaultValueLiveData<>(KbsPin.EMPTY);
|
||||
private final DefaultValueLiveData<PinKeyboardType> keyboard = new DefaultValueLiveData<>(PinKeyboardType.NUMERIC);
|
||||
private final DefaultValueLiveData<SaveAnimation> saveAnimation = new DefaultValueLiveData<>(SaveAnimation.NONE);
|
||||
private final DefaultValueLiveData<LabelState> label = new DefaultValueLiveData<>(LabelState.EMPTY);
|
||||
|
||||
private final KbsPin pinToConfirm;
|
||||
|
||||
private ConfirmKbsPinViewModel(@NonNull KbsPin pinToConfirm,
|
||||
@NonNull PinKeyboardType keyboard,
|
||||
@NonNull ConfirmKbsPinRepository repository)
|
||||
{
|
||||
this.keyboard.setValue(keyboard);
|
||||
|
||||
this.pinToConfirm = pinToConfirm;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
LiveData<SaveAnimation> getSaveAnimation() {
|
||||
return Transformations.distinctUntilChanged(saveAnimation);
|
||||
}
|
||||
|
||||
LiveData<LabelState> getLabel() {
|
||||
return Transformations.distinctUntilChanged(label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void confirm() {
|
||||
KbsPin userEntry = this.userEntry.getValue();
|
||||
|
||||
if (pinToConfirm.toString().equals(userEntry.toString())) {
|
||||
this.label.setValue(LabelState.CREATING_PIN);
|
||||
this.saveAnimation.setValue(SaveAnimation.LOADING);
|
||||
|
||||
repository.setPin(pinToConfirm, this.keyboard.getValue(), this::handleResult);
|
||||
} else {
|
||||
this.userEntry.setValue(KbsPin.EMPTY);
|
||||
this.label.setValue(LabelState.PIN_DOES_NOT_MATCH);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveData<KbsPin> getUserEntry() {
|
||||
return userEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveData<PinKeyboardType> getKeyboard() {
|
||||
return keyboard;
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public void setUserEntry(String userEntry) {
|
||||
this.userEntry.setValue(KbsPin.from(userEntry));
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public void toggleAlphaNumeric() {
|
||||
this.keyboard.setValue(this.keyboard.getValue().getOther());
|
||||
}
|
||||
|
||||
private void handleResult(PinSetResult result) {
|
||||
switch (result) {
|
||||
case SUCCESS:
|
||||
this.saveAnimation.setValue(SaveAnimation.SUCCESS);
|
||||
break;
|
||||
case FAILURE:
|
||||
this.saveAnimation.setValue(SaveAnimation.FAILURE);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown state: " + result.name());
|
||||
}
|
||||
}
|
||||
|
||||
enum LabelState {
|
||||
RE_ENTER_PIN,
|
||||
PIN_DOES_NOT_MATCH,
|
||||
CREATING_PIN,
|
||||
EMPTY
|
||||
}
|
||||
|
||||
enum SaveAnimation {
|
||||
NONE,
|
||||
LOADING,
|
||||
SUCCESS,
|
||||
FAILURE
|
||||
}
|
||||
|
||||
static final class Factory implements ViewModelProvider.Factory {
|
||||
|
||||
private final KbsPin pinToConfirm;
|
||||
private final PinKeyboardType keyboard;
|
||||
private final ConfirmKbsPinRepository repository;
|
||||
|
||||
Factory(@NonNull KbsPin pinToConfirm,
|
||||
@NonNull PinKeyboardType keyboard,
|
||||
@NonNull ConfirmKbsPinRepository repository)
|
||||
{
|
||||
this.pinToConfirm = pinToConfirm;
|
||||
this.keyboard = keyboard;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||
//noinspection unchecked
|
||||
return (T) new ConfirmKbsPinViewModel(pinToConfirm, keyboard, repository);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,16 +10,16 @@ import androidx.lifecycle.ViewModelProvider
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.lock.v2.ConfirmKbsPinViewModel.SaveAnimation
|
||||
import org.thoughtcrime.securesms.lock.v2.ConfirmSvrPinViewModel.SaveAnimation
|
||||
import org.thoughtcrime.securesms.megaphone.Megaphones
|
||||
import org.thoughtcrime.securesms.registration.RegistrationUtil
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.thoughtcrime.securesms.util.SpanUtil
|
||||
|
||||
internal class ConfirmKbsPinFragment : BaseKbsPinFragment<ConfirmKbsPinViewModel>() {
|
||||
internal class ConfirmSvrPinFragment : BaseSvrPinFragment<ConfirmSvrPinViewModel>() {
|
||||
|
||||
override fun initializeViewStates() {
|
||||
val args = ConfirmKbsPinFragmentArgs.fromBundle(requireArguments())
|
||||
val args = ConfirmSvrPinFragmentArgs.fromBundle(requireArguments())
|
||||
if (args.isPinChange) {
|
||||
initializeViewStatesForPinChange()
|
||||
} else {
|
||||
@@ -28,14 +28,13 @@ internal class ConfirmKbsPinFragment : BaseKbsPinFragment<ConfirmKbsPinViewModel
|
||||
ViewCompat.setAutofillHints(input, HintConstants.AUTOFILL_HINT_NEW_PASSWORD)
|
||||
}
|
||||
|
||||
override fun initializeViewModel(): ConfirmKbsPinViewModel {
|
||||
val args = ConfirmKbsPinFragmentArgs.fromBundle(requireArguments())
|
||||
override fun initializeViewModel(): ConfirmSvrPinViewModel {
|
||||
val args = ConfirmSvrPinFragmentArgs.fromBundle(requireArguments())
|
||||
val userEntry = args.userEntry!!
|
||||
val keyboard = args.keyboard
|
||||
val repository = ConfirmKbsPinRepository()
|
||||
val factory = ConfirmKbsPinViewModel.Factory(userEntry, keyboard, repository)
|
||||
val viewModel = ViewModelProvider(this, factory)[ConfirmKbsPinViewModel::class.java]
|
||||
viewModel.label.observe(viewLifecycleOwner) { label: ConfirmKbsPinViewModel.LabelState -> updateLabel(label) }
|
||||
val factory = ConfirmSvrPinViewModel.Factory(userEntry, keyboard)
|
||||
val viewModel = ViewModelProvider(this, factory)[ConfirmSvrPinViewModel::class.java]
|
||||
viewModel.label.observe(viewLifecycleOwner) { label: ConfirmSvrPinViewModel.LabelState -> updateLabel(label) }
|
||||
viewModel.saveAnimation.observe(viewLifecycleOwner) { animation: SaveAnimation -> updateSaveAnimation(animation) }
|
||||
return viewModel
|
||||
}
|
||||
@@ -58,15 +57,15 @@ internal class ConfirmKbsPinFragment : BaseKbsPinFragment<ConfirmKbsPinViewModel
|
||||
confirm.isEnabled = true
|
||||
}
|
||||
|
||||
private fun updateLabel(labelState: ConfirmKbsPinViewModel.LabelState) {
|
||||
private fun updateLabel(labelState: ConfirmSvrPinViewModel.LabelState) {
|
||||
when (labelState) {
|
||||
ConfirmKbsPinViewModel.LabelState.EMPTY -> label.text = ""
|
||||
ConfirmKbsPinViewModel.LabelState.CREATING_PIN -> {
|
||||
ConfirmSvrPinViewModel.LabelState.EMPTY -> label.text = ""
|
||||
ConfirmSvrPinViewModel.LabelState.CREATING_PIN -> {
|
||||
label.setText(R.string.ConfirmKbsPinFragment__creating_pin)
|
||||
input.isEnabled = false
|
||||
}
|
||||
ConfirmKbsPinViewModel.LabelState.RE_ENTER_PIN -> label.setText(R.string.ConfirmKbsPinFragment__re_enter_your_pin)
|
||||
ConfirmKbsPinViewModel.LabelState.PIN_DOES_NOT_MATCH -> {
|
||||
ConfirmSvrPinViewModel.LabelState.RE_ENTER_PIN -> label.setText(R.string.ConfirmKbsPinFragment__re_enter_your_pin)
|
||||
ConfirmSvrPinViewModel.LabelState.PIN_DOES_NOT_MATCH -> {
|
||||
label.text = SpanUtil.color(
|
||||
ContextCompat.getColor(requireContext(), R.color.red_500),
|
||||
getString(R.string.ConfirmKbsPinFragment__pins_dont_match)
|
||||
@@ -0,0 +1,125 @@
|
||||
package org.thoughtcrime.securesms.lock.v2;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.Transformations;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import org.thoughtcrime.securesms.pin.SvrRepository;
|
||||
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
|
||||
import org.whispersystems.signalservice.api.svr.SecureValueRecovery;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Scheduler;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
final class ConfirmSvrPinViewModel extends ViewModel implements BaseSvrPinViewModel {
|
||||
|
||||
private final DefaultValueLiveData<SvrPin> userEntry = new DefaultValueLiveData<>(SvrPin.EMPTY);
|
||||
private final DefaultValueLiveData<PinKeyboardType> keyboard = new DefaultValueLiveData<>(PinKeyboardType.NUMERIC);
|
||||
private final DefaultValueLiveData<SaveAnimation> saveAnimation = new DefaultValueLiveData<>(SaveAnimation.NONE);
|
||||
private final DefaultValueLiveData<LabelState> label = new DefaultValueLiveData<>(LabelState.EMPTY);
|
||||
|
||||
private final SvrPin pinToConfirm;
|
||||
|
||||
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||
|
||||
private ConfirmSvrPinViewModel(@NonNull SvrPin pinToConfirm, @NonNull PinKeyboardType keyboard) {
|
||||
this.keyboard.setValue(keyboard);
|
||||
this.pinToConfirm = pinToConfirm;
|
||||
}
|
||||
|
||||
LiveData<SaveAnimation> getSaveAnimation() {
|
||||
return Transformations.distinctUntilChanged(saveAnimation);
|
||||
}
|
||||
|
||||
LiveData<LabelState> getLabel() {
|
||||
return Transformations.distinctUntilChanged(label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void confirm() {
|
||||
SvrPin userEntry = this.userEntry.getValue();
|
||||
|
||||
if (pinToConfirm.toString().equals(userEntry.toString())) {
|
||||
this.label.setValue(LabelState.CREATING_PIN);
|
||||
this.saveAnimation.setValue(SaveAnimation.LOADING);
|
||||
|
||||
disposables.add(
|
||||
Single.fromCallable(() -> SvrRepository.setPin(pinToConfirm.toString(), this.keyboard.getValue()))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(result -> {
|
||||
if (result instanceof SecureValueRecovery.BackupResponse.Success) {
|
||||
this.saveAnimation.setValue(SaveAnimation.SUCCESS);
|
||||
} else {
|
||||
this.saveAnimation.setValue(SaveAnimation.FAILURE);
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.userEntry.setValue(SvrPin.EMPTY);
|
||||
this.label.setValue(LabelState.PIN_DOES_NOT_MATCH);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveData<SvrPin> getUserEntry() {
|
||||
return userEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveData<PinKeyboardType> getKeyboard() {
|
||||
return keyboard;
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public void setUserEntry(String userEntry) {
|
||||
this.userEntry.setValue(SvrPin.from(userEntry));
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public void toggleAlphaNumeric() {
|
||||
this.keyboard.setValue(this.keyboard.getValue().getOther());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
disposables.clear();
|
||||
}
|
||||
|
||||
enum LabelState {
|
||||
RE_ENTER_PIN,
|
||||
PIN_DOES_NOT_MATCH,
|
||||
CREATING_PIN,
|
||||
EMPTY
|
||||
}
|
||||
|
||||
enum SaveAnimation {
|
||||
NONE,
|
||||
LOADING,
|
||||
SUCCESS,
|
||||
FAILURE
|
||||
}
|
||||
|
||||
static final class Factory implements ViewModelProvider.Factory {
|
||||
|
||||
private final SvrPin pinToConfirm;
|
||||
private final PinKeyboardType keyboard;
|
||||
|
||||
Factory(@NonNull SvrPin pinToConfirm, @NonNull PinKeyboardType keyboard) {
|
||||
this.pinToConfirm = pinToConfirm;
|
||||
this.keyboard = keyboard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||
//noinspection unchecked
|
||||
return (T) new ConfirmSvrPinViewModel(pinToConfirm, keyboard);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,14 +17,14 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.util.DynamicRegistrationTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
|
||||
public class CreateKbsPinActivity extends BaseActivity {
|
||||
public class CreateSvrPinActivity extends BaseActivity {
|
||||
|
||||
public static final int REQUEST_NEW_PIN = 27698;
|
||||
|
||||
private final DynamicTheme dynamicTheme = new DynamicRegistrationTheme();
|
||||
|
||||
public static @NonNull Intent getIntentForPinCreate(@NonNull Context context) {
|
||||
CreateKbsPinFragmentArgs args = new CreateKbsPinFragmentArgs.Builder()
|
||||
CreateSvrPinFragmentArgs args = new CreateSvrPinFragmentArgs.Builder()
|
||||
.setIsForgotPin(false)
|
||||
.setIsPinChange(false)
|
||||
.build();
|
||||
@@ -33,7 +33,7 @@ public class CreateKbsPinActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
public static @NonNull Intent getIntentForPinChangeFromForgotPin(@NonNull Context context) {
|
||||
CreateKbsPinFragmentArgs args = new CreateKbsPinFragmentArgs.Builder()
|
||||
CreateSvrPinFragmentArgs args = new CreateSvrPinFragmentArgs.Builder()
|
||||
.setIsForgotPin(true)
|
||||
.setIsPinChange(true)
|
||||
.build();
|
||||
@@ -42,7 +42,7 @@ public class CreateKbsPinActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
public static @NonNull Intent getIntentForPinChangeFromSettings(@NonNull Context context) {
|
||||
CreateKbsPinFragmentArgs args = new CreateKbsPinFragmentArgs.Builder()
|
||||
CreateSvrPinFragmentArgs args = new CreateSvrPinFragmentArgs.Builder()
|
||||
.setIsForgotPin(false)
|
||||
.setIsPinChange(true)
|
||||
.build();
|
||||
@@ -50,8 +50,8 @@ public class CreateKbsPinActivity extends BaseActivity {
|
||||
return getIntentWithArgs(context, args);
|
||||
}
|
||||
|
||||
private static @NonNull Intent getIntentWithArgs(@NonNull Context context, @NonNull CreateKbsPinFragmentArgs args) {
|
||||
return new Intent(context, CreateKbsPinActivity.class).putExtras(args.toBundle());
|
||||
private static @NonNull Intent getIntentWithArgs(@NonNull Context context, @NonNull CreateSvrPinFragmentArgs args) {
|
||||
return new Intent(context, CreateSvrPinActivity.class).putExtras(args.toBundle());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -68,7 +68,7 @@ public class CreateKbsPinActivity extends BaseActivity {
|
||||
|
||||
setContentView(R.layout.create_kbs_pin_activity);
|
||||
|
||||
CreateKbsPinFragmentArgs arguments = CreateKbsPinFragmentArgs.fromBundle(getIntent().getExtras());
|
||||
CreateSvrPinFragmentArgs arguments = CreateSvrPinFragmentArgs.fromBundle(getIntent().getExtras());
|
||||
|
||||
NavGraph graph = Navigation.findNavController(this, R.id.nav_host_fragment).getGraph();
|
||||
Navigation.findNavController(this, R.id.nav_host_fragment).setGraph(graph, arguments.toBundle());
|
||||
@@ -10,14 +10,14 @@ import androidx.core.view.ViewCompat
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.Navigation.findNavController
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinViewModel.NavigationEvent
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinViewModel.PinErrorEvent
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinViewModel.NavigationEvent
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinViewModel.PinErrorEvent
|
||||
import org.thoughtcrime.securesms.util.SpanUtil
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
class CreateKbsPinFragment : BaseKbsPinFragment<CreateKbsPinViewModel?>() {
|
||||
class CreateSvrPinFragment : BaseSvrPinFragment<CreateSvrPinViewModel?>() {
|
||||
override fun initializeViewStates() {
|
||||
val args = CreateKbsPinFragmentArgs.fromBundle(requireArguments())
|
||||
val args = CreateSvrPinFragmentArgs.fromBundle(requireArguments())
|
||||
if (args.isPinChange) {
|
||||
initializeViewStatesForPinChange(args.isForgotPin)
|
||||
} else {
|
||||
@@ -40,9 +40,9 @@ class CreateKbsPinFragment : BaseKbsPinFragment<CreateKbsPinViewModel?>() {
|
||||
description.setLearnMoreVisible(true)
|
||||
}
|
||||
|
||||
override fun initializeViewModel(): CreateKbsPinViewModel {
|
||||
val viewModel = ViewModelProvider(this)[CreateKbsPinViewModel::class.java]
|
||||
val args = CreateKbsPinFragmentArgs.fromBundle(requireArguments())
|
||||
override fun initializeViewModel(): CreateSvrPinViewModel {
|
||||
val viewModel = ViewModelProvider(this)[CreateSvrPinViewModel::class.java]
|
||||
val args = CreateSvrPinFragmentArgs.fromBundle(requireArguments())
|
||||
viewModel.navigationEvents.observe(viewLifecycleOwner) { e: NavigationEvent -> onConfirmPin(e.userEntry, e.keyboard, args.isPinChange) }
|
||||
viewModel.errorEvents.observe(viewLifecycleOwner) { e: PinErrorEvent ->
|
||||
if (e == PinErrorEvent.WEAK_PIN) {
|
||||
@@ -62,8 +62,8 @@ class CreateKbsPinFragment : BaseKbsPinFragment<CreateKbsPinViewModel?>() {
|
||||
return viewModel
|
||||
}
|
||||
|
||||
private fun onConfirmPin(userEntry: KbsPin, keyboard: PinKeyboardType, isPinChange: Boolean) {
|
||||
val action = CreateKbsPinFragmentDirections.actionConfirmPin()
|
||||
private fun onConfirmPin(userEntry: SvrPin, keyboard: PinKeyboardType, isPinChange: Boolean) {
|
||||
val action = CreateSvrPinFragmentDirections.actionConfirmPin()
|
||||
action.userEntry = userEntry
|
||||
action.keyboard = keyboard
|
||||
action.isPinChange = isPinChange
|
||||
@@ -79,7 +79,7 @@ class CreateKbsPinFragment : BaseKbsPinFragment<CreateKbsPinViewModel?>() {
|
||||
}
|
||||
|
||||
private fun getPinLengthRestrictionText(@PluralsRes plurals: Int): String {
|
||||
return resources.getQuantityString(plurals, KbsConstants.MINIMUM_PIN_LENGTH, KbsConstants.MINIMUM_PIN_LENGTH)
|
||||
return resources.getQuantityString(plurals, SvrConstants.MINIMUM_PIN_LENGTH, SvrConstants.MINIMUM_PIN_LENGTH)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -2,23 +2,23 @@ package org.thoughtcrime.securesms.lock.v2;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Preconditions;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||
import org.whispersystems.signalservice.api.kbs.PinValidityChecker;
|
||||
import org.whispersystems.signalservice.api.util.Preconditions;
|
||||
|
||||
public final class CreateKbsPinViewModel extends ViewModel implements BaseKbsPinViewModel {
|
||||
public final class CreateSvrPinViewModel extends ViewModel implements BaseSvrPinViewModel {
|
||||
|
||||
private final MutableLiveData<KbsPin> userEntry = new MutableLiveData<>(KbsPin.EMPTY);
|
||||
private final MutableLiveData<SvrPin> userEntry = new MutableLiveData<>(SvrPin.EMPTY);
|
||||
private final MutableLiveData<PinKeyboardType> keyboard = new MutableLiveData<>(PinKeyboardType.NUMERIC);
|
||||
private final SingleLiveEvent<NavigationEvent> events = new SingleLiveEvent<>();
|
||||
private final SingleLiveEvent<PinErrorEvent> errors = new SingleLiveEvent<>();
|
||||
|
||||
@Override
|
||||
public LiveData<KbsPin> getUserEntry() {
|
||||
public LiveData<SvrPin> getUserEntry() {
|
||||
return userEntry;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ public final class CreateKbsPinViewModel extends ViewModel implements BaseKbsPin
|
||||
@Override
|
||||
@MainThread
|
||||
public void setUserEntry(String userEntry) {
|
||||
this.userEntry.setValue(KbsPin.from(userEntry));
|
||||
this.userEntry.setValue(SvrPin.from(userEntry));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,7 +46,7 @@ public final class CreateKbsPinViewModel extends ViewModel implements BaseKbsPin
|
||||
@Override
|
||||
@MainThread
|
||||
public void confirm() {
|
||||
KbsPin pin = Preconditions.checkNotNull(this.getUserEntry().getValue());
|
||||
SvrPin pin = Preconditions.checkNotNull(this.getUserEntry().getValue());
|
||||
PinKeyboardType keyboard = Preconditions.checkNotNull(this.getKeyboard().getValue());
|
||||
|
||||
if (PinValidityChecker.valid(pin.toString())) {
|
||||
@@ -57,15 +57,15 @@ public final class CreateKbsPinViewModel extends ViewModel implements BaseKbsPin
|
||||
}
|
||||
|
||||
static final class NavigationEvent {
|
||||
private final KbsPin userEntry;
|
||||
private final SvrPin userEntry;
|
||||
private final PinKeyboardType keyboard;
|
||||
|
||||
NavigationEvent(@NonNull KbsPin userEntry, @NonNull PinKeyboardType keyboard) {
|
||||
NavigationEvent(@NonNull SvrPin userEntry, @NonNull PinKeyboardType keyboard) {
|
||||
this.userEntry = userEntry;
|
||||
this.keyboard = keyboard;
|
||||
}
|
||||
|
||||
KbsPin getUserEntry() {
|
||||
SvrPin getUserEntry() {
|
||||
return userEntry;
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package org.thoughtcrime.securesms.lock.v2;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
public final class RegistrationLockUtil {
|
||||
|
||||
private RegistrationLockUtil() {}
|
||||
|
||||
public static boolean userHasRegistrationLock(@NonNull Context context) {
|
||||
return TextSecurePreferences.isV1RegistrationLockEnabled(context) || SignalStore.kbsValues().isV2RegistrationLockEnabled();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.thoughtcrime.securesms.lock.v2;
|
||||
|
||||
public final class KbsConstants {
|
||||
public final class SvrConstants {
|
||||
|
||||
public static final int MINIMUM_PIN_LENGTH = 4;
|
||||
|
||||
private KbsConstants() { }
|
||||
private SvrConstants() { }
|
||||
}
|
||||
@@ -13,14 +13,14 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.util.DynamicRegistrationTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
|
||||
public class KbsMigrationActivity extends BaseActivity {
|
||||
public class SvrMigrationActivity extends BaseActivity {
|
||||
|
||||
public static final int REQUEST_NEW_PIN = CreateKbsPinActivity.REQUEST_NEW_PIN;
|
||||
public static final int REQUEST_NEW_PIN = CreateSvrPinActivity.REQUEST_NEW_PIN;
|
||||
|
||||
private final DynamicTheme dynamicTheme = new DynamicRegistrationTheme();
|
||||
|
||||
public static Intent createIntent() {
|
||||
return new Intent(ApplicationDependencies.getApplication(), KbsMigrationActivity.class);
|
||||
return new Intent(ApplicationDependencies.getApplication(), SvrMigrationActivity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -6,17 +6,17 @@ import android.os.Parcelable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public final class KbsPin implements Parcelable {
|
||||
public final class SvrPin implements Parcelable {
|
||||
|
||||
public static KbsPin EMPTY = new KbsPin("");
|
||||
public static SvrPin EMPTY = new SvrPin("");
|
||||
|
||||
private final String pin;
|
||||
|
||||
private KbsPin(String pin) {
|
||||
private SvrPin(String pin) {
|
||||
this.pin = pin;
|
||||
}
|
||||
|
||||
private KbsPin(Parcel in) {
|
||||
private SvrPin(Parcel in) {
|
||||
pin = in.readString();
|
||||
}
|
||||
|
||||
@@ -25,14 +25,14 @@ public final class KbsPin implements Parcelable {
|
||||
return pin;
|
||||
}
|
||||
|
||||
public static KbsPin from(@Nullable String pin) {
|
||||
public static SvrPin from(@Nullable String pin) {
|
||||
if (pin == null) return EMPTY;
|
||||
|
||||
pin = pin.trim();
|
||||
|
||||
if (pin.length() == 0) return EMPTY;
|
||||
|
||||
return new KbsPin(pin);
|
||||
return new SvrPin(pin);
|
||||
}
|
||||
|
||||
public int length() {
|
||||
@@ -49,15 +49,15 @@ public final class KbsPin implements Parcelable {
|
||||
dest.writeString(pin);
|
||||
}
|
||||
|
||||
public static final Creator<KbsPin> CREATOR = new Creator<KbsPin>() {
|
||||
public static final Creator<SvrPin> CREATOR = new Creator<SvrPin>() {
|
||||
@Override
|
||||
public KbsPin createFromParcel(Parcel in) {
|
||||
return new KbsPin(in);
|
||||
public SvrPin createFromParcel(Parcel in) {
|
||||
return new SvrPin(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KbsPin[] newArray(int size) {
|
||||
return new KbsPin[size];
|
||||
public SvrPin[] newArray(int size) {
|
||||
return new SvrPin[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -24,7 +24,7 @@ import org.thoughtcrime.securesms.pin.PinOptOutDialog;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.navigation.SafeNavigation;
|
||||
|
||||
public final class KbsSplashFragment extends Fragment {
|
||||
public final class SvrSplashFragment extends Fragment {
|
||||
|
||||
private TextView title;
|
||||
private TextView description;
|
||||
@@ -55,7 +55,7 @@ public final class KbsSplashFragment extends Fragment {
|
||||
primaryAction.setOnClickListener(v -> onCreatePin());
|
||||
secondaryAction.setOnClickListener(v -> onLearnMore());
|
||||
|
||||
if (RegistrationLockUtil.userHasRegistrationLock(requireContext())) {
|
||||
if (SignalStore.svr().isRegistrationLockEnabled()) {
|
||||
setUpRegLockEnabled();
|
||||
} else {
|
||||
setUpRegLockDisabled();
|
||||
@@ -80,7 +80,7 @@ public final class KbsSplashFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(@NonNull Menu menu) {
|
||||
if (RegistrationLockUtil.userHasRegistrationLock(requireContext())) {
|
||||
if (SignalStore.svr().isRegistrationLockEnabled()) {
|
||||
menu.clear();
|
||||
}
|
||||
}
|
||||
@@ -113,9 +113,9 @@ public final class KbsSplashFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void onCreatePin() {
|
||||
KbsSplashFragmentDirections.ActionCreateKbsPin action = KbsSplashFragmentDirections.actionCreateKbsPin();
|
||||
SvrSplashFragmentDirections.ActionCreateKbsPin action = SvrSplashFragmentDirections.actionCreateKbsPin();
|
||||
|
||||
action.setIsPinChange(SignalStore.kbsValues().hasPin());
|
||||
action.setIsPinChange(SignalStore.svr().hasPin());
|
||||
|
||||
SafeNavigation.safeNavigate(Navigation.findNavController(requireView()), action);
|
||||
}
|
||||
@@ -5,8 +5,6 @@ import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.pin.PinState;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
public class LogSectionPin implements LogSection {
|
||||
|
||||
@@ -17,14 +15,12 @@ public class LogSectionPin implements LogSection {
|
||||
|
||||
@Override
|
||||
public @NonNull CharSequence getContent(@NonNull Context context) {
|
||||
return new StringBuilder().append("State: ").append(PinState.getState()).append("\n")
|
||||
.append("Last Successful Reminder Entry: ").append(SignalStore.pinValues().getLastSuccessfulEntryTime()).append("\n")
|
||||
return new StringBuilder().append("Last Successful Reminder Entry: ").append(SignalStore.pinValues().getLastSuccessfulEntryTime()).append("\n")
|
||||
.append("Next Reminder Interval: ").append(SignalStore.pinValues().getCurrentInterval()).append("\n")
|
||||
.append("ReglockV1: ").append(TextSecurePreferences.isV1RegistrationLockEnabled(context)).append("\n")
|
||||
.append("ReglockV2: ").append(SignalStore.kbsValues().isV2RegistrationLockEnabled()).append("\n")
|
||||
.append("Signal PIN: ").append(SignalStore.kbsValues().hasPin()).append("\n")
|
||||
.append("Opted Out: ").append(SignalStore.kbsValues().hasOptedOut()).append("\n")
|
||||
.append("Last Creation Failed: ").append(SignalStore.kbsValues().lastPinCreateFailed()).append("\n")
|
||||
.append("Reglock: ").append(SignalStore.svr().isRegistrationLockEnabled()).append("\n")
|
||||
.append("Signal PIN: ").append(SignalStore.svr().hasPin()).append("\n")
|
||||
.append("Opted Out: ").append(SignalStore.svr().hasOptedOut()).append("\n")
|
||||
.append("Last Creation Failed: ").append(SignalStore.svr().lastPinCreateFailed()).append("\n")
|
||||
.append("Needs Account Restore: ").append(SignalStore.storageService().needsAccountRestore()).append("\n")
|
||||
.append("PIN Required at Registration: ").append(SignalStore.registrationValues().pinWasRequiredAtRegistration()).append("\n")
|
||||
.append("Registration Complete: ").append(SignalStore.registrationValues().isRegistrationComplete());
|
||||
|
||||
@@ -30,8 +30,8 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.keyvalue.SmsExportPhase;
|
||||
import org.thoughtcrime.securesms.lock.SignalPinReminderDialog;
|
||||
import org.thoughtcrime.securesms.lock.SignalPinReminders;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
import org.thoughtcrime.securesms.lock.v2.KbsMigrationActivity;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity;
|
||||
import org.thoughtcrime.securesms.lock.v2.SvrMigrationActivity;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
@@ -164,7 +164,7 @@ public final class Megaphones {
|
||||
.enableSnooze(null)
|
||||
.setOnVisibleListener((megaphone, listener) -> {
|
||||
if (new NetworkConstraint.Factory(ApplicationDependencies.getApplication()).create().isMet()) {
|
||||
listener.onMegaphoneNavigationRequested(KbsMigrationActivity.createIntent(), KbsMigrationActivity.REQUEST_NEW_PIN);
|
||||
listener.onMegaphoneNavigationRequested(SvrMigrationActivity.createIntent(), SvrMigrationActivity.REQUEST_NEW_PIN);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
@@ -174,9 +174,9 @@ public final class Megaphones {
|
||||
.setTitle(R.string.KbsMegaphone__create_a_pin)
|
||||
.setBody(R.string.KbsMegaphone__pins_keep_information_thats_stored_with_signal_encrytped)
|
||||
.setActionButton(R.string.KbsMegaphone__create_pin, (megaphone, listener) -> {
|
||||
Intent intent = CreateKbsPinActivity.getIntentForPinCreate(ApplicationDependencies.getApplication());
|
||||
Intent intent = CreateSvrPinActivity.getIntentForPinCreate(ApplicationDependencies.getApplication());
|
||||
|
||||
listener.onMegaphoneNavigationRequested(intent, CreateKbsPinActivity.REQUEST_NEW_PIN);
|
||||
listener.onMegaphoneNavigationRequested(intent, CreateSvrPinActivity.REQUEST_NEW_PIN);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -43,11 +43,11 @@ class PinsForAllSchedule implements MegaphoneSchedule {
|
||||
}
|
||||
|
||||
private static boolean isEnabled() {
|
||||
if (SignalStore.kbsValues().hasOptedOut()) {
|
||||
if (SignalStore.svr().hasOptedOut()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SignalStore.kbsValues().hasPin()) {
|
||||
if (SignalStore.svr().hasPin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -55,10 +55,6 @@ class PinsForAllSchedule implements MegaphoneSchedule {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (newlyRegisteredRegistrationLockV1User()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SignalStore.registrationValues().pinWasRequiredAtRegistration()) {
|
||||
return false;
|
||||
}
|
||||
@@ -68,11 +64,6 @@ class PinsForAllSchedule implements MegaphoneSchedule {
|
||||
|
||||
private static boolean pinCreationFailedDuringRegistration() {
|
||||
return SignalStore.registrationValues().pinWasRequiredAtRegistration() &&
|
||||
!SignalStore.kbsValues().hasPin() &&
|
||||
!TextSecurePreferences.isV1RegistrationLockEnabled(ApplicationDependencies.getApplication());
|
||||
}
|
||||
|
||||
private static boolean newlyRegisteredRegistrationLockV1User() {
|
||||
return SignalStore.registrationValues().pinWasRequiredAtRegistration() && TextSecurePreferences.isV1RegistrationLockEnabled(ApplicationDependencies.getApplication());
|
||||
!SignalStore.svr().hasPin();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ final class SignalPinReminderSchedule implements MegaphoneSchedule {
|
||||
|
||||
@Override
|
||||
public boolean shouldDisplay(int seenCount, long lastSeen, long firstVisible, long currentTime) {
|
||||
if (SignalStore.kbsValues().hasOptedOut()) {
|
||||
if (SignalStore.svr().hasOptedOut()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SignalStore.kbsValues().hasPin()) {
|
||||
if (!SignalStore.svr().hasPin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ public class ApplicationMigrations {
|
||||
static final int JUMBOMOJI_DOWNLOAD = 52;
|
||||
static final int FIX_EMOJI_QUALITY = 53;
|
||||
static final int CHANGE_NUMBER_CAPABILITY_4 = 54;
|
||||
static final int KBS_MIGRATION = 55;
|
||||
//static final int KBS_MIGRATION = 55;
|
||||
static final int PNI_IDENTITY = 56;
|
||||
static final int PNI_IDENTITY_2 = 57;
|
||||
static final int PNI_IDENTITY_3 = 58;
|
||||
@@ -106,7 +106,7 @@ public class ApplicationMigrations {
|
||||
static final int REFRESH_EXPIRING_CREDENTIAL = 62;
|
||||
static final int EMOJI_SEARCH_INDEX_10 = 63;
|
||||
static final int REFRESH_PNI_REGISTRATION_ID = 64;
|
||||
static final int KBS_MIGRATION_2 = 65;
|
||||
//static final int KBS_MIGRATION_2 = 65;
|
||||
static final int PNI_2 = 66;
|
||||
static final int SYSTEM_NAME_SYNC = 67;
|
||||
static final int STORY_VIEWED_STATE = 68;
|
||||
@@ -132,9 +132,10 @@ public class ApplicationMigrations {
|
||||
static final int DEDUPE_DB_MIGRATION = 88;
|
||||
static final int DEDUPE_DB_MIGRATION_2 = 89;
|
||||
static final int EMOJI_VERSION_8 = 90;
|
||||
static final int SVR2_MIRROR = 91;
|
||||
}
|
||||
|
||||
public static final int CURRENT_VERSION = 90;
|
||||
public static final int CURRENT_VERSION = 91;
|
||||
|
||||
/**
|
||||
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
|
||||
@@ -452,9 +453,9 @@ public class ApplicationMigrations {
|
||||
jobs.put(Version.CHANGE_NUMBER_CAPABILITY_4,new AttributesMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.KBS_MIGRATION) {
|
||||
jobs.put(Version.KBS_MIGRATION, new KbsEnclaveMigrationJob());
|
||||
}
|
||||
// if (lastSeenVersion < Version.KBS_MIGRATION) {
|
||||
// jobs.put(Version.KBS_MIGRATION, new KbsEnclaveMigrationJob());
|
||||
// }
|
||||
|
||||
if (lastSeenVersion < Version.PNI_IDENTITY) {
|
||||
jobs.put(Version.PNI_IDENTITY, new PniAccountInitializationMigrationJob());
|
||||
@@ -492,9 +493,9 @@ public class ApplicationMigrations {
|
||||
jobs.put(Version.REFRESH_PNI_REGISTRATION_ID, new AttributesMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.KBS_MIGRATION_2) {
|
||||
jobs.put(Version.KBS_MIGRATION_2, new KbsEnclaveMigrationJob());
|
||||
}
|
||||
// if (lastSeenVersion < Version.KBS_MIGRATION_2) {
|
||||
// jobs.put(Version.KBS_MIGRATION_2, new KbsEnclaveMigrationJob());
|
||||
// }
|
||||
|
||||
if (lastSeenVersion < Version.PNI_2) {
|
||||
jobs.put(Version.PNI_2, new PniMigrationJob());
|
||||
@@ -596,6 +597,10 @@ public class ApplicationMigrations {
|
||||
jobs.put(Version.EMOJI_VERSION_8, new EmojiDownloadMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.SVR2_MIRROR) {
|
||||
jobs.put(Version.SVR2_MIRROR, new Svr2MirrorMigrationJob());
|
||||
}
|
||||
|
||||
return jobs;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
package org.thoughtcrime.securesms.migrations;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobs.KbsEnclaveMigrationWorkerJob;
|
||||
|
||||
/**
|
||||
* A job to be run whenever we add a new KBS enclave. In order to prevent this moderately-expensive
|
||||
* task from blocking the network for too long, this task simply enqueues another non-migration job,
|
||||
* {@link KbsEnclaveMigrationWorkerJob}, to do the heavy lifting.
|
||||
*/
|
||||
public class KbsEnclaveMigrationJob extends MigrationJob {
|
||||
|
||||
public static final String KEY = "KbsEnclaveMigrationJob";
|
||||
|
||||
KbsEnclaveMigrationJob() {
|
||||
this(new Parameters.Builder().build());
|
||||
}
|
||||
|
||||
private KbsEnclaveMigrationJob(@NonNull Parameters parameters) {
|
||||
super(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUiBlocking() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performMigration() {
|
||||
ApplicationDependencies.getJobManager().add(new KbsEnclaveMigrationWorkerJob());
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean shouldRetry(@NonNull Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class Factory implements Job.Factory<KbsEnclaveMigrationJob> {
|
||||
@Override
|
||||
public @NonNull KbsEnclaveMigrationJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) {
|
||||
return new KbsEnclaveMigrationJob(parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,15 +37,15 @@ public final class PinOptOutMigration extends MigrationJob {
|
||||
|
||||
@Override
|
||||
void performMigration() {
|
||||
if (SignalStore.kbsValues().hasOptedOut() && SignalStore.kbsValues().hasPin()) {
|
||||
if (SignalStore.svr().hasOptedOut() && SignalStore.svr().hasPin()) {
|
||||
Log.w(TAG, "Discovered a legacy opt-out user! Resetting the state.");
|
||||
|
||||
SignalStore.kbsValues().optOut();
|
||||
SignalStore.svr().optOut();
|
||||
ApplicationDependencies.getJobManager().startChain(new RefreshAttributesJob())
|
||||
.then(new RefreshOwnProfileJob())
|
||||
.then(new StorageForcePushJob())
|
||||
.enqueue();
|
||||
} else if (SignalStore.kbsValues().hasOptedOut()) {
|
||||
} else if (SignalStore.svr().hasOptedOut()) {
|
||||
Log.i(TAG, "Discovered an opt-out user, but they're already in a good state. No action required.");
|
||||
} else {
|
||||
Log.i(TAG, "Discovered a normal PIN user. No action required.");
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package org.thoughtcrime.securesms.migrations;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.jobs.BaseJob;
|
||||
import org.thoughtcrime.securesms.pin.PinState;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Migrates an existing V1 registration lock user to a V2 registration lock that is backed by a
|
||||
* Signal PIN.
|
||||
*
|
||||
* Deliberately not a {@link MigrationJob} because it is not something that needs to run at app start.
|
||||
* This migration can run at anytime.
|
||||
*/
|
||||
public final class RegistrationPinV2MigrationJob extends BaseJob {
|
||||
|
||||
private static final String TAG = Log.tag(RegistrationPinV2MigrationJob.class);
|
||||
|
||||
public static final String KEY = "RegistrationPinV2MigrationJob";
|
||||
|
||||
public RegistrationPinV2MigrationJob() {
|
||||
this(new Parameters.Builder()
|
||||
.setQueue(KEY)
|
||||
.setMaxInstancesForFactory(1)
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setLifespan(Job.Parameters.IMMORTAL)
|
||||
.setMaxAttempts(Job.Parameters.UNLIMITED)
|
||||
.build());
|
||||
}
|
||||
|
||||
private RegistrationPinV2MigrationJob(@NonNull Parameters parameters) {
|
||||
super(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable byte[] serialize() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRun() throws IOException, UnauthenticatedResponseException, InvalidKeyException {
|
||||
if (!TextSecurePreferences.isV1RegistrationLockEnabled(context)) {
|
||||
Log.i(TAG, "Registration lock disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
//noinspection deprecation Only acceptable place to read the old pin.
|
||||
String pinValue = TextSecurePreferences.getDeprecatedV1RegistrationLockPin(context);
|
||||
|
||||
if (pinValue == null | TextUtils.isEmpty(pinValue)) {
|
||||
Log.i(TAG, "No old pin to migrate");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Migrating pin to Key Backup Service");
|
||||
PinState.onMigrateToRegistrationLockV2(context, pinValue);
|
||||
Log.i(TAG, "Pin migrated to Key Backup Service");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onShouldRetry(@NonNull Exception e) {
|
||||
return e instanceof IOException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
}
|
||||
|
||||
public static class Factory implements Job.Factory<RegistrationPinV2MigrationJob> {
|
||||
@Override
|
||||
public @NonNull RegistrationPinV2MigrationJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) {
|
||||
return new RegistrationPinV2MigrationJob(parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.thoughtcrime.securesms.migrations
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobs.Svr2MirrorJob
|
||||
|
||||
/**
|
||||
* Mirrors the user's SVR1 data to SVR2.
|
||||
*/
|
||||
internal class Svr2MirrorMigrationJob(
|
||||
parameters: Parameters = Parameters.Builder().build()
|
||||
) : MigrationJob(parameters) {
|
||||
|
||||
companion object {
|
||||
val TAG = Log.tag(Svr2MirrorMigrationJob::class.java)
|
||||
const val KEY = "Svr2MirrorMigrationJob"
|
||||
}
|
||||
|
||||
override fun getFactoryKey(): String = KEY
|
||||
|
||||
override fun isUiBlocking(): Boolean = false
|
||||
|
||||
override fun performMigration() {
|
||||
ApplicationDependencies.getJobManager().add(Svr2MirrorJob())
|
||||
}
|
||||
|
||||
override fun shouldRetry(e: Exception): Boolean = false
|
||||
|
||||
class Factory : Job.Factory<Svr2MirrorMigrationJob> {
|
||||
override fun create(parameters: Parameters, serializedData: ByteArray?): Svr2MirrorMigrationJob {
|
||||
return Svr2MirrorMigrationJob(parameters)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.thoughtcrime.securesms.payments.preferences;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@@ -31,7 +30,7 @@ import org.thoughtcrime.securesms.components.reminder.ReminderView;
|
||||
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
|
||||
import org.thoughtcrime.securesms.help.HelpFragment;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity;
|
||||
import org.thoughtcrime.securesms.payments.FiatMoneyUtil;
|
||||
import org.thoughtcrime.securesms.payments.MoneyView;
|
||||
import org.thoughtcrime.securesms.payments.backup.RecoveryPhraseStates;
|
||||
@@ -369,7 +368,7 @@ public class PaymentsHomeFragment extends LoggingFragment {
|
||||
|
||||
@Override
|
||||
public void onUpdatePin() {
|
||||
startActivityForResult(CreateKbsPinActivity.getIntentForPinChangeFromSettings(requireContext()), CreateKbsPinActivity.REQUEST_NEW_PIN);
|
||||
startActivityForResult(CreateSvrPinActivity.getIntentForPinChangeFromSettings(requireContext()), CreateSvrPinActivity.REQUEST_NEW_PIN);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
package org.thoughtcrime.securesms.pin;
|
||||
|
||||
import android.app.backup.BackupManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.svr2.PinHash;
|
||||
import org.thoughtcrime.securesms.KbsEnclave;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.whispersystems.signalservice.api.KbsPinData;
|
||||
import org.whispersystems.signalservice.api.KeyBackupService;
|
||||
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
||||
import org.whispersystems.signalservice.api.kbs.PinHashUtil;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
/**
|
||||
* Using provided or already stored authorization, provides various get token data from KBS
|
||||
* and generate {@link KbsPinData}.
|
||||
*/
|
||||
public class KbsRepository {
|
||||
|
||||
private static final String TAG = Log.tag(KbsRepository.class);
|
||||
|
||||
public void getToken(@NonNull Consumer<Optional<TokenData>> callback) {
|
||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||
try {
|
||||
callback.accept(Optional.ofNullable(getTokenSync(null)));
|
||||
} catch (IOException e) {
|
||||
callback.accept(Optional.empty());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param authorization If this is being called before the user is registered (i.e. as part of
|
||||
* reglock), you must pass in an authorization token that can be used to
|
||||
* retrieve a backup. Otherwise, pass in null and we'll fetch one.
|
||||
*/
|
||||
public Single<ServiceResponse<TokenData>> getToken(@Nullable String authorization) {
|
||||
return Single.<ServiceResponse<TokenData>>fromCallable(() -> {
|
||||
try {
|
||||
return ServiceResponse.forResult(getTokenSync(authorization), 200, null);
|
||||
} catch (IOException e) {
|
||||
return ServiceResponse.forUnknownError(e);
|
||||
}
|
||||
}).subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and store a new KBS authorization.
|
||||
*/
|
||||
public void refreshAuthorization() throws IOException {
|
||||
for (KbsEnclave enclave : KbsEnclaves.all()) {
|
||||
KeyBackupService kbs = ApplicationDependencies.getKeyBackupService(enclave);
|
||||
|
||||
try {
|
||||
String authorization = kbs.getAuthorization();
|
||||
backupAuthToken(authorization);
|
||||
} catch (NonSuccessfulResponseCodeException e) {
|
||||
if (e.getCode() == 404) {
|
||||
Log.i(TAG, "Enclave decommissioned, skipping", e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @NonNull TokenData getTokenSync(@Nullable String authorization) throws IOException {
|
||||
TokenData firstKnownTokenData = null;
|
||||
|
||||
for (KbsEnclave enclave : KbsEnclaves.all()) {
|
||||
KeyBackupService kbs = ApplicationDependencies.getKeyBackupService(enclave);
|
||||
TokenResponse token;
|
||||
|
||||
try {
|
||||
authorization = authorization == null ? kbs.getAuthorization() : authorization;
|
||||
backupAuthToken(authorization);
|
||||
token = kbs.getToken(authorization);
|
||||
} catch (NonSuccessfulResponseCodeException e) {
|
||||
if (e.getCode() == 404) {
|
||||
Log.i(TAG, "Enclave decommissioned, skipping", e);
|
||||
continue;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
TokenData tokenData = new TokenData(enclave, authorization, token);
|
||||
|
||||
if (tokenData.getTriesRemaining() > 0) {
|
||||
Log.i(TAG, "Found data! " + enclave.getEnclaveName());
|
||||
return tokenData;
|
||||
} else if (firstKnownTokenData == null) {
|
||||
Log.i(TAG, "No data, but storing as the first response. " + enclave.getEnclaveName());
|
||||
firstKnownTokenData = tokenData;
|
||||
} else {
|
||||
Log.i(TAG, "No data, and we already have a 'first response'. " + enclave.getEnclaveName());
|
||||
}
|
||||
}
|
||||
|
||||
return Objects.requireNonNull(firstKnownTokenData);
|
||||
}
|
||||
|
||||
private static void backupAuthToken(String token) {
|
||||
final boolean tokenIsNew = SignalStore.kbsValues().appendAuthTokenToList(token);
|
||||
if (tokenIsNew && SignalStore.kbsValues().hasPin()) {
|
||||
new BackupManager(ApplicationDependencies.getApplication()).dataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked during registration to restore the master key based on the server response during
|
||||
* verification.
|
||||
*
|
||||
* Does not affect {@link PinState}.
|
||||
*/
|
||||
public static synchronized @Nullable KbsPinData restoreMasterKey(@Nullable String pin,
|
||||
@NonNull KbsEnclave enclave,
|
||||
@Nullable String basicStorageCredentials,
|
||||
@NonNull TokenResponse tokenResponse)
|
||||
throws IOException, KeyBackupSystemWrongPinException, KeyBackupSystemNoDataException
|
||||
{
|
||||
Log.i(TAG, "restoreMasterKey()");
|
||||
|
||||
if (pin == null) return null;
|
||||
|
||||
if (basicStorageCredentials == null) {
|
||||
throw new AssertionError("Cannot restore KBS key, no storage credentials supplied. Enclave: " + enclave.getEnclaveName());
|
||||
}
|
||||
|
||||
Log.i(TAG, "Preparing to restore from " + enclave.getEnclaveName());
|
||||
return restoreMasterKeyFromEnclave(enclave, pin, basicStorageCredentials, tokenResponse);
|
||||
}
|
||||
|
||||
private static @NonNull KbsPinData restoreMasterKeyFromEnclave(@NonNull KbsEnclave enclave,
|
||||
@NonNull String pin,
|
||||
@NonNull String basicStorageCredentials,
|
||||
@NonNull TokenResponse tokenResponse)
|
||||
throws IOException, KeyBackupSystemWrongPinException, KeyBackupSystemNoDataException
|
||||
{
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService(enclave);
|
||||
KeyBackupService.RestoreSession session = keyBackupService.newRegistrationSession(basicStorageCredentials, tokenResponse);
|
||||
|
||||
try {
|
||||
Log.i(TAG, "Restoring pin from KBS");
|
||||
|
||||
PinHash hashedPin = PinHashUtil.hashPin(pin, session.hashSalt());
|
||||
KbsPinData kbsData = session.restorePin(hashedPin);
|
||||
|
||||
if (kbsData != null) {
|
||||
Log.i(TAG, "Found registration lock token on KBS.");
|
||||
} else {
|
||||
throw new AssertionError("Null not expected");
|
||||
}
|
||||
|
||||
return kbsData;
|
||||
} catch (UnauthenticatedResponseException | InvalidKeyException e) {
|
||||
Log.w(TAG, "Failed to restore key", e);
|
||||
throw new IOException(e);
|
||||
} catch (KeyBackupServicePinException e) {
|
||||
Log.w(TAG, "Incorrect pin", e);
|
||||
throw new KeyBackupSystemWrongPinException(e.getToken());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package org.thoughtcrime.securesms.pin;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
|
||||
public final class KeyBackupSystemWrongPinException extends Exception {
|
||||
|
||||
private final TokenResponse tokenResponse;
|
||||
|
||||
public KeyBackupSystemWrongPinException(@NonNull TokenResponse tokenResponse){
|
||||
this.tokenResponse = tokenResponse;
|
||||
}
|
||||
|
||||
public @NonNull TokenResponse getTokenResponse() {
|
||||
return tokenResponse;
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ public final class PinOptOutDialog {
|
||||
AlertDialog progress = SimpleProgressDialog.show(context);
|
||||
|
||||
SimpleTask.run(() -> {
|
||||
PinState.onPinOptOut();
|
||||
SvrRepository.optOutOfPin();
|
||||
return null;
|
||||
}, success -> {
|
||||
Log.i(TAG, "Disable operation finished.");
|
||||
|
||||
@@ -9,7 +9,7 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||
import org.thoughtcrime.securesms.MainActivity;
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
|
||||
@@ -32,7 +32,7 @@ public final class PinRestoreActivity extends AppCompatActivity {
|
||||
|
||||
void navigateToPinCreation() {
|
||||
final Intent main = MainActivity.clearTop(this);
|
||||
final Intent createPin = CreateKbsPinActivity.getIntentForPinCreate(this);
|
||||
final Intent createPin = CreateSvrPinActivity.getIntentForPinCreate(this);
|
||||
final Intent chained = PassphraseRequiredActivity.chainIntent(createPin, main);
|
||||
|
||||
startActivity(chained);
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.v2.KbsConstants;
|
||||
import org.thoughtcrime.securesms.lock.v2.SvrConstants;
|
||||
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
||||
@@ -113,7 +113,7 @@ public class PinRestoreEntryFragment extends LoggingFragment {
|
||||
private void initViewModel() {
|
||||
viewModel = new ViewModelProvider(this).get(PinRestoreViewModel.class);
|
||||
|
||||
viewModel.getTriesRemaining().observe(getViewLifecycleOwner(), this::presentTriesRemaining);
|
||||
viewModel.triesRemaining.observe(getViewLifecycleOwner(), this::presentTriesRemaining);
|
||||
viewModel.getEvent().observe(getViewLifecycleOwner(), this::presentEvent);
|
||||
}
|
||||
|
||||
@@ -194,9 +194,9 @@ public class PinRestoreEntryFragment extends LoggingFragment {
|
||||
private void onNeedHelpClicked() {
|
||||
new MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.PinRestoreEntryFragment_need_help)
|
||||
.setMessage(getString(R.string.PinRestoreEntryFragment_your_pin_is_a_d_digit_code, KbsConstants.MINIMUM_PIN_LENGTH))
|
||||
.setMessage(getString(R.string.PinRestoreEntryFragment_your_pin_is_a_d_digit_code, SvrConstants.MINIMUM_PIN_LENGTH))
|
||||
.setPositiveButton(R.string.PinRestoreEntryFragment_create_new_pin, ((dialog, which) -> {
|
||||
PinState.onPinRestoreForgottenOrSkipped();
|
||||
SvrRepository.onPinRestoreForgottenOrSkipped();
|
||||
((PinRestoreActivity) requireActivity()).navigateToPinCreation();
|
||||
}))
|
||||
.setNeutralButton(R.string.PinRestoreEntryFragment_contact_support, (dialog, which) -> {
|
||||
@@ -218,7 +218,7 @@ public class PinRestoreEntryFragment extends LoggingFragment {
|
||||
.setTitle(R.string.PinRestoreEntryFragment_skip_pin_entry)
|
||||
.setMessage(R.string.PinRestoreEntryFragment_if_you_cant_remember_your_pin)
|
||||
.setPositiveButton(R.string.PinRestoreEntryFragment_create_new_pin, (dialog, which) -> {
|
||||
PinState.onPinRestoreForgottenOrSkipped();
|
||||
SvrRepository.onPinRestoreForgottenOrSkipped();
|
||||
((PinRestoreActivity) requireActivity()).navigateToPinCreation();
|
||||
})
|
||||
.setNegativeButton(R.string.PinRestoreEntryFragment_cancel, null)
|
||||
@@ -226,7 +226,7 @@ public class PinRestoreEntryFragment extends LoggingFragment {
|
||||
}
|
||||
|
||||
private void onAccountLocked() {
|
||||
PinState.onPinRestoreForgottenOrSkipped();
|
||||
SvrRepository.onPinRestoreForgottenOrSkipped();
|
||||
SafeNavigation.safeNavigate(Navigation.findNavController(requireView()), PinRestoreEntryFragmentDirections.actionAccountLocked());
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public class PinRestoreLockedFragment extends LoggingFragment {
|
||||
View learnMoreButton = view.findViewById(R.id.pin_locked_learn_more);
|
||||
|
||||
createPinButton.setOnClickListener(v -> {
|
||||
PinState.onPinRestoreForgottenOrSkipped();
|
||||
SvrRepository.onPinRestoreForgottenOrSkipped();
|
||||
((PinRestoreActivity) requireActivity()).navigateToPinCreation();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
package org.thoughtcrime.securesms.pin;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.NewRegistrationUsernameSyncJob;
|
||||
import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob;
|
||||
import org.thoughtcrime.securesms.jobs.StorageSyncJob;
|
||||
import org.signal.core.util.Stopwatch;
|
||||
import org.whispersystems.signalservice.api.KbsPinData;
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class PinRestoreRepository {
|
||||
|
||||
private static final String TAG = Log.tag(PinRestoreRepository.class);
|
||||
|
||||
private final Executor executor = SignalExecutors.UNBOUNDED;
|
||||
|
||||
void submitPin(@NonNull String pin, @NonNull TokenData tokenData, @NonNull Callback<PinResultData> callback) {
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
Stopwatch stopwatch = new Stopwatch("PinSubmission");
|
||||
|
||||
KbsPinData kbsData = KbsRepository.restoreMasterKey(pin, tokenData.getEnclave(), tokenData.getBasicAuth(), tokenData.getTokenResponse());
|
||||
PinState.onSignalPinRestore(ApplicationDependencies.getApplication(), Objects.requireNonNull(kbsData), pin);
|
||||
stopwatch.split("MasterKey");
|
||||
|
||||
ApplicationDependencies.getJobManager().runSynchronously(new StorageAccountRestoreJob(), StorageAccountRestoreJob.LIFESPAN);
|
||||
stopwatch.split("AccountRestore");
|
||||
|
||||
ApplicationDependencies
|
||||
.getJobManager()
|
||||
.startChain(new StorageSyncJob())
|
||||
.then(new NewRegistrationUsernameSyncJob())
|
||||
.enqueueAndBlockUntilCompletion(TimeUnit.SECONDS.toMillis(10));
|
||||
stopwatch.split("ContactRestore");
|
||||
|
||||
stopwatch.stop(TAG);
|
||||
|
||||
callback.onComplete(new PinResultData(PinResult.SUCCESS, tokenData));
|
||||
} catch (IOException e) {
|
||||
callback.onComplete(new PinResultData(PinResult.NETWORK_ERROR, tokenData));
|
||||
} catch (KeyBackupSystemNoDataException e) {
|
||||
callback.onComplete(new PinResultData(PinResult.LOCKED, tokenData));
|
||||
} catch (KeyBackupSystemWrongPinException e) {
|
||||
callback.onComplete(new PinResultData(PinResult.INCORRECT, TokenData.withResponse(tokenData, e.getTokenResponse())));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
interface Callback<T> {
|
||||
void onComplete(@NonNull T value);
|
||||
}
|
||||
|
||||
static class PinResultData {
|
||||
private final PinResult result;
|
||||
private final TokenData tokenData;
|
||||
|
||||
PinResultData(@NonNull PinResult result, @NonNull TokenData tokenData) {
|
||||
this.result = result;
|
||||
this.tokenData = tokenData;
|
||||
}
|
||||
|
||||
public @NonNull PinResult getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public @NonNull TokenData getTokenData() {
|
||||
return tokenData;
|
||||
}
|
||||
}
|
||||
|
||||
enum PinResult {
|
||||
SUCCESS, INCORRECT, LOCKED, NETWORK_ERROR
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package org.thoughtcrime.securesms.pin;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.v2.KbsConstants;
|
||||
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType;
|
||||
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
|
||||
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||
|
||||
public class PinRestoreViewModel extends ViewModel {
|
||||
|
||||
private final PinRestoreRepository repo;
|
||||
private final DefaultValueLiveData<TriesRemaining> triesRemaining;
|
||||
private final SingleLiveEvent<Event> event;
|
||||
private final KbsRepository kbsRepository;
|
||||
|
||||
private volatile TokenData tokenData;
|
||||
|
||||
public PinRestoreViewModel() {
|
||||
this.repo = new PinRestoreRepository();
|
||||
this.kbsRepository = new KbsRepository();
|
||||
this.triesRemaining = new DefaultValueLiveData<>(new TriesRemaining(10, false));
|
||||
this.event = new SingleLiveEvent<>();
|
||||
|
||||
kbsRepository.getToken(token -> {
|
||||
if (token.isPresent()) {
|
||||
updateTokenData(token.get(), false);
|
||||
} else {
|
||||
event.postValue(Event.NETWORK_ERROR);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void onPinSubmitted(@NonNull String pin, @NonNull PinKeyboardType pinKeyboardType) {
|
||||
int trimmedLength = pin.replace(" ", "").length();
|
||||
|
||||
if (trimmedLength == 0) {
|
||||
event.postValue(Event.EMPTY_PIN);
|
||||
return;
|
||||
}
|
||||
|
||||
if (trimmedLength < KbsConstants.MINIMUM_PIN_LENGTH) {
|
||||
event.postValue(Event.PIN_TOO_SHORT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tokenData != null) {
|
||||
repo.submitPin(pin, tokenData, result -> {
|
||||
|
||||
switch (result.getResult()) {
|
||||
case SUCCESS:
|
||||
SignalStore.pinValues().setKeyboardType(pinKeyboardType);
|
||||
SignalStore.storageService().setNeedsAccountRestore(false);
|
||||
event.postValue(Event.SUCCESS);
|
||||
break;
|
||||
case LOCKED:
|
||||
event.postValue(Event.PIN_LOCKED);
|
||||
break;
|
||||
case INCORRECT:
|
||||
event.postValue(Event.PIN_INCORRECT);
|
||||
updateTokenData(result.getTokenData(), true);
|
||||
break;
|
||||
case NETWORK_ERROR:
|
||||
event.postValue(Event.NETWORK_ERROR);
|
||||
break;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
kbsRepository.getToken(token -> {
|
||||
if (token.isPresent()) {
|
||||
updateTokenData(token.get(), false);
|
||||
onPinSubmitted(pin, pinKeyboardType);
|
||||
} else {
|
||||
event.postValue(Event.NETWORK_ERROR);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull DefaultValueLiveData<TriesRemaining> getTriesRemaining() {
|
||||
return triesRemaining;
|
||||
}
|
||||
|
||||
@NonNull LiveData<Event> getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
private void updateTokenData(@NonNull TokenData tokenData, boolean incorrectGuess) {
|
||||
this.tokenData = tokenData;
|
||||
triesRemaining.postValue(new TriesRemaining(tokenData.getTriesRemaining(), incorrectGuess));
|
||||
}
|
||||
|
||||
enum Event {
|
||||
SUCCESS, EMPTY_PIN, PIN_TOO_SHORT, PIN_INCORRECT, PIN_LOCKED, NETWORK_ERROR
|
||||
}
|
||||
|
||||
static class TriesRemaining {
|
||||
private final int triesRemaining;
|
||||
private final boolean hasIncorrectGuess;
|
||||
|
||||
TriesRemaining(int triesRemaining, boolean hasIncorrectGuess) {
|
||||
this.triesRemaining = triesRemaining;
|
||||
this.hasIncorrectGuess = hasIncorrectGuess;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return triesRemaining;
|
||||
}
|
||||
|
||||
public boolean hasIncorrectGuess() {
|
||||
return hasIncorrectGuess;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.thoughtcrime.securesms.pin
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType
|
||||
import org.thoughtcrime.securesms.lock.v2.SvrConstants
|
||||
import org.thoughtcrime.securesms.util.DefaultValueLiveData
|
||||
import org.thoughtcrime.securesms.util.SingleLiveEvent
|
||||
import org.whispersystems.signalservice.api.svr.SecureValueRecovery
|
||||
|
||||
class PinRestoreViewModel : ViewModel() {
|
||||
private val repo: SvrRepository = SvrRepository
|
||||
|
||||
@JvmField
|
||||
val triesRemaining: DefaultValueLiveData<TriesRemaining> = DefaultValueLiveData(TriesRemaining(10, false))
|
||||
|
||||
private val event: SingleLiveEvent<Event> = SingleLiveEvent()
|
||||
|
||||
private val disposables = CompositeDisposable()
|
||||
|
||||
fun onPinSubmitted(pin: String, pinKeyboardType: PinKeyboardType) {
|
||||
val trimmedLength = pin.trim().length
|
||||
if (trimmedLength == 0) {
|
||||
event.postValue(Event.EMPTY_PIN)
|
||||
return
|
||||
}
|
||||
if (trimmedLength < SvrConstants.MINIMUM_PIN_LENGTH) {
|
||||
event.postValue(Event.PIN_TOO_SHORT)
|
||||
return
|
||||
}
|
||||
|
||||
disposables += Single
|
||||
.fromCallable { repo.restoreMasterKeyPostRegistration(pin, pinKeyboardType) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { result ->
|
||||
when (result) {
|
||||
is SecureValueRecovery.RestoreResponse.Success -> {
|
||||
event.postValue(Event.SUCCESS)
|
||||
}
|
||||
is SecureValueRecovery.RestoreResponse.PinMismatch -> {
|
||||
event.postValue(Event.PIN_INCORRECT)
|
||||
triesRemaining.postValue(TriesRemaining(result.triesRemaining, true))
|
||||
}
|
||||
SecureValueRecovery.RestoreResponse.Missing -> {
|
||||
event.postValue(Event.PIN_LOCKED)
|
||||
}
|
||||
is SecureValueRecovery.RestoreResponse.NetworkError -> {
|
||||
event.postValue(Event.NETWORK_ERROR)
|
||||
}
|
||||
is SecureValueRecovery.RestoreResponse.ApplicationError -> {
|
||||
event.postValue(Event.NETWORK_ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getEvent(): LiveData<Event> {
|
||||
return event
|
||||
}
|
||||
|
||||
enum class Event {
|
||||
SUCCESS,
|
||||
EMPTY_PIN,
|
||||
PIN_TOO_SHORT,
|
||||
PIN_INCORRECT,
|
||||
PIN_LOCKED,
|
||||
NETWORK_ERROR
|
||||
}
|
||||
|
||||
class TriesRemaining(val count: Int, private val hasIncorrectGuess: Boolean) {
|
||||
fun hasIncorrectGuess(): Boolean {
|
||||
return hasIncorrectGuess
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,435 +0,0 @@
|
||||
package org.thoughtcrime.securesms.pin;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.svr2.PinHash;
|
||||
import org.thoughtcrime.securesms.KbsEnclave;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobTracker;
|
||||
import org.thoughtcrime.securesms.jobs.ClearFallbackKbsEnclaveJob;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
|
||||
import org.thoughtcrime.securesms.jobs.StorageForcePushJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.KbsValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.RegistrationLockReminders;
|
||||
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType;
|
||||
import org.thoughtcrime.securesms.megaphone.Megaphones;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.KbsPinData;
|
||||
import org.whispersystems.signalservice.api.KeyBackupService;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.api.kbs.PinHashUtil;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class PinState {
|
||||
|
||||
private static final String TAG = Log.tag(PinState.class);
|
||||
|
||||
/**
|
||||
* Invoked after a user has successfully registered. Ensures all the necessary state is updated.
|
||||
*/
|
||||
public static synchronized void onRegistration(@NonNull Context context,
|
||||
@Nullable KbsPinData kbsData,
|
||||
@Nullable String pin,
|
||||
boolean hasPinToRestore,
|
||||
boolean setRegistrationLockEnabled)
|
||||
{
|
||||
Log.i(TAG, "onRegistration()");
|
||||
|
||||
TextSecurePreferences.setV1RegistrationLockPin(context, pin);
|
||||
|
||||
if (kbsData == null && pin != null) {
|
||||
Log.i(TAG, "Registration Lock V1");
|
||||
SignalStore.kbsValues().clearRegistrationLockAndPin();
|
||||
TextSecurePreferences.setV1RegistrationLockEnabled(context, true);
|
||||
TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis());
|
||||
TextSecurePreferences.setRegistrationLockNextReminderInterval(context, RegistrationLockReminders.INITIAL_INTERVAL);
|
||||
} else if (kbsData != null && pin != null) {
|
||||
if (setRegistrationLockEnabled) {
|
||||
Log.i(TAG, "Registration Lock V2");
|
||||
TextSecurePreferences.setV1RegistrationLockEnabled(context, false);
|
||||
SignalStore.kbsValues().setV2RegistrationLockEnabled(true);
|
||||
} else {
|
||||
Log.i(TAG, "ReRegistration Skip SMS");
|
||||
}
|
||||
SignalStore.kbsValues().setKbsMasterKey(kbsData, pin);
|
||||
SignalStore.pinValues().resetPinReminders();
|
||||
resetPinRetryCount(context, pin);
|
||||
ClearFallbackKbsEnclaveJob.clearAll();
|
||||
} else if (hasPinToRestore) {
|
||||
Log.i(TAG, "Has a PIN to restore.");
|
||||
SignalStore.kbsValues().clearRegistrationLockAndPin();
|
||||
TextSecurePreferences.setV1RegistrationLockEnabled(context, false);
|
||||
SignalStore.storageService().setNeedsAccountRestore(true);
|
||||
} else {
|
||||
Log.i(TAG, "No registration lock or PIN at all.");
|
||||
SignalStore.kbsValues().clearRegistrationLockAndPin();
|
||||
TextSecurePreferences.setV1RegistrationLockEnabled(context, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the user is going through the PIN restoration flow (which is separate from reglock).
|
||||
*/
|
||||
public static synchronized void onSignalPinRestore(@NonNull Context context, @NonNull KbsPinData kbsData, @NonNull String pin) {
|
||||
Log.i(TAG, "onSignalPinRestore()");
|
||||
|
||||
SignalStore.kbsValues().setKbsMasterKey(kbsData, pin);
|
||||
SignalStore.kbsValues().setV2RegistrationLockEnabled(false);
|
||||
SignalStore.pinValues().resetPinReminders();
|
||||
SignalStore.kbsValues().setPinForgottenOrSkipped(false);
|
||||
SignalStore.storageService().setNeedsAccountRestore(false);
|
||||
resetPinRetryCount(context, pin);
|
||||
ClearFallbackKbsEnclaveJob.clearAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the user skips out on PIN restoration or otherwise fails to remember their PIN.
|
||||
*/
|
||||
public static synchronized void onPinRestoreForgottenOrSkipped() {
|
||||
SignalStore.kbsValues().clearRegistrationLockAndPin();
|
||||
SignalStore.storageService().setNeedsAccountRestore(false);
|
||||
SignalStore.kbsValues().setPinForgottenOrSkipped(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked whenever the Signal PIN is changed or created.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static synchronized void onPinChangedOrCreated(@NonNull Context context, @NonNull String pin, @NonNull PinKeyboardType keyboard)
|
||||
throws IOException, UnauthenticatedResponseException, InvalidKeyException
|
||||
{
|
||||
Log.i(TAG, "onPinChangedOrCreated()");
|
||||
|
||||
KbsEnclave kbsEnclave = KbsEnclaves.current();
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
boolean isFirstPin = !kbsValues.hasPin() || kbsValues.hasOptedOut();
|
||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService(kbsEnclave);
|
||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
||||
PinHash pinHash = PinHashUtil.hashPin(pin, pinChangeSession.hashSalt());
|
||||
KbsPinData kbsData = pinChangeSession.setPin(pinHash, masterKey);
|
||||
|
||||
kbsValues.setKbsMasterKey(kbsData, pin);
|
||||
kbsValues.setPinForgottenOrSkipped(false);
|
||||
TextSecurePreferences.clearRegistrationLockV1(context);
|
||||
SignalStore.pinValues().setKeyboardType(keyboard);
|
||||
SignalStore.pinValues().resetPinReminders();
|
||||
ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.PINS_FOR_ALL);
|
||||
|
||||
if (isFirstPin) {
|
||||
Log.i(TAG, "First time setting a PIN. Refreshing attributes to set the 'storage' capability. Enclave: " + kbsEnclave.getEnclaveName());
|
||||
bestEffortRefreshAttributes();
|
||||
} else {
|
||||
Log.i(TAG, "Not the first time setting a PIN. Enclave: " + kbsEnclave.getEnclaveName());
|
||||
ApplicationDependencies.getJobManager().add(new RefreshAttributesJob());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when PIN creation fails.
|
||||
*/
|
||||
public static synchronized void onPinCreateFailure() {
|
||||
Log.i(TAG, "onPinCreateFailure()");
|
||||
if (getState() == State.NO_REGISTRATION_LOCK) {
|
||||
SignalStore.kbsValues().onPinCreateFailure();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the user has enabled the "PIN opt out" setting.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static synchronized void onPinOptOut() {
|
||||
Log.i(TAG, "onPinOptOutEnabled()");
|
||||
assertState(State.PIN_WITH_REGISTRATION_LOCK_DISABLED, State.NO_REGISTRATION_LOCK);
|
||||
|
||||
optOutOfPin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked whenever a Signal PIN user enables registration lock.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static synchronized void onEnableRegistrationLockForUserWithPin() throws IOException {
|
||||
Log.i(TAG, "onEnableRegistrationLockForUserWithPin()");
|
||||
|
||||
if (getState() == State.PIN_WITH_REGISTRATION_LOCK_ENABLED) {
|
||||
Log.i(TAG, "Registration lock already enabled. Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
assertState(State.PIN_WITH_REGISTRATION_LOCK_DISABLED);
|
||||
|
||||
|
||||
KbsEnclave kbsEnclave = KbsEnclaves.current();
|
||||
Log.i(TAG, "Enclave: " + kbsEnclave.getEnclaveName());
|
||||
|
||||
SignalStore.kbsValues().setV2RegistrationLockEnabled(false);
|
||||
ApplicationDependencies.getKeyBackupService(kbsEnclave)
|
||||
.newPinChangeSession(SignalStore.kbsValues().getRegistrationLockTokenResponse())
|
||||
.enableRegistrationLock(SignalStore.kbsValues().getOrCreateMasterKey());
|
||||
SignalStore.kbsValues().setV2RegistrationLockEnabled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked whenever a Signal PIN user disables registration lock.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static synchronized void onDisableRegistrationLockForUserWithPin() throws IOException {
|
||||
Log.i(TAG, "onDisableRegistrationLockForUserWithPin()");
|
||||
|
||||
if (getState() == State.PIN_WITH_REGISTRATION_LOCK_DISABLED) {
|
||||
Log.i(TAG, "Registration lock already disabled. Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
assertState(State.PIN_WITH_REGISTRATION_LOCK_ENABLED);
|
||||
|
||||
SignalStore.kbsValues().setV2RegistrationLockEnabled(true);
|
||||
ApplicationDependencies.getKeyBackupService(KbsEnclaves.current())
|
||||
.newPinChangeSession(SignalStore.kbsValues().getRegistrationLockTokenResponse())
|
||||
.disableRegistrationLock();
|
||||
SignalStore.kbsValues().setV2RegistrationLockEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should only be called by {@link org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob}.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static synchronized void onMigrateToRegistrationLockV2(@NonNull Context context, @NonNull String pin)
|
||||
throws IOException, UnauthenticatedResponseException, InvalidKeyException
|
||||
{
|
||||
Log.i(TAG, "onMigrateToRegistrationLockV2()");
|
||||
|
||||
KbsEnclave kbsEnclave = KbsEnclaves.current();
|
||||
Log.i(TAG, "Enclave: " + kbsEnclave.getEnclaveName());
|
||||
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService(kbsEnclave);
|
||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
||||
PinHash pinHash = PinHashUtil.hashPin(pin, pinChangeSession.hashSalt());
|
||||
KbsPinData kbsData = pinChangeSession.setPin(pinHash, masterKey);
|
||||
|
||||
pinChangeSession.enableRegistrationLock(masterKey);
|
||||
|
||||
kbsValues.setKbsMasterKey(kbsData, pin);
|
||||
TextSecurePreferences.clearRegistrationLockV1(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should only be called by {@link org.thoughtcrime.securesms.jobs.KbsEnclaveMigrationWorkerJob}.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static synchronized void onMigrateToNewEnclave(@NonNull String pin)
|
||||
throws IOException, UnauthenticatedResponseException
|
||||
{
|
||||
Log.i(TAG, "onMigrateToNewEnclave()");
|
||||
assertState(State.PIN_WITH_REGISTRATION_LOCK_DISABLED, State.PIN_WITH_REGISTRATION_LOCK_ENABLED);
|
||||
|
||||
Log.i(TAG, "Migrating to enclave " + KbsEnclaves.current().getEnclaveName());
|
||||
setPinOnEnclave(KbsEnclaves.current(), pin, SignalStore.kbsValues().getOrCreateMasterKey());
|
||||
|
||||
ClearFallbackKbsEnclaveJob.clearAll();
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private static void bestEffortRefreshAttributes() {
|
||||
Optional<JobTracker.JobState> result = ApplicationDependencies.getJobManager().runSynchronously(new RefreshAttributesJob(), TimeUnit.SECONDS.toMillis(10));
|
||||
|
||||
if (result.isPresent() && result.get() == JobTracker.JobState.SUCCESS) {
|
||||
Log.i(TAG, "Attributes were refreshed successfully.");
|
||||
} else if (result.isPresent()) {
|
||||
Log.w(TAG, "Attribute refresh finished, but was not successful. Enqueuing one for later. (Result: " + result.get() + ")");
|
||||
ApplicationDependencies.getJobManager().add(new RefreshAttributesJob());
|
||||
} else {
|
||||
Log.w(TAG, "Job did not finish in the allotted time. It'll finish later.");
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private static void bestEffortForcePushStorage() {
|
||||
Optional<JobTracker.JobState> result = ApplicationDependencies.getJobManager().runSynchronously(new StorageForcePushJob(), TimeUnit.SECONDS.toMillis(10));
|
||||
|
||||
if (result.isPresent() && result.get() == JobTracker.JobState.SUCCESS) {
|
||||
Log.i(TAG, "Storage was force-pushed successfully.");
|
||||
} else if (result.isPresent()) {
|
||||
Log.w(TAG, "Storage force-pushed finished, but was not successful. Enqueuing one for later. (Result: " + result.get() + ")");
|
||||
ApplicationDependencies.getJobManager().add(new RefreshAttributesJob());
|
||||
} else {
|
||||
Log.w(TAG, "Storage fore push did not finish in the allotted time. It'll finish later.");
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private static void resetPinRetryCount(@NonNull Context context, @Nullable String pin) {
|
||||
if (pin == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setPinOnEnclave(KbsEnclaves.current(), pin, SignalStore.kbsValues().getOrCreateMasterKey());
|
||||
TextSecurePreferences.clearRegistrationLockV1(context);
|
||||
Log.i(TAG, "Pin set/attempts reset on KBS");
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "May have failed to reset pin attempts!", e);
|
||||
} catch (UnauthenticatedResponseException e) {
|
||||
Log.w(TAG, "Failed to reset pin attempts", e);
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private static @NonNull KbsPinData setPinOnEnclave(@NonNull KbsEnclave enclave, @NonNull String pin, @NonNull MasterKey masterKey)
|
||||
throws IOException, UnauthenticatedResponseException
|
||||
{
|
||||
Log.i(TAG, "Setting PIN on enclave: " + enclave.getEnclaveName());
|
||||
|
||||
KeyBackupService kbs = ApplicationDependencies.getKeyBackupService(enclave);
|
||||
KeyBackupService.PinChangeSession pinChangeSession = kbs.newPinChangeSession();
|
||||
PinHash pinHash = PinHashUtil.hashPin(pin, pinChangeSession.hashSalt());
|
||||
KbsPinData newData = pinChangeSession.setPin(pinHash, masterKey);
|
||||
|
||||
SignalStore.kbsValues().setKbsMasterKey(newData, pin);
|
||||
|
||||
return newData;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private static void optOutOfPin() {
|
||||
SignalStore.kbsValues().optOut();
|
||||
|
||||
ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.PINS_FOR_ALL);
|
||||
|
||||
bestEffortRefreshAttributes();
|
||||
bestEffortForcePushStorage();
|
||||
}
|
||||
|
||||
private static @NonNull State assertState(State... allowed) {
|
||||
State currentState = getState();
|
||||
|
||||
for (State state : allowed) {
|
||||
if (currentState == state) {
|
||||
return currentState;
|
||||
}
|
||||
}
|
||||
|
||||
switch (currentState) {
|
||||
case NO_REGISTRATION_LOCK: throw new InvalidState_NoRegistrationLock();
|
||||
case REGISTRATION_LOCK_V1: throw new InvalidState_RegistrationLockV1();
|
||||
case PIN_WITH_REGISTRATION_LOCK_ENABLED: throw new InvalidState_PinWithRegistrationLockEnabled();
|
||||
case PIN_WITH_REGISTRATION_LOCK_DISABLED: throw new InvalidState_PinWithRegistrationLockDisabled();
|
||||
case PIN_OPT_OUT: throw new InvalidState_PinOptOut();
|
||||
default: throw new IllegalStateException("Expected: " + Arrays.toString(allowed) + ", Actual: " + currentState);
|
||||
}
|
||||
}
|
||||
|
||||
public static @NonNull State getState() {
|
||||
Context context = ApplicationDependencies.getApplication();
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
|
||||
boolean v1Enabled = TextSecurePreferences.isV1RegistrationLockEnabled(context);
|
||||
boolean v2Enabled = kbsValues.isV2RegistrationLockEnabled();
|
||||
boolean hasPin = kbsValues.hasPin();
|
||||
boolean optedOut = kbsValues.hasOptedOut();
|
||||
|
||||
if (optedOut && !v2Enabled && !v1Enabled) {
|
||||
return State.PIN_OPT_OUT;
|
||||
}
|
||||
|
||||
if (!v1Enabled && !v2Enabled && !hasPin) {
|
||||
return State.NO_REGISTRATION_LOCK;
|
||||
}
|
||||
|
||||
if (v1Enabled && !v2Enabled && !hasPin) {
|
||||
return State.REGISTRATION_LOCK_V1;
|
||||
}
|
||||
|
||||
if (v2Enabled && hasPin) {
|
||||
TextSecurePreferences.setV1RegistrationLockEnabled(context, false);
|
||||
return State.PIN_WITH_REGISTRATION_LOCK_ENABLED;
|
||||
}
|
||||
|
||||
if (!v2Enabled && hasPin) {
|
||||
TextSecurePreferences.setV1RegistrationLockEnabled(context, false);
|
||||
return State.PIN_WITH_REGISTRATION_LOCK_DISABLED;
|
||||
}
|
||||
|
||||
throw new InvalidInferredStateError(String.format(Locale.ENGLISH, "Invalid state! v1: %b, v2: %b, pin: %b", v1Enabled, v2Enabled, hasPin));
|
||||
}
|
||||
|
||||
private enum State {
|
||||
/**
|
||||
* User has nothing -- either in the process of registration, or pre-PIN-migration
|
||||
*/
|
||||
NO_REGISTRATION_LOCK("no_registration_lock"),
|
||||
|
||||
/**
|
||||
* User has a V1 registration lock set
|
||||
*/
|
||||
REGISTRATION_LOCK_V1("registration_lock_v1"),
|
||||
|
||||
/**
|
||||
* User has a PIN, and registration lock is enabled.
|
||||
*/
|
||||
PIN_WITH_REGISTRATION_LOCK_ENABLED("pin_with_registration_lock_enabled"),
|
||||
|
||||
/**
|
||||
* User has a PIN, but registration lock is disabled.
|
||||
*/
|
||||
PIN_WITH_REGISTRATION_LOCK_DISABLED("pin_with_registration_lock_disabled"),
|
||||
|
||||
/**
|
||||
* The user has opted out of creating a PIN. In this case, we will generate a high-entropy PIN
|
||||
* on their behalf.
|
||||
*/
|
||||
PIN_OPT_OUT("pin_opt_out");
|
||||
|
||||
/**
|
||||
* Using a string key so that people can rename/reorder values in the future without breaking
|
||||
* serialization.
|
||||
*/
|
||||
private final String key;
|
||||
|
||||
State(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public @NonNull String serialize() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public static @NonNull State deserialize(@NonNull String serialized) {
|
||||
for (State state : values()) {
|
||||
if (state.key.equals(serialized)) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No state for value: " + serialized);
|
||||
}
|
||||
}
|
||||
|
||||
private static class InvalidInferredStateError extends Error {
|
||||
InvalidInferredStateError(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static class InvalidState_NoRegistrationLock extends IllegalStateException {}
|
||||
private static class InvalidState_RegistrationLockV1 extends IllegalStateException {}
|
||||
private static class InvalidState_PinWithRegistrationLockEnabled extends IllegalStateException {}
|
||||
private static class InvalidState_PinWithRegistrationLockDisabled extends IllegalStateException {}
|
||||
private static class InvalidState_PinOptOut extends IllegalStateException {}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ public final class RegistrationLockV2Dialog {
|
||||
|
||||
SimpleTask.run(SignalExecutors.UNBOUNDED, () -> {
|
||||
try {
|
||||
PinState.onEnableRegistrationLockForUserWithPin();
|
||||
SvrRepository.enableRegistrationLockForUserWithPin();
|
||||
Log.i(TAG, "Successfully enabled registration lock.");
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
@@ -87,7 +87,7 @@ public final class RegistrationLockV2Dialog {
|
||||
|
||||
SimpleTask.run(SignalExecutors.UNBOUNDED, () -> {
|
||||
try {
|
||||
PinState.onDisableRegistrationLockForUserWithPin();
|
||||
SvrRepository.disableRegistrationLockForUserWithPin();
|
||||
Log.i(TAG, "Successfully disabled registration lock.");
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -0,0 +1,411 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.pin
|
||||
|
||||
import android.app.backup.BackupManager
|
||||
import androidx.annotation.WorkerThread
|
||||
import org.signal.core.util.Stopwatch
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.BuildConfig
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.JobTracker
|
||||
import org.thoughtcrime.securesms.jobs.NewRegistrationUsernameSyncJob
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
|
||||
import org.thoughtcrime.securesms.jobs.ResetSvrGuessCountJob
|
||||
import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob
|
||||
import org.thoughtcrime.securesms.jobs.StorageForcePushJob
|
||||
import org.thoughtcrime.securesms.jobs.StorageSyncJob
|
||||
import org.thoughtcrime.securesms.jobs.Svr2MirrorJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType
|
||||
import org.thoughtcrime.securesms.megaphone.Megaphones
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.SvrAuthCredentialSet
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.whispersystems.signalservice.api.SvrNoDataException
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey
|
||||
import org.whispersystems.signalservice.api.svr.SecureValueRecovery
|
||||
import org.whispersystems.signalservice.api.svr.SecureValueRecovery.BackupResponse
|
||||
import org.whispersystems.signalservice.api.svr.SecureValueRecovery.RestoreResponse
|
||||
import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV1
|
||||
import org.whispersystems.signalservice.internal.push.AuthCredentials
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
object SvrRepository {
|
||||
|
||||
val TAG = Log.tag(SvrRepository::class.java)
|
||||
|
||||
private val svr2: SecureValueRecovery = ApplicationDependencies.getSignalServiceAccountManager().getSecureValueRecoveryV2(BuildConfig.SVR2_MRENCLAVE)
|
||||
private val svr1: SecureValueRecovery = SecureValueRecoveryV1(ApplicationDependencies.getKeyBackupService(BuildConfig.KBS_ENCLAVE))
|
||||
|
||||
/** An ordered list of SVR implementations. They should be in priority order, with the most important one listed first. */
|
||||
private val implementations: List<SecureValueRecovery> = listOf(svr2, svr1)
|
||||
|
||||
/**
|
||||
* A lock that ensures that only one thread at a time is altering the various pieces of SVR state.
|
||||
*
|
||||
* External usage of this should be limited to one-time migrations. Any routine operation that needs the lock should go in
|
||||
* this repository instead.
|
||||
*/
|
||||
val operationLock = ReentrantLock()
|
||||
|
||||
/**
|
||||
* Restores the master key from the first available SVR implementation available.
|
||||
*
|
||||
* This is intended to be called before registration has been completed, requiring
|
||||
* that you pass in the credentials provided during registration to access SVR.
|
||||
*
|
||||
* You could be hitting this because the user has reglock (and therefore need to
|
||||
* restore the master key before you can register), or you may be doing the
|
||||
* sms-skip flow.
|
||||
*/
|
||||
@JvmStatic
|
||||
@WorkerThread
|
||||
@Throws(IOException::class, SvrWrongPinException::class, SvrNoDataException::class)
|
||||
fun restoreMasterKeyPreRegistration(credentials: SvrAuthCredentialSet, userPin: String): MasterKey {
|
||||
operationLock.withLock {
|
||||
Log.i(TAG, "restoreMasterKeyPreRegistration()", true)
|
||||
|
||||
val operations: List<Pair<SecureValueRecovery, () -> RestoreResponse>> = listOf(
|
||||
svr2 to { restoreMasterKeyPreRegistration(svr2, credentials.svr2, userPin) },
|
||||
svr1 to { restoreMasterKeyPreRegistration(svr1, credentials.svr1, userPin) }
|
||||
)
|
||||
|
||||
for ((implementation, operation) in operations) {
|
||||
when (val response: RestoreResponse = operation()) {
|
||||
is RestoreResponse.Success -> {
|
||||
Log.i(TAG, "[restoreMasterKeyPreRegistration] Successfully restored master key. $implementation", true)
|
||||
if (implementation == svr2) {
|
||||
SignalStore.svr().appendAuthTokenToList(response.authorization.asBasic())
|
||||
}
|
||||
|
||||
return response.masterKey
|
||||
}
|
||||
|
||||
is RestoreResponse.PinMismatch -> {
|
||||
Log.i(TAG, "[restoreMasterKeyPreRegistration] Incorrect PIN. $implementation", true)
|
||||
throw SvrWrongPinException(response.triesRemaining)
|
||||
}
|
||||
|
||||
is RestoreResponse.NetworkError -> {
|
||||
Log.i(TAG, "[restoreMasterKeyPreRegistration] Network error. $implementation", response.exception, true)
|
||||
throw response.exception
|
||||
}
|
||||
|
||||
is RestoreResponse.ApplicationError -> {
|
||||
Log.i(TAG, "[restoreMasterKeyPreRegistration] Application error. $implementation", response.exception, true)
|
||||
throw IOException(response.exception)
|
||||
}
|
||||
|
||||
RestoreResponse.Missing -> {
|
||||
Log.w(TAG, "[restoreMasterKeyPreRegistration] No data found for $implementation | Continuing to next implementation.", true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.w(TAG, "[restoreMasterKeyPreRegistration] No data found for any implementation!", true)
|
||||
|
||||
throw SvrNoDataException()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the master key from the first available SVR implementation available.
|
||||
*
|
||||
* This is intended to be called after the user has registered, allowing the function
|
||||
* to fetch credentials on its own.
|
||||
*/
|
||||
@WorkerThread
|
||||
fun restoreMasterKeyPostRegistration(userPin: String, pinKeyboardType: PinKeyboardType): RestoreResponse {
|
||||
val stopwatch = Stopwatch("pin-submission")
|
||||
|
||||
operationLock.withLock {
|
||||
for (implementation in implementations) {
|
||||
when (val response: RestoreResponse = implementation.restoreDataPostRegistration(userPin)) {
|
||||
is RestoreResponse.Success -> {
|
||||
Log.i(TAG, "[restoreMasterKeyPostRegistration] Successfully restored master key. $implementation", true)
|
||||
stopwatch.split("restore")
|
||||
|
||||
SignalStore.svr().setMasterKey(response.masterKey, userPin)
|
||||
SignalStore.svr().isRegistrationLockEnabled = false
|
||||
SignalStore.pinValues().resetPinReminders()
|
||||
SignalStore.svr().isPinForgottenOrSkipped = false
|
||||
SignalStore.storageService().setNeedsAccountRestore(false)
|
||||
SignalStore.pinValues().keyboardType = pinKeyboardType
|
||||
SignalStore.storageService().setNeedsAccountRestore(false)
|
||||
if (implementation == svr2) {
|
||||
SignalStore.svr().appendAuthTokenToList(response.authorization.asBasic())
|
||||
}
|
||||
ApplicationDependencies.getJobManager().add(ResetSvrGuessCountJob())
|
||||
stopwatch.split("metadata")
|
||||
|
||||
ApplicationDependencies.getJobManager().runSynchronously(StorageAccountRestoreJob(), StorageAccountRestoreJob.LIFESPAN)
|
||||
stopwatch.split("account-restore")
|
||||
|
||||
ApplicationDependencies
|
||||
.getJobManager()
|
||||
.startChain(StorageSyncJob())
|
||||
.then(NewRegistrationUsernameSyncJob())
|
||||
.enqueueAndBlockUntilCompletion(TimeUnit.SECONDS.toMillis(10))
|
||||
stopwatch.split("contact-restore")
|
||||
|
||||
if (implementation != svr2) {
|
||||
ApplicationDependencies.getJobManager().add(Svr2MirrorJob())
|
||||
}
|
||||
|
||||
stopwatch.stop(TAG)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
is RestoreResponse.PinMismatch -> {
|
||||
Log.i(TAG, "[restoreMasterKeyPostRegistration] Incorrect PIN. $implementation", true)
|
||||
return response
|
||||
}
|
||||
|
||||
is RestoreResponse.NetworkError -> {
|
||||
Log.i(TAG, "[restoreMasterKeyPostRegistration] Network error. $implementation", response.exception, true)
|
||||
return response
|
||||
}
|
||||
|
||||
is RestoreResponse.ApplicationError -> {
|
||||
Log.i(TAG, "[restoreMasterKeyPostRegistration] Application error. $implementation", response.exception, true)
|
||||
return response
|
||||
}
|
||||
|
||||
RestoreResponse.Missing -> {
|
||||
Log.w(TAG, "[restoreMasterKeyPostRegistration] No data found for: $implementation | Continuing to next implementation.", true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.w(TAG, "[restoreMasterKeyPostRegistration] No data found for any implementation!", true)
|
||||
return RestoreResponse.Missing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user's PIN the one specified, updating local stores as necessary.
|
||||
* The resulting Single will not throw an error in any expected case, only if there's a runtime exception.
|
||||
*/
|
||||
@WorkerThread
|
||||
@JvmStatic
|
||||
fun setPin(userPin: String, keyboardType: PinKeyboardType): BackupResponse {
|
||||
return operationLock.withLock {
|
||||
val masterKey: MasterKey = SignalStore.svr().getOrCreateMasterKey()
|
||||
|
||||
val responses: List<BackupResponse> = implementations
|
||||
.filter { it != svr2 || FeatureFlags.svr2() }
|
||||
.map { it.setPin(userPin, masterKey) }
|
||||
.map { it.execute() }
|
||||
|
||||
Log.i(TAG, "[setPin] Responses: $responses", true)
|
||||
|
||||
val error: BackupResponse? = responses.map {
|
||||
when (it) {
|
||||
is BackupResponse.ApplicationError -> it
|
||||
BackupResponse.ExposeFailure -> it
|
||||
is BackupResponse.NetworkError -> it
|
||||
BackupResponse.ServerRejected -> it
|
||||
BackupResponse.EnclaveNotFound -> null
|
||||
is BackupResponse.Success -> null
|
||||
}
|
||||
}.firstOrNull()
|
||||
|
||||
val overallResponse = error
|
||||
?: responses.firstOrNull { it is BackupResponse.Success }
|
||||
?: responses[0]
|
||||
|
||||
if (overallResponse is BackupResponse.Success) {
|
||||
Log.i(TAG, "[setPin] Success!", true)
|
||||
|
||||
SignalStore.svr().setMasterKey(masterKey, userPin)
|
||||
SignalStore.svr().isPinForgottenOrSkipped = false
|
||||
SignalStore.svr().appendAuthTokenToList(overallResponse.authorization.asBasic())
|
||||
|
||||
SignalStore.pinValues().keyboardType = keyboardType
|
||||
SignalStore.pinValues().resetPinReminders()
|
||||
|
||||
ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.PINS_FOR_ALL)
|
||||
|
||||
ApplicationDependencies.getJobManager().add(RefreshAttributesJob())
|
||||
} else {
|
||||
Log.w(TAG, "[setPin] Failed to set PIN! $overallResponse", true)
|
||||
|
||||
if (hasNoRegistrationLock) {
|
||||
SignalStore.svr().onPinCreateFailure()
|
||||
}
|
||||
}
|
||||
|
||||
overallResponse
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked after a user has successfully registered. Ensures all the necessary state is updated.
|
||||
*/
|
||||
@WorkerThread
|
||||
@JvmStatic
|
||||
fun onRegistrationComplete(
|
||||
masterKey: MasterKey?,
|
||||
userPin: String?,
|
||||
hasPinToRestore: Boolean,
|
||||
setRegistrationLockEnabled: Boolean
|
||||
) {
|
||||
Log.i(TAG, "[onRegistrationComplete] Starting", true)
|
||||
operationLock.withLock {
|
||||
if (masterKey == null && userPin != null) {
|
||||
error("If masterKey is present, pin must also be present!")
|
||||
}
|
||||
|
||||
if (masterKey != null && userPin != null) {
|
||||
if (setRegistrationLockEnabled) {
|
||||
Log.i(TAG, "[onRegistrationComplete] Registration Lock", true)
|
||||
SignalStore.svr().isRegistrationLockEnabled = true
|
||||
} else {
|
||||
Log.i(TAG, "[onRegistrationComplete] ReRegistration Skip SMS", true)
|
||||
}
|
||||
|
||||
SignalStore.svr().setMasterKey(masterKey, userPin)
|
||||
SignalStore.pinValues().resetPinReminders()
|
||||
|
||||
ApplicationDependencies.getJobManager().add(ResetSvrGuessCountJob())
|
||||
} else if (hasPinToRestore) {
|
||||
Log.i(TAG, "[onRegistrationComplete] Has a PIN to restore.", true)
|
||||
SignalStore.svr().clearRegistrationLockAndPin()
|
||||
SignalStore.storageService().setNeedsAccountRestore(true)
|
||||
} else {
|
||||
Log.i(TAG, "[onRegistrationComplete] No registration lock or PIN at all.", true)
|
||||
SignalStore.svr().clearRegistrationLockAndPin()
|
||||
}
|
||||
}
|
||||
|
||||
ApplicationDependencies.getJobManager().add(RefreshAttributesJob())
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the user skips out on PIN restoration or otherwise fails to remember their PIN.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun onPinRestoreForgottenOrSkipped() {
|
||||
operationLock.withLock {
|
||||
SignalStore.svr().clearRegistrationLockAndPin()
|
||||
SignalStore.storageService().setNeedsAccountRestore(false)
|
||||
SignalStore.svr().isPinForgottenOrSkipped = true
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@WorkerThread
|
||||
fun optOutOfPin() {
|
||||
operationLock.withLock {
|
||||
SignalStore.svr().optOut()
|
||||
|
||||
ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.PINS_FOR_ALL)
|
||||
|
||||
bestEffortRefreshAttributes()
|
||||
bestEffortForcePushStorage()
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@WorkerThread
|
||||
@Throws(IOException::class)
|
||||
fun enableRegistrationLockForUserWithPin() {
|
||||
operationLock.withLock {
|
||||
check(SignalStore.svr().hasPin() && !SignalStore.svr().hasOptedOut()) { "Must have a PIN to set a registration lock!" }
|
||||
|
||||
Log.i(TAG, "[enableRegistrationLockForUserWithPin] Enabling registration lock.", true)
|
||||
ApplicationDependencies.getSignalServiceAccountManager().enableRegistrationLock(SignalStore.svr().getOrCreateMasterKey())
|
||||
SignalStore.svr().isRegistrationLockEnabled = true
|
||||
Log.i(TAG, "[enableRegistrationLockForUserWithPin] Registration lock successfully enabled.", true)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@WorkerThread
|
||||
@Throws(IOException::class)
|
||||
fun disableRegistrationLockForUserWithPin() {
|
||||
operationLock.withLock {
|
||||
check(SignalStore.svr().hasPin() && !SignalStore.svr().hasOptedOut()) { "Must have a PIN to disable registration lock!" }
|
||||
|
||||
Log.i(TAG, "[disableRegistrationLockForUserWithPin] Disabling registration lock.", true)
|
||||
ApplicationDependencies.getSignalServiceAccountManager().disableRegistrationLock()
|
||||
SignalStore.svr().isRegistrationLockEnabled = false
|
||||
Log.i(TAG, "[disableRegistrationLockForUserWithPin] Registration lock successfully disabled.", true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches new SVR credentials and persists them in the backup store to be used during re-registration.
|
||||
*/
|
||||
@WorkerThread
|
||||
@Throws(IOException::class)
|
||||
fun refreshAndStoreAuthorization() {
|
||||
try {
|
||||
val credentials: AuthCredentials = svr2.authorization()
|
||||
backupSvrCredentials(credentials)
|
||||
} catch (e: Throwable) {
|
||||
if (e is IOException) {
|
||||
throw e
|
||||
} else {
|
||||
throw IOException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun restoreMasterKeyPreRegistration(svr: SecureValueRecovery, credentials: AuthCredentials?, userPin: String): RestoreResponse {
|
||||
return if (credentials == null) {
|
||||
RestoreResponse.Missing
|
||||
} else {
|
||||
svr.restoreDataPreRegistration(credentials, userPin)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun bestEffortRefreshAttributes() {
|
||||
val result = ApplicationDependencies.getJobManager().runSynchronously(RefreshAttributesJob(), TimeUnit.SECONDS.toMillis(10))
|
||||
if (result.isPresent && result.get() == JobTracker.JobState.SUCCESS) {
|
||||
Log.i(TAG, "Attributes were refreshed successfully.", true)
|
||||
} else if (result.isPresent) {
|
||||
Log.w(TAG, "Attribute refresh finished, but was not successful. Enqueuing one for later. (Result: " + result.get() + ")", true)
|
||||
ApplicationDependencies.getJobManager().add(RefreshAttributesJob())
|
||||
} else {
|
||||
Log.w(TAG, "Job did not finish in the allotted time. It'll finish later.", true)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun bestEffortForcePushStorage() {
|
||||
val result = ApplicationDependencies.getJobManager().runSynchronously(StorageForcePushJob(), TimeUnit.SECONDS.toMillis(10))
|
||||
if (result.isPresent && result.get() == JobTracker.JobState.SUCCESS) {
|
||||
Log.i(TAG, "Storage was force-pushed successfully.", true)
|
||||
} else if (result.isPresent) {
|
||||
Log.w(TAG, "Storage force-pushed finished, but was not successful. Enqueuing one for later. (Result: " + result.get() + ")", true)
|
||||
ApplicationDependencies.getJobManager().add(RefreshAttributesJob())
|
||||
} else {
|
||||
Log.w(TAG, "Storage fore push did not finish in the allotted time. It'll finish later.", true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun backupSvrCredentials(credentials: AuthCredentials) {
|
||||
val tokenIsNew = SignalStore.svr().appendAuthTokenToList(credentials.asBasic())
|
||||
|
||||
if (tokenIsNew && SignalStore.svr().hasPin()) {
|
||||
BackupManager(ApplicationDependencies.getApplication()).dataChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private val hasNoRegistrationLock: Boolean
|
||||
get() {
|
||||
return !SignalStore.svr().isRegistrationLockEnabled &&
|
||||
!SignalStore.svr().hasPin() &&
|
||||
!SignalStore.svr().hasOptedOut()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.thoughtcrime.securesms.pin;
|
||||
|
||||
public final class SvrWrongPinException extends Exception {
|
||||
|
||||
private final int triesRemaining;
|
||||
|
||||
public SvrWrongPinException(int triesRemaining){
|
||||
this.triesRemaining = triesRemaining;
|
||||
}
|
||||
|
||||
public int getTriesRemaining() {
|
||||
return triesRemaining;
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package org.thoughtcrime.securesms.pin;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.KbsEnclave;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
|
||||
public class TokenData implements Parcelable {
|
||||
private final KbsEnclave enclave;
|
||||
private final String basicAuth;
|
||||
private final TokenResponse tokenResponse;
|
||||
|
||||
TokenData(@NonNull KbsEnclave enclave, @NonNull String basicAuth, @NonNull TokenResponse tokenResponse) {
|
||||
this.enclave = enclave;
|
||||
this.basicAuth = basicAuth;
|
||||
this.tokenResponse = tokenResponse;
|
||||
}
|
||||
|
||||
private TokenData(Parcel in) {
|
||||
this.enclave = new KbsEnclave(in.readString(), in.readString(), in.readString());
|
||||
this.basicAuth = in.readString();
|
||||
|
||||
byte[] backupId = in.createByteArray();
|
||||
byte[] token = in.createByteArray();
|
||||
|
||||
this.tokenResponse = new TokenResponse(backupId, token, in.readInt());
|
||||
}
|
||||
|
||||
public static @NonNull TokenData withResponse(@NonNull TokenData data, @NonNull TokenResponse response) {
|
||||
return new TokenData(data.getEnclave(), data.getBasicAuth(), response);
|
||||
}
|
||||
|
||||
public int getTriesRemaining() {
|
||||
return tokenResponse.getTries();
|
||||
}
|
||||
|
||||
public @NonNull String getBasicAuth() {
|
||||
return basicAuth;
|
||||
}
|
||||
|
||||
public @NonNull TokenResponse getTokenResponse() {
|
||||
return tokenResponse;
|
||||
}
|
||||
|
||||
public @NonNull KbsEnclave getEnclave() {
|
||||
return enclave;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(enclave.getEnclaveName());
|
||||
dest.writeString(enclave.getServiceId());
|
||||
dest.writeString(enclave.getMrEnclave());
|
||||
|
||||
dest.writeString(basicAuth);
|
||||
|
||||
dest.writeByteArray(tokenResponse.getBackupId());
|
||||
dest.writeByteArray(tokenResponse.getToken());
|
||||
dest.writeInt(tokenResponse.getTries());
|
||||
}
|
||||
|
||||
public static final Creator<TokenData> CREATOR = new Creator<TokenData>() {
|
||||
@Override
|
||||
public TokenData createFromParcel(Parcel in) {
|
||||
return new TokenData(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TokenData[] newArray(int size) {
|
||||
return new TokenData[size];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
@@ -12,11 +11,10 @@ import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity;
|
||||
import org.thoughtcrime.securesms.payments.backup.PaymentsRecoveryStartFragmentArgs;
|
||||
import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity;
|
||||
import org.thoughtcrime.securesms.pin.PinOptOutDialog;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
public class AdvancedPinPreferenceFragment extends ListSummaryPreferenceFragment {
|
||||
|
||||
@@ -41,7 +39,7 @@ public class AdvancedPinPreferenceFragment extends ListSummaryPreferenceFragment
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
if (requestCode == CreateKbsPinActivity.REQUEST_NEW_PIN && resultCode == CreateKbsPinActivity.RESULT_OK) {
|
||||
if (requestCode == CreateSvrPinActivity.REQUEST_NEW_PIN && resultCode == CreateSvrPinActivity.RESULT_OK) {
|
||||
Snackbar.make(requireView(), R.string.ApplicationPreferencesActivity_pin_created, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
@@ -50,7 +48,7 @@ public class AdvancedPinPreferenceFragment extends ListSummaryPreferenceFragment
|
||||
Preference enable = this.findPreference(PREF_ENABLE);
|
||||
Preference disable = this.findPreference(PREF_DISABLE);
|
||||
|
||||
if (SignalStore.kbsValues().hasOptedOut()) {
|
||||
if (SignalStore.svr().hasOptedOut()) {
|
||||
enable.setVisible(true);
|
||||
disable.setVisible(false);
|
||||
|
||||
@@ -70,8 +68,7 @@ public class AdvancedPinPreferenceFragment extends ListSummaryPreferenceFragment
|
||||
}
|
||||
|
||||
private void onPreferenceChanged(boolean enabled) {
|
||||
boolean hasRegistrationLock = TextSecurePreferences.isV1RegistrationLockEnabled(requireContext()) ||
|
||||
SignalStore.kbsValues().isV2RegistrationLockEnabled();
|
||||
boolean hasRegistrationLock = SignalStore.svr().isRegistrationLockEnabled();
|
||||
|
||||
if (!enabled && hasRegistrationLock) {
|
||||
new MaterialAlertDialogBuilder(requireContext())
|
||||
@@ -102,7 +99,7 @@ public class AdvancedPinPreferenceFragment extends ListSummaryPreferenceFragment
|
||||
Snackbar.make(requireView(), R.string.ApplicationPreferencesActivity_pin_disabled, Snackbar.LENGTH_SHORT).show();
|
||||
});
|
||||
} else {
|
||||
startActivityForResult(CreateKbsPinActivity.getIntentForPinCreate(requireContext()), CreateKbsPinActivity.REQUEST_NEW_PIN);
|
||||
startActivityForResult(CreateSvrPinActivity.getIntentForPinCreate(requireContext()), CreateSvrPinActivity.REQUEST_NEW_PIN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.thoughtcrime.securesms.jobs.PreKeysSyncJob;
|
||||
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationIds;
|
||||
import org.thoughtcrime.securesms.pin.PinState;
|
||||
import org.thoughtcrime.securesms.pin.SvrRepository;
|
||||
import org.thoughtcrime.securesms.push.AccountManagerFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
@@ -102,13 +102,8 @@ public final class RegistrationRepository {
|
||||
{
|
||||
return Single.<ServiceResponse<VerifyResponse>>fromCallable(() -> {
|
||||
try {
|
||||
String pin = response.getPin();
|
||||
registerAccountInternal(registrationData, response, setRegistrationLockEnabled);
|
||||
|
||||
if (pin != null && !pin.isEmpty()) {
|
||||
PinState.onPinChangedOrCreated(context, pin, SignalStore.pinValues().getKeyboardType());
|
||||
}
|
||||
|
||||
JobManager jobManager = ApplicationDependencies.getJobManager();
|
||||
jobManager.add(new DirectoryRefreshJob(false));
|
||||
jobManager.add(new RotateCertificateJob());
|
||||
@@ -176,7 +171,7 @@ public final class RegistrationRepository {
|
||||
TextSecurePreferences.setUnauthorizedReceived(context, false);
|
||||
NotificationManagerCompat.from(context).cancel(NotificationIds.UNREGISTERED_NOTIFICATION_ID);
|
||||
|
||||
PinState.onRegistration(context, response.getKbsData(), response.getPin(), hasPin, setRegistrationLockEnabled);
|
||||
SvrRepository.onRegistrationComplete(response.getMasterKey(), response.getPin(), hasPin, setRegistrationLockEnabled);
|
||||
|
||||
ApplicationDependencies.closeConnections();
|
||||
ApplicationDependencies.getIncomingMessageObserver();
|
||||
@@ -226,13 +221,13 @@ public final class RegistrationRepository {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Single<BackupAuthCheckProcessor> getKbsAuthCredential(@NonNull RegistrationData registrationData, List<String> usernamePasswords) {
|
||||
public Single<BackupAuthCheckProcessor> getSvrAuthCredential(@NonNull RegistrationData registrationData, List<String> usernamePasswords) {
|
||||
SignalServiceAccountManager accountManager = AccountManagerFactory.getInstance().createUnauthenticated(context, registrationData.getE164(), SignalServiceAddress.DEFAULT_DEVICE_ID, registrationData.getPassword());
|
||||
|
||||
return accountManager.checkBackupAuthCredentials(registrationData.getE164(), usernamePasswords)
|
||||
.map(BackupAuthCheckProcessor::new)
|
||||
.doOnSuccess(processor -> {
|
||||
if (SignalStore.kbsValues().removeAuthTokens(processor.getInvalid())) {
|
||||
if (SignalStore.svr().removeAuthTokens(processor.getInvalid())) {
|
||||
new BackupManager(context).dataChanged();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ public final class RegistrationUtil {
|
||||
if (!SignalStore.registrationValues().isRegistrationComplete() &&
|
||||
SignalStore.account().isRegistered() &&
|
||||
!Recipient.self().getProfileName().isEmpty() &&
|
||||
(SignalStore.kbsValues().hasPin() || SignalStore.kbsValues().hasOptedOut()))
|
||||
(SignalStore.svr().hasPin() || SignalStore.svr().hasOptedOut()))
|
||||
{
|
||||
Log.i(TAG, "Marking registration completed.", new Throwable());
|
||||
SignalStore.registrationValues().setRegistrationComplete();
|
||||
|
||||
@@ -10,15 +10,15 @@ import org.signal.libsignal.protocol.IdentityKeyPair
|
||||
import org.thoughtcrime.securesms.AppCapabilities
|
||||
import org.thoughtcrime.securesms.gcm.FcmUtil
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.pin.KeyBackupSystemWrongPinException
|
||||
import org.thoughtcrime.securesms.pin.SvrWrongPinException
|
||||
import org.thoughtcrime.securesms.push.AccountManagerFactory
|
||||
import org.thoughtcrime.securesms.registration.PushChallengeRequest.PushChallengeEvent
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.KbsPinData
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager
|
||||
import org.whispersystems.signalservice.api.SvrNoDataException
|
||||
import org.whispersystems.signalservice.api.account.AccountAttributes
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse
|
||||
@@ -157,7 +157,7 @@ class VerifyAccountRepository(private val context: Application) {
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun registerAccount(sessionId: String?, registrationData: RegistrationData, pin: String? = null, kbsPinDataProducer: KbsPinDataProducer? = null): Single<ServiceResponse<VerifyResponse>> {
|
||||
fun registerAccount(sessionId: String?, registrationData: RegistrationData, pin: String? = null, masterKeyProducer: MasterKeyProducer? = null): Single<ServiceResponse<VerifyResponse>> {
|
||||
val universalUnidentifiedAccess: Boolean = TextSecurePreferences.isUniversalUnidentifiedAccess(context)
|
||||
val unidentifiedAccessKey: ByteArray = UnidentifiedAccess.deriveAccessKeyFrom(registrationData.profileKey)
|
||||
|
||||
@@ -168,15 +168,14 @@ class VerifyAccountRepository(private val context: Application) {
|
||||
registrationData.password
|
||||
)
|
||||
|
||||
val kbsData = kbsPinDataProducer?.produceKbsPinData()
|
||||
val registrationLockV2: String? = kbsData?.masterKey?.deriveRegistrationLock()
|
||||
val masterKey: MasterKey? = masterKeyProducer?.produceMasterKey()
|
||||
val registrationLock: String? = masterKey?.deriveRegistrationLock()
|
||||
|
||||
val accountAttributes = AccountAttributes(
|
||||
signalingKey = null,
|
||||
registrationId = registrationData.registrationId,
|
||||
fetchesMessages = registrationData.isNotFcm,
|
||||
pin = pin,
|
||||
registrationLock = registrationLockV2,
|
||||
registrationLock = registrationLock,
|
||||
unidentifiedAccessKey = unidentifiedAccessKey,
|
||||
unrestrictedUnidentifiedAccess = universalUnidentifiedAccess,
|
||||
capabilities = AppCapabilities.getCapabilities(true),
|
||||
@@ -197,7 +196,7 @@ class VerifyAccountRepository(private val context: Application) {
|
||||
|
||||
return Single.fromCallable {
|
||||
val response = accountManager.registerAccount(sessionId, registrationData.recoveryPassword, accountAttributes, aciPreKeyCollection, pniPreKeyCollection, registrationData.fcmToken, true)
|
||||
VerifyResponse.from(response, kbsData, pin, aciPreKeyCollection, pniPreKeyCollection)
|
||||
VerifyResponse.from(response, masterKey, pin, aciPreKeyCollection, pniPreKeyCollection)
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
@@ -207,9 +206,9 @@ class VerifyAccountRepository(private val context: Application) {
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
interface KbsPinDataProducer {
|
||||
@Throws(IOException::class, KeyBackupSystemWrongPinException::class, KeyBackupSystemNoDataException::class)
|
||||
fun produceKbsPinData(): KbsPinData
|
||||
interface MasterKeyProducer {
|
||||
@Throws(IOException::class, SvrWrongPinException::class, SvrNoDataException::class)
|
||||
fun produceMasterKey(): MasterKey
|
||||
}
|
||||
|
||||
enum class Mode(val isSmsRetrieverSupported: Boolean) {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package org.thoughtcrime.securesms.registration
|
||||
|
||||
import org.whispersystems.signalservice.api.KbsPinData
|
||||
import org.whispersystems.signalservice.api.account.PreKeyCollection
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse
|
||||
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
|
||||
|
||||
data class VerifyResponse(
|
||||
val verifyAccountResponse: VerifyAccountResponse,
|
||||
val kbsData: KbsPinData?,
|
||||
val masterKey: MasterKey?,
|
||||
val pin: String?,
|
||||
val aciPreKeyCollection: PreKeyCollection?,
|
||||
val pniPreKeyCollection: PreKeyCollection?
|
||||
@@ -15,13 +15,13 @@ data class VerifyResponse(
|
||||
companion object {
|
||||
fun from(
|
||||
response: ServiceResponse<VerifyAccountResponse>,
|
||||
kbsData: KbsPinData?,
|
||||
masterKey: MasterKey?,
|
||||
pin: String?,
|
||||
aciPreKeyCollection: PreKeyCollection?,
|
||||
pniPreKeyCollection: PreKeyCollection?
|
||||
): ServiceResponse<VerifyResponse> {
|
||||
return if (response.result.isPresent) {
|
||||
ServiceResponse.forResult(VerifyResponse(response.result.get(), kbsData, pin, aciPreKeyCollection, pniPreKeyCollection), 200, null)
|
||||
ServiceResponse.forResult(VerifyResponse(response.result.get(), masterKey, pin, aciPreKeyCollection, pniPreKeyCollection), 200, null)
|
||||
} else {
|
||||
ServiceResponse.coerceError(response)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package org.thoughtcrime.securesms.registration
|
||||
|
||||
import org.thoughtcrime.securesms.pin.KeyBackupSystemWrongPinException
|
||||
import org.thoughtcrime.securesms.pin.TokenData
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException
|
||||
import org.thoughtcrime.securesms.pin.SvrWrongPinException
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.SvrAuthCredentialSet
|
||||
import org.whispersystems.signalservice.api.SvrNoDataException
|
||||
import org.whispersystems.signalservice.api.push.exceptions.IncorrectRegistrationRecoveryPasswordException
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse
|
||||
import org.whispersystems.signalservice.internal.ServiceResponseProcessor
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse
|
||||
import org.whispersystems.signalservice.internal.push.LockedException
|
||||
|
||||
/**
|
||||
@@ -16,7 +14,19 @@ import org.whispersystems.signalservice.internal.push.LockedException
|
||||
*/
|
||||
sealed class VerifyResponseProcessor(response: ServiceResponse<VerifyResponse>) : ServiceResponseProcessor<VerifyResponse>(response) {
|
||||
|
||||
open val tokenData: TokenData? = null
|
||||
open val svrTriesRemaining: Int?
|
||||
get() = (error as? SvrWrongPinException)?.triesRemaining
|
||||
|
||||
open val svrAuthCredentials: SvrAuthCredentialSet?
|
||||
get() {
|
||||
return error?.let {
|
||||
if (it is LockedException) {
|
||||
SvrAuthCredentialSet(it.svr1Credentials, it.svr2Credentials)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override fun authorizationFailed(): Boolean {
|
||||
return super.authorizationFailed()
|
||||
@@ -34,10 +44,6 @@ sealed class VerifyResponseProcessor(response: ServiceResponse<VerifyResponse>)
|
||||
return super.getError()
|
||||
}
|
||||
|
||||
fun invalidSession(): Boolean {
|
||||
return error is NoSuchSessionException
|
||||
}
|
||||
|
||||
fun getLockedException(): LockedException {
|
||||
return error as LockedException
|
||||
}
|
||||
@@ -50,34 +56,24 @@ sealed class VerifyResponseProcessor(response: ServiceResponse<VerifyResponse>)
|
||||
return error is IncorrectRegistrationRecoveryPasswordException
|
||||
}
|
||||
|
||||
abstract fun isKbsLocked(): Boolean
|
||||
/** True if the account has reglock enabled but all guesses have been exhausted, otherwise false. */
|
||||
abstract fun isRegistrationLockPresentAndSvrExhausted(): Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify processor specific to verifying without needing to handle registration lock.
|
||||
*/
|
||||
class VerifyResponseWithoutKbs(response: ServiceResponse<VerifyResponse>) : VerifyResponseProcessor(response) {
|
||||
override fun isKbsLocked(): Boolean {
|
||||
return registrationLock() && getLockedException().basicStorageCredentials == null
|
||||
override fun isRegistrationLockPresentAndSvrExhausted(): Boolean {
|
||||
return registrationLock() && getLockedException().svr1Credentials == null && getLockedException().svr2Credentials == null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify processor specific to verifying and successfully retrieving KBS information to
|
||||
* later attempt to verif with registration lock data (pin).
|
||||
* Verify processor indicating we cannot register until registration lock has been resolved.
|
||||
*/
|
||||
class VerifyResponseWithSuccessfulKbs(response: ServiceResponse<VerifyResponse>, override val tokenData: TokenData) : VerifyResponseProcessor(response) {
|
||||
override fun isKbsLocked(): Boolean {
|
||||
return registrationLock() && tokenData.triesRemaining == 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify processor specific to verifying and unsuccessfully retrieving KBS information that
|
||||
* is required for attempting to verify a registration locked account.
|
||||
*/
|
||||
class VerifyResponseWithFailedKbs(response: ServiceResponse<TokenData>) : VerifyResponseProcessor(ServiceResponse.coerceError(response)) {
|
||||
override fun isKbsLocked(): Boolean {
|
||||
class VerifyResponseHitRegistrationLock(response: ServiceResponse<VerifyResponse>) : VerifyResponseProcessor(response) {
|
||||
override fun isRegistrationLockPresentAndSvrExhausted(): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -86,18 +82,14 @@ class VerifyResponseWithFailedKbs(response: ServiceResponse<TokenData>) : Verify
|
||||
* Process responses from attempting to verify an account with registration lock for use in
|
||||
* account registration.
|
||||
*/
|
||||
class VerifyResponseWithRegistrationLockProcessor(response: ServiceResponse<VerifyResponse>, override val tokenData: TokenData?) : VerifyResponseProcessor(response) {
|
||||
class VerifyResponseWithRegistrationLockProcessor(response: ServiceResponse<VerifyResponse>, override val svrAuthCredentials: SvrAuthCredentialSet?) : VerifyResponseProcessor(response) {
|
||||
|
||||
fun wrongPin(): Boolean {
|
||||
return error is KeyBackupSystemWrongPinException
|
||||
return error is SvrWrongPinException
|
||||
}
|
||||
|
||||
fun getTokenResponse(): TokenResponse {
|
||||
return (error as KeyBackupSystemWrongPinException).tokenResponse
|
||||
}
|
||||
|
||||
override fun isKbsLocked(): Boolean {
|
||||
return error is KeyBackupSystemNoDataException
|
||||
override fun isRegistrationLockPresentAndSvrExhausted(): Boolean {
|
||||
return error is SvrNoDataException
|
||||
}
|
||||
|
||||
fun updatedIfRegistrationFailed(response: ServiceResponse<VerifyResponse>): VerifyResponseWithRegistrationLockProcessor {
|
||||
@@ -105,12 +97,12 @@ class VerifyResponseWithRegistrationLockProcessor(response: ServiceResponse<Veri
|
||||
return this
|
||||
}
|
||||
|
||||
return VerifyResponseWithRegistrationLockProcessor(ServiceResponse.coerceError(response), tokenData)
|
||||
return VerifyResponseWithRegistrationLockProcessor(ServiceResponse.coerceError(response), svrAuthCredentials)
|
||||
}
|
||||
|
||||
override fun isServerSentError(): Boolean {
|
||||
return super.isServerSentError() ||
|
||||
error is KeyBackupSystemWrongPinException ||
|
||||
error is KeyBackupSystemNoDataException
|
||||
error is SvrWrongPinException ||
|
||||
error is SvrNoDataException
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,11 +172,9 @@ public abstract class BaseEnterSmsCodeFragment<ViewModel extends BaseRegistratio
|
||||
handleSuccessfulVerify();
|
||||
} else if (processor.rateLimit()) {
|
||||
handleRateLimited();
|
||||
} else if (processor.registrationLock() && !processor.isKbsLocked()) {
|
||||
} else if (processor.registrationLock() && !processor.isRegistrationLockPresentAndSvrExhausted()) {
|
||||
LockedException lockedException = processor.getLockedException();
|
||||
handleRegistrationLock(lockedException.getTimeRemaining());
|
||||
} else if (processor.isKbsLocked()) {
|
||||
handleKbsAccountLocked();
|
||||
} else if (processor.authorizationFailed()) {
|
||||
handleIncorrectCodeError();
|
||||
} else {
|
||||
@@ -227,7 +225,7 @@ public abstract class BaseEnterSmsCodeFragment<ViewModel extends BaseRegistratio
|
||||
});
|
||||
}
|
||||
|
||||
protected void handleKbsAccountLocked() {
|
||||
protected void handleSvrAccountLocked() {
|
||||
navigateToKbsAccountLocked();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,12 +21,12 @@ import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType;
|
||||
import org.thoughtcrime.securesms.pin.TokenData;
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.BaseRegistrationViewModel;
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
@@ -114,10 +114,9 @@ public abstract class BaseRegistrationLockFragment extends LoggingFragment {
|
||||
viewModel.getLockedTimeRemaining()
|
||||
.observe(getViewLifecycleOwner(), t -> timeRemaining = t);
|
||||
|
||||
TokenData keyBackupCurrentToken = viewModel.getKeyBackupCurrentToken();
|
||||
Integer triesRemaining = viewModel.getSvrTriesRemaining();
|
||||
|
||||
if (keyBackupCurrentToken != null) {
|
||||
int triesRemaining = keyBackupCurrentToken.getTriesRemaining();
|
||||
if (triesRemaining != null) {
|
||||
if (triesRemaining <= 3) {
|
||||
int daysRemaining = getLockoutDays(timeRemaining);
|
||||
|
||||
@@ -177,8 +176,8 @@ public abstract class BaseRegistrationLockFragment extends LoggingFragment {
|
||||
if (processor.hasResult()) {
|
||||
handleSuccessfulPinEntry(pin);
|
||||
} else if (processor.wrongPin()) {
|
||||
onIncorrectKbsRegistrationLockPin(processor.getTokenData());
|
||||
} else if (processor.isKbsLocked() || processor.registrationLock()) {
|
||||
onIncorrectKbsRegistrationLockPin(Objects.requireNonNull(processor.getSvrTriesRemaining()));
|
||||
} else if (processor.isRegistrationLockPresentAndSvrExhausted() || processor.registrationLock()) {
|
||||
onKbsAccountLocked();
|
||||
} else if (processor.rateLimit()) {
|
||||
onRateLimited();
|
||||
@@ -191,35 +190,33 @@ public abstract class BaseRegistrationLockFragment extends LoggingFragment {
|
||||
disposables.add(verify);
|
||||
}
|
||||
|
||||
public void onIncorrectKbsRegistrationLockPin(@NonNull TokenData tokenData) {
|
||||
public void onIncorrectKbsRegistrationLockPin(int svrTriesRemaining) {
|
||||
pinButton.cancelSpinning();
|
||||
pinEntry.getText().clear();
|
||||
enableAndFocusPinEntry();
|
||||
|
||||
viewModel.setKeyBackupTokenData(tokenData);
|
||||
viewModel.setSvrTriesRemaining(svrTriesRemaining);
|
||||
|
||||
int triesRemaining = tokenData.getTriesRemaining();
|
||||
|
||||
if (triesRemaining == 0) {
|
||||
if (svrTriesRemaining == 0) {
|
||||
Log.w(TAG, "Account locked. User out of attempts on KBS.");
|
||||
onAccountLocked();
|
||||
return;
|
||||
}
|
||||
|
||||
if (triesRemaining == 3) {
|
||||
if (svrTriesRemaining == 3) {
|
||||
int daysRemaining = getLockoutDays(timeRemaining);
|
||||
|
||||
new MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.RegistrationLockFragment__incorrect_pin)
|
||||
.setMessage(getTriesRemainingDialogMessage(triesRemaining, daysRemaining))
|
||||
.setMessage(getTriesRemainingDialogMessage(svrTriesRemaining, daysRemaining))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
if (triesRemaining > 5) {
|
||||
if (svrTriesRemaining > 5) {
|
||||
errorLabel.setText(R.string.RegistrationLockFragment__incorrect_pin_try_again);
|
||||
} else {
|
||||
errorLabel.setText(requireContext().getResources().getQuantityString(R.plurals.RegistrationLockFragment__incorrect_pin_d_attempts_remaining, triesRemaining, triesRemaining));
|
||||
errorLabel.setText(requireContext().getResources().getQuantityString(R.plurals.RegistrationLockFragment__incorrect_pin_d_attempts_remaining, svrTriesRemaining, svrTriesRemaining));
|
||||
forgotPin.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,7 +470,6 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R
|
||||
: R.string.RegistrationActivity_a_verification_code_will_be_sent_to_this_number,
|
||||
e164number,
|
||||
() -> {
|
||||
exitInProgressUiState();
|
||||
ViewUtil.hideKeyboard(context, number.getEditText());
|
||||
onConfirmed.run();
|
||||
},
|
||||
|
||||
@@ -14,8 +14,8 @@ import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.LoggingFragment
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.databinding.PinRestoreEntryFragmentBinding
|
||||
import org.thoughtcrime.securesms.lock.v2.KbsConstants
|
||||
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType
|
||||
import org.thoughtcrime.securesms.lock.v2.SvrConstants
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponseWithRegistrationLockProcessor
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.ReRegisterWithPinViewModel
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel
|
||||
@@ -80,7 +80,7 @@ class ReRegisterWithPinFragment : LoggingFragment(R.layout.pin_restore_entry_fra
|
||||
|
||||
binding.pinRestoreKeyboardToggle.setIconResource(getPinEntryKeyboardType().other.iconResource)
|
||||
|
||||
reRegisterViewModel.updateTokenData(registrationViewModel.keyBackupCurrentToken)
|
||||
reRegisterViewModel.updateSvrTriesRemaining(registrationViewModel.svrTriesRemaining)
|
||||
|
||||
disposables += reRegisterViewModel.triesRemaining.subscribe(this::updateTriesRemaining)
|
||||
}
|
||||
@@ -93,7 +93,7 @@ class ReRegisterWithPinFragment : LoggingFragment(R.layout.pin_restore_entry_fra
|
||||
private fun handlePinEntry() {
|
||||
val pin: String? = binding.pinRestorePinInput.text?.toString()
|
||||
|
||||
val trimmedLength = pin?.replace(" ", "")?.length ?: 0
|
||||
val trimmedLength = pin?.trim()?.length ?: 0
|
||||
if (trimmedLength == 0) {
|
||||
Toast.makeText(requireContext(), R.string.RegistrationActivity_you_must_enter_your_registration_lock_PIN, Toast.LENGTH_LONG).show()
|
||||
enableAndFocusPinEntry()
|
||||
@@ -126,12 +126,12 @@ class ReRegisterWithPinFragment : LoggingFragment(R.layout.pin_restore_entry_fra
|
||||
reRegisterViewModel.hasIncorrectGuess = true
|
||||
|
||||
if (processor is VerifyResponseWithRegistrationLockProcessor && processor.wrongPin()) {
|
||||
reRegisterViewModel.updateTokenData(processor.tokenData)
|
||||
if (processor.tokenData != null) {
|
||||
registrationViewModel.setKeyBackupTokenData(processor.tokenData)
|
||||
reRegisterViewModel.updateSvrTriesRemaining(processor.svrTriesRemaining)
|
||||
if (processor.svrTriesRemaining != null) {
|
||||
registrationViewModel.svrTriesRemaining = processor.svrTriesRemaining
|
||||
}
|
||||
return@subscribe
|
||||
} else if (processor.isKbsLocked()) {
|
||||
} else if (processor.isRegistrationLockPresentAndSvrExhausted()) {
|
||||
Log.w(TAG, "Unable to continue skip flow, KBS is locked")
|
||||
onAccountLocked()
|
||||
} else if (processor.isIncorrectRegistrationRecoveryPassword()) {
|
||||
@@ -215,7 +215,7 @@ class ReRegisterWithPinFragment : LoggingFragment(R.layout.pin_restore_entry_fra
|
||||
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.PinRestoreEntryFragment_need_help)
|
||||
.setMessage(getString(message, KbsConstants.MINIMUM_PIN_LENGTH))
|
||||
.setMessage(getString(message, SvrConstants.MINIMUM_PIN_LENGTH))
|
||||
.setPositiveButton(R.string.PinRestoreEntryFragment_skip) { _, _ -> onSkipPinEntry() }
|
||||
.setNeutralButton(R.string.PinRestoreEntryFragment_contact_support) { _, _ ->
|
||||
val body = SupportEmailUtil.generateSupportEmailBody(requireContext(), R.string.ReRegisterWithPinFragment_support_email_subject, null, null)
|
||||
|
||||
@@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceProfileContentUpdateJob
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob
|
||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity
|
||||
import org.thoughtcrime.securesms.pin.PinRestoreActivity
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper
|
||||
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity
|
||||
@@ -51,7 +51,7 @@ class RegistrationCompleteFragment : LoggingFragment() {
|
||||
val isProfileNameEmpty = Recipient.self().profileName.isEmpty
|
||||
val isAvatarEmpty = !AvatarHelper.hasAvatar(activity, Recipient.self().id)
|
||||
val needsProfile = isProfileNameEmpty || isAvatarEmpty
|
||||
val needsPin = !SignalStore.kbsValues().hasPin() && !viewModel.isReregister
|
||||
val needsPin = !SignalStore.svr().hasPin() && !viewModel.isReregister
|
||||
|
||||
Log.i(TAG, "Pin restore flow not required. Profile name: $isProfileNameEmpty | Profile avatar: $isAvatarEmpty | Needs PIN: $needsPin")
|
||||
|
||||
@@ -66,7 +66,7 @@ class RegistrationCompleteFragment : LoggingFragment() {
|
||||
var startIntent = MainActivity.clearTop(activity)
|
||||
|
||||
if (needsPin) {
|
||||
startIntent = chainIntents(CreateKbsPinActivity.getIntentForPinCreate(activity), startIntent)
|
||||
startIntent = chainIntents(CreateSvrPinActivity.getIntentForPinCreate(activity), startIntent)
|
||||
}
|
||||
|
||||
if (needsProfile) {
|
||||
|
||||
@@ -58,7 +58,7 @@ object RegistrationViewDelegate {
|
||||
setMessage(message)
|
||||
setPositiveButton(android.R.string.ok) { _, _ -> onConfirmed.run() }
|
||||
setNegativeButton(R.string.RegistrationActivity_edit_number) { _, _ -> onEditNumber.run() }
|
||||
setOnDismissListener { onEditNumber.run() }
|
||||
setOnCancelListener { onEditNumber.run() }
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,16 +12,13 @@ import com.google.i18n.phonenumbers.Phonenumber;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.pin.KbsRepository;
|
||||
import org.thoughtcrime.securesms.pin.TokenData;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationSessionProcessor;
|
||||
import org.thoughtcrime.securesms.registration.VerifyAccountRepository;
|
||||
import org.thoughtcrime.securesms.registration.VerifyAccountRepository.Mode;
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponse;
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponseProcessor;
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponseWithFailedKbs;
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponseWithRegistrationLockProcessor;
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponseWithSuccessfulKbs;
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponseHitRegistrationLock;
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponseWithoutKbs;
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||
|
||||
@@ -48,7 +45,8 @@ public abstract class BaseRegistrationViewModel extends ViewModel {
|
||||
private static final String STATE_PUSH_TIMED_OUT = "PUSH_TIMED_OUT";
|
||||
private static final String STATE_INCORRECT_CODE_ATTEMPTS = "STATE_INCORRECT_CODE_ATTEMPTS";
|
||||
private static final String STATE_REQUEST_RATE_LIMITER = "REQUEST_RATE_LIMITER";
|
||||
private static final String STATE_KBS_TOKEN = "KBS_TOKEN";
|
||||
private static final String STATE_SVR_AUTH = "SVR_AUTH";
|
||||
private static final String STATE_SVR_TRIES_REMAINING = "SVR_TRIES_REMAINING";
|
||||
private static final String STATE_TIME_REMAINING = "TIME_REMAINING";
|
||||
private static final String STATE_CAN_CALL_AT_TIME = "CAN_CALL_AT_TIME";
|
||||
private static final String STATE_CAN_SMS_AT_TIME = "CAN_SMS_AT_TIME";
|
||||
@@ -56,24 +54,21 @@ public abstract class BaseRegistrationViewModel extends ViewModel {
|
||||
|
||||
protected final SavedStateHandle savedState;
|
||||
protected final VerifyAccountRepository verifyAccountRepository;
|
||||
protected final KbsRepository kbsRepository;
|
||||
|
||||
public BaseRegistrationViewModel(@NonNull SavedStateHandle savedStateHandle,
|
||||
@NonNull VerifyAccountRepository verifyAccountRepository,
|
||||
@NonNull KbsRepository kbsRepository,
|
||||
@NonNull String password)
|
||||
{
|
||||
this.savedState = savedStateHandle;
|
||||
|
||||
this.verifyAccountRepository = verifyAccountRepository;
|
||||
this.kbsRepository = kbsRepository;
|
||||
|
||||
setInitialDefaultValue(STATE_NUMBER, NumberViewState.INITIAL);
|
||||
setInitialDefaultValue(STATE_REGISTRATION_SECRET, password);
|
||||
setInitialDefaultValue(STATE_VERIFICATION_CODE, "");
|
||||
setInitialDefaultValue(STATE_INCORRECT_CODE_ATTEMPTS, 0);
|
||||
setInitialDefaultValue(STATE_REQUEST_RATE_LIMITER, new LocalCodeRequestRateLimiter(60_000));
|
||||
setInitialDefaultValue(STATE_RECOVERY_PASSWORD, SignalStore.kbsValues().getRecoveryPassword());
|
||||
setInitialDefaultValue(STATE_RECOVERY_PASSWORD, SignalStore.svr().getRecoveryPassword());
|
||||
setInitialDefaultValue(STATE_PUSH_TIMED_OUT, false);
|
||||
}
|
||||
|
||||
@@ -188,12 +183,20 @@ public abstract class BaseRegistrationViewModel extends ViewModel {
|
||||
return challengeKeys;
|
||||
}
|
||||
|
||||
public @Nullable TokenData getKeyBackupCurrentToken() {
|
||||
return savedState.get(STATE_KBS_TOKEN);
|
||||
protected void setSvrAuthCredentials(SvrAuthCredentialSet credentials) {
|
||||
savedState.set(STATE_SVR_AUTH, credentials);
|
||||
}
|
||||
|
||||
public void setKeyBackupTokenData(@Nullable TokenData tokenData) {
|
||||
savedState.set(STATE_KBS_TOKEN, tokenData);
|
||||
protected @Nullable SvrAuthCredentialSet getSvrAuthCredentials() {
|
||||
return savedState.get(STATE_SVR_AUTH);
|
||||
}
|
||||
|
||||
public @Nullable Integer getSvrTriesRemaining() {
|
||||
return savedState.get(STATE_SVR_TRIES_REMAINING);
|
||||
}
|
||||
|
||||
public void setSvrTriesRemaining(@Nullable Integer triesRemaining) {
|
||||
savedState.set(STATE_SVR_TRIES_REMAINING, triesRemaining);
|
||||
}
|
||||
|
||||
public void setRecoveryPassword(@Nullable String recoveryPassword) {
|
||||
@@ -338,56 +341,54 @@ public abstract class BaseRegistrationViewModel extends ViewModel {
|
||||
|
||||
return verifyAccountWithoutRegistrationLock()
|
||||
.flatMap(response -> {
|
||||
if (response.getResult().isPresent() && response.getResult().get().getKbsData() != null) {
|
||||
if (response.getResult().isPresent() && response.getResult().get().getMasterKey() != null) {
|
||||
return onVerifySuccessWithRegistrationLock(new VerifyResponseWithRegistrationLockProcessor(response, null), response.getResult().get().getPin());
|
||||
}
|
||||
|
||||
VerifyResponseProcessor processor = new VerifyResponseWithoutKbs(response);
|
||||
if (processor.hasResult()) {
|
||||
return onVerifySuccess(processor);
|
||||
} else if (processor.registrationLock() && !processor.isKbsLocked()) {
|
||||
return kbsRepository.getToken(processor.getLockedException().getBasicStorageCredentials())
|
||||
.map(r -> r.getResult().isPresent() ? new VerifyResponseWithSuccessfulKbs(processor.getResponse(), r.getResult().get())
|
||||
: new VerifyResponseWithFailedKbs(r));
|
||||
} else if (processor.registrationLock() && !processor.isRegistrationLockPresentAndSvrExhausted()) {
|
||||
return Single.just(new VerifyResponseHitRegistrationLock(processor.getResponse()));
|
||||
}
|
||||
return Single.just(processor);
|
||||
})
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnSuccess(processor -> {
|
||||
if (processor.registrationLock() && !processor.isKbsLocked()) {
|
||||
if (processor.registrationLock() && !processor.isRegistrationLockPresentAndSvrExhausted()) {
|
||||
setLockedTimeRemaining(processor.getLockedException().getTimeRemaining());
|
||||
setKeyBackupTokenData(processor.getTokenData());
|
||||
} else if (processor.isKbsLocked()) {
|
||||
setSvrTriesRemaining(processor.getSvrTriesRemaining());
|
||||
setSvrAuthCredentials(processor.getSvrAuthCredentials());
|
||||
} else if (processor.isRegistrationLockPresentAndSvrExhausted()) {
|
||||
setLockedTimeRemaining(processor.getLockedException().getTimeRemaining());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Single<VerifyResponseWithRegistrationLockProcessor> verifyCodeAndRegisterAccountWithRegistrationLock(@NonNull String pin) {
|
||||
TokenData kbsTokenData = Objects.requireNonNull(getKeyBackupCurrentToken());
|
||||
SvrAuthCredentialSet authCredentials = Objects.requireNonNull(getSvrAuthCredentials());
|
||||
|
||||
return verifyAccountWithRegistrationLock(pin, kbsTokenData)
|
||||
.map(r -> new VerifyResponseWithRegistrationLockProcessor(r, kbsTokenData))
|
||||
return verifyAccountWithRegistrationLock(pin, authCredentials)
|
||||
.map(r -> new VerifyResponseWithRegistrationLockProcessor(r, authCredentials))
|
||||
.flatMap(processor -> {
|
||||
if (processor.hasResult()) {
|
||||
return onVerifySuccessWithRegistrationLock(processor, pin);
|
||||
} else if (processor.wrongPin()) {
|
||||
TokenData newToken = TokenData.withResponse(kbsTokenData, processor.getTokenResponse());
|
||||
return Single.just(new VerifyResponseWithRegistrationLockProcessor(processor.getResponse(), newToken));
|
||||
return Single.just(new VerifyResponseWithRegistrationLockProcessor(processor.getResponse(), authCredentials));
|
||||
}
|
||||
return Single.just(processor);
|
||||
})
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnSuccess(processor -> {
|
||||
if (processor.wrongPin()) {
|
||||
setKeyBackupTokenData(processor.getTokenData());
|
||||
setSvrTriesRemaining(processor.getSvrTriesRemaining());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract Single<ServiceResponse<VerifyResponse>> verifyAccountWithoutRegistrationLock();
|
||||
|
||||
protected abstract Single<ServiceResponse<VerifyResponse>> verifyAccountWithRegistrationLock(@NonNull String pin, @NonNull TokenData kbsTokenData);
|
||||
protected abstract Single<ServiceResponse<VerifyResponse>> verifyAccountWithRegistrationLock(@NonNull String pin, @NonNull SvrAuthCredentialSet svrAuthCredentials);
|
||||
|
||||
protected abstract Single<VerifyResponseProcessor> onVerifySuccess(@NonNull VerifyResponseProcessor processor);
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject
|
||||
import org.thoughtcrime.securesms.pin.TokenData
|
||||
|
||||
/**
|
||||
* Used during re-registration flow when pin entry is required to skip SMS verification. Mostly tracks
|
||||
@@ -19,14 +18,14 @@ class ReRegisterWithPinViewModel : ViewModel() {
|
||||
private val _triesRemaining: BehaviorSubject<Int> = BehaviorSubject.createDefault(10)
|
||||
val triesRemaining: Observable<Int> = _triesRemaining.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
fun updateTokenData(tokenData: TokenData?) {
|
||||
if (tokenData == null) {
|
||||
fun updateSvrTriesRemaining(triesRemaining: Int?) {
|
||||
if (triesRemaining == null) {
|
||||
isLocalVerification = true
|
||||
if (hasIncorrectGuess) {
|
||||
_triesRemaining.onNext((_triesRemaining.value!! - 1).coerceAtLeast(0))
|
||||
}
|
||||
} else {
|
||||
_triesRemaining.onNext(tokenData.triesRemaining)
|
||||
_triesRemaining.onNext(triesRemaining)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,8 @@ import org.thoughtcrime.securesms.jobs.NewRegistrationUsernameSyncJob;
|
||||
import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob;
|
||||
import org.thoughtcrime.securesms.jobs.StorageSyncJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.pin.KbsRepository;
|
||||
import org.thoughtcrime.securesms.pin.KeyBackupSystemWrongPinException;
|
||||
import org.thoughtcrime.securesms.pin.TokenData;
|
||||
import org.thoughtcrime.securesms.pin.SvrWrongPinException;
|
||||
import org.thoughtcrime.securesms.pin.SvrRepository;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationData;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationRepository;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationSessionProcessor;
|
||||
@@ -29,13 +28,13 @@ import org.thoughtcrime.securesms.registration.VerifyResponseWithRegistrationLoc
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponseWithoutKbs;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.api.KbsPinData;
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
||||
import org.whispersystems.signalservice.api.SvrNoDataException;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.api.kbs.PinHashUtil;
|
||||
import org.whispersystems.signalservice.api.push.ServiceIdType;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.IncorrectCodeException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.IncorrectRegistrationRecoveryPasswordException;
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse;
|
||||
import org.whispersystems.util.Base64;
|
||||
|
||||
@@ -67,10 +66,9 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
|
||||
public RegistrationViewModel(@NonNull SavedStateHandle savedStateHandle,
|
||||
boolean isReregister,
|
||||
@NonNull VerifyAccountRepository verifyAccountRepository,
|
||||
@NonNull KbsRepository kbsRepository,
|
||||
@NonNull RegistrationRepository registrationRepository)
|
||||
{
|
||||
super(savedStateHandle, verifyAccountRepository, kbsRepository, Util.getSecret(18));
|
||||
super(savedStateHandle, verifyAccountRepository, Util.getSecret(18));
|
||||
|
||||
this.registrationRepository = registrationRepository;
|
||||
|
||||
@@ -174,14 +172,12 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
|
||||
})
|
||||
.flatMap(verifyAccountWithoutKbsResponse -> {
|
||||
VerifyResponseProcessor processor = new VerifyResponseWithoutKbs(verifyAccountWithoutKbsResponse);
|
||||
String pin = SignalStore.kbsValues().getPin();
|
||||
String pin = SignalStore.svr().getPin();
|
||||
|
||||
if ((processor.isKbsLocked() || processor.registrationLock()) && SignalStore.kbsValues().getRegistrationLockToken() != null && pin != null) {
|
||||
KbsPinData pinData = new KbsPinData(SignalStore.kbsValues().getOrCreateMasterKey(), SignalStore.kbsValues().getRegistrationLockTokenResponse());
|
||||
|
||||
return verifyAccountRepository.registerAccount(sessionId, getRegistrationData(), pin, () -> pinData)
|
||||
if ((processor.isRegistrationLockPresentAndSvrExhausted() || processor.registrationLock()) && SignalStore.svr().getRegistrationLockToken() != null && pin != null) {
|
||||
return verifyAccountRepository.registerAccount(sessionId, getRegistrationData(), pin, () -> SignalStore.svr().getOrCreateMasterKey())
|
||||
.map(verifyAccountWithPinResponse -> {
|
||||
if (verifyAccountWithPinResponse.getResult().isPresent() && verifyAccountWithPinResponse.getResult().get().getKbsData() != null) {
|
||||
if (verifyAccountWithPinResponse.getResult().isPresent() && verifyAccountWithPinResponse.getResult().get().getMasterKey() != null) {
|
||||
return verifyAccountWithPinResponse;
|
||||
} else {
|
||||
return verifyAccountWithoutKbsResponse;
|
||||
@@ -195,7 +191,7 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Single<ServiceResponse<VerifyResponse>> verifyAccountWithRegistrationLock(@NonNull String pin, @NonNull TokenData kbsTokenData) {
|
||||
protected Single<ServiceResponse<VerifyResponse>> verifyAccountWithRegistrationLock(@NonNull String pin, @NonNull SvrAuthCredentialSet svrAuthCredentials) {
|
||||
final String sessionId = getSessionId();
|
||||
if (sessionId == null) {
|
||||
throw new IllegalStateException("No valid registration session");
|
||||
@@ -210,7 +206,7 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
|
||||
})
|
||||
.<ServiceResponse<VerifyResponse>>flatMap(processor -> {
|
||||
if (processor.isAlreadyVerified() || (processor.hasResult() && processor.isVerified())) {
|
||||
return verifyAccountRepository.registerAccount(sessionId, getRegistrationData(), pin, () -> Objects.requireNonNull(KbsRepository.restoreMasterKey(pin, kbsTokenData.getEnclave(), kbsTokenData.getBasicAuth(), kbsTokenData.getTokenResponse())));
|
||||
return verifyAccountRepository.registerAccount(sessionId, getRegistrationData(), pin, () -> SvrRepository.restoreMasterKeyPreRegistration(svrAuthCredentials, pin));
|
||||
} else {
|
||||
return Single.just(ServiceResponse.coerceError(processor.getResponse()));
|
||||
}
|
||||
@@ -250,18 +246,17 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
|
||||
return updateFcmTokenValue().subscribeOn(Schedulers.io())
|
||||
.observeOn(Schedulers.io())
|
||||
.onErrorReturnItem("")
|
||||
.flatMap(s -> verifyReRegisterWithRecoveryPassword(pin, data.pinData));
|
||||
.flatMap(s -> verifyReRegisterWithRecoveryPassword(pin, data.masterKey));
|
||||
} else {
|
||||
throw new IllegalStateException("Unable to get token or master key");
|
||||
throw new IncorrectRegistrationRecoveryPasswordException();
|
||||
}
|
||||
})
|
||||
.onErrorReturn(t -> new VerifyResponseWithRegistrationLockProcessor(ServiceResponse.forUnknownError(t), getKeyBackupCurrentToken()))
|
||||
.onErrorReturn(t -> new VerifyResponseWithRegistrationLockProcessor(ServiceResponse.forUnknownError(t), getSvrAuthCredentials()))
|
||||
.map(p -> {
|
||||
if (p instanceof VerifyResponseWithRegistrationLockProcessor) {
|
||||
VerifyResponseWithRegistrationLockProcessor lockProcessor = (VerifyResponseWithRegistrationLockProcessor) p;
|
||||
if (lockProcessor.wrongPin() && lockProcessor.getTokenData() != null) {
|
||||
TokenData newToken = TokenData.withResponse(lockProcessor.getTokenData(), lockProcessor.getTokenResponse());
|
||||
return new VerifyResponseWithRegistrationLockProcessor(lockProcessor.getResponse(), newToken);
|
||||
if (lockProcessor.wrongPin() && lockProcessor.getSvrTriesRemaining() != null) {
|
||||
return new VerifyResponseWithRegistrationLockProcessor(lockProcessor.getResponse(), lockProcessor.getSvrAuthCredentials());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,37 +272,33 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
|
||||
|
||||
@WorkerThread
|
||||
private @NonNull ReRegistrationData verifyReRegisterWithPinInternal(@NonNull String pin)
|
||||
throws KeyBackupSystemWrongPinException, IOException, KeyBackupSystemNoDataException
|
||||
throws SvrWrongPinException, IOException, SvrNoDataException
|
||||
{
|
||||
String localPinHash = SignalStore.kbsValues().getLocalPinHash();
|
||||
String localPinHash = SignalStore.svr().getLocalPinHash();
|
||||
|
||||
if (hasRecoveryPassword() && localPinHash != null) {
|
||||
if (PinHashUtil.verifyLocalPinHash(localPinHash, pin)) {
|
||||
Log.i(TAG, "Local pin matches input, attempting registration");
|
||||
return ReRegistrationData.canProceed(new KbsPinData(SignalStore.kbsValues().getOrCreateMasterKey(), SignalStore.kbsValues().getRegistrationLockTokenResponse()));
|
||||
return ReRegistrationData.canProceed(SignalStore.svr().getOrCreateMasterKey());
|
||||
} else {
|
||||
throw new KeyBackupSystemWrongPinException(new TokenResponse(null, null, 0));
|
||||
throw new SvrWrongPinException(0);
|
||||
}
|
||||
} else {
|
||||
TokenData data = getKeyBackupCurrentToken();
|
||||
if (data == null) {
|
||||
Log.w(TAG, "No token data, abort skip flow");
|
||||
SvrAuthCredentialSet authCredentials = getSvrAuthCredentials();
|
||||
if (authCredentials == null) {
|
||||
Log.w(TAG, "No SVR auth credentials, abort skip flow");
|
||||
return ReRegistrationData.cannotProceed();
|
||||
}
|
||||
|
||||
KbsPinData kbsPinData = KbsRepository.restoreMasterKey(pin, data.getEnclave(), data.getBasicAuth(), data.getTokenResponse());
|
||||
if (kbsPinData == null || kbsPinData.getMasterKey() == null) {
|
||||
Log.w(TAG, "No kbs data, abort skip flow");
|
||||
return ReRegistrationData.cannotProceed();
|
||||
}
|
||||
MasterKey masterKey = SvrRepository.restoreMasterKeyPreRegistration(authCredentials, pin);
|
||||
|
||||
setRecoveryPassword(kbsPinData.getMasterKey().deriveRegistrationRecoveryPassword());
|
||||
setKeyBackupTokenData(data);
|
||||
return ReRegistrationData.canProceed(kbsPinData);
|
||||
setRecoveryPassword(masterKey.deriveRegistrationRecoveryPassword());
|
||||
setSvrTriesRemaining(10);
|
||||
return ReRegistrationData.canProceed(masterKey);
|
||||
}
|
||||
}
|
||||
|
||||
private Single<VerifyResponseProcessor> verifyReRegisterWithRecoveryPassword(@NonNull String pin, @NonNull KbsPinData pinData) {
|
||||
private Single<VerifyResponseProcessor> verifyReRegisterWithRecoveryPassword(@NonNull String pin, @NonNull MasterKey masterKey) {
|
||||
RegistrationData registrationData = getRegistrationData();
|
||||
if (registrationData.getRecoveryPassword() == null) {
|
||||
throw new IllegalStateException("No valid recovery password");
|
||||
@@ -319,9 +310,10 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
|
||||
.map(VerifyResponseWithoutKbs::new)
|
||||
.flatMap(processor -> {
|
||||
if (processor.registrationLock()) {
|
||||
return verifyAccountRepository.registerAccount(null, registrationData, pin, () -> pinData)
|
||||
setSvrAuthCredentials(processor.getSvrAuthCredentials());
|
||||
return verifyAccountRepository.registerAccount(null, registrationData, pin, () -> masterKey)
|
||||
.onErrorReturn(ServiceResponse::forUnknownError)
|
||||
.map(r -> new VerifyResponseWithRegistrationLockProcessor(r, getKeyBackupCurrentToken()));
|
||||
.map(r -> new VerifyResponseWithRegistrationLockProcessor(r, processor.getSvrAuthCredentials()));
|
||||
} else {
|
||||
return Single.just(processor);
|
||||
}
|
||||
@@ -329,14 +321,14 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
|
||||
.flatMap(processor -> {
|
||||
if (processor.hasResult()) {
|
||||
VerifyResponse verifyResponse = processor.getResult();
|
||||
boolean setRegistrationLockEnabled = verifyResponse.getKbsData() != null;
|
||||
boolean setRegistrationLockEnabled = verifyResponse.getMasterKey() != null;
|
||||
|
||||
if (!setRegistrationLockEnabled) {
|
||||
verifyResponse = new VerifyResponse(processor.getResult().getVerifyAccountResponse(), pinData, pin, verifyResponse.getAciPreKeyCollection(), verifyResponse.getPniPreKeyCollection());
|
||||
verifyResponse = new VerifyResponse(processor.getResult().getVerifyAccountResponse(), masterKey, pin, verifyResponse.getAciPreKeyCollection(), verifyResponse.getPniPreKeyCollection());
|
||||
}
|
||||
|
||||
return registrationRepository.registerAccount(registrationData, verifyResponse, setRegistrationLockEnabled)
|
||||
.map(r -> new VerifyResponseWithRegistrationLockProcessor(r, getKeyBackupCurrentToken()));
|
||||
.map(r -> new VerifyResponseWithRegistrationLockProcessor(r, getSvrAuthCredentials()));
|
||||
} else {
|
||||
return Single.just(processor);
|
||||
}
|
||||
@@ -360,8 +352,8 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
|
||||
}
|
||||
|
||||
private Single<Boolean> checkForValidKbsAuthCredentials() {
|
||||
final List<String> kbsAuthTokenList = SignalStore.kbsValues().getKbsAuthTokenList();
|
||||
List<String> usernamePasswords = kbsAuthTokenList
|
||||
final List<String> svrAuthTokenList = SignalStore.svr().getAuthTokenList();
|
||||
List<String> usernamePasswords = svrAuthTokenList
|
||||
.stream()
|
||||
.limit(10)
|
||||
.map(t -> {
|
||||
@@ -377,23 +369,8 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
|
||||
return Single.just(false);
|
||||
}
|
||||
|
||||
return registrationRepository.getKbsAuthCredential(getRegistrationData(), usernamePasswords)
|
||||
.flatMap(p -> {
|
||||
if (p.getValid() != null) {
|
||||
return kbsRepository.getToken(p.getValid())
|
||||
.flatMap(r -> {
|
||||
if (r.getResult().isPresent()) {
|
||||
TokenData tokenData = r.getResult().get();
|
||||
setKeyBackupTokenData(tokenData);
|
||||
return Single.just(tokenData.getTriesRemaining() > 0);
|
||||
} else {
|
||||
return Single.just(false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return Single.just(false);
|
||||
}
|
||||
})
|
||||
return registrationRepository.getSvrAuthCredential(getRegistrationData(), usernamePasswords)
|
||||
.flatMap(p -> Single.just(p.getValid() != null))
|
||||
.onErrorReturnItem(false)
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
}
|
||||
@@ -432,20 +409,20 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
|
||||
}
|
||||
|
||||
private static class ReRegistrationData {
|
||||
public boolean canProceed;
|
||||
public KbsPinData pinData;
|
||||
public boolean canProceed;
|
||||
public MasterKey masterKey;
|
||||
|
||||
private ReRegistrationData(boolean canProceed, @Nullable KbsPinData pinData) {
|
||||
private ReRegistrationData(boolean canProceed, @Nullable MasterKey masterKey) {
|
||||
this.canProceed = canProceed;
|
||||
this.pinData = pinData;
|
||||
this.masterKey = masterKey;
|
||||
}
|
||||
|
||||
public static ReRegistrationData cannotProceed() {
|
||||
return new ReRegistrationData(false, null);
|
||||
}
|
||||
|
||||
public static ReRegistrationData canProceed(@NonNull KbsPinData pinData) {
|
||||
return new ReRegistrationData(true, pinData);
|
||||
public static ReRegistrationData canProceed(@NonNull MasterKey masterKey) {
|
||||
return new ReRegistrationData(true, masterKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,7 +440,6 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
|
||||
return modelClass.cast(new RegistrationViewModel(handle,
|
||||
isReregister,
|
||||
new VerifyAccountRepository(ApplicationDependencies.getApplication()),
|
||||
new KbsRepository(),
|
||||
new RegistrationRepository(ApplicationDependencies.getApplication())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.registration.viewmodel
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.whispersystems.signalservice.internal.push.AuthCredentials
|
||||
|
||||
@Parcelize
|
||||
data class SvrAuthCredentialSet(
|
||||
private val svr1Credentials: ParcelableAuthCredentials?,
|
||||
private val svr2Credentials: ParcelableAuthCredentials?
|
||||
) : Parcelable {
|
||||
constructor(
|
||||
svr1Credentials: AuthCredentials?,
|
||||
svr2Credentials: AuthCredentials?
|
||||
) : this(ParcelableAuthCredentials.createOrNull(svr1Credentials), ParcelableAuthCredentials.createOrNull(svr2Credentials))
|
||||
|
||||
val svr1: AuthCredentials? = svr1Credentials?.credentials()
|
||||
val svr2: AuthCredentials? = svr2Credentials?.credentials()
|
||||
|
||||
@Parcelize
|
||||
data class ParcelableAuthCredentials(private val username: String, private val password: String) : Parcelable {
|
||||
|
||||
companion object {
|
||||
fun createOrNull(creds: AuthCredentials?): ParcelableAuthCredentials? {
|
||||
return if (creds != null) {
|
||||
ParcelableAuthCredentials(creds.username(), creds.password())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun credentials(): AuthCredentials {
|
||||
return AuthCredentials.create(username, password)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||
import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver;
|
||||
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
@@ -109,6 +108,7 @@ public final class FeatureFlags {
|
||||
private static final String MAX_ATTACHMENT_COUNT = "android.attachments.maxCount";
|
||||
private static final String MAX_ATTACHMENT_RECEIVE_SIZE_BYTES = "global.attachments.maxReceiveBytes";
|
||||
private static final String MAX_ATTACHMENT_SIZE_BYTES = "global.attachments.maxBytes";
|
||||
private static final String SVR2_KILLSWITCH = "android.svr2.killSwitch";
|
||||
|
||||
/**
|
||||
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
||||
@@ -169,7 +169,8 @@ public final class FeatureFlags {
|
||||
MAX_ATTACHMENT_COUNT,
|
||||
MAX_ATTACHMENT_RECEIVE_SIZE_BYTES,
|
||||
MAX_ATTACHMENT_SIZE_BYTES,
|
||||
AD_HOC_CALLING
|
||||
AD_HOC_CALLING,
|
||||
SVR2_KILLSWITCH
|
||||
);
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -235,7 +236,8 @@ public final class FeatureFlags {
|
||||
EDIT_MESSAGE_SEND,
|
||||
MAX_ATTACHMENT_COUNT,
|
||||
MAX_ATTACHMENT_RECEIVE_SIZE_BYTES,
|
||||
MAX_ATTACHMENT_SIZE_BYTES
|
||||
MAX_ATTACHMENT_SIZE_BYTES,
|
||||
SVR2_KILLSWITCH
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -243,7 +245,8 @@ public final class FeatureFlags {
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final Set<String> STICKY = SetUtil.newHashSet(
|
||||
VERIFY_V2
|
||||
VERIFY_V2,
|
||||
SVR2_KILLSWITCH
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -752,6 +755,14 @@ public final class FeatureFlags {
|
||||
return getInteger(MESSAGE_PROCESSOR_DELAY, 300);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not SVR2 should be used at all. Defaults to true. In practice this is reserved as a killswitch.
|
||||
*/
|
||||
public static boolean svr2() {
|
||||
// Despite us always inverting the value, it's important that this defaults to false so that the STICKY property works as intended
|
||||
return !getBoolean(SVR2_KILLSWITCH, false);
|
||||
}
|
||||
|
||||
private enum VersionFlag {
|
||||
/** The flag is no set */
|
||||
OFF,
|
||||
|
||||
@@ -65,7 +65,7 @@ public final class SupportEmailUtil {
|
||||
"\n" +
|
||||
context.getString(R.string.SupportEmailUtil_signal_package) + " " + getSignalPackage(context) +
|
||||
"\n" +
|
||||
context.getString(R.string.SupportEmailUtil_registration_lock) + " " + getRegistrationLockEnabled(context) +
|
||||
context.getString(R.string.SupportEmailUtil_registration_lock) + " " + getRegistrationLockEnabled() +
|
||||
"\n" +
|
||||
context.getString(R.string.SupportEmailUtil_locale) + " " + Locale.getDefault().toString();
|
||||
}
|
||||
@@ -86,7 +86,7 @@ public final class SupportEmailUtil {
|
||||
return String.format("%s (%s)", BuildConfig.APPLICATION_ID, AppSignatureUtil.getAppSignature(context));
|
||||
}
|
||||
|
||||
private static CharSequence getRegistrationLockEnabled(@NonNull Context context) {
|
||||
return String.valueOf(TextSecurePreferences.isV1RegistrationLockEnabled(context) || SignalStore.kbsValues().isV2RegistrationLockEnabled());
|
||||
private static CharSequence getRegistrationLockEnabled() {
|
||||
return String.valueOf(SignalStore.svr().isRegistrationLockEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,14 +334,6 @@ public class TextSecurePreferences {
|
||||
return getStringPreference(context, REGISTRATION_LOCK_PIN_PREF_V1, null);
|
||||
}
|
||||
|
||||
public static void clearRegistrationLockV1(@NonNull Context context) {
|
||||
//noinspection deprecation
|
||||
getSharedPreferences(context)
|
||||
.edit()
|
||||
.remove(REGISTRATION_LOCK_PIN_PREF_V1)
|
||||
.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use only for migrations to the Key Backup Store registration pinV2.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user