diff --git a/changelog.d/8616.misc b/changelog.d/8616.misc new file mode 100644 index 0000000000..58a2596287 --- /dev/null +++ b/changelog.d/8616.misc @@ -0,0 +1 @@ +If an external account manager is configured on the server, use it to delete other sessions and hide the multi session deletion. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt index 78672adb00..6b94452e39 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt @@ -85,6 +85,11 @@ data class HomeServerCapabilities( * External account management url for use with MSC3824 delegated OIDC, provided in Wellknown. */ val externalAccountManagementUrl: String? = null, + + /** + * Authentication issuer for use with MSC3824 delegated OIDC, provided in Wellknown. + */ + val authenticationIssuer: String? = null, ) { enum class RoomCapabilitySupport { @@ -141,6 +146,8 @@ data class HomeServerCapabilities( return cap?.preferred ?: cap?.support?.lastOrNull() } + val delegatedOidcAuthEnabled: Boolean = authenticationIssuer != null + companion object { const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L const val ROOM_CAP_KNOCK = "knock" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index bbef75a21d..e852c61185 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -298,7 +298,7 @@ internal class DefaultAuthenticationService @Inject constructor( } // If an m.login.sso flow is present that is flagged as being for MSC3824 OIDC compatibility then we only return that flow - val oidcCompatibilityFlow = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == "m.login.sso" && it.delegatedOidcCompatibilty == true } + val oidcCompatibilityFlow = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == "m.login.sso" && it.delegatedOidcCompatibility == true } val flows = if (oidcCompatibilityFlow != null) listOf(oidcCompatibilityFlow) else loginFlowResponse.flows val supportsGetLoginTokenFlow = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == "m.login.token" && it.getLoginToken == true } != null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt index ea749a56b8..2e52740ed4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt @@ -51,7 +51,7 @@ internal data class LoginFlow( * See [MSC3824](https://github.com/matrix-org/matrix-spec-proposals/pull/3824) */ @Json(name = "org.matrix.msc3824.delegated_oidc_compatibility") - val delegatedOidcCompatibilty: Boolean? = null, + val delegatedOidcCompatibility: Boolean? = null, /** * Whether a login flow of type m.login.token could accept a token issued using /login/get_token. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index bca189134b..4376323df4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -69,6 +69,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo049 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo050 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo051 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo052 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo053 import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -77,7 +78,7 @@ internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer ) : MatrixRealmMigration( dbName = "Session", - schemaVersion = 52L, + schemaVersion = 53L, ) { /** * Forces all RealmSessionStoreMigration instances to be equal. @@ -139,5 +140,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 50) MigrateSessionTo050(realm).perform() if (oldVersion < 51) MigrateSessionTo051(realm).perform() if (oldVersion < 52) MigrateSessionTo052(realm).perform() + if (oldVersion < 53) MigrateSessionTo053(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt index b566cf7cb6..f5cf88e2c1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt @@ -49,6 +49,7 @@ internal object HomeServerCapabilitiesMapper { canRemotelyTogglePushNotificationsOfDevices = entity.canRemotelyTogglePushNotificationsOfDevices, canRedactRelatedEvents = entity.canRedactEventWithRelations, externalAccountManagementUrl = entity.externalAccountManagementUrl, + authenticationIssuer = entity.authenticationIssuer, ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo053.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo053.kt new file mode 100644 index 0000000000..32fac1ad4b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo053.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields +import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo053(realm: DynamicRealm) : RealmMigrator(realm, 53) { + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("HomeServerCapabilitiesEntity") + ?.addField(HomeServerCapabilitiesEntityFields.AUTHENTICATION_ISSUER, String::class.java) + ?.forceRefreshOfHomeServerCapabilities() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt index 35a5c654de..79aaf6d4ce 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt @@ -36,6 +36,7 @@ internal open class HomeServerCapabilitiesEntity( var canRemotelyTogglePushNotificationsOfDevices: Boolean = false, var canRedactEventWithRelations: Boolean = false, var externalAccountManagementUrl: String? = null, + var authenticationIssuer: String? = null, ) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt index 71f6ebd6e3..b973de9fd3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt @@ -165,6 +165,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( Timber.v("Extracted integration config : $config") realm.insertOrUpdate(config) } + homeServerCapabilitiesEntity.authenticationIssuer = getWellknownResult.wellKnown.unstableDelegatedAuthConfig?.issuer homeServerCapabilitiesEntity.externalAccountManagementUrl = getWellknownResult.wellKnown.unstableDelegatedAuthConfig?.accountManagementUrl } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewEvents.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewEvents.kt index c97f353110..de9671e360 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewEvents.kt @@ -50,4 +50,6 @@ sealed class DevicesViewEvents : VectorViewEvents { data class ShowManuallyVerify(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesViewEvents() object PromptResetSecrets : DevicesViewEvents() + + data class OpenBrowser(val url: String) : DevicesViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt index 526a6006e1..5a1e602808 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt @@ -346,6 +346,20 @@ class DevicesViewModel @AssistedInject constructor( private fun handleDelete(action: DevicesAction.Delete) { val deviceId = action.deviceId + val accountManagementUrl = session.homeServerCapabilitiesService().getHomeServerCapabilities().externalAccountManagementUrl + if (accountManagementUrl != null) { + // Open external browser to delete this session + _viewEvents.post( + DevicesViewEvents.OpenBrowser( + url = accountManagementUrl.removeSuffix("/") + "?action=session_end&device_id=$deviceId" + ) + ) + } else { + doDelete(deviceId) + } + } + + private fun doDelete(deviceId: String) { setState { copy( request = Loading() diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt index 5e2f0b21f2..9cb314b2b7 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt @@ -35,6 +35,7 @@ import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.utils.openUrlInChromeCustomTab import im.vector.app.databinding.DialogBaseEditTextBinding import im.vector.app.databinding.FragmentGenericRecyclerBinding import im.vector.app.features.auth.ReAuthActivity @@ -95,6 +96,9 @@ class VectorSettingsDevicesFragment : is DevicesViewEvents.PromptResetSecrets -> { navigator.open4SSetup(requireActivity(), SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET) } + is DevicesViewEvents.OpenBrowser -> { + openUrlInChromeCustomTab(requireContext(), null, it.url) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt index f2e6b25f32..43530cb17c 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt @@ -35,12 +35,13 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth import timber.log.Timber class DevicesViewModel @AssistedInject constructor( @Assisted initialState: DevicesViewState, - activeSessionHolder: ActiveSessionHolder, + private val activeSessionHolder: ActiveSessionHolder, private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase, private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase, private val refreshDevicesOnCryptoDevicesChangeUseCase: RefreshDevicesOnCryptoDevicesChangeUseCase, @@ -69,6 +70,19 @@ class DevicesViewModel @AssistedInject constructor( refreshDeviceList() refreshIpAddressVisibility() observePreferences() + initDelegatedOidcAuthEnabled() + } + + private fun initDelegatedOidcAuthEnabled() { + setState { + copy( + delegatedOidcAuthEnabled = activeSessionHolder.getSafeActiveSession() + ?.homeServerCapabilitiesService() + ?.getHomeServerCapabilities() + ?.delegatedOidcAuthEnabled + .orFalse() + ) + } } override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt index 75d0f132bb..6d6baa9d5f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt @@ -26,6 +26,7 @@ data class DevicesViewState( val devices: Async = Uninitialized, val isLoading: Boolean = false, val isShowingIpAddress: Boolean = false, + val delegatedOidcAuthEnabled: Boolean = false, ) : MavericksState data class DeviceFullInfoList( diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt index ec21970f94..bf90120b96 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt @@ -290,8 +290,8 @@ class VectorSettingsDevicesFragment : val unverifiedSessionsCount = deviceFullInfoList?.unverifiedSessionsCount ?: 0 renderSecurityRecommendations(inactiveSessionsCount, unverifiedSessionsCount) - renderCurrentSessionView(currentDeviceInfo, hasOtherDevices = otherDevices?.isNotEmpty().orFalse()) - renderOtherSessionsView(otherDevices, state.isShowingIpAddress) + renderCurrentSessionView(currentDeviceInfo, hasOtherDevices = otherDevices?.isNotEmpty().orFalse(), state) + renderOtherSessionsView(otherDevices, state) } else { hideSecurityRecommendations() hideCurrentSessionView() @@ -347,13 +347,16 @@ class VectorSettingsDevicesFragment : hideInactiveSessionsRecommendation() } - private fun renderOtherSessionsView(otherDevices: List?, isShowingIpAddress: Boolean) { + private fun renderOtherSessionsView(otherDevices: List?, state: DevicesViewState) { + val isShowingIpAddress = state.isShowingIpAddress if (otherDevices.isNullOrEmpty()) { hideOtherSessionsView() } else { views.deviceListHeaderOtherSessions.isVisible = true val colorDestructive = colorProvider.getColorFromAttribute(R.attr.colorError) val multiSignoutItem = views.deviceListHeaderOtherSessions.menu.findItem(R.id.otherSessionsHeaderMultiSignout) + // Hide multi signout if the homeserver delegates the account management + multiSignoutItem.isVisible = state.delegatedOidcAuthEnabled.not() val nbDevices = otherDevices.size multiSignoutItem.title = stringProvider.getQuantityString(R.plurals.device_manager_other_sessions_multi_signout_all, nbDevices, nbDevices) multiSignoutItem.setTextColor(colorDestructive) @@ -377,23 +380,24 @@ class VectorSettingsDevicesFragment : views.deviceListOtherSessions.isVisible = false } - private fun renderCurrentSessionView(currentDeviceInfo: DeviceFullInfo?, hasOtherDevices: Boolean) { + private fun renderCurrentSessionView(currentDeviceInfo: DeviceFullInfo?, hasOtherDevices: Boolean, state: DevicesViewState) { currentDeviceInfo?.let { - renderCurrentSessionHeaderView(hasOtherDevices) + renderCurrentSessionHeaderView(hasOtherDevices, state) renderCurrentSessionListView(it) } ?: run { hideCurrentSessionView() } } - private fun renderCurrentSessionHeaderView(hasOtherDevices: Boolean) { + private fun renderCurrentSessionHeaderView(hasOtherDevices: Boolean, state: DevicesViewState) { views.deviceListHeaderCurrentSession.isVisible = true val colorDestructive = colorProvider.getColorFromAttribute(R.attr.colorError) val signoutSessionItem = views.deviceListHeaderCurrentSession.menu.findItem(R.id.currentSessionHeaderSignout) signoutSessionItem.setTextColor(colorDestructive) val signoutOtherSessionsItem = views.deviceListHeaderCurrentSession.menu.findItem(R.id.currentSessionHeaderSignoutOtherSessions) signoutOtherSessionsItem.setTextColor(colorDestructive) - signoutOtherSessionsItem.isVisible = hasOtherDevices + // Hide signout other sessions if the homeserver delegates the account management + signoutOtherSessionsItem.isVisible = hasOtherDevices && state.delegatedOidcAuthEnabled.not() } private fun renderCurrentSessionListView(currentDeviceInfo: DeviceFullInfo) { diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt index cee90986cd..a5897be58c 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt @@ -103,10 +103,15 @@ class OtherSessionsFragment : val nbDevices = viewState.devices()?.size ?: 0 stringProvider.getQuantityString(R.plurals.device_manager_other_sessions_multi_signout_all, nbDevices, nbDevices) } - multiSignoutItem.isVisible = if (viewState.isSelectModeEnabled) { - viewState.devices.invoke()?.any { it.isSelected }.orFalse() + multiSignoutItem.isVisible = if (viewState.delegatedOidcAuthEnabled) { + // Hide multi signout if the homeserver delegates the account management + false } else { - viewState.devices.invoke()?.isNotEmpty().orFalse() + if (viewState.isSelectModeEnabled) { + viewState.devices.invoke()?.any { it.isSelected }.orFalse() + } else { + viewState.devices.invoke()?.isNotEmpty().orFalse() + } } val showAsActionFlag = if (viewState.isSelectModeEnabled) MenuItem.SHOW_AS_ACTION_IF_ROOM else MenuItem.SHOW_AS_ACTION_NEVER multiSignoutItem.setShowAsAction(showAsActionFlag or MenuItem.SHOW_AS_ACTION_WITH_TEXT) @@ -308,7 +313,10 @@ class OtherSessionsFragment : ) ) views.otherSessionsNotFoundTextView.text = getString(R.string.device_manager_other_sessions_no_inactive_sessions_found) - updateSecurityLearnMoreButton(R.string.device_manager_learn_more_sessions_inactive_title, R.string.device_manager_learn_more_sessions_inactive) + updateSecurityLearnMoreButton( + R.string.device_manager_learn_more_sessions_inactive_title, + R.string.device_manager_learn_more_sessions_inactive + ) } DeviceManagerFilterType.ALL_SESSIONS -> { /* NOOP. View is not visible */ } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt index a5282e7ba2..fd463a933c 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt @@ -36,12 +36,13 @@ import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsReAuthN import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsUseCase import kotlinx.coroutines.Job import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth import timber.log.Timber class OtherSessionsViewModel @AssistedInject constructor( @Assisted private val initialState: OtherSessionsViewState, - activeSessionHolder: ActiveSessionHolder, + private val activeSessionHolder: ActiveSessionHolder, private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase, private val signoutSessionsUseCase: SignoutSessionsUseCase, private val pendingAuthHandler: PendingAuthHandler, @@ -65,6 +66,19 @@ class OtherSessionsViewModel @AssistedInject constructor( observeDevices(initialState.currentFilter) refreshIpAddressVisibility() observePreferences() + initDelegatedOidcAuthEnabled() + } + + private fun initDelegatedOidcAuthEnabled() { + setState { + copy( + delegatedOidcAuthEnabled = activeSessionHolder.getSafeActiveSession() + ?.homeServerCapabilitiesService() + ?.getHomeServerCapabilities() + ?.delegatedOidcAuthEnabled + .orFalse() + ) + } } override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.kt index f4dd3640ee..dcff652768 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.kt @@ -29,6 +29,7 @@ data class OtherSessionsViewState( val isSelectModeEnabled: Boolean = false, val isLoading: Boolean = false, val isShowingIpAddress: Boolean = false, + val delegatedOidcAuthEnabled: Boolean = false, ) : MavericksState { constructor(args: OtherSessionsArgs) : this(excludeCurrentDevice = args.excludeCurrentDevice) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt index 399f99201b..7b10939072 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt @@ -39,6 +39,7 @@ import im.vector.app.core.platform.VectorMenuProvider import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.openUrlInChromeCustomTab import im.vector.app.databinding.FragmentSessionOverviewBinding import im.vector.app.features.auth.ReAuthActivity import im.vector.app.features.crypto.recover.SetupMode @@ -135,10 +136,19 @@ class SessionOverviewFragment : activity?.let { SignOutUiWorker(it).perform() } } - private fun confirmSignoutOtherSession() { - activity?.let { - buildConfirmSignoutDialogUseCase.execute(it, this::signoutSession) - .show() + private fun confirmSignoutOtherSession() = withState(viewModel) { state -> + if (state.externalAccountManagementUrl != null) { + // Manage in external account manager + openUrlInChromeCustomTab( + requireContext(), + null, + state.externalAccountManagementUrl.removeSuffix("/") + "?action=session_end&device_id=${state.deviceId}" + ) + } else { + activity?.let { + buildConfirmSignoutDialogUseCase.execute(it, this::signoutSession) + .show() + } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt index 55866cb8c4..80db4717f0 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt @@ -75,6 +75,18 @@ class SessionOverviewViewModel @AssistedInject constructor( observeNotificationsStatus(initialState.deviceId) refreshIpAddressVisibility() observePreferences() + initExternalAccountManagementUrl() + } + + private fun initExternalAccountManagementUrl() { + setState { + copy( + externalAccountManagementUrl = activeSessionHolder.getSafeActiveSession() + ?.homeServerCapabilitiesService() + ?.getHomeServerCapabilities() + ?.externalAccountManagementUrl + ) + } } override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt index 0f66605f98..f6a1307a5f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt @@ -29,6 +29,7 @@ data class SessionOverviewViewState( val isLoading: Boolean = false, val notificationsStatus: NotificationsStatus = NotificationsStatus.NOT_SUPPORTED, val isShowingIpAddress: Boolean = false, + val externalAccountManagementUrl: String? = null, ) : MavericksState { constructor(args: SessionOverviewArgs) : this( deviceId = args.deviceId