Skip to content

[PM-19108] Add privileged app management screen #4862

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: innovation-sprint/privileged-apps/trust-dialog
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,16 @@ object Fido2ProviderModule {

@Provides
@Singleton
fun provideFido2PrivilegedAppRepository(
fun providePrivilegedAppRepository(
fido2PrivilegedAppDiskSource: Fido2PrivilegedAppDiskSource,
assetManager: AssetManager,
dispatcherManager: DispatcherManager,
json: Json,
): PrivilegedAppRepository =
PrivilegedAppRepositoryImpl(
fido2PrivilegedAppDiskSource = fido2PrivilegedAppDiskSource,
assetManager = assetManager,
dispatcherManager = dispatcherManager,
json = json,
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.x8bit.bitwarden.data.autofill.fido2.model

/**
* Represents privileged applications that are trusted by various sources.
*/
data class PrivilegedAppData(
val googleTrustedApps: PrivilegedAppAllowListJson,
val communityTrustedApps: PrivilegedAppAllowListJson,
val userTrustedApps: PrivilegedAppAllowListJson,
)
Original file line number Diff line number Diff line change
@@ -1,27 +1,49 @@
package com.x8bit.bitwarden.data.autofill.fido2.repository

import com.x8bit.bitwarden.data.autofill.fido2.model.PrivilegedAppAllowListJson
import kotlinx.coroutines.flow.Flow
import com.x8bit.bitwarden.data.autofill.fido2.model.PrivilegedAppData
import com.x8bit.bitwarden.data.platform.repository.model.DataState
import kotlinx.coroutines.flow.StateFlow

/**
* Repository for managing privileged apps trusted by the user.
*/
interface PrivilegedAppRepository {

/**
* Flow that represents the trusted privileged apps data.
*/
val trustedAppDataStateFlow: StateFlow<DataState<PrivilegedAppData>>

/**
* Flow of the user's trusted privileged apps.
*/
val userTrustedPrivilegedAppsFlow: Flow<PrivilegedAppAllowListJson>
val userTrustedAppsFlow: StateFlow<DataState<PrivilegedAppAllowListJson>>

/**
* Flow of the Google's trusted privileged apps.
*/
val googleTrustedPrivilegedAppsFlow: StateFlow<DataState<PrivilegedAppAllowListJson>>

/**
* Flow of the community's trusted privileged apps.
*/
val communityTrustedAppsFlow: StateFlow<DataState<PrivilegedAppAllowListJson>>

/**
* List the user's trusted privileged apps.
*/
suspend fun getAllUserTrustedPrivilegedApps(): PrivilegedAppAllowListJson
suspend fun getUserTrustedPrivilegedAppsOrNull(): PrivilegedAppAllowListJson?

/**
* List Google's trusted privileged apps.
*/
suspend fun getGoogleTrustedPrivilegedAppsOrNull(): PrivilegedAppAllowListJson?

/**
* Returns true if the given [packageName] and [signature] are trusted.
* List community's trusted privileged apps.
*/
suspend fun isPrivilegedAppAllowed(packageName: String, signature: String): Boolean
suspend fun getCommunityTrustedPrivilegedAppsOrNull(): PrivilegedAppAllowListJson?

/**
* Adds the given [packageName] and [signature] to the list of trusted privileged apps.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,33 @@ package com.x8bit.bitwarden.data.autofill.fido2.repository
import com.x8bit.bitwarden.data.autofill.fido2.datasource.disk.Fido2PrivilegedAppDiskSource
import com.x8bit.bitwarden.data.autofill.fido2.datasource.disk.entity.Fido2PrivilegedAppInfoEntity
import com.x8bit.bitwarden.data.autofill.fido2.model.PrivilegedAppAllowListJson
import kotlinx.coroutines.flow.Flow
import com.x8bit.bitwarden.data.autofill.fido2.model.PrivilegedAppData
import com.x8bit.bitwarden.data.platform.manager.AssetManager
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.platform.repository.model.DataState
import com.x8bit.bitwarden.data.platform.repository.util.combineDataStates
import com.x8bit.bitwarden.data.platform.util.decodeFromStringOrNull
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json

/**
* A "stop timeout delay" in milliseconds used to let a shared coroutine continue to run for the
* specified period of time after it no longer has subscribers.
*/
private const val STOP_TIMEOUT_DELAY_MS: Long = 1000L
private const val GOOGLE_ALLOW_LIST_FILE_NAME = "fido2_privileged_google.json"
private const val COMMUNITY_ALLOW_LIST_FILE_NAME = "fido2_privileged_community.json"
private const val ANDROID_TYPE = "android"
private const val RELEASE_BUILD = "release"

Expand All @@ -15,25 +38,104 @@ private const val RELEASE_BUILD = "release"
*/
class PrivilegedAppRepositoryImpl(
private val fido2PrivilegedAppDiskSource: Fido2PrivilegedAppDiskSource,
private val assetManager: AssetManager,
dispatcherManager: DispatcherManager,
private val json: Json,
) : PrivilegedAppRepository {

override val userTrustedPrivilegedAppsFlow: Flow<PrivilegedAppAllowListJson> =
fido2PrivilegedAppDiskSource.userTrustedPrivilegedAppsFlow
.map { it.toFido2PrivilegedAppAllowListJson() }
private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)
private val ioScope = CoroutineScope(dispatcherManager.io)

private val mutableUserTrustedAppsFlow =
MutableStateFlow<DataState<PrivilegedAppAllowListJson>>(DataState.Loading)
private val mutableGoogleTrustedAppsFlow =
MutableStateFlow<DataState<PrivilegedAppAllowListJson>>(DataState.Loading)
private val mutableCommunityTrustedPrivilegedAppsFlow =
MutableStateFlow<DataState<PrivilegedAppAllowListJson>>(DataState.Loading)

override val trustedAppDataStateFlow: StateFlow<DataState<PrivilegedAppData>> =
combine(
userTrustedAppsFlow,
googleTrustedPrivilegedAppsFlow,
communityTrustedAppsFlow,
) { userAppsState, googleAppsState, communityAppsState ->
combineDataStates(
userAppsState,
googleAppsState,
communityAppsState,
) { userApps, googleApps, communityApps ->
PrivilegedAppData(
googleTrustedApps = googleApps,
communityTrustedApps = communityApps,
userTrustedApps = userApps,
)
}
}
.stateIn(
scope = unconfinedScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = STOP_TIMEOUT_DELAY_MS),
initialValue = DataState.Loading,
)

override val userTrustedAppsFlow: StateFlow<DataState<PrivilegedAppAllowListJson>>
get() = mutableUserTrustedAppsFlow.asStateFlow()

override val googleTrustedPrivilegedAppsFlow: StateFlow<DataState<PrivilegedAppAllowListJson>>
get() = mutableGoogleTrustedAppsFlow.asStateFlow()

override val communityTrustedAppsFlow: StateFlow<DataState<PrivilegedAppAllowListJson>>
get() = mutableCommunityTrustedPrivilegedAppsFlow.asStateFlow()

init {
ioScope.launch {
val googleAppsDataState = assetManager.readAsset(fileName = GOOGLE_ALLOW_LIST_FILE_NAME)
.map { json.decodeFromString<PrivilegedAppAllowListJson>(it) }
.fold(
onSuccess = { DataState.Loaded(it) },
onFailure = { DataState.Error(it) },
)

val communityAppsDataState =
assetManager.readAsset(fileName = COMMUNITY_ALLOW_LIST_FILE_NAME)
.map { json.decodeFromString<PrivilegedAppAllowListJson>(it) }
.fold(
onSuccess = { DataState.Loaded(it) },
onFailure = { DataState.Error(it) },
)

override suspend fun getAllUserTrustedPrivilegedApps(): PrivilegedAppAllowListJson =
fido2PrivilegedAppDiskSource.getAllUserTrustedPrivilegedApps()
mutableGoogleTrustedAppsFlow.value = googleAppsDataState
mutableCommunityTrustedPrivilegedAppsFlow.value = communityAppsDataState

fido2PrivilegedAppDiskSource.userTrustedPrivilegedAppsFlow
.map { DataState.Loaded(it.toFido2PrivilegedAppAllowListJson()) }
.onEach {
mutableUserTrustedAppsFlow.value = it
}
.launchIn(ioScope)
}
}

override suspend fun getUserTrustedPrivilegedAppsOrNull(): PrivilegedAppAllowListJson =
fido2PrivilegedAppDiskSource
.getAllUserTrustedPrivilegedApps()
.toFido2PrivilegedAppAllowListJson()

override suspend fun isPrivilegedAppAllowed(
packageName: String,
signature: String,
): Boolean = fido2PrivilegedAppDiskSource
.isPrivilegedAppTrustedByUser(
packageName = packageName,
signature = signature,
)
override suspend fun getGoogleTrustedPrivilegedAppsOrNull(): PrivilegedAppAllowListJson? =
withContext(ioScope.coroutineContext) {
assetManager
.readAsset(fileName = GOOGLE_ALLOW_LIST_FILE_NAME)
.map { json.decodeFromStringOrNull<PrivilegedAppAllowListJson>(it) }
.getOrNull()
}

override suspend fun getCommunityTrustedPrivilegedAppsOrNull(): PrivilegedAppAllowListJson? {
return withContext(ioScope.coroutineContext) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this specifically for the json .decodeFromStringOrNull part?

assetManager
.readAsset(fileName = COMMUNITY_ALLOW_LIST_FILE_NAME)
.map { json.decodeFromStringOrNull<PrivilegedAppAllowListJson>(it) }
.getOrNull()
}
}

override suspend fun addTrustedPrivilegedApp(
packageName: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.autoFillDestina
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.blockautofill.blockAutoFillDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.blockautofill.navigateToBlockAutoFillScreen
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.navigateToAutoFill
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.privilegedapps.about.aboutPrivilegedAppsDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.privilegedapps.about.navigateToAboutPrivilegedAppsScreen
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.privilegedapps.list.navigateToPrivilegedAppsList
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.privilegedapps.list.privilegedAppsListDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.other.navigateToOther
import com.x8bit.bitwarden.ui.platform.feature.settings.other.otherDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.vault.navigateToVaultSettings
Expand Down Expand Up @@ -66,6 +70,10 @@ fun NavGraphBuilder.settingsGraph(
onNavigateBack = { navController.popBackStack() },
onNavigateToBlockAutoFillScreen = { navController.navigateToBlockAutoFillScreen() },
onNavigateToSetupAutofill = onNavigateToSetupAutoFillScreen,
onNavigateToTrustedAppsScreen = { navController.navigateToPrivilegedAppsList() },
onNavigateToAboutPrivilegedAppsScreen = {
navController.navigateToAboutPrivilegedAppsScreen()
},
)
otherDestination(onNavigateBack = { navController.popBackStack() })
vaultSettingsDestination(
Expand All @@ -75,6 +83,8 @@ fun NavGraphBuilder.settingsGraph(
onNavigateToImportLogins = onNavigateToImportLogins,
)
blockAutoFillDestination(onNavigateBack = { navController.popBackStack() })
privilegedAppsListDestination(onNavigateBack = { navController.popBackStack() })
aboutPrivilegedAppsDestination(navigateBack = { navController.popBackStack() })
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ fun NavGraphBuilder.autoFillDestination(
onNavigateBack: () -> Unit,
onNavigateToBlockAutoFillScreen: () -> Unit,
onNavigateToSetupAutofill: () -> Unit,
onNavigateToTrustedAppsScreen: () -> Unit,
onNavigateToAboutPrivilegedAppsScreen: () -> Unit,
) {
composableWithPushTransitions(
route = AUTO_FILL_ROUTE,
Expand All @@ -22,6 +24,8 @@ fun NavGraphBuilder.autoFillDestination(
onNavigateBack = onNavigateBack,
onNavigateToBlockAutoFillScreen = onNavigateToBlockAutoFillScreen,
onNavigateToSetupAutofill = onNavigateToSetupAutofill,
onNavigateToPrivilegedAppsScreen = onNavigateToTrustedAppsScreen,
onNavigateToAboutPrivilegedAppsScreen = onNavigateToAboutPrivilegedAppsScreen,
)
}
}
Expand Down
Loading