mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
feat(android): Add support for per-app VPN configurable through MDM (#3657)
Refs #3613
This commit is contained in:
@@ -3,7 +3,6 @@ package dev.firezone.android.core.data
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import dev.firezone.android.BuildConfig
|
||||
import dev.firezone.android.core.data.model.Config
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
@@ -19,7 +18,6 @@ internal class RepositoryImpl
|
||||
private val context: Context,
|
||||
private val coroutineDispatcher: CoroutineDispatcher,
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
private val appRestrictions: Bundle,
|
||||
) : Repository {
|
||||
override fun getConfigSync(): Config {
|
||||
return Config(
|
||||
@@ -69,15 +67,10 @@ internal class RepositoryImpl
|
||||
|
||||
override fun getToken(): Flow<String?> =
|
||||
flow {
|
||||
emit(
|
||||
appRestrictions.getString(TOKEN_KEY, null)
|
||||
?: sharedPreferences.getString(TOKEN_KEY, null),
|
||||
)
|
||||
emit(sharedPreferences.getString(TOKEN_KEY, null))
|
||||
}.flowOn(coroutineDispatcher)
|
||||
|
||||
override fun getTokenSync(): String? =
|
||||
appRestrictions.getString(TOKEN_KEY, null)
|
||||
?: sharedPreferences.getString(TOKEN_KEY, null)
|
||||
override fun getTokenSync(): String? = sharedPreferences.getString(TOKEN_KEY, null)
|
||||
|
||||
override fun getStateSync(): String? = sharedPreferences.getString(STATE_KEY, null)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package dev.firezone.android.core.di
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import androidx.core.content.getSystemService
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
@@ -16,6 +17,11 @@ import kotlinx.coroutines.CoroutineDispatcher
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class DataModule {
|
||||
@Provides
|
||||
internal fun provideApplicationRestrictions(
|
||||
@ApplicationContext context: Context,
|
||||
): Bundle = (context.getSystemService(Context.RESTRICTIONS_SERVICE) as android.content.RestrictionsManager).applicationRestrictions
|
||||
|
||||
@Provides
|
||||
internal fun provideRepository(
|
||||
@ApplicationContext context: Context,
|
||||
@@ -26,9 +32,5 @@ class DataModule {
|
||||
context,
|
||||
coroutineDispatcher,
|
||||
sharedPreferences,
|
||||
(
|
||||
context.getSystemService(Context.RESTRICTIONS_SERVICE)
|
||||
as android.content.RestrictionsManager
|
||||
).applicationRestrictions,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package dev.firezone.android.features.splash.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
@@ -10,7 +11,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dev.firezone.android.core.data.Repository
|
||||
import dev.firezone.android.tunnel.TunnelService
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -21,6 +21,7 @@ internal class SplashViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
private val repo: Repository,
|
||||
private val applicationRestrictions: Bundle,
|
||||
) : ViewModel() {
|
||||
private val actionMutableLiveData = MutableLiveData<ViewAction>()
|
||||
val actionLiveData: LiveData<ViewAction> = actionMutableLiveData
|
||||
@@ -32,15 +33,14 @@ internal class SplashViewModel
|
||||
if (!hasVpnPermissions(context)) {
|
||||
actionMutableLiveData.postValue(ViewAction.NavigateToVpnPermission)
|
||||
} else {
|
||||
repo.getToken().collect {
|
||||
if (it.isNullOrBlank()) {
|
||||
actionMutableLiveData.postValue(ViewAction.NavigateToSignIn)
|
||||
} else {
|
||||
// token will be re-read by the TunnelService
|
||||
if (!TunnelService.isRunning(context)) TunnelService.start(context)
|
||||
val token = applicationRestrictions.getString("token") ?: repo.getTokenSync()
|
||||
if (token.isNullOrBlank()) {
|
||||
actionMutableLiveData.postValue(ViewAction.NavigateToSignIn)
|
||||
} else {
|
||||
// token will be re-read by the TunnelService
|
||||
if (!TunnelService.isRunning(context)) TunnelService.start(context)
|
||||
|
||||
actionMutableLiveData.postValue(ViewAction.NavigateToSession)
|
||||
}
|
||||
actionMutableLiveData.postValue(ViewAction.NavigateToSession)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.content.Intent
|
||||
import android.net.VpnService
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
@@ -38,6 +39,9 @@ class TunnelService : VpnService() {
|
||||
@Inject
|
||||
internal lateinit var repo: Repository
|
||||
|
||||
@Inject
|
||||
internal lateinit var appRestrictions: Bundle
|
||||
|
||||
@Inject
|
||||
internal lateinit var moshi: Moshi
|
||||
|
||||
@@ -168,7 +172,7 @@ class TunnelService : VpnService() {
|
||||
Log.d(TAG, "onDisconnect: $error")
|
||||
Firebase.crashlytics.log("onDisconnect: $error")
|
||||
|
||||
// This is a no-op if the token is being read from MDM
|
||||
// Clear any user tokens and actorNames
|
||||
repo.clearToken()
|
||||
repo.clearActorName()
|
||||
|
||||
@@ -214,7 +218,7 @@ class TunnelService : VpnService() {
|
||||
}
|
||||
|
||||
private fun connect() {
|
||||
val token = repo.getTokenSync()
|
||||
val token = appRestrictions.getString("token") ?: repo.getTokenSync()
|
||||
val config = repo.getConfigSync()
|
||||
|
||||
if (!token.isNullOrBlank()) {
|
||||
@@ -321,6 +325,20 @@ class TunnelService : VpnService() {
|
||||
Firebase.crashlytics.log("IPv6 Address: $tunnelIpv6Address")
|
||||
addAddress(tunnelIpv6Address!!, 128)
|
||||
|
||||
appRestrictions.getString("allowedApplications")?.let {
|
||||
Firebase.crashlytics.log("Allowed applications: $it")
|
||||
it.split(",").forEach { p ->
|
||||
addAllowedApplication(p.trim())
|
||||
}
|
||||
}
|
||||
|
||||
appRestrictions.getString("disallowedApplications")?.let {
|
||||
Firebase.crashlytics.log("Disallowed applications: $it")
|
||||
it.split(",").forEach { p ->
|
||||
addDisallowedApplication(p.trim())
|
||||
}
|
||||
}
|
||||
|
||||
setSession(SESSION_NAME)
|
||||
setMtu(MTU)
|
||||
}.establish()!!.let {
|
||||
|
||||
@@ -39,5 +39,9 @@
|
||||
|
||||
<!-- Managed Configuration -->
|
||||
<string name="config_token_title">Token</string>
|
||||
<string name="config_token_description">The token used for authentication. Set this to a service account token to achieve headless operation.</string>
|
||||
<string name="config_token_description">The token used for authentication. Set this to a service account token to enable headless operation.</string>
|
||||
<string name="config_allowed_applications_title">Allowed Applications</string>
|
||||
<string name="config_allowed_applications_description">A comma-separated list of application package IDs that are allowed to use the Firezone tunnel. If this list is empty, all applications are allowed.</string>
|
||||
<string name="config_disallowed_applications_title">Disallowed Applications</string>
|
||||
<string name="config_disallowed_applications_description">A comma-separated list of application package IDs that are disallowed to use the Firezone tunnel and will be routed outside of it. If this list is empty, no applications are disallowed.</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,15 @@
|
||||
android:restrictionType="string"
|
||||
android:title="@string/config_token_title" />
|
||||
|
||||
<restriction
|
||||
android:description="@string/config_allowed_applications_description"
|
||||
android:key="allowedApplications"
|
||||
android:restrictionType="string"
|
||||
android:title="@string/config_allowed_applications_title" />
|
||||
|
||||
<restriction
|
||||
android:description="@string/config_disallowed_applications_description"
|
||||
android:key="disallowedApplications"
|
||||
android:restrictionType="string"
|
||||
android:title="@string/config_disallowed_applications_title" />
|
||||
</restrictions>
|
||||
|
||||
Reference in New Issue
Block a user