summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
authorJonatan Rhodin <jonatan.rhodin@mullvad.net>2024-06-13 16:31:40 +0200
committerJonatan Rhodin <jonatan.rhodin@mullvad.net>2024-06-14 13:27:05 +0200
commite80e9ee68559fcd32747c28a829e70d2121e9344 (patch)
tree0caefca40bdc425b44a5c1dc719b1f409933fb6f /android
parent6fb4623503fda3df77a9c7c3cc152a45506ba1a4 (diff)
downloadmullvadvpn-e80e9ee68559fcd32747c28a829e70d2121e9344.tar.xz
mullvadvpn-e80e9ee68559fcd32747c28a829e70d2121e9344.zip
Add api acccess method functionality
Diffstat (limited to 'android')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/ApiAccessRepository.kt85
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt70
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt77
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt59
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AddApiAccessMethodError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethod.kt23
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethodId.kt14
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethodName.kt19
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethodSetting.kt8
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Cipher.kt34
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetApiAccessMethodError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetCurrentApiAccessMethodError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/InvalidDataError.kt27
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NewAccessMethodSetting.kt11
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Port.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RemoveApiAccessMethodError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetApiAccessMethodError.kt5
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Settings.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SocksAuth.kt6
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TestApiAccessMethodError.kt9
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/UnknownApiAccessMethodError.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/UpdateApiAccessMethodError.kt3
22 files changed, 476 insertions, 3 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/ApiAccessRepository.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/ApiAccessRepository.kt
new file mode 100644
index 0000000000..bba17d84a0
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/ApiAccessRepository.kt
@@ -0,0 +1,85 @@
+package net.mullvad.mullvadvpn.repository
+
+import arrow.core.raise.either
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
+import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethod
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodId
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodName
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodSetting
+import net.mullvad.mullvadvpn.lib.model.GetApiAccessMethodError
+import net.mullvad.mullvadvpn.lib.model.NewAccessMethodSetting
+
+class ApiAccessRepository(
+ private val managementService: ManagementService,
+ dispatcher: CoroutineDispatcher = Dispatchers.IO
+) {
+ val accessMethods =
+ managementService.settings
+ .mapNotNull { it.apiAccessMethodSettings }
+ .stateIn(CoroutineScope(dispatcher), SharingStarted.Eagerly, null)
+
+ val currentAccessMethod =
+ managementService.currentAccessMethod.stateIn(
+ CoroutineScope(dispatcher),
+ SharingStarted.Eagerly,
+ null
+ )
+
+ suspend fun addApiAccessMethod(newAccessMethodSetting: NewAccessMethodSetting) =
+ managementService.addApiAccessMethod(newAccessMethodSetting)
+
+ suspend fun removeApiAccessMethod(apiAccessMethodId: ApiAccessMethodId) =
+ managementService.removeApiAccessMethod(apiAccessMethodId)
+
+ suspend fun setCurrentApiAccessMethod(apiAccessMethodId: ApiAccessMethodId) =
+ managementService.setApiAccessMethod(apiAccessMethodId)
+
+ private suspend fun updateApiAccessMethod(apiAccessMethodSetting: ApiAccessMethodSetting) =
+ managementService.updateApiAccessMethod(apiAccessMethodSetting)
+
+ suspend fun updateApiAccessMethod(
+ apiAccessMethodId: ApiAccessMethodId,
+ apiAccessMethodName: ApiAccessMethodName,
+ apiAccessMethod: ApiAccessMethod
+ ) = either {
+ val apiAccessMethodSetting = getApiAccessMethodSettingById(apiAccessMethodId).bind()
+ updateApiAccessMethod(
+ apiAccessMethodSetting.copy(
+ id = apiAccessMethodId,
+ name = apiAccessMethodName,
+ apiAccessMethod = apiAccessMethod
+ )
+ )
+ .bind()
+ }
+
+ suspend fun testCustomApiAccessMethod(customProxy: ApiAccessMethod.CustomProxy) =
+ managementService.testCustomApiAccessMethod(customProxy)
+
+ suspend fun testApiAccessMethodById(apiAccessMethodId: ApiAccessMethodId) =
+ managementService.testApiAccessMethodById(apiAccessMethodId)
+
+ fun getApiAccessMethodSettingById(id: ApiAccessMethodId) =
+ either<GetApiAccessMethodError, ApiAccessMethodSetting> {
+ accessMethods.value?.firstOrNull { it.id == id }
+ ?: raise(GetApiAccessMethodError.NotFound)
+ }
+
+ fun apiAccessMethodSettingById(id: ApiAccessMethodId): Flow<ApiAccessMethodSetting> =
+ accessMethods.mapNotNull { it?.firstOrNull { accessMethod -> accessMethod.id == id } }
+
+ fun enabledApiAccessMethods(): Flow<List<ApiAccessMethodSetting>> =
+ accessMethods.mapNotNull { it?.filter { accessMethod -> accessMethod.enabled } }
+
+ suspend fun setEnabledApiAccessMethod(id: ApiAccessMethodId, enabled: Boolean) = either {
+ val accessMethod = getApiAccessMethodSettingById(id).bind()
+ updateApiAccessMethod(accessMethod.copy(enabled = enabled)).bind()
+ }
+}
diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt
index f8073323c8..987b7e56ea 100644
--- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt
+++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt
@@ -3,6 +3,8 @@ package net.mullvad.mullvadvpn.lib.daemon.grpc
import android.net.LocalSocketAddress
import android.util.Log
import arrow.core.Either
+import arrow.core.raise.either
+import arrow.core.raise.ensure
import arrow.optics.copy
import arrow.optics.dsl.index
import arrow.optics.typeclasses.Index
@@ -42,7 +44,11 @@ import net.mullvad.mullvadvpn.lib.daemon.grpc.util.LogInterceptor
import net.mullvad.mullvadvpn.lib.daemon.grpc.util.connectivityFlow
import net.mullvad.mullvadvpn.lib.model.AccountData
import net.mullvad.mullvadvpn.lib.model.AccountNumber
+import net.mullvad.mullvadvpn.lib.model.AddApiAccessMethodError
import net.mullvad.mullvadvpn.lib.model.AddSplitTunnelingAppError
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethod
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodId
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodSetting
import net.mullvad.mullvadvpn.lib.model.AppId
import net.mullvad.mullvadvpn.lib.model.AppVersionInfo as ModelAppVersionInfo
import net.mullvad.mullvadvpn.lib.model.ClearAllOverridesError
@@ -67,6 +73,7 @@ import net.mullvad.mullvadvpn.lib.model.GetAccountHistoryError
import net.mullvad.mullvadvpn.lib.model.GetDeviceListError
import net.mullvad.mullvadvpn.lib.model.GetDeviceStateError
import net.mullvad.mullvadvpn.lib.model.LoginAccountError
+import net.mullvad.mullvadvpn.lib.model.NewAccessMethodSetting
import net.mullvad.mullvadvpn.lib.model.ObfuscationSettings
import net.mullvad.mullvadvpn.lib.model.Ownership as ModelOwnership
import net.mullvad.mullvadvpn.lib.model.PlayPurchase
@@ -84,9 +91,11 @@ import net.mullvad.mullvadvpn.lib.model.RelayItemId as ModelRelayItemId
import net.mullvad.mullvadvpn.lib.model.RelayList as ModelRelayList
import net.mullvad.mullvadvpn.lib.model.RelayList
import net.mullvad.mullvadvpn.lib.model.RelaySettings
+import net.mullvad.mullvadvpn.lib.model.RemoveApiAccessMethodError
import net.mullvad.mullvadvpn.lib.model.RemoveSplitTunnelingAppError
import net.mullvad.mullvadvpn.lib.model.SelectedObfuscation
import net.mullvad.mullvadvpn.lib.model.SetAllowLanError
+import net.mullvad.mullvadvpn.lib.model.SetApiAccessMethodError
import net.mullvad.mullvadvpn.lib.model.SetAutoConnectError
import net.mullvad.mullvadvpn.lib.model.SetDnsOptionsError
import net.mullvad.mullvadvpn.lib.model.SetObfuscationOptionsError
@@ -96,8 +105,11 @@ import net.mullvad.mullvadvpn.lib.model.SetWireguardMtuError
import net.mullvad.mullvadvpn.lib.model.SetWireguardQuantumResistantError
import net.mullvad.mullvadvpn.lib.model.Settings as ModelSettings
import net.mullvad.mullvadvpn.lib.model.SettingsPatchError
+import net.mullvad.mullvadvpn.lib.model.TestApiAccessMethodError
import net.mullvad.mullvadvpn.lib.model.TunnelState as ModelTunnelState
+import net.mullvad.mullvadvpn.lib.model.UnknownApiAccessMethodError
import net.mullvad.mullvadvpn.lib.model.UnknownCustomListError
+import net.mullvad.mullvadvpn.lib.model.UpdateApiAccessMethodError
import net.mullvad.mullvadvpn.lib.model.UpdateCustomListError
import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken
import net.mullvad.mullvadvpn.lib.model.WireguardConstraints as ModelWireguardConstraints
@@ -161,6 +173,10 @@ class ManagementService(
val wireguardEndpointData: Flow<ModelWireguardEndpointData> =
relayList.mapNotNull { it.wireguardEndpointData }
+ private val _mutableCurrentAccessMethod = MutableStateFlow<ApiAccessMethodSetting?>(null)
+ val currentAccessMethod: Flow<ApiAccessMethodSetting> =
+ _mutableCurrentAccessMethod.filterNotNull()
+
fun start() {
// Just to ensure that connection is set up since the connection won't be setup without a
// call to the daemon
@@ -196,9 +212,11 @@ class ManagementService(
_mutableVersionInfo.update { event.versionInfo.toDomain() }
ManagementInterface.DaemonEvent.EventCase.DEVICE ->
_mutableDeviceState.update { event.device.newState.toDomain() }
+ ManagementInterface.DaemonEvent.EventCase.NEW_ACCESS_METHOD -> {
+ _mutableCurrentAccessMethod.update { event.newAccessMethod.toDomain() }
+ }
ManagementInterface.DaemonEvent.EventCase.REMOVE_DEVICE -> {}
ManagementInterface.DaemonEvent.EventCase.EVENT_NOT_SET -> {}
- ManagementInterface.DaemonEvent.EventCase.NEW_ACCESS_METHOD -> {}
}
}
}
@@ -297,6 +315,7 @@ class ManagementService(
async { _mutableSettings.update { getSettings() } },
async { _mutableVersionInfo.update { getVersionInfo() } },
async { _mutableRelayList.update { getRelayList() } },
+ async { _mutableCurrentAccessMethod.update { getCurrentApiAccessMethod() } }
)
}
}
@@ -572,6 +591,55 @@ class ManagementService(
Either.catch { grpc.getWwwAuthToken(Empty.getDefaultInstance()) }
.map { WebsiteAuthToken.fromString(it.value) }
+ suspend fun addApiAccessMethod(
+ newAccessMethodSetting: NewAccessMethodSetting
+ ): Either<AddApiAccessMethodError, ApiAccessMethodId> =
+ Either.catch { grpc.addApiAccessMethod(newAccessMethodSetting.fromDomain()) }
+ .mapLeft(AddApiAccessMethodError::Unknown)
+ .map { ApiAccessMethodId.fromString(it.value) }
+
+ suspend fun removeApiAccessMethod(
+ apiAccessMethodId: ApiAccessMethodId
+ ): Either<RemoveApiAccessMethodError, Unit> =
+ Either.catch { grpc.removeApiAccessMethod(apiAccessMethodId.fromDomain()) }
+ .mapLeft(RemoveApiAccessMethodError::Unknown)
+ .mapEmpty()
+
+ suspend fun setApiAccessMethod(
+ apiAccessMethodId: ApiAccessMethodId
+ ): Either<SetApiAccessMethodError, Unit> =
+ Either.catch { grpc.setApiAccessMethod(apiAccessMethodId.fromDomain()) }
+ .mapLeft(SetApiAccessMethodError::Unknown)
+ .mapEmpty()
+
+ suspend fun updateApiAccessMethod(
+ apiAccessMethodSetting: ApiAccessMethodSetting
+ ): Either<UpdateApiAccessMethodError, Unit> =
+ Either.catch { grpc.updateApiAccessMethod(apiAccessMethodSetting.fromDomain()) }
+ .mapLeft(::UnknownApiAccessMethodError)
+ .mapEmpty()
+
+ private suspend fun getCurrentApiAccessMethod(): ApiAccessMethodSetting =
+ grpc.getCurrentApiAccessMethod(Empty.getDefaultInstance()).toDomain()
+
+ suspend fun testCustomApiAccessMethod(
+ customProxy: ApiAccessMethod.CustomProxy
+ ): Either<TestApiAccessMethodError, Unit> =
+ Either.catch { grpc.testCustomApiAccessMethod(customProxy.fromDomain()) }
+ .mapLeftStatus { TestApiAccessMethodError.Grpc }
+ .map { result ->
+ either { ensure(result.value) { TestApiAccessMethodError.CouldNotAccess } }
+ }
+
+ suspend fun testApiAccessMethodById(
+ apiAccessMethodId: ApiAccessMethodId
+ ): Either<TestApiAccessMethodError, Unit> =
+ Either.catch { grpc.testApiAccessMethodById(apiAccessMethodId.fromDomain()) }
+ .mapLeftStatus { TestApiAccessMethodError.Grpc }
+ .map { result ->
+ either { ensure(result.value) { TestApiAccessMethodError.CouldNotAccess } }
+ }
+
private fun <A> Either<A, Empty>.mapEmpty() = map {}
private inline fun <B, C> Either<Throwable, B>.mapLeftStatus(
diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt
index 4efd8c452e..014bafb85b 100644
--- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt
+++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt
@@ -1,6 +1,9 @@
package net.mullvad.mullvadvpn.lib.daemon.grpc.mapper
import mullvad_daemon.management_interface.ManagementInterface
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethod
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodId
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodSetting
import net.mullvad.mullvadvpn.lib.model.Constraint
import net.mullvad.mullvadvpn.lib.model.CustomDnsOptions
import net.mullvad.mullvadvpn.lib.model.CustomList
@@ -9,6 +12,7 @@ import net.mullvad.mullvadvpn.lib.model.DefaultDnsOptions
import net.mullvad.mullvadvpn.lib.model.DnsOptions
import net.mullvad.mullvadvpn.lib.model.DnsState
import net.mullvad.mullvadvpn.lib.model.GeoLocationId
+import net.mullvad.mullvadvpn.lib.model.NewAccessMethodSetting
import net.mullvad.mullvadvpn.lib.model.ObfuscationSettings
import net.mullvad.mullvadvpn.lib.model.Ownership
import net.mullvad.mullvadvpn.lib.model.PlayPurchase
@@ -18,6 +22,8 @@ import net.mullvad.mullvadvpn.lib.model.Providers
import net.mullvad.mullvadvpn.lib.model.RelayItemId
import net.mullvad.mullvadvpn.lib.model.RelaySettings
import net.mullvad.mullvadvpn.lib.model.SelectedObfuscation
+import net.mullvad.mullvadvpn.lib.model.SocksAuth
+import net.mullvad.mullvadvpn.lib.model.TransportProtocol
import net.mullvad.mullvadvpn.lib.model.Udp2TcpObfuscationSettings
import net.mullvad.mullvadvpn.lib.model.WireguardConstraints
@@ -160,3 +166,74 @@ internal fun PlayPurchase.fromDomain(): ManagementInterface.PlayPurchase =
.setPurchaseToken(purchaseToken.fromDomain())
.setProductId(productId)
.build()
+
+internal fun NewAccessMethodSetting.fromDomain(): ManagementInterface.NewAccessMethodSetting =
+ ManagementInterface.NewAccessMethodSetting.newBuilder()
+ .setName(name.value)
+ .setEnabled(enabled)
+ .setAccessMethod(
+ ManagementInterface.AccessMethod.newBuilder().setCustom(apiAccessMethod.fromDomain())
+ )
+ .build()
+
+internal fun ApiAccessMethod.fromDomain(): ManagementInterface.AccessMethod =
+ ManagementInterface.AccessMethod.newBuilder()
+ .let {
+ when (this) {
+ ApiAccessMethod.Direct ->
+ it.setDirect(ManagementInterface.AccessMethod.Direct.getDefaultInstance())
+ ApiAccessMethod.Bridges ->
+ it.setBridges(ManagementInterface.AccessMethod.Bridges.getDefaultInstance())
+ is ApiAccessMethod.CustomProxy -> it.setCustom(this.fromDomain())
+ }
+ }
+ .build()
+
+internal fun ApiAccessMethod.CustomProxy.fromDomain(): ManagementInterface.CustomProxy =
+ ManagementInterface.CustomProxy.newBuilder()
+ .let {
+ when (this) {
+ is ApiAccessMethod.CustomProxy.Shadowsocks -> it.setShadowsocks(this.fromDomain())
+ is ApiAccessMethod.CustomProxy.Socks5Remote -> it.setSocks5Remote(this.fromDomain())
+ }
+ }
+ .build()
+
+internal fun ApiAccessMethod.CustomProxy.Socks5Remote.fromDomain():
+ ManagementInterface.Socks5Remote =
+ ManagementInterface.Socks5Remote.newBuilder().setIp(ip).setPort(port.value).let {
+ auth?.let { auth -> it.setAuth(auth.fromDomain()) }
+ it.build()
+ }
+
+internal fun SocksAuth.fromDomain(): ManagementInterface.SocksAuth =
+ ManagementInterface.SocksAuth.newBuilder().setUsername(username).setPassword(password).build()
+
+internal fun ApiAccessMethod.CustomProxy.Shadowsocks.fromDomain(): ManagementInterface.Shadowsocks =
+ ManagementInterface.Shadowsocks.newBuilder()
+ .setIp(ip)
+ .setCipher(cipher.label)
+ .setPort(port.value)
+ .let {
+ if (password != null) {
+ it.setPassword(password)
+ }
+ it.build()
+ }
+
+internal fun TransportProtocol.fromDomain(): ManagementInterface.TransportProtocol =
+ when (this) {
+ TransportProtocol.Tcp -> ManagementInterface.TransportProtocol.TCP
+ TransportProtocol.Udp -> ManagementInterface.TransportProtocol.UDP
+ }
+
+internal fun ApiAccessMethodId.fromDomain(): ManagementInterface.UUID =
+ ManagementInterface.UUID.newBuilder().setValue(value.toString()).build()
+
+internal fun ApiAccessMethodSetting.fromDomain(): ManagementInterface.AccessMethodSetting =
+ ManagementInterface.AccessMethodSetting.newBuilder()
+ .setName(name.value)
+ .setId(id.fromDomain())
+ .setEnabled(enabled)
+ .setAccessMethod(apiAccessMethod.fromDomain())
+ .build()
diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt
index 59a94f62dc..13ebe74350 100644
--- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt
+++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt
@@ -13,8 +13,13 @@ import net.mullvad.mullvadvpn.lib.model.AccountData
import net.mullvad.mullvadvpn.lib.model.AccountId
import net.mullvad.mullvadvpn.lib.model.AccountNumber
import net.mullvad.mullvadvpn.lib.model.ActionAfterDisconnect
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethod
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodId
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodName
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodSetting
import net.mullvad.mullvadvpn.lib.model.AppId
import net.mullvad.mullvadvpn.lib.model.AppVersionInfo
+import net.mullvad.mullvadvpn.lib.model.Cipher
import net.mullvad.mullvadvpn.lib.model.Constraint
import net.mullvad.mullvadvpn.lib.model.CustomDnsOptions
import net.mullvad.mullvadvpn.lib.model.CustomList
@@ -53,6 +58,7 @@ import net.mullvad.mullvadvpn.lib.model.RelayOverride
import net.mullvad.mullvadvpn.lib.model.RelaySettings
import net.mullvad.mullvadvpn.lib.model.SelectedObfuscation
import net.mullvad.mullvadvpn.lib.model.Settings
+import net.mullvad.mullvadvpn.lib.model.SocksAuth
import net.mullvad.mullvadvpn.lib.model.SplitTunnelSettings
import net.mullvad.mullvadvpn.lib.model.TransportProtocol
import net.mullvad.mullvadvpn.lib.model.TunnelEndpoint
@@ -246,7 +252,8 @@ internal fun ManagementInterface.Settings.toDomain(): Settings =
tunnelOptions = tunnelOptions.toDomain(),
relayOverrides = relayOverridesList.map { it.toDomain() },
showBetaReleases = showBetaReleases,
- splitTunnelSettings = splitTunnel.toDomain()
+ splitTunnelSettings = splitTunnel.toDomain(),
+ apiAccessMethodSettings = apiAccessMethods.toDomain()
)
internal fun ManagementInterface.RelayOverride.toDomain(): RelayOverride =
@@ -519,3 +526,53 @@ internal fun ManagementInterface.SplitTunnelSettings.toDomain(): SplitTunnelSett
internal fun ManagementInterface.PlayPurchasePaymentToken.toDomain(): PlayPurchasePaymentToken =
PlayPurchasePaymentToken(value = token)
+
+internal fun ManagementInterface.ApiAccessMethodSettings.toDomain(): List<ApiAccessMethodSetting> =
+ listOf(direct.toDomain(), mullvadBridges.toDomain()).plus(customList.map { it.toDomain() })
+
+internal fun ManagementInterface.AccessMethodSetting.toDomain(): ApiAccessMethodSetting =
+ ApiAccessMethodSetting(
+ id = ApiAccessMethodId.fromString(id.value),
+ name = ApiAccessMethodName.fromString(name),
+ enabled = enabled,
+ apiAccessMethod = accessMethod.toDomain()
+ )
+
+internal fun ManagementInterface.AccessMethod.toDomain(): ApiAccessMethod =
+ when {
+ hasDirect() -> ApiAccessMethod.Direct
+ hasBridges() -> ApiAccessMethod.Bridges
+ hasCustom() -> custom.toDomain()
+ else -> error("Type not found")
+ }
+
+internal fun ManagementInterface.CustomProxy.toDomain(): ApiAccessMethod.CustomProxy =
+ when {
+ hasShadowsocks() -> shadowsocks.toDomain()
+ hasSocks5Remote() -> socks5Remote.toDomain()
+ hasSocks5Local() -> error("Socks5 local not supported")
+ else -> error("Custom proxy not found")
+ }
+
+internal fun ManagementInterface.Shadowsocks.toDomain(): ApiAccessMethod.CustomProxy.Shadowsocks =
+ ApiAccessMethod.CustomProxy.Shadowsocks(
+ ip = ip,
+ port = Port(port),
+ password = password,
+ cipher = Cipher.fromString(cipher)
+ )
+
+internal fun ManagementInterface.Socks5Remote.toDomain(): ApiAccessMethod.CustomProxy.Socks5Remote =
+ ApiAccessMethod.CustomProxy.Socks5Remote(
+ ip = ip,
+ port = Port(port),
+ auth =
+ if (hasAuth()) {
+ auth.toDomain()
+ } else {
+ null
+ }
+ )
+
+internal fun ManagementInterface.SocksAuth.toDomain(): SocksAuth =
+ SocksAuth(username = username, password = password)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AddApiAccessMethodError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AddApiAccessMethodError.kt
new file mode 100644
index 0000000000..d0c741e53c
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AddApiAccessMethodError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface AddApiAccessMethodError {
+ data class Unknown(val t: Throwable) : AddApiAccessMethodError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethod.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethod.kt
new file mode 100644
index 0000000000..d8762af391
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethod.kt
@@ -0,0 +1,23 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+sealed interface ApiAccessMethod : Parcelable {
+ @Parcelize data object Direct : ApiAccessMethod
+
+ @Parcelize data object Bridges : ApiAccessMethod
+
+ sealed interface CustomProxy : ApiAccessMethod {
+ @Parcelize
+ data class Socks5Remote(val ip: String, val port: Port, val auth: SocksAuth?) : CustomProxy
+
+ @Parcelize
+ data class Shadowsocks(
+ val ip: String,
+ val port: Port,
+ val password: String?,
+ val cipher: Cipher
+ ) : CustomProxy
+ }
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethodId.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethodId.kt
new file mode 100644
index 0000000000..a6dc0628df
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethodId.kt
@@ -0,0 +1,14 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import android.os.Parcelable
+import java.util.UUID
+import kotlinx.parcelize.Parcelize
+
+@JvmInline
+@Parcelize
+value class ApiAccessMethodId private constructor(val value: UUID) : Parcelable {
+
+ companion object {
+ fun fromString(id: String) = ApiAccessMethodId(value = UUID.fromString(id))
+ }
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethodName.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethodName.kt
new file mode 100644
index 0000000000..b1eada2982
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethodName.kt
@@ -0,0 +1,19 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+@JvmInline
+value class ApiAccessMethodName private constructor(val value: String) : Parcelable {
+ override fun toString() = value
+
+ companion object {
+ const val MAX_LENGTH = 30
+
+ fun fromString(name: String): ApiAccessMethodName {
+ val trimmedName = name.trim().take(MAX_LENGTH)
+ return ApiAccessMethodName(trimmedName)
+ }
+ }
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethodSetting.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethodSetting.kt
new file mode 100644
index 0000000000..07e1c185df
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethodSetting.kt
@@ -0,0 +1,8 @@
+package net.mullvad.mullvadvpn.lib.model
+
+data class ApiAccessMethodSetting(
+ val id: ApiAccessMethodId,
+ val name: ApiAccessMethodName,
+ val enabled: Boolean,
+ val apiAccessMethod: ApiAccessMethod
+)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Cipher.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Cipher.kt
new file mode 100644
index 0000000000..4571c824dd
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Cipher.kt
@@ -0,0 +1,34 @@
+package net.mullvad.mullvadvpn.lib.model
+
+// All suppported shadowsocks ciphers
+enum class Cipher(val label: String) {
+ AES_128_CFB("aes-128-cfb"),
+ AES_128_CFB1("aes-128-cfb1"),
+ AES_128_CFB8("aes-128-cfb8"),
+ AES_128_CFB128("aes-128-cfb128"),
+ AES_256_CFB("aes-256-cfb"),
+ AES_256_CFB1("aes-256-cfb1"),
+ AES_256_CFB8("aes-256-cfb8"),
+ AES_256_CFB128("aes-256-cfb128"),
+ RC4("rc4"),
+ RC4_MD5("rc4-md5"),
+ CHACHA20("chacha20"),
+ SALSA20("salsa20"),
+ CHACHA20_IETF("chacha20-ietf"),
+ AES_128_GCM("aes-128-gcm"),
+ AES_256_GCM("aes-256-gcm"),
+ CHACHA20_IETF_POLY1305("chacha20-ietf-poly1305"),
+ XCHACHA20_IETF_POLY1305("xchacha20-ietf-poly1305"),
+ AES_128_PMAC_SIV("aes-128-pmac-siv"),
+ AES_256_PMAC_SIV("aes-256-pmac-siv");
+
+ override fun toString(): String = label
+
+ companion object {
+ fun fromString(input: String) = Cipher.entries.first { it.label == input }
+
+ fun listAll() = Cipher.entries.sortedBy { it.label }
+
+ fun first() = listAll().first()
+ }
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetApiAccessMethodError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetApiAccessMethodError.kt
new file mode 100644
index 0000000000..47f2ad29cc
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetApiAccessMethodError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface GetApiAccessMethodError : UpdateApiAccessMethodError {
+ data object NotFound : GetApiAccessMethodError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetCurrentApiAccessMethodError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetCurrentApiAccessMethodError.kt
new file mode 100644
index 0000000000..54c9791d0b
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/GetCurrentApiAccessMethodError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface GetCurrentApiAccessMethodError {
+ data class Unknown(val t: Throwable) : GetCurrentApiAccessMethodError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/InvalidDataError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/InvalidDataError.kt
new file mode 100644
index 0000000000..450d94e691
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/InvalidDataError.kt
@@ -0,0 +1,27 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface InvalidDataError {
+ sealed interface NameError : InvalidDataError {
+ data object Required : NameError
+ }
+
+ sealed interface ServerIpError : InvalidDataError {
+ data object Required : ServerIpError
+
+ data object Invalid : ServerIpError
+ }
+
+ sealed interface PortError : InvalidDataError {
+ data object Required : PortError
+
+ data class Invalid(val portError: ParsePortError) : PortError
+ }
+
+ sealed interface UserNameError : InvalidDataError {
+ data object Required : UserNameError
+ }
+
+ sealed interface PasswordError : InvalidDataError {
+ data object Required : PasswordError
+ }
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NewAccessMethodSetting.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NewAccessMethodSetting.kt
new file mode 100644
index 0000000000..990dc300bc
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/NewAccessMethodSetting.kt
@@ -0,0 +1,11 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class NewAccessMethodSetting(
+ val name: ApiAccessMethodName,
+ val enabled: Boolean,
+ val apiAccessMethod: ApiAccessMethod.CustomProxy
+) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Port.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Port.kt
index 5ce44d0565..e6ca1e01b9 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Port.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Port.kt
@@ -9,6 +9,9 @@ import kotlinx.parcelize.Parcelize
@JvmInline
@Parcelize
value class Port(val value: Int) : Parcelable {
+
+ override fun toString(): String = value.toString()
+
companion object {
fun fromString(value: String): Either<ParsePortError, Port> = either {
val number = value.toIntOrNull() ?: raise(ParsePortError.NotANumber(value))
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RemoveApiAccessMethodError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RemoveApiAccessMethodError.kt
new file mode 100644
index 0000000000..88516761c4
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RemoveApiAccessMethodError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface RemoveApiAccessMethodError {
+ data class Unknown(val t: Throwable) : RemoveApiAccessMethodError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetApiAccessMethodError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetApiAccessMethodError.kt
new file mode 100644
index 0000000000..1fa0544a82
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SetApiAccessMethodError.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface SetApiAccessMethodError {
+ data class Unknown(val t: Throwable) : SetApiAccessMethodError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Settings.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Settings.kt
index c5191531be..e801397b27 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Settings.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Settings.kt
@@ -12,7 +12,8 @@ data class Settings(
val tunnelOptions: TunnelOptions,
val relayOverrides: List<RelayOverride>,
val showBetaReleases: Boolean,
- val splitTunnelSettings: SplitTunnelSettings
+ val splitTunnelSettings: SplitTunnelSettings,
+ val apiAccessMethodSettings: List<ApiAccessMethodSetting>
) {
companion object
}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SocksAuth.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SocksAuth.kt
new file mode 100644
index 0000000000..ff17641d63
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/SocksAuth.kt
@@ -0,0 +1,6 @@
+package net.mullvad.mullvadvpn.lib.model
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize data class SocksAuth(val username: String, val password: String) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TestApiAccessMethodError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TestApiAccessMethodError.kt
new file mode 100644
index 0000000000..ce69919110
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TestApiAccessMethodError.kt
@@ -0,0 +1,9 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface TestApiAccessMethodError {
+ data object CouldNotAccess : TestApiAccessMethodError
+
+ data object Grpc : TestApiAccessMethodError
+
+ data class Unknown(val t: Throwable) : TestApiAccessMethodError
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/UnknownApiAccessMethodError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/UnknownApiAccessMethodError.kt
new file mode 100644
index 0000000000..06cb81fc5e
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/UnknownApiAccessMethodError.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model
+
+data class UnknownApiAccessMethodError(val throwable: Throwable) : UpdateApiAccessMethodError
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/UpdateApiAccessMethodError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/UpdateApiAccessMethodError.kt
new file mode 100644
index 0000000000..0597967375
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/UpdateApiAccessMethodError.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model
+
+sealed interface UpdateApiAccessMethodError