Merge pull request #1128 from vector-im/feature/bma/cleanupOidc

Cleanup OIDC
This commit is contained in:
Benoit Marty 2023-08-28 15:08:36 +02:00 committed by GitHub
commit 011fa6af67
5 changed files with 40 additions and 10 deletions

View file

@ -22,7 +22,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.lifecycleScope
import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.composable.Children
import com.bumble.appyx.core.lifecycle.subscribe
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.Plugin
@ -33,6 +35,8 @@ import com.bumble.appyx.navmodel.backstack.operation.singleTop
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.login.api.oidc.OidcAction
import io.element.android.features.login.api.oidc.OidcActionFlow
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
import io.element.android.features.login.impl.oidc.CustomTabAvailabilityChecker import io.element.android.features.login.impl.oidc.CustomTabAvailabilityChecker
import io.element.android.features.login.impl.oidc.customtab.CustomTabHandler import io.element.android.features.login.impl.oidc.customtab.CustomTabHandler
@ -51,6 +55,8 @@ import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.auth.OidcDetails import io.element.android.libraries.matrix.api.auth.OidcDetails
import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.theme.ElementTheme
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@ContributesNode(AppScope::class) @ContributesNode(AppScope::class)
@ -61,6 +67,7 @@ class LoginFlowNode @AssistedInject constructor(
private val customTabHandler: CustomTabHandler, private val customTabHandler: CustomTabHandler,
private val accountProviderDataSource: AccountProviderDataSource, private val accountProviderDataSource: AccountProviderDataSource,
private val defaultLoginUserStory: DefaultLoginUserStory, private val defaultLoginUserStory: DefaultLoginUserStory,
private val oidcActionFlow: OidcActionFlow,
) : BackstackNode<LoginFlowNode.NavTarget>( ) : BackstackNode<LoginFlowNode.NavTarget>(
backstack = BackStack( backstack = BackStack(
initialElement = NavTarget.ConfirmAccountProvider, initialElement = NavTarget.ConfirmAccountProvider,
@ -78,9 +85,26 @@ class LoginFlowNode @AssistedInject constructor(
private val inputs: Inputs = inputs() private val inputs: Inputs = inputs()
private var customChromeTabStarted = false
override fun onBuilt() { override fun onBuilt() {
super.onBuilt() super.onBuilt()
defaultLoginUserStory.setLoginFlowIsDone(false) defaultLoginUserStory.setLoginFlowIsDone(false)
lifecycle.subscribe(
onResume = {
if (customChromeTabStarted) {
customChromeTabStarted = false
// Workaround to detect that the Custom Chrome Tab has been closed
// If there is no coming OidcAction (that would end this Node),
// consider that the user has cancelled the login
// by pressing back or by closing the Custom Chrome Tab.
lifecycleScope.launch {
delay(5000)
oidcActionFlow.post(OidcAction.GoBack)
}
}
}
)
} }
sealed interface NavTarget : Parcelable { sealed interface NavTarget : Parcelable {
@ -113,7 +137,10 @@ class LoginFlowNode @AssistedInject constructor(
override fun onOidcDetails(oidcDetails: OidcDetails) { override fun onOidcDetails(oidcDetails: OidcDetails) {
if (customTabAvailabilityChecker.supportCustomTab()) { if (customTabAvailabilityChecker.supportCustomTab()) {
// In this case open a Chrome Custom tab // In this case open a Chrome Custom tab
activity?.let { customTabHandler.open(it, darkTheme, oidcDetails.url) } activity?.let {
customChromeTabStarted = true
customTabHandler.open(it, darkTheme, oidcDetails.url)
}
} else { } else {
// Fallback to WebView mode // Fallback to WebView mode
backstack.push(NavTarget.OidcView(oidcDetails)) backstack.push(NavTarget.OidcView(oidcDetails))

View file

@ -68,9 +68,9 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor(
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
launch { defaultOidcActionFlow.collect { oidcAction ->
defaultOidcActionFlow.collect { if (oidcAction != null) {
onOidcAction(it, loginFlowAction) onOidcAction(oidcAction, loginFlowAction)
} }
} }
} }
@ -113,10 +113,9 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor(
} }
private suspend fun onOidcAction( private suspend fun onOidcAction(
oidcAction: OidcAction?, oidcAction: OidcAction,
loginFlowAction: MutableState<Async<LoginFlow>>, loginFlowAction: MutableState<Async<LoginFlow>>,
) { ) {
oidcAction ?: return
loginFlowAction.value = Async.Loading() loginFlowAction.value = Async.Loading()
when (oidcAction) { when (oidcAction) {
OidcAction.GoBack -> { OidcAction.GoBack -> {

View file

@ -34,7 +34,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight
@Composable @Composable
fun LogoutPreferenceView( fun LogoutPreferenceView(
state: LogoutPreferenceState, state: LogoutPreferenceState,
onSuccessLogout: (String?) -> Unit = {} onSuccessLogout: (logoutUrlResult: String?) -> Unit
) { ) {
val eventSink = state.eventSink val eventSink = state.eventSink
if (state.logoutAction is Async.Success) { if (state.logoutAction is Async.Success) {
@ -96,5 +96,8 @@ internal fun LogoutPreferenceViewDarkPreview() = ElementPreviewDark { ContentToP
@Composable @Composable
private fun ContentToPreview() { private fun ContentToPreview() {
LogoutPreferenceView(aLogoutPreferenceState()) LogoutPreferenceView(
aLogoutPreferenceState(),
onSuccessLogout = {}
)
} }

View file

@ -57,7 +57,7 @@ fun PreferencesRootView(
onOpenRageShake: () -> Unit, onOpenRageShake: () -> Unit,
onOpenAbout: () -> Unit, onOpenAbout: () -> Unit,
onOpenDeveloperSettings: () -> Unit, onOpenDeveloperSettings: () -> Unit,
onSuccessLogout: (String?) -> Unit, onSuccessLogout: (logoutUrlResult: String?) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage) val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)

View file

@ -58,7 +58,8 @@ interface MatrixClient : Closeable {
/** /**
* Logout the user. * Logout the user.
* Returns an optional URL. When the URL is there, it should be presented to the user after logout for RP initiated logout on their account page. * Returns an optional URL. When the URL is there, it should be presented to the user after logout for
* Relying Party (RP) initiated logout on their account page.
*/ */
suspend fun logout(): String? suspend fun logout(): String?
suspend fun loadUserDisplayName(): Result<String> suspend fun loadUserDisplayName(): Result<String>