mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-12-18 15:22:07 +03:00
Merge pull request #8620 from vector-im/feature/bma/oidcSessionEnd
Feature/bma/OIDC session end
This commit is contained in:
commit
ec9a066900
21 changed files with 148 additions and 20 deletions
1
changelog.d/8616.misc
Normal file
1
changelog.d/8616.misc
Normal file
|
@ -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.
|
|
@ -85,6 +85,11 @@ data class HomeServerCapabilities(
|
||||||
* External account management url for use with MSC3824 delegated OIDC, provided in Wellknown.
|
* External account management url for use with MSC3824 delegated OIDC, provided in Wellknown.
|
||||||
*/
|
*/
|
||||||
val externalAccountManagementUrl: String? = null,
|
val externalAccountManagementUrl: String? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication issuer for use with MSC3824 delegated OIDC, provided in Wellknown.
|
||||||
|
*/
|
||||||
|
val authenticationIssuer: String? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
enum class RoomCapabilitySupport {
|
enum class RoomCapabilitySupport {
|
||||||
|
@ -141,6 +146,8 @@ data class HomeServerCapabilities(
|
||||||
return cap?.preferred ?: cap?.support?.lastOrNull()
|
return cap?.preferred ?: cap?.support?.lastOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val delegatedOidcAuthEnabled: Boolean = authenticationIssuer != null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
|
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
|
||||||
const val ROOM_CAP_KNOCK = "knock"
|
const val ROOM_CAP_KNOCK = "knock"
|
||||||
|
|
|
@ -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
|
// 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 flows = if (oidcCompatibilityFlow != null) listOf(oidcCompatibilityFlow) else loginFlowResponse.flows
|
||||||
|
|
||||||
val supportsGetLoginTokenFlow = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == "m.login.token" && it.getLoginToken == true } != null
|
val supportsGetLoginTokenFlow = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == "m.login.token" && it.getLoginToken == true } != null
|
||||||
|
|
|
@ -51,7 +51,7 @@ internal data class LoginFlow(
|
||||||
* See [MSC3824](https://github.com/matrix-org/matrix-spec-proposals/pull/3824)
|
* See [MSC3824](https://github.com/matrix-org/matrix-spec-proposals/pull/3824)
|
||||||
*/
|
*/
|
||||||
@Json(name = "org.matrix.msc3824.delegated_oidc_compatibility")
|
@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.
|
* Whether a login flow of type m.login.token could accept a token issued using /login/get_token.
|
||||||
|
|
|
@ -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.MigrateSessionTo050
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo051
|
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.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.Normalizer
|
||||||
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -77,7 +78,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
||||||
private val normalizer: Normalizer
|
private val normalizer: Normalizer
|
||||||
) : MatrixRealmMigration(
|
) : MatrixRealmMigration(
|
||||||
dbName = "Session",
|
dbName = "Session",
|
||||||
schemaVersion = 52L,
|
schemaVersion = 53L,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Forces all RealmSessionStoreMigration instances to be equal.
|
* Forces all RealmSessionStoreMigration instances to be equal.
|
||||||
|
@ -139,5 +140,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
||||||
if (oldVersion < 50) MigrateSessionTo050(realm).perform()
|
if (oldVersion < 50) MigrateSessionTo050(realm).perform()
|
||||||
if (oldVersion < 51) MigrateSessionTo051(realm).perform()
|
if (oldVersion < 51) MigrateSessionTo051(realm).perform()
|
||||||
if (oldVersion < 52) MigrateSessionTo052(realm).perform()
|
if (oldVersion < 52) MigrateSessionTo052(realm).perform()
|
||||||
|
if (oldVersion < 53) MigrateSessionTo053(realm).perform()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ internal object HomeServerCapabilitiesMapper {
|
||||||
canRemotelyTogglePushNotificationsOfDevices = entity.canRemotelyTogglePushNotificationsOfDevices,
|
canRemotelyTogglePushNotificationsOfDevices = entity.canRemotelyTogglePushNotificationsOfDevices,
|
||||||
canRedactRelatedEvents = entity.canRedactEventWithRelations,
|
canRedactRelatedEvents = entity.canRedactEventWithRelations,
|
||||||
externalAccountManagementUrl = entity.externalAccountManagementUrl,
|
externalAccountManagementUrl = entity.externalAccountManagementUrl,
|
||||||
|
authenticationIssuer = entity.authenticationIssuer,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,6 +36,7 @@ internal open class HomeServerCapabilitiesEntity(
|
||||||
var canRemotelyTogglePushNotificationsOfDevices: Boolean = false,
|
var canRemotelyTogglePushNotificationsOfDevices: Boolean = false,
|
||||||
var canRedactEventWithRelations: Boolean = false,
|
var canRedactEventWithRelations: Boolean = false,
|
||||||
var externalAccountManagementUrl: String? = null,
|
var externalAccountManagementUrl: String? = null,
|
||||||
|
var authenticationIssuer: String? = null,
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
companion object
|
companion object
|
||||||
|
|
|
@ -165,6 +165,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
||||||
Timber.v("Extracted integration config : $config")
|
Timber.v("Extracted integration config : $config")
|
||||||
realm.insertOrUpdate(config)
|
realm.insertOrUpdate(config)
|
||||||
}
|
}
|
||||||
|
homeServerCapabilitiesEntity.authenticationIssuer = getWellknownResult.wellKnown.unstableDelegatedAuthConfig?.issuer
|
||||||
homeServerCapabilitiesEntity.externalAccountManagementUrl = getWellknownResult.wellKnown.unstableDelegatedAuthConfig?.accountManagementUrl
|
homeServerCapabilitiesEntity.externalAccountManagementUrl = getWellknownResult.wellKnown.unstableDelegatedAuthConfig?.accountManagementUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,4 +50,6 @@ sealed class DevicesViewEvents : VectorViewEvents {
|
||||||
data class ShowManuallyVerify(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesViewEvents()
|
data class ShowManuallyVerify(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesViewEvents()
|
||||||
|
|
||||||
object PromptResetSecrets : DevicesViewEvents()
|
object PromptResetSecrets : DevicesViewEvents()
|
||||||
|
|
||||||
|
data class OpenBrowser(val url: String) : DevicesViewEvents()
|
||||||
}
|
}
|
||||||
|
|
|
@ -346,6 +346,20 @@ class DevicesViewModel @AssistedInject constructor(
|
||||||
private fun handleDelete(action: DevicesAction.Delete) {
|
private fun handleDelete(action: DevicesAction.Delete) {
|
||||||
val deviceId = action.deviceId
|
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 {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
request = Loading()
|
request = Loading()
|
||||||
|
|
|
@ -35,6 +35,7 @@ import im.vector.app.core.extensions.cleanup
|
||||||
import im.vector.app.core.extensions.configureWith
|
import im.vector.app.core.extensions.configureWith
|
||||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
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.DialogBaseEditTextBinding
|
||||||
import im.vector.app.databinding.FragmentGenericRecyclerBinding
|
import im.vector.app.databinding.FragmentGenericRecyclerBinding
|
||||||
import im.vector.app.features.auth.ReAuthActivity
|
import im.vector.app.features.auth.ReAuthActivity
|
||||||
|
@ -95,6 +96,9 @@ class VectorSettingsDevicesFragment :
|
||||||
is DevicesViewEvents.PromptResetSecrets -> {
|
is DevicesViewEvents.PromptResetSecrets -> {
|
||||||
navigator.open4SSetup(requireActivity(), SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET)
|
navigator.open4SSetup(requireActivity(), SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET)
|
||||||
}
|
}
|
||||||
|
is DevicesViewEvents.OpenBrowser -> {
|
||||||
|
openUrlInChromeCustomTab(requireContext(), null, it.url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,12 +35,13 @@ import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth
|
import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class DevicesViewModel @AssistedInject constructor(
|
class DevicesViewModel @AssistedInject constructor(
|
||||||
@Assisted initialState: DevicesViewState,
|
@Assisted initialState: DevicesViewState,
|
||||||
activeSessionHolder: ActiveSessionHolder,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
|
private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
|
||||||
private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
|
private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
|
||||||
private val refreshDevicesOnCryptoDevicesChangeUseCase: RefreshDevicesOnCryptoDevicesChangeUseCase,
|
private val refreshDevicesOnCryptoDevicesChangeUseCase: RefreshDevicesOnCryptoDevicesChangeUseCase,
|
||||||
|
@ -69,6 +70,19 @@ class DevicesViewModel @AssistedInject constructor(
|
||||||
refreshDeviceList()
|
refreshDeviceList()
|
||||||
refreshIpAddressVisibility()
|
refreshIpAddressVisibility()
|
||||||
observePreferences()
|
observePreferences()
|
||||||
|
initDelegatedOidcAuthEnabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initDelegatedOidcAuthEnabled() {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
delegatedOidcAuthEnabled = activeSessionHolder.getSafeActiveSession()
|
||||||
|
?.homeServerCapabilitiesService()
|
||||||
|
?.getHomeServerCapabilities()
|
||||||
|
?.delegatedOidcAuthEnabled
|
||||||
|
.orFalse()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||||
|
|
|
@ -26,6 +26,7 @@ data class DevicesViewState(
|
||||||
val devices: Async<DeviceFullInfoList> = Uninitialized,
|
val devices: Async<DeviceFullInfoList> = Uninitialized,
|
||||||
val isLoading: Boolean = false,
|
val isLoading: Boolean = false,
|
||||||
val isShowingIpAddress: Boolean = false,
|
val isShowingIpAddress: Boolean = false,
|
||||||
|
val delegatedOidcAuthEnabled: Boolean = false,
|
||||||
) : MavericksState
|
) : MavericksState
|
||||||
|
|
||||||
data class DeviceFullInfoList(
|
data class DeviceFullInfoList(
|
||||||
|
|
|
@ -290,8 +290,8 @@ class VectorSettingsDevicesFragment :
|
||||||
val unverifiedSessionsCount = deviceFullInfoList?.unverifiedSessionsCount ?: 0
|
val unverifiedSessionsCount = deviceFullInfoList?.unverifiedSessionsCount ?: 0
|
||||||
|
|
||||||
renderSecurityRecommendations(inactiveSessionsCount, unverifiedSessionsCount)
|
renderSecurityRecommendations(inactiveSessionsCount, unverifiedSessionsCount)
|
||||||
renderCurrentSessionView(currentDeviceInfo, hasOtherDevices = otherDevices?.isNotEmpty().orFalse())
|
renderCurrentSessionView(currentDeviceInfo, hasOtherDevices = otherDevices?.isNotEmpty().orFalse(), state)
|
||||||
renderOtherSessionsView(otherDevices, state.isShowingIpAddress)
|
renderOtherSessionsView(otherDevices, state)
|
||||||
} else {
|
} else {
|
||||||
hideSecurityRecommendations()
|
hideSecurityRecommendations()
|
||||||
hideCurrentSessionView()
|
hideCurrentSessionView()
|
||||||
|
@ -347,13 +347,16 @@ class VectorSettingsDevicesFragment :
|
||||||
hideInactiveSessionsRecommendation()
|
hideInactiveSessionsRecommendation()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderOtherSessionsView(otherDevices: List<DeviceFullInfo>?, isShowingIpAddress: Boolean) {
|
private fun renderOtherSessionsView(otherDevices: List<DeviceFullInfo>?, state: DevicesViewState) {
|
||||||
|
val isShowingIpAddress = state.isShowingIpAddress
|
||||||
if (otherDevices.isNullOrEmpty()) {
|
if (otherDevices.isNullOrEmpty()) {
|
||||||
hideOtherSessionsView()
|
hideOtherSessionsView()
|
||||||
} else {
|
} else {
|
||||||
views.deviceListHeaderOtherSessions.isVisible = true
|
views.deviceListHeaderOtherSessions.isVisible = true
|
||||||
val colorDestructive = colorProvider.getColorFromAttribute(R.attr.colorError)
|
val colorDestructive = colorProvider.getColorFromAttribute(R.attr.colorError)
|
||||||
val multiSignoutItem = views.deviceListHeaderOtherSessions.menu.findItem(R.id.otherSessionsHeaderMultiSignout)
|
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
|
val nbDevices = otherDevices.size
|
||||||
multiSignoutItem.title = stringProvider.getQuantityString(R.plurals.device_manager_other_sessions_multi_signout_all, nbDevices, nbDevices)
|
multiSignoutItem.title = stringProvider.getQuantityString(R.plurals.device_manager_other_sessions_multi_signout_all, nbDevices, nbDevices)
|
||||||
multiSignoutItem.setTextColor(colorDestructive)
|
multiSignoutItem.setTextColor(colorDestructive)
|
||||||
|
@ -377,23 +380,24 @@ class VectorSettingsDevicesFragment :
|
||||||
views.deviceListOtherSessions.isVisible = false
|
views.deviceListOtherSessions.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderCurrentSessionView(currentDeviceInfo: DeviceFullInfo?, hasOtherDevices: Boolean) {
|
private fun renderCurrentSessionView(currentDeviceInfo: DeviceFullInfo?, hasOtherDevices: Boolean, state: DevicesViewState) {
|
||||||
currentDeviceInfo?.let {
|
currentDeviceInfo?.let {
|
||||||
renderCurrentSessionHeaderView(hasOtherDevices)
|
renderCurrentSessionHeaderView(hasOtherDevices, state)
|
||||||
renderCurrentSessionListView(it)
|
renderCurrentSessionListView(it)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
hideCurrentSessionView()
|
hideCurrentSessionView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderCurrentSessionHeaderView(hasOtherDevices: Boolean) {
|
private fun renderCurrentSessionHeaderView(hasOtherDevices: Boolean, state: DevicesViewState) {
|
||||||
views.deviceListHeaderCurrentSession.isVisible = true
|
views.deviceListHeaderCurrentSession.isVisible = true
|
||||||
val colorDestructive = colorProvider.getColorFromAttribute(R.attr.colorError)
|
val colorDestructive = colorProvider.getColorFromAttribute(R.attr.colorError)
|
||||||
val signoutSessionItem = views.deviceListHeaderCurrentSession.menu.findItem(R.id.currentSessionHeaderSignout)
|
val signoutSessionItem = views.deviceListHeaderCurrentSession.menu.findItem(R.id.currentSessionHeaderSignout)
|
||||||
signoutSessionItem.setTextColor(colorDestructive)
|
signoutSessionItem.setTextColor(colorDestructive)
|
||||||
val signoutOtherSessionsItem = views.deviceListHeaderCurrentSession.menu.findItem(R.id.currentSessionHeaderSignoutOtherSessions)
|
val signoutOtherSessionsItem = views.deviceListHeaderCurrentSession.menu.findItem(R.id.currentSessionHeaderSignoutOtherSessions)
|
||||||
signoutOtherSessionsItem.setTextColor(colorDestructive)
|
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) {
|
private fun renderCurrentSessionListView(currentDeviceInfo: DeviceFullInfo) {
|
||||||
|
|
|
@ -103,10 +103,15 @@ class OtherSessionsFragment :
|
||||||
val nbDevices = viewState.devices()?.size ?: 0
|
val nbDevices = viewState.devices()?.size ?: 0
|
||||||
stringProvider.getQuantityString(R.plurals.device_manager_other_sessions_multi_signout_all, nbDevices, nbDevices)
|
stringProvider.getQuantityString(R.plurals.device_manager_other_sessions_multi_signout_all, nbDevices, nbDevices)
|
||||||
}
|
}
|
||||||
multiSignoutItem.isVisible = if (viewState.isSelectModeEnabled) {
|
multiSignoutItem.isVisible = if (viewState.delegatedOidcAuthEnabled) {
|
||||||
viewState.devices.invoke()?.any { it.isSelected }.orFalse()
|
// Hide multi signout if the homeserver delegates the account management
|
||||||
|
false
|
||||||
} else {
|
} 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
|
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)
|
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)
|
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 */
|
DeviceManagerFilterType.ALL_SESSIONS -> { /* NOOP. View is not visible */
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 im.vector.app.features.settings.devices.v2.signout.SignoutSessionsUseCase
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth
|
import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class OtherSessionsViewModel @AssistedInject constructor(
|
class OtherSessionsViewModel @AssistedInject constructor(
|
||||||
@Assisted private val initialState: OtherSessionsViewState,
|
@Assisted private val initialState: OtherSessionsViewState,
|
||||||
activeSessionHolder: ActiveSessionHolder,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
|
private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
|
||||||
private val signoutSessionsUseCase: SignoutSessionsUseCase,
|
private val signoutSessionsUseCase: SignoutSessionsUseCase,
|
||||||
private val pendingAuthHandler: PendingAuthHandler,
|
private val pendingAuthHandler: PendingAuthHandler,
|
||||||
|
@ -65,6 +66,19 @@ class OtherSessionsViewModel @AssistedInject constructor(
|
||||||
observeDevices(initialState.currentFilter)
|
observeDevices(initialState.currentFilter)
|
||||||
refreshIpAddressVisibility()
|
refreshIpAddressVisibility()
|
||||||
observePreferences()
|
observePreferences()
|
||||||
|
initDelegatedOidcAuthEnabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initDelegatedOidcAuthEnabled() {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
delegatedOidcAuthEnabled = activeSessionHolder.getSafeActiveSession()
|
||||||
|
?.homeServerCapabilitiesService()
|
||||||
|
?.getHomeServerCapabilities()
|
||||||
|
?.delegatedOidcAuthEnabled
|
||||||
|
.orFalse()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||||
|
|
|
@ -29,6 +29,7 @@ data class OtherSessionsViewState(
|
||||||
val isSelectModeEnabled: Boolean = false,
|
val isSelectModeEnabled: Boolean = false,
|
||||||
val isLoading: Boolean = false,
|
val isLoading: Boolean = false,
|
||||||
val isShowingIpAddress: Boolean = false,
|
val isShowingIpAddress: Boolean = false,
|
||||||
|
val delegatedOidcAuthEnabled: Boolean = false,
|
||||||
) : MavericksState {
|
) : MavericksState {
|
||||||
|
|
||||||
constructor(args: OtherSessionsArgs) : this(excludeCurrentDevice = args.excludeCurrentDevice)
|
constructor(args: OtherSessionsArgs) : this(excludeCurrentDevice = args.excludeCurrentDevice)
|
||||||
|
|
|
@ -39,6 +39,7 @@ import im.vector.app.core.platform.VectorMenuProvider
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.core.resources.DrawableProvider
|
import im.vector.app.core.resources.DrawableProvider
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.core.utils.openUrlInChromeCustomTab
|
||||||
import im.vector.app.databinding.FragmentSessionOverviewBinding
|
import im.vector.app.databinding.FragmentSessionOverviewBinding
|
||||||
import im.vector.app.features.auth.ReAuthActivity
|
import im.vector.app.features.auth.ReAuthActivity
|
||||||
import im.vector.app.features.crypto.recover.SetupMode
|
import im.vector.app.features.crypto.recover.SetupMode
|
||||||
|
@ -135,10 +136,19 @@ class SessionOverviewFragment :
|
||||||
activity?.let { SignOutUiWorker(it).perform() }
|
activity?.let { SignOutUiWorker(it).perform() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun confirmSignoutOtherSession() {
|
private fun confirmSignoutOtherSession() = withState(viewModel) { state ->
|
||||||
activity?.let {
|
if (state.externalAccountManagementUrl != null) {
|
||||||
buildConfirmSignoutDialogUseCase.execute(it, this::signoutSession)
|
// Manage in external account manager
|
||||||
.show()
|
openUrlInChromeCustomTab(
|
||||||
|
requireContext(),
|
||||||
|
null,
|
||||||
|
state.externalAccountManagementUrl.removeSuffix("/") + "?action=session_end&device_id=${state.deviceId}"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
activity?.let {
|
||||||
|
buildConfirmSignoutDialogUseCase.execute(it, this::signoutSession)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,18 @@ class SessionOverviewViewModel @AssistedInject constructor(
|
||||||
observeNotificationsStatus(initialState.deviceId)
|
observeNotificationsStatus(initialState.deviceId)
|
||||||
refreshIpAddressVisibility()
|
refreshIpAddressVisibility()
|
||||||
observePreferences()
|
observePreferences()
|
||||||
|
initExternalAccountManagementUrl()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initExternalAccountManagementUrl() {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
externalAccountManagementUrl = activeSessionHolder.getSafeActiveSession()
|
||||||
|
?.homeServerCapabilitiesService()
|
||||||
|
?.getHomeServerCapabilities()
|
||||||
|
?.externalAccountManagementUrl
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||||
|
|
|
@ -29,6 +29,7 @@ data class SessionOverviewViewState(
|
||||||
val isLoading: Boolean = false,
|
val isLoading: Boolean = false,
|
||||||
val notificationsStatus: NotificationsStatus = NotificationsStatus.NOT_SUPPORTED,
|
val notificationsStatus: NotificationsStatus = NotificationsStatus.NOT_SUPPORTED,
|
||||||
val isShowingIpAddress: Boolean = false,
|
val isShowingIpAddress: Boolean = false,
|
||||||
|
val externalAccountManagementUrl: String? = null,
|
||||||
) : MavericksState {
|
) : MavericksState {
|
||||||
constructor(args: SessionOverviewArgs) : this(
|
constructor(args: SessionOverviewArgs) : this(
|
||||||
deviceId = args.deviceId
|
deviceId = args.deviceId
|
||||||
|
|
Loading…
Reference in a new issue