From b1158121e17fff500d83a8f1bd5fe7b7d021c93f Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Tue, 12 Sep 2023 10:56:41 +0300 Subject: [PATCH 1/5] Prepare for new sprint --- Config/AppVersion.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index ad10bbd70..eabcc9b01 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.11.2 -CURRENT_PROJECT_VERSION = 1.11.2 +MARKETING_VERSION = 1.11.3 +CURRENT_PROJECT_VERSION = 1.11.3 From af664c38bf2c05e0ee71dda78287175ac2e6a282 Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Tue, 12 Sep 2023 13:46:36 +0100 Subject: [PATCH 2/5] Hide sign out x/all buttons in the Device Manager when using OIDC. (#7674) --- .../UserSessionsFlowCoordinator.swift | 4 +- .../UserOtherSessionsCoordinator.swift | 2 + .../MockUserOtherSessionsScreenState.swift | 5 +++ .../UserOtherSessionsViewModelTests.swift | 40 +++++++++++++------ .../UserOtherSessionsModels.swift | 1 + .../UserOtherSessionsViewModel.swift | 4 +- .../View/UserOtherSessions.swift | 1 + .../View/UserOtherSessionsToolbar.swift | 3 +- .../UserSessionsOverviewCoordinator.swift | 5 ++- .../MockUserSessionsOverviewScreenState.swift | 2 +- .../UserSessionsOverviewViewModelTests.swift | 16 ++++++-- .../UserSessionsOverviewModels.swift | 1 + .../UserSessionsOverviewViewModel.swift | 4 +- .../View/UserSessionsOverview.swift | 12 +++--- changelog.d/7672.bugfix | 1 + 15 files changed, 72 insertions(+), 29 deletions(-) create mode 100644 changelog.d/7672.bugfix diff --git a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift index 041f5a1ae..5225b9fd0 100644 --- a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift @@ -188,9 +188,11 @@ final class UserSessionsFlowCoordinator: NSObject, Coordinator, Presentable { private func createOtherSessionsCoordinator(sessionInfos: [UserSessionInfo], filterBy filter: UserOtherSessionsFilter, title: String) -> UserOtherSessionsCoordinator { + let shouldShowDeviceLogout = parameters.session.homeserverWellknown.authentication == nil let parameters = UserOtherSessionsCoordinatorParameters(sessionInfos: sessionInfos, filter: filter, - title: title) + title: title, + showDeviceLogout: shouldShowDeviceLogout) return UserOtherSessionsCoordinator(parameters: parameters) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Coordinator/UserOtherSessionsCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Coordinator/UserOtherSessionsCoordinator.swift index 8f9dab072..a848a2bf8 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Coordinator/UserOtherSessionsCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Coordinator/UserOtherSessionsCoordinator.swift @@ -21,6 +21,7 @@ struct UserOtherSessionsCoordinatorParameters { let sessionInfos: [UserSessionInfo] let filter: UserOtherSessionsFilter let title: String + let showDeviceLogout: Bool } final class UserOtherSessionsCoordinator: Coordinator, Presentable { @@ -40,6 +41,7 @@ final class UserOtherSessionsCoordinator: Coordinator, Presentable { let viewModel = UserOtherSessionsViewModel(sessionInfos: parameters.sessionInfos, filter: parameters.filter, title: parameters.title, + showDeviceLogout: parameters.showDeviceLogout, settingsService: RiotSettings.shared) let view = UserOtherSessions(viewModel: viewModel.context) userOtherSessionsViewModel = viewModel diff --git a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/MockUserOtherSessionsScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/MockUserOtherSessionsScreenState.swift index e81bb7f05..e357e77d0 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/MockUserOtherSessionsScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/MockUserOtherSessionsScreenState.swift @@ -49,26 +49,31 @@ enum MockUserOtherSessionsScreenState: MockScreenState, CaseIterable { viewModel = UserOtherSessionsViewModel(sessionInfos: allSessions(), filter: .all, title: VectorL10n.userSessionsOverviewOtherSessionsSectionTitle, + showDeviceLogout: true, settingsService: MockUserSessionSettings()) case .none: viewModel = UserOtherSessionsViewModel(sessionInfos: [], filter: .all, title: VectorL10n.userSessionsOverviewOtherSessionsSectionTitle, + showDeviceLogout: true, settingsService: MockUserSessionSettings()) case .inactiveSessions: viewModel = UserOtherSessionsViewModel(sessionInfos: inactiveSessions(), filter: .inactive, title: VectorL10n.userOtherSessionSecurityRecommendationTitle, + showDeviceLogout: true, settingsService: MockUserSessionSettings()) case .unverifiedSessions: viewModel = UserOtherSessionsViewModel(sessionInfos: unverifiedSessions(), filter: .unverified, title: VectorL10n.userOtherSessionSecurityRecommendationTitle, + showDeviceLogout: true, settingsService: MockUserSessionSettings()) case .verifiedSessions: viewModel = UserOtherSessionsViewModel(sessionInfos: verifiedSessions(), filter: .verified, title: VectorL10n.userOtherSessionSecurityRecommendationTitle, + showDeviceLogout: true, settingsService: MockUserSessionSettings()) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/Unit/UserOtherSessionsViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/Unit/UserOtherSessionsViewModelTests.swift index 270891d91..58d8b2dc7 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/Unit/UserOtherSessionsViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/Unit/UserOtherSessionsViewModelTests.swift @@ -63,7 +63,8 @@ class UserOtherSessionsViewModelTests: XCTestCase { emptyItemsTitle: VectorL10n.userOtherSessionNoInactiveSessions, allItemsSelected: false, enableSignOutButton: false, - showLocationInfo: false) + showLocationInfo: false, + showDeviceLogout: true) XCTAssertEqual(sut.state, expectedState) } @@ -81,7 +82,8 @@ class UserOtherSessionsViewModelTests: XCTestCase { emptyItemsTitle: "", allItemsSelected: false, enableSignOutButton: false, - showLocationInfo: false) + showLocationInfo: false, + showDeviceLogout: true) XCTAssertEqual(sut.state, expectedState) } @@ -104,7 +106,8 @@ class UserOtherSessionsViewModelTests: XCTestCase { emptyItemsTitle: VectorL10n.userOtherSessionNoUnverifiedSessions, allItemsSelected: false, enableSignOutButton: false, - showLocationInfo: false) + showLocationInfo: false, + showDeviceLogout: true) XCTAssertEqual(expectedItems.count, 2) XCTAssertEqual(sut.state, expectedState) } @@ -123,7 +126,8 @@ class UserOtherSessionsViewModelTests: XCTestCase { emptyItemsTitle: VectorL10n.userOtherSessionNoVerifiedSessions, allItemsSelected: false, enableSignOutButton: false, - showLocationInfo: false) + showLocationInfo: false, + showDeviceLogout: true) XCTAssertEqual(sut.state, expectedState) } @@ -139,7 +143,8 @@ class UserOtherSessionsViewModelTests: XCTestCase { emptyItemsTitle: VectorL10n.userOtherSessionNoVerifiedSessions, allItemsSelected: false, enableSignOutButton: false, - showLocationInfo: false) + showLocationInfo: false, + showDeviceLogout: true) XCTAssertEqual(sut.state, expectedState) } @@ -155,7 +160,8 @@ class UserOtherSessionsViewModelTests: XCTestCase { emptyItemsTitle: VectorL10n.userOtherSessionNoUnverifiedSessions, allItemsSelected: false, enableSignOutButton: false, - showLocationInfo: false) + showLocationInfo: false, + showDeviceLogout: true) XCTAssertEqual(sut.state, expectedState) } @@ -171,7 +177,8 @@ class UserOtherSessionsViewModelTests: XCTestCase { emptyItemsTitle: VectorL10n.userOtherSessionNoInactiveSessions, allItemsSelected: false, enableSignOutButton: false, - showLocationInfo: false) + showLocationInfo: false, + showDeviceLogout: true) XCTAssertEqual(sut.state, expectedState) } @@ -192,7 +199,8 @@ class UserOtherSessionsViewModelTests: XCTestCase { emptyItemsTitle: "", allItemsSelected: true, enableSignOutButton: true, - showLocationInfo: false) + showLocationInfo: false, + showDeviceLogout: true) XCTAssertEqual(sut.state, expectedState) } @@ -213,7 +221,8 @@ class UserOtherSessionsViewModelTests: XCTestCase { emptyItemsTitle: "", allItemsSelected: false, enableSignOutButton: false, - showLocationInfo: false) + showLocationInfo: false, + showDeviceLogout: true) XCTAssertEqual(sut.state, expectedState) } @@ -233,7 +242,8 @@ class UserOtherSessionsViewModelTests: XCTestCase { emptyItemsTitle: "", allItemsSelected: false, enableSignOutButton: true, - showLocationInfo: false) + showLocationInfo: false, + showDeviceLogout: true) XCTAssertEqual(sut.state, expectedState) } @@ -253,7 +263,8 @@ class UserOtherSessionsViewModelTests: XCTestCase { emptyItemsTitle: "", allItemsSelected: true, enableSignOutButton: true, - showLocationInfo: false) + showLocationInfo: false, + showDeviceLogout: true) XCTAssertEqual(sut.state, expectedState) } @@ -273,7 +284,8 @@ class UserOtherSessionsViewModelTests: XCTestCase { emptyItemsTitle: "", allItemsSelected: false, enableSignOutButton: false, - showLocationInfo: false) + showLocationInfo: false, + showDeviceLogout: true) XCTAssertEqual(sut.state, expectedState) } @@ -296,7 +308,8 @@ class UserOtherSessionsViewModelTests: XCTestCase { emptyItemsTitle: "", allItemsSelected: false, enableSignOutButton: false, - showLocationInfo: false) + showLocationInfo: false, + showDeviceLogout: true) XCTAssertEqual(sut.state, expectedState) } @@ -352,6 +365,7 @@ class UserOtherSessionsViewModelTests: XCTestCase { UserOtherSessionsViewModel(sessionInfos: sessionInfos, filter: filter, title: title, + showDeviceLogout: true, settingsService: MockUserSessionSettings()) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/UserOtherSessionsModels.swift b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/UserOtherSessionsModels.swift index deeb5ab95..d486e1cc4 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/UserOtherSessionsModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/UserOtherSessionsModels.swift @@ -43,6 +43,7 @@ struct UserOtherSessionsViewState: BindableState, Equatable { var allItemsSelected: Bool var enableSignOutButton: Bool var showLocationInfo: Bool + var showDeviceLogout: Bool } struct UserOtherSessionsBindings: Equatable { diff --git a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/UserOtherSessionsViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/UserOtherSessionsViewModel.swift index 84ea6f9ad..abfb5e99f 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/UserOtherSessionsViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/UserOtherSessionsViewModel.swift @@ -28,6 +28,7 @@ class UserOtherSessionsViewModel: UserOtherSessionsViewModelType, UserOtherSessi init(sessionInfos: [UserSessionInfo], filter: UserOtherSessionsFilter, title: String, + showDeviceLogout: Bool, settingsService: UserSessionSettingsProtocol) { self.sessionInfos = sessionInfos defaultTitle = title @@ -41,7 +42,8 @@ class UserOtherSessionsViewModel: UserOtherSessionsViewModelType, UserOtherSessi emptyItemsTitle: filter.userOtherSessionsViewEmptyResultsTitle, allItemsSelected: false, enableSignOutButton: false, - showLocationInfo: settingsService.showIPAddressesInSessionsManager)) + showLocationInfo: settingsService.showIPAddressesInSessionsManager, + showDeviceLogout: showDeviceLogout)) } // MARK: - Public diff --git a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessions.swift b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessions.swift index 41d79fe54..92eb505b0 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessions.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessions.swift @@ -60,6 +60,7 @@ struct UserOtherSessions: View { set: { _ in withAnimation { viewModel.send(viewAction: .showLocationInfo) } }), allItemsSelected: viewModel.viewState.allItemsSelected, sessionCount: viewModel.viewState.sessionItems.count, + showDeviceLogout: viewModel.viewState.showDeviceLogout, onToggleSelection: { viewModel.send(viewAction: .toggleAllSelection) }, onSignOut: { viewModel.send(viewAction: .logoutAllUserSessions) }) } diff --git a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessionsToolbar.swift b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessionsToolbar.swift index 331ede0c3..d3c0b3bb0 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessionsToolbar.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessionsToolbar.swift @@ -24,6 +24,7 @@ struct UserOtherSessionsToolbar: ToolbarContent { @Binding var isShowLocationEnabled: Bool let allItemsSelected: Bool let sessionCount: Int + let showDeviceLogout: Bool let onToggleSelection: () -> Void let onSignOut: () -> Void @@ -94,7 +95,7 @@ struct UserOtherSessionsToolbar: ToolbarContent { Label(showLocationInfo: isShowLocationEnabled) } - if sessionCount > 0 { + if sessionCount > 0, showDeviceLogout { DestructiveButton { onSignOut() } label: { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift index 7d3d0437c..20a88aaf1 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift @@ -39,7 +39,10 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { self.parameters = parameters service = parameters.service - viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: parameters.service, settingsService: RiotSettings.shared) + let shouldShowDeviceLogout = parameters.session.homeserverWellknown.authentication == nil + viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: parameters.service, + settingsService: RiotSettings.shared, + showDeviceLogout: shouldShowDeviceLogout) hostingViewController = VectorHostingController(rootView: UserSessionsOverview(viewModel: viewModel.context)) hostingViewController.vc_setLargeTitleDisplayMode(.never) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/MockUserSessionsOverviewScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/MockUserSessionsOverviewScreenState.swift index e09586b83..6132febae 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/MockUserSessionsOverviewScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/MockUserSessionsOverviewScreenState.swift @@ -51,7 +51,7 @@ enum MockUserSessionsOverviewScreenState: MockScreenState, CaseIterable { fatalError() } - let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service, settingsService: MockUserSessionSettings()) + let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service, settingsService: MockUserSessionSettings(), showDeviceLogout: true) return ( [service, viewModel], diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/Unit/UserSessionsOverviewViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/Unit/UserSessionsOverviewViewModelTests.swift index 8ce396a6e..27237983f 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/Unit/UserSessionsOverviewViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/Unit/UserSessionsOverviewViewModelTests.swift @@ -21,7 +21,9 @@ import XCTest class UserSessionsOverviewViewModelTests: XCTestCase { func testInitialStateEmpty() { - let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService(), settingsService: MockUserSessionSettings()) + let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService(), + settingsService: MockUserSessionSettings(), + showDeviceLogout: true) XCTAssertNil(viewModel.state.currentSessionViewData) XCTAssertTrue(viewModel.state.unverifiedSessionsViewData.isEmpty) @@ -31,7 +33,9 @@ class UserSessionsOverviewViewModelTests: XCTestCase { } func testLoadOnDidAppear() { - let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService(), settingsService: MockUserSessionSettings()) + let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService(), + settingsService: MockUserSessionSettings(), + showDeviceLogout: true) viewModel.process(viewAction: .viewAppeared) XCTAssertNotNil(viewModel.state.currentSessionViewData) @@ -42,7 +46,9 @@ class UserSessionsOverviewViewModelTests: XCTestCase { } func testSimpleActionProcessing() { - let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService(), settingsService: MockUserSessionSettings()) + let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService(), + settingsService: MockUserSessionSettings(), + showDeviceLogout: true) var result: UserSessionsOverviewViewModelResult? viewModel.completion = { action in @@ -69,7 +75,9 @@ class UserSessionsOverviewViewModelTests: XCTestCase { let service = MockUserSessionsOverviewService() service.updateOverviewData { _ in } - let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service, settingsService: MockUserSessionSettings()) + let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service, + settingsService: MockUserSessionSettings(), + showDeviceLogout: true) var result: UserSessionsOverviewViewModelResult? viewModel.completion = { action in diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift index a7429f12f..74f35a087 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift @@ -51,6 +51,7 @@ struct UserSessionsOverviewViewState: BindableState { var showLoadingIndicator = false var linkDeviceButtonVisible = false var showLocationInfo: Bool + var showDeviceLogout: Bool } enum UserSessionsOverviewViewAction { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift index a2d92628a..244126008 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift @@ -24,11 +24,11 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess var completion: ((UserSessionsOverviewViewModelResult) -> Void)? - init(userSessionsOverviewService: UserSessionsOverviewServiceProtocol, settingsService: UserSessionSettingsProtocol) { + init(userSessionsOverviewService: UserSessionsOverviewServiceProtocol, settingsService: UserSessionSettingsProtocol, showDeviceLogout: Bool) { self.userSessionsOverviewService = userSessionsOverviewService self.settingsService = settingsService - super.init(initialViewState: .init(showLocationInfo: settingsService.showIPAddressesInSessionsManager)) + super.init(initialViewState: .init(showLocationInfo: settingsService.showIPAddressesInSessionsManager, showDeviceLogout: showDeviceLogout)) userSessionsOverviewService.overviewDataPublisher.sink { [weak self] overviewData in self?.updateViewState(with: overviewData) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift index 9cfc89ca4..c78718346 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsOverview.swift @@ -125,7 +125,7 @@ struct UserSessionsOverview: View { Label(VectorL10n.signOut, systemImage: "rectangle.portrait.and.arrow.right.fill") } } - if viewModel.viewState.otherSessionsViewData.count > 0 { + if viewModel.viewState.otherSessionsViewData.count > 0, viewModel.viewState.showDeviceLogout { DestructiveButton { viewModel.send(viewAction: .logoutOtherSessions) } label: { @@ -149,10 +149,12 @@ struct UserSessionsOverview: View { Label(showLocationInfo: viewModel.viewState.showLocationInfo) } - DestructiveButton { - viewModel.send(viewAction: .logoutOtherSessions) - } label: { - Label(VectorL10n.userOtherSessionMenuSignOutSessions(String(viewModel.viewState.otherSessionsViewData.count)), systemImage: "rectangle.portrait.and.arrow.forward.fill") + if viewModel.viewState.showDeviceLogout { + DestructiveButton { + viewModel.send(viewAction: .logoutOtherSessions) + } label: { + Label(VectorL10n.userOtherSessionMenuSignOutSessions(String(viewModel.viewState.otherSessionsViewData.count)), systemImage: "rectangle.portrait.and.arrow.forward.fill") + } } } label: { menuImage diff --git a/changelog.d/7672.bugfix b/changelog.d/7672.bugfix new file mode 100644 index 000000000..d46797f9c --- /dev/null +++ b/changelog.d/7672.bugfix @@ -0,0 +1 @@ +Hide Sign Out X/All Sessions buttons in the Device Manager when using OIDC. \ No newline at end of file From 13b7d110079d9041198f2812da5314e4cd07e1e2 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 12 Sep 2023 18:05:52 +0100 Subject: [PATCH 3/5] Use ASWebAuthenticationSession to display OIDC account management URL (#7671) Co-authored-by: Doug --- .../SSO/SSOAccountService.swift | 48 +++++++++++++++++++ .../SSO/SSOAuthenticationPresenter.swift | 4 +- .../SSO/SSOAuthenticationService.swift | 10 +++- .../ManageSessionViewController.m | 43 +++++++++++++---- .../Modules/Settings/SettingsViewController.m | 34 ++++++++++++- .../UserSessionsFlowCoordinator.swift | 37 +++++++++++--- changelog.d/7671.bugfix | 1 + 7 files changed, 158 insertions(+), 19 deletions(-) create mode 100644 Riot/Modules/Authentication/SSO/SSOAccountService.swift create mode 100644 changelog.d/7671.bugfix diff --git a/Riot/Modules/Authentication/SSO/SSOAccountService.swift b/Riot/Modules/Authentication/SSO/SSOAccountService.swift new file mode 100644 index 000000000..4623fb84f --- /dev/null +++ b/Riot/Modules/Authentication/SSO/SSOAccountService.swift @@ -0,0 +1,48 @@ +// +// Copyright 2020 New Vector Ltd +// +// 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. +// + +import Foundation + +@objcMembers +/// A service for the SSOAuthenticationPresenter that allows to open an OIDC account management URL. +/// +/// Both `callBackURLScheme` and `loginToken` are unneeded for this use case and return `nil`. +final class SSOAccountService: NSObject, SSOAuthenticationServiceProtocol { + + // MARK: - Properties + + private let accountURL: URL + + let callBackURLScheme: String? = nil + + // MARK: - Setup + + init(accountURL: URL) { + self.accountURL = accountURL + super.init() + } + + // MARK: - Public + + func authenticationURL(for identityProvider: String?, transactionId: String) -> URL? { + accountURL + } + + func loginToken(from url: URL) -> String? { + MXLog.error("The account service shouldn't receive a completion callback.") + return nil + } +} diff --git a/Riot/Modules/Authentication/SSO/SSOAuthenticationPresenter.swift b/Riot/Modules/Authentication/SSO/SSOAuthenticationPresenter.swift index 1145d8651..b45f3a804 100644 --- a/Riot/Modules/Authentication/SSO/SSOAuthenticationPresenter.swift +++ b/Riot/Modules/Authentication/SSO/SSOAuthenticationPresenter.swift @@ -37,7 +37,7 @@ final class SSOAuthenticationPresenter: NSObject { // MARK: - Properties - private let ssoAuthenticationService: SSOAuthenticationService + private let ssoAuthenticationService: SSOAuthenticationServiceProtocol // MARK: Private @@ -53,7 +53,7 @@ final class SSOAuthenticationPresenter: NSObject { // MARK: - Setup - init(ssoAuthenticationService: SSOAuthenticationService) { + init(ssoAuthenticationService: SSOAuthenticationServiceProtocol) { self.ssoAuthenticationService = ssoAuthenticationService super.init() } diff --git a/Riot/Modules/Authentication/SSO/SSOAuthenticationService.swift b/Riot/Modules/Authentication/SSO/SSOAuthenticationService.swift index 706c3782d..cb3d36d07 100644 --- a/Riot/Modules/Authentication/SSO/SSOAuthenticationService.swift +++ b/Riot/Modules/Authentication/SSO/SSOAuthenticationService.swift @@ -22,8 +22,16 @@ enum SSOAuthenticationServiceError: Error { case unknown } +@objc protocol SSOAuthenticationServiceProtocol { + var callBackURLScheme: String? { get } + + func authenticationURL(for identityProvider: String?, transactionId: String) -> URL? + + func loginToken(from url: URL) -> String? +} + @objcMembers -final class SSOAuthenticationService: NSObject { +final class SSOAuthenticationService: NSObject, SSOAuthenticationServiceProtocol { // MARK: - Constants diff --git a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m index e1fd5c5e8..665f970ac 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -45,7 +45,7 @@ enum { }; -@interface ManageSessionViewController () +@interface ManageSessionViewController () { // The device to display MXDevice *device; @@ -64,6 +64,8 @@ enum { @property (nonatomic, strong) ReauthenticationCoordinatorBridgePresenter *reauthenticationCoordinatorBridgePresenter; +@property (nonatomic, strong) SSOAuthenticationPresenter *ssoAuthenticationPresenter; + @end @implementation ManageSessionViewController @@ -679,17 +681,19 @@ enum { { UIAlertController *alert = [UIAlertController alertControllerWithTitle: [VectorL10n manageSessionRedirect] message: nil preferredStyle:UIAlertControllerStyleAlert]; - __weak typeof(self) weakSelf = self; + MXWeakify(self); UIAlertAction *action = [UIAlertAction actionWithTitle:[VectorL10n ok] style:UIAlertActionStyleDefault handler: ^(UIAlertAction * action) { - [UIApplication.sharedApplication openURL:url options:@{} completionHandler:^(BOOL success) { - if (success && weakSelf) - { - [weakSelf withdrawViewControllerAnimated:YES completion:nil]; - } - }]; + MXStrongifyAndReturnIfNil(self); + SSOAccountService *service = [[SSOAccountService alloc] initWithAccountURL:url]; + SSOAuthenticationPresenter *presenter = [[SSOAuthenticationPresenter alloc] initWithSsoAuthenticationService:service]; + presenter.delegate = self; + self.ssoAuthenticationPresenter = presenter; + + [presenter presentForIdentityProvider:nil with:@"" from:self animated:YES]; }]; + [alert addAction: action]; [self presentViewController:alert animated:YES completion:nil]; } @@ -755,4 +759,27 @@ enum { [self reloadDeviceWithCompletion:^{}]; } +#pragma mark - SSOAuthenticationPresenterDelegate + +- (void)ssoAuthenticationPresenterDidCancel:(SSOAuthenticationPresenter *)presenter +{ + self.ssoAuthenticationPresenter = nil; + MXLogDebug(@"OIDC account management complete.") + [self withdrawViewControllerAnimated:YES completion:nil]; +} + +- (void)ssoAuthenticationPresenter:(SSOAuthenticationPresenter *)presenter authenticationDidFailWithError:(NSError *)error +{ + self.ssoAuthenticationPresenter = nil; + MXLogError(@"OIDC account management failed.") +} + +- (void)ssoAuthenticationPresenter:(SSOAuthenticationPresenter *)presenter + authenticationSucceededWithToken:(NSString *)token + usingIdentityProvider:(SSOIdentityProvider *)identityProvider +{ + self.ssoAuthenticationPresenter = nil; + MXLogWarning(@"Unexpected callback after OIDC account management.") +} + @end diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 32871e008..6f9c22465 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -204,7 +204,8 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate, ServiceTermsModalCoordinatorBridgePresenterDelegate, TableViewSectionsDelegate, ThreadsBetaCoordinatorBridgePresenterDelegate, -ChangePasswordCoordinatorBridgePresenterDelegate> +ChangePasswordCoordinatorBridgePresenterDelegate, +SSOAuthenticationPresenterDelegate> { // Current alert (if any). __weak UIAlertController *currentAlert; @@ -300,6 +301,8 @@ ChangePasswordCoordinatorBridgePresenterDelegate> @property (nonatomic) BOOL isPreparingIdentityService; @property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter; +@property (nonatomic, strong) SSOAuthenticationPresenter *ssoAuthenticationPresenter; + @property (nonatomic) AnalyticsScreenTracker *screenTracker; @end @@ -3926,7 +3929,12 @@ ChangePasswordCoordinatorBridgePresenterDelegate> { NSURL *url = [NSURL URLWithString: self.mainSession.homeserverWellknown.authentication.account]; if (url) { - [UIApplication.sharedApplication openURL:url options:@{} completionHandler:nil]; + SSOAccountService *service = [[SSOAccountService alloc] initWithAccountURL:url]; + SSOAuthenticationPresenter *presenter = [[SSOAuthenticationPresenter alloc] initWithSsoAuthenticationService:service]; + presenter.delegate = self; + self.ssoAuthenticationPresenter = presenter; + + [presenter presentForIdentityProvider:nil with:@"" from:self animated:YES]; } } @@ -4602,4 +4610,26 @@ ChangePasswordCoordinatorBridgePresenterDelegate> [self.userSessionsFlowCoordinatorBridgePresenter pushFrom:self.navigationController animated:YES]; } +#pragma mark - SSOAuthenticationPresenterDelegate + +- (void)ssoAuthenticationPresenterDidCancel:(SSOAuthenticationPresenter *)presenter +{ + self.ssoAuthenticationPresenter = nil; + MXLogDebug(@"OIDC account management complete.") +} + +- (void)ssoAuthenticationPresenter:(SSOAuthenticationPresenter *)presenter authenticationDidFailWithError:(NSError *)error +{ + self.ssoAuthenticationPresenter = nil; + MXLogError(@"OIDC account management failed.") +} + +- (void)ssoAuthenticationPresenter:(SSOAuthenticationPresenter *)presenter + authenticationSucceededWithToken:(NSString *)token + usingIdentityProvider:(SSOIdentityProvider *)identityProvider +{ + self.ssoAuthenticationPresenter = nil; + MXLogWarning(@"Unexpected callback after OIDC account management.") +} + @end diff --git a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift index 5225b9fd0..ff24b1aa3 100644 --- a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift @@ -32,6 +32,7 @@ final class UserSessionsFlowCoordinator: NSObject, Coordinator, Presentable { private var errorPresenter: MXKErrorPresentation private var indicatorPresenter: UserIndicatorTypePresenterProtocol private var loadingIndicator: UserIndicator? + private var ssoAuthenticationPresenter: SSOAuthenticationPresenter? /// The root coordinator for user session management. private weak var sessionsOverviewCoordinator: UserSessionsOverviewCoordinator? @@ -199,12 +200,14 @@ final class UserSessionsFlowCoordinator: NSObject, Coordinator, Presentable { private func openDeviceLogoutRedirectURL(_ url: URL) { let alert = UIAlertController(title: VectorL10n.manageSessionRedirect, message: nil, preferredStyle: .alert) alert.addAction(UIAlertAction(title: VectorL10n.ok, style: .default) { [weak self] _ in - UIApplication.shared.open(url) { [weak self] success in - guard success else { - return - } - self?.popToSessionsOverview() - } + guard let self else { return } + + let service = SSOAccountService(accountURL: url) + let presenter = SSOAuthenticationPresenter(ssoAuthenticationService: service) + presenter.delegate = self + self.ssoAuthenticationPresenter = presenter + + presenter.present(forIdentityProvider: nil, with: "", from: self.toPresentable(), animated: true) }) alert.popoverPresentationController?.sourceView = toPresentable().view navigationRouter.present(alert, animated: true) @@ -549,3 +552,25 @@ private extension UserOtherSessionsFilter { } } } + +// MARK: ASWebAuthenticationPresentationContextProviding + +extension UserSessionsFlowCoordinator: SSOAuthenticationPresenterDelegate { + func ssoAuthenticationPresenterDidCancel(_ presenter: SSOAuthenticationPresenter) { + ssoAuthenticationPresenter = nil + MXLog.info("OIDC account management complete.") + popToSessionsOverview() + } + + func ssoAuthenticationPresenter(_ presenter: SSOAuthenticationPresenter, authenticationDidFailWithError error: Error) { + ssoAuthenticationPresenter = nil + MXLog.error("OIDC account management failed.") + } + + func ssoAuthenticationPresenter(_ presenter: SSOAuthenticationPresenter, + authenticationSucceededWithToken token: String, + usingIdentityProvider identityProvider: SSOIdentityProvider?) { + ssoAuthenticationPresenter = nil + MXLog.warning("Unexpected callback after OIDC account management.") + } +} diff --git a/changelog.d/7671.bugfix b/changelog.d/7671.bugfix new file mode 100644 index 000000000..99d153ad4 --- /dev/null +++ b/changelog.d/7671.bugfix @@ -0,0 +1 @@ +Show OIDC account management UI using embedded browser instead of system browser. \ No newline at end of file From ae0c6bc2e287f2bf23110ef481561351a43b1c47 Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Wed, 13 Sep 2023 15:07:49 +0100 Subject: [PATCH 4/5] Also hide session selection with OIDC in the device manager. (#7675) --- .../View/UserOtherSessionsToolbar.swift | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessionsToolbar.swift b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessionsToolbar.swift index d3c0b3bb0..ba844904e 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessionsToolbar.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessionsToolbar.swift @@ -82,12 +82,14 @@ struct UserOtherSessionsToolbar: ToolbarContent { private func optionsMenu() -> some View { Button { } label: { Menu { - Button { - isEditModeEnabled = true - } label: { - Label(VectorL10n.userOtherSessionMenuSelectSessions, systemImage: "checkmark.circle") + if showDeviceLogout { // As you can only sign out the selected sessions, we don't allow selection when you're unable to sign out devices. + Button { + isEditModeEnabled = true + } label: { + Label(VectorL10n.userOtherSessionMenuSelectSessions, systemImage: "checkmark.circle") + } + .disabled(sessionCount == 0) } - .disabled(sessionCount == 0) Button { isShowLocationEnabled.toggle() From a5c56937a081580082e4ddf3d93f692d3d1962ff Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 13 Sep 2023 15:19:11 +0100 Subject: [PATCH 5/5] version++ --- CHANGES.md | 8 ++++++++ changelog.d/7671.bugfix | 1 - changelog.d/7672.bugfix | 1 - 3 files changed, 8 insertions(+), 2 deletions(-) delete mode 100644 changelog.d/7671.bugfix delete mode 100644 changelog.d/7672.bugfix diff --git a/CHANGES.md b/CHANGES.md index 3a269699d..9aacbd168 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,11 @@ +## Changes in 1.11.3 (2023-09-13) + +🐛 Bugfixes + +- Show OIDC account management UI using embedded browser instead of system browser. ([#7671](https://github.com/vector-im/element-ios/issues/7671)) +- Hide Sign Out X/All Sessions buttons in the Device Manager when using OIDC. ([#7672](https://github.com/vector-im/element-ios/issues/7672)) + + ## Changes in 1.11.2 (2023-09-12) 🙌 Improvements diff --git a/changelog.d/7671.bugfix b/changelog.d/7671.bugfix deleted file mode 100644 index 99d153ad4..000000000 --- a/changelog.d/7671.bugfix +++ /dev/null @@ -1 +0,0 @@ -Show OIDC account management UI using embedded browser instead of system browser. \ No newline at end of file diff --git a/changelog.d/7672.bugfix b/changelog.d/7672.bugfix deleted file mode 100644 index d46797f9c..000000000 --- a/changelog.d/7672.bugfix +++ /dev/null @@ -1 +0,0 @@ -Hide Sign Out X/All Sessions buttons in the Device Manager when using OIDC. \ No newline at end of file