summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJonatan Rhodin <jonatan.rhodin@mullvad.net>2025-09-09 12:53:08 +0200
committerJonatan Rhodin <jonatan.rhodin@mullvad.net>2025-09-09 12:53:08 +0200
commit708498ffa4ac7fd211af506e9d8237536b3ed335 (patch)
tree807c8db111427dbeead95ea36ceb13e1a20197c3
parentb79b4c95ada5e4a38cbb05e7533e8a06daeca069 (diff)
parentb80d26872f8346aa7f470b658569e94b007b0dbe (diff)
downloadmullvadvpn-708498ffa4ac7fd211af506e9d8237536b3ed335.tar.xz
mullvadvpn-708498ffa4ac7fd211af506e9d8237536b3ed335.zip
Merge branch 'entry-relay-is-blocked-in-the-exit-list-even-though-droid-2160'
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt1
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/ModifyMultihopUseCase.kt10
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/LocationUtil.kt9
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SearchLocationViewModel.kt15
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModel.kt10
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SearchLocationViewModelTest.kt6
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModelTest.kt52
7 files changed, 94 insertions, 9 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
index e24ad8e9ef..c9bd4391af 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
@@ -277,6 +277,7 @@ val uiModule = module {
get(),
get(),
get(),
+ get(),
)
}
viewModel { (relayListType: RelayListType) ->
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/ModifyMultihopUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/ModifyMultihopUseCase.kt
index e8edfc64b8..e4f832ff12 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/ModifyMultihopUseCase.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/ModifyMultihopUseCase.kt
@@ -16,6 +16,8 @@ import net.mullvad.mullvadvpn.repository.CustomListsRepository
import net.mullvad.mullvadvpn.repository.RelayListRepository
import net.mullvad.mullvadvpn.repository.SettingsRepository
import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository
+import net.mullvad.mullvadvpn.util.isDaitaDirectOnly
+import net.mullvad.mullvadvpn.util.isDaitaEnabled
class ModifyMultihopUseCase(
private val relayListRepository: RelayListRepository,
@@ -37,7 +39,13 @@ class ModifyMultihopUseCase(
}
?.convertCustomListWithOnlyHostNameToHostName()
?.bind()
- ensure(!changeId.isSameHost(other)) { ModifyMultihopError.EntrySameAsExit(change.item) }
+ // If DAITA is enabled and direct only is disabled, allow same relay for entry and
+ // exit.
+ if (!settings.isDaitaEnabled() || settings.isDaitaDirectOnly()) {
+ ensure(!changeId.isSameHost(other)) {
+ ModifyMultihopError.EntrySameAsExit(change.item)
+ }
+ }
when (change) {
is MultihopChange.Entry ->
wireguardConstraintsRepository.setEntryLocation(change.item.id)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/LocationUtil.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/LocationUtil.kt
index 2561f7f007..51c24be0f1 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/LocationUtil.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/LocationUtil.kt
@@ -19,8 +19,15 @@ internal fun RelayListType.isEntryAndBlocked(settings: Settings?): Boolean {
return settings?.entryBlocked() == true
}
-private fun Settings.entryBlocked() =
+internal fun Settings.entryBlocked() =
isDaitaEnabled() && !isDaitaDirectOnly() && isMultihopEnabled()
+// If entry is blocked and we are on the exit list we should ignore any entry selection
+internal fun ignoreEntrySelection(settings: Settings?, relayListType: RelayListType) =
+ settings?.entryBlocked() == true && relayListType.isMultihopExit()
+
+private fun RelayListType.isMultihopExit() =
+ this is RelayListType.Multihop && multihopRelayListType == MultihopRelayListType.EXIT
+
private fun RelayListType.isMultihopEntry() =
this is RelayListType.Multihop && multihopRelayListType == MultihopRelayListType.ENTRY
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SearchLocationViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SearchLocationViewModel.kt
index ce67ddba99..fde0b3b94d 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SearchLocationViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SearchLocationViewModel.kt
@@ -25,6 +25,7 @@ import net.mullvad.mullvadvpn.lib.model.RelayItemId
import net.mullvad.mullvadvpn.relaylist.newFilterOnSearch
import net.mullvad.mullvadvpn.repository.CustomListsRepository
import net.mullvad.mullvadvpn.repository.RelayListFilterRepository
+import net.mullvad.mullvadvpn.repository.SettingsRepository
import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository
import net.mullvad.mullvadvpn.usecase.FilterChip
import net.mullvad.mullvadvpn.usecase.FilterChipUseCase
@@ -50,6 +51,7 @@ class SearchLocationViewModel(
private val filterChipUseCase: FilterChipUseCase,
private val selectHopUseCase: SelectHopUseCase,
private val modifyMultihopUseCase: ModifyMultihopUseCase,
+ private val settingsRepository: SettingsRepository,
filteredRelayListUseCase: FilteredRelayListUseCase,
filteredCustomListRelayItemsUseCase: FilterCustomListsRelayItemUseCase,
selectedLocationUseCase: SelectedLocationUseCase,
@@ -89,6 +91,7 @@ class SearchLocationViewModel(
relayCountries = relayCountries,
)
val expandedItems = expandSet.with(expandOverrides)
+ val settings = settingsRepository.settingsUpdates.value
Lce.Content(
SearchLocationUiState(
searchTerm = searchTerm,
@@ -102,10 +105,14 @@ class SearchLocationViewModel(
selectedByThisEntryExitList =
selectedItem.selectedByThisEntryExitList(relayListType),
selectedByOtherEntryExitList =
- selectedItem.selectedByOtherEntryExitList(
- relayListType,
- customLists,
- ),
+ if (ignoreEntrySelection(settings, relayListType)) {
+ null
+ } else {
+ selectedItem.selectedByOtherEntryExitList(
+ relayListType,
+ customLists,
+ )
+ },
expandedItems = expandedItems,
),
customLists = customLists,
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModel.kt
index ff67946361..bd9929c87d 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModel.kt
@@ -82,6 +82,7 @@ class SelectLocationListViewModel(
expandedItems = expandedItems,
)
} else {
+ val settings = settingsRepository.settingsUpdates.value
relayListItems(
relayCountries = relayCountries,
relayListType = relayListType,
@@ -91,10 +92,13 @@ class SelectLocationListViewModel(
selectedByThisEntryExitList =
selectedItem.selectedByThisEntryExitList(relayListType),
selectedByOtherEntryExitList =
- selectedItem.selectedByOtherEntryExitList(relayListType, customLists),
+ if (ignoreEntrySelection(settings, relayListType)) {
+ null
+ } else {
+ selectedItem.selectedByOtherEntryExitList(relayListType, customLists)
+ },
expandedItems = expandedItems,
- isEntryBlocked =
- relayListType.isEntryAndBlocked(settingsRepository.settingsUpdates.value),
+ isEntryBlocked = settings?.entryBlocked() == true,
)
}
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SearchLocationViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SearchLocationViewModelTest.kt
index 8aa1e9b038..a71c95863f 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SearchLocationViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SearchLocationViewModelTest.kt
@@ -16,10 +16,12 @@ import net.mullvad.mullvadvpn.lib.model.Constraint
import net.mullvad.mullvadvpn.lib.model.GeoLocationId
import net.mullvad.mullvadvpn.lib.model.RelayItem
import net.mullvad.mullvadvpn.lib.model.RelayItemSelection
+import net.mullvad.mullvadvpn.lib.model.Settings
import net.mullvad.mullvadvpn.lib.model.WireguardConstraints
import net.mullvad.mullvadvpn.lib.ui.component.relaylist.RelayListItem
import net.mullvad.mullvadvpn.repository.CustomListsRepository
import net.mullvad.mullvadvpn.repository.RelayListFilterRepository
+import net.mullvad.mullvadvpn.repository.SettingsRepository
import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository
import net.mullvad.mullvadvpn.usecase.FilterChip
import net.mullvad.mullvadvpn.usecase.FilterChipUseCase
@@ -50,6 +52,7 @@ class SearchLocationViewModelTest {
private val mockCustomListsRelayItemUseCase: CustomListsRelayItemUseCase = mockk()
private val mockSelectHopUseCase: SelectHopUseCase = mockk()
private val mockModifyMultihopUseCase: ModifyMultihopUseCase = mockk()
+ private val mockSettingsRepository: SettingsRepository = mockk()
private val filteredRelayList = MutableStateFlow<List<RelayItem.Location.Country>>(emptyList())
private val selectedLocation =
@@ -59,6 +62,7 @@ class SearchLocationViewModelTest {
private val customListRelayItems = MutableStateFlow<List<RelayItem.CustomList>>(emptyList())
private val filterChips = MutableStateFlow<List<FilterChip>>(emptyList())
private val wireguardConstraints = MutableStateFlow<WireguardConstraints>(mockk(relaxed = true))
+ private val settingsFlow = MutableStateFlow(mockk<Settings>(relaxed = true))
private lateinit var viewModel: SearchLocationViewModel
@@ -72,6 +76,7 @@ class SearchLocationViewModelTest {
every { mockFilterChipUseCase(any()) } returns filterChips
every { mockWireguardConstraintsRepository.wireguardConstraints } returns
wireguardConstraints
+ every { mockSettingsRepository.settingsUpdates } returns settingsFlow
viewModel =
SearchLocationViewModel(
@@ -86,6 +91,7 @@ class SearchLocationViewModelTest {
customListsRelayItemUseCase = mockCustomListsRelayItemUseCase,
selectHopUseCase = mockSelectHopUseCase,
modifyMultihopUseCase = mockModifyMultihopUseCase,
+ settingsRepository = mockSettingsRepository,
savedStateHandle =
SearchLocationNavArgs(relayListType = RelayListType.Single).toSavedStateHandle(),
)
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModelTest.kt
index 8c01d592e5..436e061d1d 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/location/SelectLocationListViewModelTest.kt
@@ -3,9 +3,13 @@ package net.mullvad.mullvadvpn.viewmodel.location
import app.cash.turbine.test
import io.mockk.every
import io.mockk.mockk
+import io.mockk.mockkStatic
+import io.mockk.unmockkAll
+import io.mockk.verify
import kotlin.test.assertIs
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
+import net.mullvad.mullvadvpn.compose.state.MultihopRelayListType
import net.mullvad.mullvadvpn.compose.state.RelayListType
import net.mullvad.mullvadvpn.compose.state.SelectLocationListUiState
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
@@ -26,6 +30,7 @@ import net.mullvad.mullvadvpn.usecase.SelectedLocationUseCase
import net.mullvad.mullvadvpn.usecase.customlists.CustomListsRelayItemUseCase
import net.mullvad.mullvadvpn.usecase.customlists.FilterCustomListsRelayItemUseCase
import net.mullvad.mullvadvpn.util.Lce
+import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
@@ -68,6 +73,14 @@ class SelectLocationListViewModelTest {
every { mockCustomListRelayItemsUseCase() } returns customListRelayItems
every { mockSettingsRepository.settingsUpdates } returns settings
every { recentsUseCase(any()) } returns recentsRelayItems
+
+ mockkStatic(RELAY_ITEM_LIST_CREATOR_CLASS)
+ mockkStatic(LOCATION_UTIL_CLASS)
+ }
+
+ @AfterEach
+ fun tearDown() {
+ unmockkAll()
}
@Test
@@ -127,6 +140,40 @@ class SelectLocationListViewModelTest {
}
}
+ @Test
+ fun `given relay list type exit and entry blocked isEntryBlocked should be true`() = runTest {
+ // Arrange
+ viewModel =
+ createSelectLocationListViewModel(RelayListType.Multihop(MultihopRelayListType.EXIT))
+ filteredRelayList.value = testCountries
+ val exitLocation = Constraint.Only(GeoLocationId.Country("us"))
+ selectedLocationFlow.value =
+ RelayItemSelection.Multiple(
+ entryLocation = Constraint.Only(GeoLocationId.Country("se")),
+ exitLocation = exitLocation,
+ )
+ every { settings.value.entryBlocked() } returns true
+
+ // Act, Assert
+ viewModel.uiState.test {
+ awaitItem()
+
+ verify {
+ relayListItems(
+ relayListType = RelayListType.Multihop(MultihopRelayListType.EXIT),
+ relayCountries = testCountries,
+ customLists = any(),
+ recents = any(),
+ selectedItem = any(),
+ selectedByThisEntryExitList = exitLocation.getOrNull(),
+ selectedByOtherEntryExitList = null,
+ expandedItems = emptySet(),
+ isEntryBlocked = true,
+ )
+ }
+ }
+ }
+
private fun createSelectLocationListViewModel(relayListType: RelayListType) =
SelectLocationListViewModel(
relayListType = relayListType,
@@ -157,6 +204,11 @@ class SelectLocationListViewModelTest {
}
companion object {
+ private const val RELAY_ITEM_LIST_CREATOR_CLASS =
+ "net.mullvad.mullvadvpn.viewmodel.location.RelayItemListCreatorKt"
+ private const val LOCATION_UTIL_CLASS =
+ "net.mullvad.mullvadvpn.viewmodel.location.LocationUtilKt"
+
private val testCountries =
listOf(
RelayItem.Location.Country(