diff --git a/BroadcastUploadExtension/target.yml b/BroadcastUploadExtension/target.yml index 1ef21b6b3..b119b433e 100644 --- a/BroadcastUploadExtension/target.yml +++ b/BroadcastUploadExtension/target.yml @@ -38,5 +38,6 @@ targets: sources: - path: . - path: ../Config/BuildSettings.swift + - path: ../Config/MDMSettings.swift - path: ../Riot/Categories/Bundle.swift - path: ../Riot/Modules/Room/TimelineCells/Styles/RoomTimelineStyleIdentifier.swift diff --git a/CHANGES.md b/CHANGES.md index 25f3abef4..9aacbd168 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,23 @@ +## 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 + +- Upgrade MatrixSDK version ([v0.27.2](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.27.2)). + +🐛 Bugfixes + +- Fix an issue where rooms were not correctly sorted after forwarding a message. ([#7641](https://github.com/vector-im/element-ios/issues/7641)) +- Add phone number UI and explanatory text is hidden if the 3 pid changes capability is disabled. ([#7670](https://github.com/vector-im/element-ios/issues/7670)) + + ## Changes in 1.11.1 (2023-08-29) ✨ Features diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index 417da0c49..04b7b8341 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -102,13 +102,16 @@ final class BuildSettings: NSObject { static let forceHomeserverSelection = false /// Default server proposed on the authentication screen - static let serverConfigDefaultHomeserverUrlString = "https://matrix.org" + static var serverConfigDefaultHomeserverUrlString: String { + MDMSettings.serverConfigDefaultHomeserverUrlString ?? "https://matrix.org" + } /// Default identity server static let serverConfigDefaultIdentityServerUrlString = "https://vector.im" - static let serverConfigSygnalAPIUrlString = "https://matrix.org/_matrix/push/v1/notify" - + static var serverConfigSygnalAPIUrlString: String { + MDMSettings.serverConfigSygnalAPIUrlString ?? "https://matrix.org/_matrix/push/v1/notify" + } // MARK: - Legal URLs @@ -144,7 +147,9 @@ final class BuildSettings: NSObject { // This baseURL is used to generate permalinks within the app (E.g. timeline message permalinks). // Optional String that when set is used as permalink base, when nil matrix.to format is used. // Example value would be "https://www.example.com", note there is no trailing '/'. - static let clientPermalinkBaseUrl: String? = nil + static var clientPermalinkBaseUrl: String? { + MDMSettings.clientPermalinkBaseUrl + } // MARK: - VoIP static var allowVoIPUsage: Bool { diff --git a/Config/MDMSettings.swift b/Config/MDMSettings.swift new file mode 100644 index 000000000..a6699a617 --- /dev/null +++ b/Config/MDMSettings.swift @@ -0,0 +1,52 @@ +// +// Copyright 2023 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 + +enum MDMSettings { + private static let appleManagedConfigurationKey = "com.apple.configuration.managed" + + private enum Key: String { + case serverConfigDefaultHomeserverUrlString = "im.vector.app.serverConfigDefaultHomeserverUrlString" + case serverConfigSygnalAPIUrlString = "im.vector.app.serverConfigSygnalAPIUrlString" + case clientPermalinkBaseUrl = "im.vector.app.clientPermalinkBaseUrl" + } + + static var serverConfigDefaultHomeserverUrlString: String? { + valueForKey(.serverConfigDefaultHomeserverUrlString) as? String + } + + static var serverConfigSygnalAPIUrlString: String? { + valueForKey(.serverConfigSygnalAPIUrlString) as? String + } + + static var clientPermalinkBaseUrl: String? { + valueForKey(.clientPermalinkBaseUrl) as? String + } + + // MARK: - Private + + static private func valueForKey(_ key: Key) -> Any? { + guard let managedConfiguration = UserDefaults.standard.dictionary(forKey: appleManagedConfigurationKey) else { + print("MDM configuration missing") + return nil + } + + print("Retrieved MDM configuration: \(managedConfiguration)") + + return managedConfiguration[key.rawValue] + } +} diff --git a/INSTALL.md b/INSTALL.md index 792ae4fef..c740b1137 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -71,6 +71,25 @@ Be sure to use compatible branches for Element iOS and MatrixSDK. For example, i **Important**: By working with [XcodeGen](https://github.com/yonaskolb/XcodeGen) you will need to use the _New Build System_ in Xcode, to have your some of the xcconfig variables taken into account. It should be enabled by default on the latest Xcode versions, but if you need to enable it go to Xcode menu and select `File > Workspace Settings… > Build System` and then choose `New Build System`. +- **Running a local rust MatrixCryptoSDK locally** + +If you want to debug locally or test local changes of the rust `MatrixSDKCrypto` with a local `MatrixSDK`, you must checkout [matrix-rust-sdk](https://github.com/matrix-org/matrix-rust-sdk), and follow the [instructions in the repository](https://github.com/matrix-org/matrix-rust-sdk/tree/main/bindings/apple). + +Once the framework is built using `./build_crypto_xcframework.sh` you will have to move `bindings/apple/MatrixSDKCrypto-Local.podspec` to the root of the `matrix-rust-sdk` folder and rename it to `MatrixSDKCrypto.podspec` then update `s.version` with the current pod version: + +``` + s.version = "0.3.12" +``` + +Then in the element-ios `Podfile`, add the following line under the existing `pod 'MatrixSDK' [..]`: + +``` +pod 'MatrixSDKCrypto', :path => '../matrix-rust-sdk/MatrixSDKCrypto.podspec' +``` + +Run `pod install` to refresh all. + + ### `$matrixSDKVersion` Modification Every time you change the `$matrixSDKVersion` variable in the `Podfile`, you have to run the `pod install` command again. diff --git a/Podfile b/Podfile index 6be9cdf9c..75e170560 100644 --- a/Podfile +++ b/Podfile @@ -16,7 +16,7 @@ use_frameworks! # - `{ :specHash => {sdk spec hash}` to depend on specific pod options (:git => …, :podspec => …) for MatrixSDK repo. Used by Fastfile during CI # # Warning: our internal tooling depends on the name of this variable name, so be sure not to change it -$matrixSDKVersion = '= 0.27.1' +$matrixSDKVersion = '= 0.27.2' # $matrixSDKVersion = :local # $matrixSDKVersion = { :branch => 'develop'} # $matrixSDKVersion = { :specHash => { git: 'https://git.io/fork123', branch: 'fix' } } diff --git a/Podfile.lock b/Podfile.lock index 2254e7cdd..ec974f0f2 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -20,31 +20,13 @@ PODS: - Down (0.11.0) - DSBottomSheet (0.3.0) - DSWaveformImage (6.1.1) - - DTCoreText (1.6.26): - - DTCoreText/Core (= 1.6.26) - - DTFoundation/Core (~> 1.7.5) - - DTFoundation/DTAnimatedGIF (~> 1.7.5) - - DTFoundation/DTHTMLParser (~> 1.7.5) - - DTFoundation/UIKit (~> 1.7.5) - - DTCoreText/Core (1.6.26): - - DTFoundation/Core (~> 1.7.5) - - DTFoundation/DTAnimatedGIF (~> 1.7.5) - - DTFoundation/DTHTMLParser (~> 1.7.5) - - DTFoundation/UIKit (~> 1.7.5) - - DTFoundation/Core (1.7.18) - - DTFoundation/DTAnimatedGIF (1.7.18) - - DTFoundation/DTHTMLParser (1.7.18): - - DTFoundation/Core - - DTFoundation/UIKit (1.7.18): - - DTFoundation/Core - - DTTJailbreakDetection (0.4.0) - FLEX (4.5.0) - FlowCommoniOS (1.12.2) - GBDeviceInfo (7.1.0): - GBDeviceInfo/Core (= 7.1.0) - GBDeviceInfo/Core (7.1.0) - GZIP (1.3.0) - - Introspect (0.12.0) + - Introspect (0.11.0) - JitsiMeetSDKLite (8.1.2-lite): - JitsiWebRTC (~> 111.0) - JitsiWebRTC (111.0.2) @@ -57,12 +39,9 @@ PODS: - LoggerAPI (1.9.200): - Logging (~> 1.1) - Logging (1.4.0) - - MatomoTracker (7.5.2): - - MatomoTracker/Core (= 7.5.2) - - MatomoTracker/Core (7.5.2) - - MatrixSDK (0.27.1): - - MatrixSDK/Core (= 0.27.1) - - MatrixSDK/Core (0.27.1): + - MatrixSDK (0.27.2): + - MatrixSDK/Core (= 0.27.2) + - MatrixSDK/Core (0.27.2): - AFNetworking (~> 4.0.0) - GZIP (~> 1.3.0) - libbase58 (~> 0.1.4) @@ -70,7 +49,7 @@ PODS: - OLMKit (~> 3.2.5) - Realm (= 10.27.0) - SwiftyBeaver (= 1.9.5) - - MatrixSDK/JingleCallStack (0.27.1): + - MatrixSDK/JingleCallStack (0.27.2): - JitsiMeetSDKLite (= 8.1.2-lite) - MatrixSDK/Core - MatrixSDKCrypto (0.3.12) @@ -79,7 +58,7 @@ PODS: - OLMKit/olmcpp (= 3.2.12) - OLMKit/olmc (3.2.12) - OLMKit/olmcpp (3.2.12) - - PostHog (2.0.2) + - PostHog (2.0.0) - ReadMoreTextView (3.0.1) - Realm (10.27.0): - Realm/Headers (= 10.27.0) @@ -94,7 +73,7 @@ PODS: - Sentry/Core (7.15.0) - SideMenu (6.5.0) - SwiftBase32 (0.9.0) - - SwiftFormat/CLI (0.52.3) + - SwiftFormat/CLI (0.50.2) - SwiftGen (6.6.2) - SwiftJWT (3.6.200): - BlueCryptor (~> 1.0) @@ -102,7 +81,7 @@ PODS: - BlueRSA (~> 1.0) - KituraContracts (~> 1.2) - LoggerAPI (~> 1.7) - - SwiftLint (0.52.4) + - SwiftLint (0.49.1) - SwiftyBeaver (1.9.5) - UICollectionViewLeftAlignedLayout (1.0.2) - UICollectionViewRightAlignedLayout (0.0.3) @@ -116,8 +95,6 @@ DEPENDENCIES: - Down (~> 0.11.0) - DSBottomSheet (~> 0.3) - DSWaveformImage (~> 6.1.1) - - DTCoreText (= 1.6.26) - - DTTJailbreakDetection (~> 0.4.0) - FLEX (~> 4.5.0) - FlowCommoniOS (~> 1.12.0) - GBDeviceInfo (~> 7.1.0) @@ -125,9 +102,8 @@ DEPENDENCIES: - KeychainAccess (~> 4.2.2) - KTCenterFlowLayout (~> 1.3.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatomoTracker (~> 7.5.2) - - MatrixSDK (from `https://dl-gitlab.example.com/bwmessenger/bundesmessenger/bundesmessenger-ios-sdk`, tag `v2.10.0_rc2`) - - MatrixSDK/JingleCallStack (from `https://dl-gitlab.example.com/bwmessenger/bundesmessenger/bundesmessenger-ios-sdk`, tag `v2.10.0_rc2`) + - MatrixSDK (= 0.27.2) + - MatrixSDK/JingleCallStack (= 0.27.2) - OLMKit - PostHog (~> 2.0.0) - ReadMoreTextView (~> 3.0.1) @@ -146,7 +122,7 @@ DEPENDENCIES: - ZXingObjC (~> 3.6.5) SPEC REPOS: - https://github.com/CocoaPods/Specs.git: + trunk: - AFNetworking - BlueCryptor - BlueECC @@ -154,9 +130,6 @@ SPEC REPOS: - Down - DSBottomSheet - DSWaveformImage - - DTCoreText - - DTFoundation - - DTTJailbreakDetection - FLEX - FlowCommoniOS - GBDeviceInfo @@ -171,7 +144,7 @@ SPEC REPOS: - libPhoneNumber-iOS - LoggerAPI - Logging - - MatomoTracker + - MatrixSDK - MatrixSDKCrypto - OLMKit - PostHog @@ -192,16 +165,6 @@ SPEC REPOS: - zxcvbn-ios - ZXingObjC -EXTERNAL SOURCES: - MatrixSDK: - :git: https://dl-gitlab.example.com/bwmessenger/bundesmessenger/bundesmessenger-ios-sdk - :tag: v2.10.0_rc2 - -CHECKOUT OPTIONS: - MatrixSDK: - :git: https://dl-gitlab.example.com/bwmessenger/bundesmessenger/bundesmessenger-ios-sdk - :tag: v2.10.0_rc2 - SPEC CHECKSUMS: AFNetworking: 3bd23d814e976cd148d7d44c3ab78017b744cd58 BlueCryptor: b0aee3d9b8f367b49b30de11cda90e1735571c24 @@ -210,14 +173,11 @@ SPEC CHECKSUMS: Down: b6ba1bc985c9d2f4e15e3b293d2207766fa12612 DSBottomSheet: ca0ac37eb5af2dd54663f86b84382ed90a59be2a DSWaveformImage: 3c718a0cf99291887ee70d1d0c18d80101d3d9ce - DTCoreText: ec749e013f2e1f76de5e7c7634642e600a7467ce - DTFoundation: a53f8cda2489208cbc71c648be177f902ee17536 - DTTJailbreakDetection: 5e356c5badc17995f65a83ed9483f787a0057b71 FLEX: e51461dd6f0bfb00643c262acdfea5d5d12c596b FlowCommoniOS: ca92071ab526dc89905495a37844fd7e78d1a7f2 GBDeviceInfo: 5d62fa85bdcce3ed288d83c28789adf1173e4376 GZIP: 416858efbe66b41b206895ac6dfd5493200d95b3 - Introspect: b66b675de8a85d9ef832f3a710d8e3c7db186884 + Introspect: 4cc1e4c34dd016540c8d86a591c231c09dafbee3 JitsiMeetSDKLite: 895213158cf62342069a10634a41d2f1c00057f7 JitsiWebRTC: 80f62908fcf2a1160e0d14b584323fb6e6be630b KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51 @@ -227,21 +187,20 @@ SPEC CHECKSUMS: libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d Logging: beeb016c9c80cf77042d62e83495816847ef108b - MatomoTracker: 1d98ddc58322fd9d65e1a6886b8e41363047bd13 - MatrixSDK: f6c197ca06aab29ff69d1105965a57d277dfcd9d + MatrixSDK: a39e229a03a00394e055ffa7490e9602d45f8a44 MatrixSDKCrypto: 25929a40733b4ab54f659aaf6a730552a0a06504 OLMKit: da115f16582e47626616874e20f7bb92222c7a51 - PostHog: f9e5c13ceea86bb5314218c85d16125b797eb332 + PostHog: 660ec6c9d80cec17b685e148f17f6785a88b597d ReadMoreTextView: 19147adf93abce6d7271e14031a00303fe28720d Realm: 9ca328bd7e700cc19703799785e37f77d1a130f2 Reusable: 6bae6a5e8aa793c9c441db0213c863a64bce9136 Sentry: 63ca44f5e0c8cea0ee5a07686b02e56104f41ef7 SideMenu: f583187d21c5b1dd04c72002be544b555a2627a2 SwiftBase32: 9399c25a80666dc66b51e10076bf591e3bbb8f17 - SwiftFormat: 5de81c42f043741a16e17ae2da012bbddc7c0b58 + SwiftFormat: 710117321c55c82675c0dc03055128efbb13c38f SwiftGen: 1366a7f71aeef49954ca5a63ba4bef6b0f24138c SwiftJWT: 88c412708f58c169d431d344c87bc79a87c830ae - SwiftLint: 1cc5cd61ba9bacb2194e340aeb47a2a37fda00b3 + SwiftLint: 32ee33ded0636d0905ef6911b2b67bbaeeedafa5 SwiftyBeaver: 84069991dd5dca07d7069100985badaca7f0ce82 UICollectionViewLeftAlignedLayout: 830bf6fa5bab9f9b464f62e3384f9d2e00b3c0f6 UICollectionViewRightAlignedLayout: 823eef8c567eba4a44c21bc2ffcb0d0d5f361e2d @@ -249,6 +208,6 @@ SPEC CHECKSUMS: zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: e7f4847564a30ed02659a311fe204021498f30e8 +PODFILE CHECKSUM: b926e281576aabcdbc2bdcb40ef3ad3e0991abe8 COCOAPODS: 1.11.3 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/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 9e33cee1d..258053c6c 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -4634,7 +4634,8 @@ static CGSize kThreadListBarButtonItemImageSize; { ForwardingShareItemSender *shareItemSender = [[ForwardingShareItemSender alloc] initWithEvent:selectedEvent]; self.shareManager = [[ShareManager alloc] initWithShareItemSender:shareItemSender - type:ShareManagerTypeForward]; + type:ShareManagerTypeForward + session:self.mainSession]; MXWeakify(self); [self.shareManager setCompletionCallback:^(ShareManagerResult result) { diff --git a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m index 7b93bd843..3b3b8a2a3 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -46,7 +46,7 @@ enum { }; -@interface ManageSessionViewController () +@interface ManageSessionViewController () { // The device to display MXDevice *device; @@ -65,6 +65,8 @@ enum { @property (nonatomic, strong) ReauthenticationCoordinatorBridgePresenter *reauthenticationCoordinatorBridgePresenter; +@property (nonatomic, strong) SSOAuthenticationPresenter *ssoAuthenticationPresenter; + @end @implementation ManageSessionViewController @@ -701,17 +703,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]; } @@ -777,4 +781,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 66f8afe94..01479ef92 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -272,7 +272,8 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate, ServiceTermsModalCoordinatorBridgePresenterDelegate, TableViewSectionsDelegate, ThreadsBetaCoordinatorBridgePresenterDelegate, -ChangePasswordCoordinatorBridgePresenterDelegate> +ChangePasswordCoordinatorBridgePresenterDelegate, +SSOAuthenticationPresenterDelegate> { // Current alert (if any). __weak UIAlertController *currentAlert; @@ -373,6 +374,8 @@ ChangePasswordCoordinatorBridgePresenterDelegate> @property (nonatomic) BOOL isPreparingIdentityService; @property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter; +@property (nonatomic, strong) SSOAuthenticationPresenter *ssoAuthenticationPresenter; + @property (nonatomic) AnalyticsScreenTracker *screenTracker; @end @@ -473,24 +476,29 @@ ChangePasswordCoordinatorBridgePresenterDelegate> [sectionUserSettings addRowWithTag: USER_SETTINGS_PHONENUMBERS_OFFSET + index]; } } - if (BWIBuildSettings.shared.settingsScreenAllowAddingEmailThreepids && - BuildSettings.settingsScreenAllowAddingEmailThreepids && - // If the threePidChanges is nil we assume the capability to be true - (!self.mainSession.homeserverCapabilities.threePidChanges || - self.mainSession.homeserverCapabilities.threePidChanges.enabled)) - { - [sectionUserSettings addRowWithTag:USER_SETTINGS_ADD_EMAIL_INDEX]; - } - if (BWIBuildSettings.shared.settingsScreenAllowAddingPhoneThreepids) - { - [sectionUserSettings addRowWithTag:USER_SETTINGS_ADD_PHONENUMBER_INDEX]; - } - if (BWIBuildSettings.shared.settingsScreenShowThreepidExplanatory) - { - NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:[VectorL10n settingsThreePidsManagementInformationPart1] attributes:@{}]; - [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:[VectorL10n settingsThreePidsManagementInformationPart2] attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.tintColor}]]; - [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:[VectorL10n settingsThreePidsManagementInformationPart3] attributes:@{}]]; - sectionUserSettings.attributedFooterTitle = attributedString; + + // If the threePidChanges is nil we assume the capability to be true + if (!self.mainSession.homeserverCapabilities.threePidChanges || + self.mainSession.homeserverCapabilities.threePidChanges.enabled) { + if (BWIBuildSettings.shared.settingsScreenAllowAddingEmailThreepids && + BuildSettings.settingsScreenAllowAddingEmailThreepids && + // If the threePidChanges is nil we assume the capability to be true + (!self.mainSession.homeserverCapabilities.threePidChanges || + self.mainSession.homeserverCapabilities.threePidChanges.enabled)) + { + [sectionUserSettings addRowWithTag:USER_SETTINGS_ADD_EMAIL_INDEX]; + } + if (BWIBuildSettings.shared.settingsScreenAllowAddingPhoneThreepids) + { + [sectionUserSettings addRowWithTag:USER_SETTINGS_ADD_PHONENUMBER_INDEX]; + } + if (BWIBuildSettings.shared.settingsScreenShowThreepidExplanatory) + { + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:[VectorL10n settingsThreePidsManagementInformationPart1] attributes:@{}]; + [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:[VectorL10n settingsThreePidsManagementInformationPart2] attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.tintColor}]]; + [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:[VectorL10n settingsThreePidsManagementInformationPart3] attributes:@{}]]; + sectionUserSettings.attributedFooterTitle = attributedString; + } } sectionUserSettings.headerTitle = [VectorL10n settingsUserSettings]; @@ -4498,7 +4506,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]; } } @@ -5510,4 +5523,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/RiotNSE/target.yml b/RiotNSE/target.yml index 67e644b4a..f016d3686 100644 --- a/RiotNSE/target.yml +++ b/RiotNSE/target.yml @@ -60,6 +60,7 @@ targets: - path: ../Config/BuM/BWIBuildSettings+BuM.swift - path: ../bwi/UserAgent/UserAgentService.swift - path: ../bwi/LoginProtection/LoginProtectionService.swift + - path: ../Config/MDMSettings.swift - path: ../Riot/Utils/DataProtectionHelper.swift - path: ../Config/CommonConfiguration.swift - path: ../Riot/Experiments/ diff --git a/RiotShareExtension/Shared/ForwardingShareItemSender.swift b/RiotShareExtension/Shared/ForwardingShareItemSender.swift index e87fa8c3f..e75b6b3fd 100644 --- a/RiotShareExtension/Shared/ForwardingShareItemSender.swift +++ b/RiotShareExtension/Shared/ForwardingShareItemSender.swift @@ -53,10 +53,11 @@ class ForwardingShareItemSender: NSObject, ShareItemSenderProtocol { var localEcho: MXEvent? room.sendMessage(withContent: event.content, threadId: nil, localEcho: &localEcho) { result in switch result { - case .success(_): - self.trackForwardMessage(room: room) case .failure(let innerError): errors.append(innerError) + default: + room.summary.resetLastMessage(nil, failure: nil, commit: false) + break } dispatchGroup.leave() @@ -72,12 +73,4 @@ class ForwardingShareItemSender: NSObject, ShareItemSenderProtocol { success() } } - - func trackForwardMessage(room: MXRoom) { - BWIAnalyticsHelper.getRoomDeviceCount(room: room) { deviceCount in - let deviceCountString = BWIAnalyticsHelper.dimensionForDeviceCount(deviceCount) - let messageType = BWIAnalyticsHelper.getForwardingType(event: self.event) - BWIAnalytics.sharedTracker.trackEventWithDimension(category: "Feature", action: "ForwardMessage", dimension: deviceCountString, value: nil, name: messageType) - } - } } diff --git a/RiotShareExtension/Shared/ShareManager.h b/RiotShareExtension/Shared/ShareManager.h index c2b110a05..5b7edb90f 100644 --- a/RiotShareExtension/Shared/ShareManager.h +++ b/RiotShareExtension/Shared/ShareManager.h @@ -36,7 +36,9 @@ typedef NS_ENUM(NSUInteger, ShareManagerResult) { @property (nonatomic, copy) void (^completionCallback)(ShareManagerResult); - (instancetype)initWithShareItemSender:(id)itemSender - type:(ShareManagerType)type; + type:(ShareManagerType)type + session:(nullable MXSession*)session; + - (UIViewController *)mainViewController; diff --git a/RiotShareExtension/Shared/ShareManager.m b/RiotShareExtension/Shared/ShareManager.m index 0e1c74cf7..7e2b0172e 100644 --- a/RiotShareExtension/Shared/ShareManager.m +++ b/RiotShareExtension/Shared/ShareManager.m @@ -31,6 +31,8 @@ @property (nonatomic, strong, readonly) ShareViewController *shareViewController; +@property (nonatomic) BOOL useCustomSession; +@property (nonatomic, strong) MXSession* session; @property (nonatomic, strong) MXKAccount *userAccount; @property (nonatomic, strong) MXFileStore *fileStore; @@ -51,11 +53,14 @@ static MXSession *fakeSession; - (instancetype)initWithShareItemSender:(id)itemSender type:(ShareManagerType)type + session:(MXSession*)session { if (self = [super init]) { _shareItemSender = itemSender; _shareItemSender.delegate = self; + _session = session; + _useCustomSession = _session == nil; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(checkUserAccount) name:kMXKAccountManagerDidRemoveAccountNotification object:nil]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(checkUserAccount) name:NSExtensionHostWillEnterForegroundNotification object:nil]; @@ -71,7 +76,20 @@ static MXSession *fakeSession; [NSBundle mxk_setLanguage:language]; [NSBundle mxk_setFallbackLanguage:@"en"]; - [self checkUserAccount]; + if (!_useCustomSession) + { + // If we don't use a custom session, we can initialize the shareViewController with our existing session + self.userAccount = [MXKAccountManager sharedManager].activeAccounts.firstObject; + ShareDataSource *roomDataSource = [[ShareDataSource alloc] initWithFileStore:_session.store + session:_session]; + + [self.shareViewController configureWithState:ShareViewControllerAccountStateConfigured + roomDataSource:roomDataSource]; + } + else + { + [self checkUserAccount]; + } } return self; @@ -95,32 +113,23 @@ static MXSession *fakeSession; MXStrongifyAndReturnIfNil(self); [self.userAccount handleUnauthenticatedWithError:error isSoftLogout:isSoftLogout isRefreshTokenAuth:isRefreshTokenAuth andCompletion:completion]; }]; - MXSession *session = [[MXSession alloc] initWithMatrixRestClient:restClient]; - [MXFileStore setPreloadOptions:0]; - - MXWeakify(session); - [session setStore:self.fileStore success:^{ - MXStrongifyAndReturnIfNil(session); - - self.selectedRooms = [NSMutableArray array]; - for (NSString *roomIdentifier in roomIdentifiers) { - MXRoom *room = [MXRoom loadRoomFromStore:self.fileStore withRoomId:roomIdentifier matrixSession:session]; - if (room) { - [self.selectedRooms addObject:room]; - } - } - - [self.shareItemSender sendItemsToRooms:self.selectedRooms success:^{ - self.selectedRooms = nil; - self.completionCallback(ShareManagerResultFinished); - } failure:^(NSArray *errors) { - self.selectedRooms = nil; - [self showFailureAlert:[VectorL10n roomEventFailedToSend]]; + if (self.useCustomSession || !self.session) + { + MXSession* session = [[MXSession alloc] initWithMatrixRestClient:restClient]; + [MXFileStore setPreloadOptions:0]; + + MXWeakify(session); + [session setStore:self.fileStore success:^{ + MXStrongifyAndReturnIfNil(session); + [self shareForRoomIdentifiers:roomIdentifiers usingSession:session]; + } failure:^(NSError *error) { + MXLogError(@"[ShareManager] Failed preparing matrix session"); }]; - - } failure:^(NSError *error) { - MXLogError(@"[ShareManager] Failed preparing matrix session"); - }]; + } + else + { + [self shareForRoomIdentifiers:roomIdentifiers usingSession:self.session]; + } } - (void)shareViewControllerDidRequestDismissal:(ShareViewController *)shareViewController @@ -142,6 +151,25 @@ static MXSession *fakeSession; #pragma mark - Private +- (void)shareForRoomIdentifiers:(NSSet *)roomIdentifiers usingSession:(MXSession*)session +{ + self.selectedRooms = [NSMutableArray array]; + for (NSString *roomIdentifier in roomIdentifiers) { + MXRoom *room = [MXRoom loadRoomFromStore:session.store withRoomId:roomIdentifier matrixSession:session]; + if (room) { + [self.selectedRooms addObject:room]; + } + } + + [self.shareItemSender sendItemsToRooms:self.selectedRooms success:^{ + self.selectedRooms = nil; + self.completionCallback(ShareManagerResultFinished); + } failure:^(NSArray *errors) { + self.selectedRooms = nil; + [self showFailureAlert:[VectorL10n roomEventFailedToSend]]; + }]; +} + - (void)showFailureAlert:(NSString *)title { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert]; diff --git a/RiotShareExtension/target.yml b/RiotShareExtension/target.yml index 242a7e373..af013c12b 100644 --- a/RiotShareExtension/target.yml +++ b/RiotShareExtension/target.yml @@ -54,6 +54,7 @@ targets: - path: ../Config/BWIBuildSettings.swift - path: ../Config/BuM/BWIBuildSettings+BuM.swift - path: ../bwi/UserAgent/UserAgentService.swift + - path: ../Config/MDMSettings.swift - path: ../Riot/Categories/Character.swift - path: ../Riot/Categories/MXKImageView.swift - path: ../Riot/Categories/MXRoom+Riot.m diff --git a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift index 4c6de9815..544d1ec70 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? @@ -188,21 +189,25 @@ 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) } 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) @@ -547,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/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 264ea3714..f6c871562 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 @@ -87,12 +88,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) if BWIBuildSettings.shared.deviceManagerShowIPAddress { Button { @@ -102,7 +105,7 @@ struct UserOtherSessionsToolbar: ToolbarContent { } } - 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 2dab5ee91..589c024d1 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 let showLocationInfo = BWIBuildSettings.shared.deviceManagerShowIPAddress && settingsService.showIPAddressesInSessionsManager - super.init(initialViewState: .init(showLocationInfo: showLocationInfo)) + super.init(initialViewState: .init(showLocationInfo: showLocationInfo, 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 a9a4908bd..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: { @@ -141,19 +141,20 @@ struct UserSessionsOverview: View { private var otherSessionsMenu: some View { Menu { - if BWIBuildSettings.shared.deviceManagerShowIPAddress { - Button { - withAnimation { - viewModel.send(viewAction: .showLocationInfo) - } - } label: { - Label(showLocationInfo: viewModel.viewState.showLocationInfo) + Button { + withAnimation { + viewModel.send(viewAction: .showLocationInfo) } - } - DestructiveButton { - viewModel.send(viewAction: .logoutOtherSessions) } label: { - Label(VectorL10n.userOtherSessionMenuSignOutSessions(String(viewModel.viewState.otherSessionsViewData.count)), systemImage: "rectangle.portrait.and.arrow.forward.fill") + Label(showLocationInfo: viewModel.viewState.showLocationInfo) + } + + 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/RiotSwiftUI/target.yml b/RiotSwiftUI/target.yml index 99d1e3927..607e87c9e 100644 --- a/RiotSwiftUI/target.yml +++ b/RiotSwiftUI/target.yml @@ -55,6 +55,7 @@ targets: - path: ../Config/BuildSettings.swift - path: ../Config/BWIBuildSettings.swift - path: ../Config/BuM/BWIBuildSettings+BuM.swift + - path: ../Config/MDMSettings.swift - path: ../Riot/Modules/Room/TimelineCells/Styles/RoomTimelineStyleIdentifier.swift - path: ../Riot/Categories/String.swift - path: ../Riot/Categories/Character.swift diff --git a/RiotSwiftUI/targetUITests.yml b/RiotSwiftUI/targetUITests.yml index 3895aefff..85c8d6272 100644 --- a/RiotSwiftUI/targetUITests.yml +++ b/RiotSwiftUI/targetUITests.yml @@ -64,6 +64,7 @@ targets: - path: ../Config/BuildSettings.swift - path: ../Config/BWIBuildSettings.swift - path: ../Config/BuM/BWIBuildSettings+BuM.swift + - path: ../Config/MDMSettings.swift - path: ../Riot/Modules/Room/TimelineCells/Styles/RoomTimelineStyleIdentifier.swift - path: ../Riot/Categories/String.swift - path: ../Riot/Categories/Character.swift diff --git a/RiotTests/target.yml b/RiotTests/target.yml index ad4d778cc..01ba2d891 100644 --- a/RiotTests/target.yml +++ b/RiotTests/target.yml @@ -65,6 +65,7 @@ targets: - path: ../Config/BuM/BWIBuildSettings+BuM.swift - path: ../Config/AppConfiguration.swift - path: ../Config/CommonConfiguration.swift + - path: ../Config/MDMSettings.swift - path: ../Riot/Categories/Bundle.swift - path: ../Riot/Managers/AppInfo/AppInfo.swift - path: ../Riot/Managers/AppInfo/AppVersion.swift diff --git a/SiriIntents/target.yml b/SiriIntents/target.yml index a2bd3c871..444e43e57 100644 --- a/SiriIntents/target.yml +++ b/SiriIntents/target.yml @@ -50,6 +50,7 @@ targets: - path: ../Config/BuildSettings.swift - path: ../Config/BWIBuildSettings.swift - path: ../Config/BuM/BWIBuildSettings+BuM.swift + - path: ../Config/MDMSettings.swift - path: ../Config/Configurable.swift - path: ../Riot/Managers/Settings/RiotSettings.swift - path: ../Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift