diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 9a741847a..cdc9afacb 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -619,6 +619,7 @@ 6851B077B4C913CC12DB6E77 /* AppLockFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE93F0CBF0D96B77111C413 /* AppLockFlowCoordinator.swift */; }; 6885C3D26FC1026E07408D3C /* RoomListActivityVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5DA892E8643240C7BC41900 /* RoomListActivityVisibility.swift */; }; 68B2DD307C57ECFABBB05323 /* DeclineAndBlockScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 127D1947BA9C6CA62E3D03EC /* DeclineAndBlockScreen.swift */; }; + 68B7308BA24EC3DC5FD87B61 /* OAuthPresenterHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04CAC77224E949D8E758E2E3 /* OAuthPresenterHook.swift */; }; 68C3AF257678F6E7BB238C3F /* AppAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FD22AEFFA20065494ED2333 /* AppAppearance.swift */; }; 695825D20A761C678809345D /* MessageForwardingScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52135BD9E0E7A091688F627A /* MessageForwardingScreenModels.swift */; }; 695BE6A2337A634F48B5DBC8 /* RoomMembersFlowCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC666DAE98245269775329B2 /* RoomMembersFlowCoordinatorTests.swift */; }; @@ -1642,6 +1643,7 @@ 046C0D3F53B0B5EF0A1F5BEA /* RoomSummaryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryTests.swift; sourceTree = ""; }; 048A21188AB19349D026BECD /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 04BB8DDE245ED86C489BA983 /* AccessibilityIdentifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityIdentifiers.swift; sourceTree = ""; }; + 04CAC77224E949D8E758E2E3 /* OAuthPresenterHook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthPresenterHook.swift; sourceTree = ""; }; 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = ""; }; 04EB6035C1F33F25F1EBFB7D /* RoomThreadListServiceProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomThreadListServiceProxyProtocol.swift; sourceTree = ""; }; 0516C69708D5CBDE1A8E77EC /* RoomDirectorySearchProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchProxyProtocol.swift; sourceTree = ""; }; @@ -3711,6 +3713,7 @@ 7AC0CD1CAFD3F8B057F9AEA5 /* ClientBuilderHook.swift */, 8B89D6C760E8CAE29CA28FB1 /* CompoundHook.swift */, 0F60FDB3AEFC60830BD289FE /* DeveloperOptionsScreenHook.swift */, + 04CAC77224E949D8E758E2E3 /* OAuthPresenterHook.swift */, D5D186A6DB8FAC5C9D0E4D61 /* RemoteSettingsHook.swift */, B343C5255FB408DDE853CFDF /* RoomScreenHook.swift */, 2A95C9B8299A36A6495DECA6 /* TracingHook.swift */, @@ -8605,6 +8608,7 @@ 6AC798F52571BE495E6AA1CE /* OAuthAccountSettingsPresenter.swift in Sources */, E32E71A1AF5E5E69E8363B26 /* OAuthAuthenticationPresenter.swift in Sources */, C525F6C2892CB0640E776B3D /* OAuthConfiguration.swift in Sources */, + 68B7308BA24EC3DC5FD87B61 /* OAuthPresenterHook.swift in Sources */, FD573B5D665824EB79EABF06 /* Observable.swift in Sources */, 11A6B8E3CBDBF0A4107FF4CE /* OnboardingFlowCoordinator.swift in Sources */, 3CE4C5071B6D2576E2473989 /* OrderedSet.swift in Sources */, diff --git a/ElementX/Sources/AppHooks/AppHooks.swift b/ElementX/Sources/AppHooks/AppHooks.swift index c35cf63c4..c3272bff5 100644 --- a/ElementX/Sources/AppHooks/AppHooks.swift +++ b/ElementX/Sources/AppHooks/AppHooks.swift @@ -34,6 +34,11 @@ class AppHooks: AppHooksProtocol { certificateValidatorHook = hook } + private(set) var oAuthPresenterHook: OAuthPresenterHookProtocol = DefaultOAuthPresenterHook() + func registerOAuthPresenterHook(_ hook: OAuthPresenterHookProtocol) { + oAuthPresenterHook = hook + } + private(set) var roomScreenHook: RoomScreenHookProtocol = DefaultRoomScreenHook() func registerRoomScreenHook(_ hook: RoomScreenHookProtocol) { roomScreenHook = hook diff --git a/ElementX/Sources/AppHooks/Hooks/OAuthPresenterHook.swift b/ElementX/Sources/AppHooks/Hooks/OAuthPresenterHook.swift new file mode 100644 index 000000000..738dac7a5 --- /dev/null +++ b/ElementX/Sources/AppHooks/Hooks/OAuthPresenterHook.swift @@ -0,0 +1,19 @@ +// +// Copyright 2025 Element Creations Ltd. +// Copyright 2025 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +import Foundation + +protocol OAuthPresenterHookProtocol { + func update(_ url: URL) -> URL +} + +struct DefaultOAuthPresenterHook: OAuthPresenterHookProtocol { + func update(_ url: URL) -> URL { + url + } +} diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 5eb7cf26a..6b4cdd720 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -720,6 +720,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg keyBackupNeeded: false, appMediator: appMediator, appSettings: appSettings, + appHooks: appHooks, userIndicatorController: ServiceLocator.shared.userIndicatorController) let coordinator = SoftLogoutScreenCoordinator(parameters: parameters) self.softLogoutCoordinator = coordinator diff --git a/ElementX/Sources/FlowCoordinators/AuthenticationFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/AuthenticationFlowCoordinator.swift index de12f30c1..3135ed8c6 100644 --- a/ElementX/Sources/FlowCoordinators/AuthenticationFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/AuthenticationFlowCoordinator.swift @@ -425,6 +425,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol { redirectURL: appSettings.oAuthRedirectURL, presentationAnchor: presentationAnchor, appMediator: appMediator, + appHooks: appHooks, userIndicatorController: userIndicatorController) oAuthPresenter = presenter diff --git a/ElementX/Sources/FlowCoordinators/ChatsTabFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/ChatsTabFlowCoordinator.swift index 2629b21c9..8b226fd33 100644 --- a/ElementX/Sources/FlowCoordinators/ChatsTabFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/ChatsTabFlowCoordinator.swift @@ -692,6 +692,7 @@ class ChatsTabFlowCoordinator: FlowCoordinatorProtocol { let parameters = EncryptionResetFlowCoordinatorParameters(userSession: userSession, appMediator: flowParameters.appMediator, appSettings: flowParameters.appSettings, + appHooks: flowParameters.appHooks, userIndicatorController: flowParameters.userIndicatorController, navigationStackCoordinator: sheetNavigationStackCoordinator, windowManger: flowParameters.windowManager) diff --git a/ElementX/Sources/FlowCoordinators/EncryptionResetFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/EncryptionResetFlowCoordinator.swift index 3e4993866..e260d56a5 100644 --- a/ElementX/Sources/FlowCoordinators/EncryptionResetFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/EncryptionResetFlowCoordinator.swift @@ -21,6 +21,7 @@ struct EncryptionResetFlowCoordinatorParameters { let userSession: UserSessionProtocol let appMediator: AppMediatorProtocol let appSettings: AppSettings + let appHooks: AppHooks let userIndicatorController: UserIndicatorControllerProtocol let navigationStackCoordinator: NavigationStackCoordinator let windowManger: WindowManagerProtocol @@ -30,6 +31,7 @@ class EncryptionResetFlowCoordinator: FlowCoordinatorProtocol { private let userSession: UserSessionProtocol private let appMediator: AppMediatorProtocol private let appSettings: AppSettings + private let appHooks: AppHooks private let userIndicatorController: UserIndicatorControllerProtocol private let navigationStackCoordinator: NavigationStackCoordinator @@ -66,6 +68,7 @@ class EncryptionResetFlowCoordinator: FlowCoordinatorProtocol { userSession = parameters.userSession appMediator = parameters.appMediator appSettings = parameters.appSettings + appHooks = parameters.appHooks userIndicatorController = parameters.userIndicatorController navigationStackCoordinator = parameters.navigationStackCoordinator windowManager = parameters.windowManger @@ -162,7 +165,8 @@ class EncryptionResetFlowCoordinator: FlowCoordinatorProtocol { accountSettingsPresenter = OAuthAccountSettingsPresenter(accountURL: url, presentationAnchor: windowManager.mainWindow, appMediator: appMediator, - appSettings: appSettings) + appSettings: appSettings, + appHooks: appHooks) accountSettingsPresenter?.start() } } diff --git a/ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift index 2398423aa..08a27ec21 100644 --- a/ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift @@ -22,6 +22,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { private let analyticsService: AnalyticsService private let appMediator: AppMediatorProtocol private let appSettings: AppSettings + private let appHooks: AppHooks private let notificationManager: NotificationManagerProtocol private let userIndicatorController: UserIndicatorControllerProtocol private let windowManager: WindowManagerProtocol @@ -69,6 +70,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { analyticsService = flowParameters.analytics appMediator = flowParameters.appMediator appSettings = flowParameters.appSettings + appHooks = flowParameters.appHooks notificationManager = flowParameters.notificationManager userIndicatorController = flowParameters.userIndicatorController windowManager = flowParameters.windowManager @@ -319,6 +321,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { let coordinator = EncryptionResetFlowCoordinator(parameters: .init(userSession: userSession, appMediator: appMediator, appSettings: appSettings, + appHooks: appHooks, userIndicatorController: userIndicatorController, navigationStackCoordinator: resetNavigationStackCoordinator, windowManger: windowManager)) diff --git a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift index e0304967f..eabcf0e7e 100644 --- a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift @@ -305,6 +305,7 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol { presentationAnchor: flowParameters.windowManager.mainWindow, appMediator: flowParameters.appMediator, appSettings: flowParameters.appSettings, + appHooks: flowParameters.appHooks, continuation: continuation) accountSettingsPresenter?.start() } diff --git a/ElementX/Sources/Screens/Authentication/OAuthAuthenticationPresenter.swift b/ElementX/Sources/Screens/Authentication/OAuthAuthenticationPresenter.swift index 7c2fe0f18..2a5a3e348 100644 --- a/ElementX/Sources/Screens/Authentication/OAuthAuthenticationPresenter.swift +++ b/ElementX/Sources/Screens/Authentication/OAuthAuthenticationPresenter.swift @@ -19,6 +19,7 @@ class OAuthAuthenticationPresenter: NSObject { private let redirectURL: URL private let presentationAnchor: UIWindow private let appMediator: AppMediatorProtocol + private let appHooks: AppHooks private let userIndicatorController: UserIndicatorControllerProtocol /// The data required to complete a request. @@ -39,11 +40,13 @@ class OAuthAuthenticationPresenter: NSObject { redirectURL: URL, presentationAnchor: UIWindow, appMediator: AppMediatorProtocol, + appHooks: AppHooks, userIndicatorController: UserIndicatorControllerProtocol) { self.authenticationService = authenticationService self.redirectURL = redirectURL self.presentationAnchor = presentationAnchor self.appMediator = appMediator + self.appHooks = appHooks self.userIndicatorController = userIndicatorController super.init() } @@ -54,9 +57,9 @@ class OAuthAuthenticationPresenter: NSObject { /// In particular if the authentication URL requires opening an external app, then the user may return /// to the app without completing (or cancelling) the authentication. func authenticate(using oAuthData: OAuthAuthorizationDataProxy) async -> Result { + let authenticationURL = appHooks.oAuthPresenterHook.update(oAuthData.url) + let response = await withCheckedContinuation { continuation in - let authenticationURL = oAuthData.url - let session = ASWebAuthenticationSession(url: authenticationURL, callback: .oAuthRedirectURL(redirectURL)) { url, error in MXLog.info("Handling callback from the session.") continuation.resume(returning: Response(url: url, isExternal: false, error: error)) diff --git a/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/SoftLogoutScreenCoordinator.swift b/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/SoftLogoutScreenCoordinator.swift index 8a65b4dd0..f26e3e243 100644 --- a/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/SoftLogoutScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/SoftLogoutScreenCoordinator.swift @@ -15,6 +15,7 @@ struct SoftLogoutScreenCoordinatorParameters { let keyBackupNeeded: Bool let appMediator: AppMediatorProtocol let appSettings: AppSettings + let appHooks: AppHooks let userIndicatorController: UserIndicatorControllerProtocol } @@ -160,6 +161,7 @@ final class SoftLogoutScreenCoordinator: CoordinatorProtocol { redirectURL: parameters.appSettings.oAuthRedirectURL, presentationAnchor: presentationAnchor, appMediator: parameters.appMediator, + appHooks: parameters.appHooks, userIndicatorController: parameters.userIndicatorController) self.oAuthPresenter = presenter switch await presenter.authenticate(using: oAuthData) { diff --git a/ElementX/Sources/Screens/Settings/AccountSettings/OAuthAccountSettingsPresenter.swift b/ElementX/Sources/Screens/Settings/AccountSettings/OAuthAccountSettingsPresenter.swift index ae852231d..573e18ac8 100644 --- a/ElementX/Sources/Screens/Settings/AccountSettings/OAuthAccountSettingsPresenter.swift +++ b/ElementX/Sources/Screens/Settings/AccountSettings/OAuthAccountSettingsPresenter.swift @@ -20,6 +20,7 @@ class OAuthAccountSettingsPresenter: NSObject { private let redirectURL: URL private let presentationAnchor: UIWindow private let appMediator: AppMediatorProtocol + private let appHooks: AppHooks typealias Continuation = AsyncStream>.Continuation private let continuation: Continuation? @@ -28,11 +29,13 @@ class OAuthAccountSettingsPresenter: NSObject { presentationAnchor: UIWindow, appMediator: AppMediatorProtocol, appSettings: AppSettings, + appHooks: AppHooks, continuation: Continuation? = nil) { self.accountURL = accountURL redirectURL = appSettings.oAuthRedirectURL self.presentationAnchor = presentationAnchor self.appMediator = appMediator + self.appHooks = appHooks self.continuation = continuation super.init() @@ -40,6 +43,8 @@ class OAuthAccountSettingsPresenter: NSObject { /// Presents a web authentication session for the supplied data. func start() { + let accountURL = appHooks.oAuthPresenterHook.update(accountURL) + let session = ASWebAuthenticationSession(url: accountURL, callback: .oAuthRedirectURL(redirectURL)) { [continuation] _, error in guard let continuation else { return } diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index 5ad352036..fefb6ba0c 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -737,6 +737,7 @@ class MockScreen: Identifiable { let coordinator = EncryptionResetFlowCoordinator(parameters: .init(userSession: userSession, appMediator: AppMediatorMock.default, appSettings: ServiceLocator.shared.settings, + appHooks: AppHooks(), userIndicatorController: userIndicatorController, navigationStackCoordinator: navigationStackCoordinator, windowManger: windowManager)) diff --git a/Enterprise b/Enterprise index 0ea087616..4bef7a6df 160000 --- a/Enterprise +++ b/Enterprise @@ -1 +1 @@ -Subproject commit 0ea087616b59bf4f2d2271b3ddf3e056966653ce +Subproject commit 4bef7a6df9abe3c914597950c8f5c45b0a21b5e0