Block additional entry points from being accessed on a linked device.

This commit is contained in:
Cody Henthorne
2026-07-16 13:38:35 -04:00
parent 2f70d780d0
commit 4cafe16e25
8 changed files with 22 additions and 10 deletions
@@ -9,10 +9,10 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
public class DeviceProvisioningActivity extends PassphraseRequiredActivity {
@SuppressWarnings("unused")
private static final String TAG = Log.tag(DeviceProvisioningActivity.class);
@Override
@@ -22,6 +22,13 @@ public class DeviceProvisioningActivity extends PassphraseRequiredActivity {
@Override
protected void onCreate(Bundle bundle, boolean ready) {
if (SignalStore.account().isLinkedDevice()) {
Log.i(TAG, "Cannot link a device from a linked device. Ignoring provisioning intent.");
startActivity(MainActivity.clearTop(this));
finish();
return;
}
AlertDialog dialog = new MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.DeviceProvisioningActivity_link_a_signal_device))
.setMessage(getString(R.string.DeviceProvisioningActivity_to_link_a_desktop_or_ipad_to_this_signal_account))
@@ -26,6 +26,10 @@ class UsernameOutOfSyncBanner(private val onActionClick: (UsernameSyncState) ->
override val enabled: Boolean
get() {
if (SignalStore.account.isLinkedDevice) {
return false
}
return when (SignalStore.account.usernameSyncState) {
AccountValues.UsernameSyncState.USERNAME_AND_LINK_CORRUPTED -> true
AccountValues.UsernameSyncState.LINK_CORRUPTED -> true
@@ -697,7 +697,7 @@ public final class ConversationUpdateItem extends FrameLayout
});
actionButton.setText(R.string.ConversationActivity__invite_to_signal);
} else if (conversationMessage.getMessageRecord().isPaymentsRequestToActivate() && !conversationMessage.getMessageRecord().isOutgoing() && !SignalStore.payments().mobileCoinPaymentsEnabled()) {
} else if (conversationMessage.getMessageRecord().isPaymentsRequestToActivate() && !conversationMessage.getMessageRecord().isOutgoing() && !SignalStore.payments().mobileCoinPaymentsEnabled() && SignalStore.account().isPrimaryDevice()) {
actionButton.setText(R.string.ConversationUpdateItem_activate_payments);
actionButton.setVisibility(VISIBLE);
actionButton.setOnClickListener(v -> {
@@ -707,7 +707,7 @@ public final class ConversationUpdateItem extends FrameLayout
passthroughClickListener.onClick(v);
}
});
} else if (conversationMessage.getMessageRecord().isPaymentsActivated() && !conversationMessage.getMessageRecord().isOutgoing()) {
} else if (conversationMessage.getMessageRecord().isPaymentsActivated() && !conversationMessage.getMessageRecord().isOutgoing() && SignalStore.account().isPrimaryDevice()) {
actionButton.setText(R.string.ConversationUpdateItem_send_payment);
actionButton.setVisibility(VISIBLE);
actionButton.setOnClickListener(v -> {
@@ -260,7 +260,7 @@ public final class MenuState {
return builder.shouldShowCopyAction(!actionMessage && !remoteDelete && hasText && !hasGift && !hasPayment && !hasPoll)
.shouldShowDeleteAction(!hasInMemory && onlyContainsCompleteMessages(selectedParts))
.shouldShowReactions(!conversationRecipient.isReleaseNotes() && !conversationRecipient.isInactiveGroup())
.shouldShowPaymentDetails(hasPayment)
.shouldShowPaymentDetails(hasPayment && SignalStore.account().isPrimaryDevice())
.shouldShowPollTerminate(hasPollTerminate)
.shouldShowPinMessage(canPinMessage)
.shouldShowUnpinMessage(canUnpinMessage)
@@ -128,7 +128,7 @@ class AttachmentKeyboardFragment : LoggingFragment(R.layout.attachment_keyboard_
private fun updateButtonsAvailable(recipient: Recipient) {
val paymentsValues = SignalStore.payments
val isPaymentsAvailable = paymentsValues.paymentsAvailability.isSendAllowed && !recipient.isSelf && !recipient.isGroup && recipient.isRegistered
val isPaymentsAvailable = paymentsValues.paymentsAvailability.isSendAllowed && !recipient.isSelf && !recipient.isGroup && recipient.isRegistered && SignalStore.account.isPrimaryDevice
if (!isPaymentsAvailable) {
attachmentKeyboardView.filterAttachmentKeyboardButtons(removePaymentFilter)
@@ -57,7 +57,7 @@ class MediaCaptureViewModel(private val repository: MediaCaptureRepository) : Vi
disposables += qrData
.throttleFirst(5, TimeUnit.SECONDS)
.filter { it.startsWith("sgnl://linkdevice") }
.filter { it.startsWith("sgnl://linkdevice") && SignalStore.account.isPrimaryDevice }
.subscribe { data ->
internalEvents.onNext(MediaCaptureEvent.DeviceLinkScannedFromQrCode)
}
@@ -65,7 +65,7 @@ class MediaCaptureViewModel(private val repository: MediaCaptureRepository) : Vi
if (SignalStore.account.isRegistered) {
disposables += qrData
.throttleFirst(5, TimeUnit.SECONDS)
.filter { it.startsWith("sgnl://rereg") && QuickRegistrationRepository.isValidReRegistrationQr(it) }
.filter { it.startsWith("sgnl://rereg") && QuickRegistrationRepository.isValidReRegistrationQr(it) && SignalStore.account.isPrimaryDevice }
.subscribe { data ->
internalEvents.onNext(MediaCaptureEvent.ReregistrationScannedFromQrCode(data))
}
@@ -9,6 +9,7 @@ import kotlinx.coroutines.rx3.await
import org.signal.core.util.logging.Log
import org.signal.mediasend.MediaRecipientId
import org.signal.mediasend.MediaSendQrRepository
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.registration.data.QuickRegistrationRepository
@@ -20,8 +21,8 @@ object MediaSendV3QrRepository : MediaSendQrRepository {
override suspend fun checkQrData(qrData: String): MediaSendQrRepository.QrCheckResult {
return when {
UsernameRepository.isValidLink(qrData) -> handleUsernameLink(qrData)
qrData.startsWith("sgnl://linkdevice") -> handleLinkDevice()
qrData.startsWith("sgnl://rereg") && QuickRegistrationRepository.isValidReRegistrationQr(qrData) -> handleReReg(qrData)
qrData.startsWith("sgnl://linkdevice") && SignalStore.account.isPrimaryDevice -> handleLinkDevice()
qrData.startsWith("sgnl://rereg") && QuickRegistrationRepository.isValidReRegistrationQr(qrData) && SignalStore.account.isPrimaryDevice -> handleReReg(qrData)
else -> MediaSendQrRepository.QrCheckResult.None
}
}
@@ -637,7 +637,7 @@ public final class Megaphones {
}
private static boolean shouldShowUseNewOnDeviceBackupsMegaphone() {
return Environment.Backups.isNewFormatSupportedForLocalBackup() && SignalStore.settings().isBackupEnabled() && (RemoteConfig.upgradeBackupsMegaphone() || RemoteConfig.internalUser());
return SignalStore.account().isPrimaryDevice() && Environment.Backups.isNewFormatSupportedForLocalBackup() && SignalStore.settings().isBackupEnabled() && (RemoteConfig.upgradeBackupsMegaphone() || RemoteConfig.internalUser());
}
private static boolean shouldShowGrantFullScreenIntentPermission(@NonNull Context context) {