diff options
| author | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-09-09 12:53:08 +0200 |
|---|---|---|
| committer | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-09-09 12:53:08 +0200 |
| commit | 708498ffa4ac7fd211af506e9d8237536b3ed335 (patch) | |
| tree | 807c8db111427dbeead95ea36ceb13e1a20197c3 | |
| parent | b79b4c95ada5e4a38cbb05e7533e8a06daeca069 (diff) | |
| parent | b80d26872f8346aa7f470b658569e94b007b0dbe (diff) | |
| download | mullvadvpn-708498ffa4ac7fd211af506e9d8237536b3ed335.tar.xz mullvadvpn-708498ffa4ac7fd211af506e9d8237536b3ed335.zip | |
Merge branch 'entry-relay-is-blocked-in-the-exit-list-even-though-droid-2160'
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( |
