Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a certificate validator hook. #3069

Merged
merged 4 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 40 additions & 14 deletions ElementX.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

49 changes: 49 additions & 0 deletions ElementX/Sources/AppHooks/AppHooks.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// Copyright 2024 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

class AppHooks: AppHooksProtocol {
#if IS_MAIN_APP
private(set) var appSettingsHook: AppSettingsHookProtocol = DefaultAppSettingsHook()
func registerAppSettingsHook(_ hook: AppSettingsHookProtocol) {
appSettingsHook = hook
}

private(set) var bugReportHook: BugReportHookProtocol = DefaultBugReportHook()
func registerBugReportHook(_ hook: BugReportHookProtocol) {
bugReportHook = hook
}

private(set) var certificateValidatorHook: CertificateValidatorHookProtocol = DefaultCertificateValidator()
func registerCertificateValidatorHook(_ hook: CertificateValidatorHookProtocol) {
certificateValidatorHook = hook
}
#endif

private(set) var clientBuilderHook: ClientBuilderHookProtocol = DefaultClientBuilderHook()
func registerClientBuilderHook(_ hook: ClientBuilderHookProtocol) {
clientBuilderHook = hook
}
}

protocol AppHooksProtocol {
func configure()
}

extension AppHooksProtocol {
func configure() { }
}
25 changes: 25 additions & 0 deletions ElementX/Sources/AppHooks/Hooks/AppSettingsHook.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// Copyright 2024 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

protocol AppSettingsHookProtocol {
func configure(_ appSettings: AppSettings) -> AppSettings
}

struct DefaultAppSettingsHook: AppSettingsHookProtocol {
func configure(_ appSettings: AppSettings) -> AppSettings { appSettings }
}
25 changes: 25 additions & 0 deletions ElementX/Sources/AppHooks/Hooks/BugReportHook.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// Copyright 2024 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

protocol BugReportHookProtocol {
func update(_ bugReport: BugReport) -> BugReport
}

struct DefaultBugReportHook: BugReportHookProtocol {
func update(_ bugReport: BugReport) -> BugReport { bugReport }
}
27 changes: 27 additions & 0 deletions ElementX/Sources/AppHooks/Hooks/CertificateValidatorHook.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// Copyright 2024 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

protocol CertificateValidatorHookProtocol {
func respondTo(_ challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?)
}

struct DefaultCertificateValidator: CertificateValidatorHookProtocol {
func respondTo(_ challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {
(.performDefaultHandling, nil)
}
}
25 changes: 25 additions & 0 deletions ElementX/Sources/AppHooks/Hooks/ClientBuilderHook.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// Copyright 2024 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 MatrixRustSDK

protocol ClientBuilderHookProtocol {
func configure(_ builder: ClientBuilder) -> ClientBuilder
}

struct DefaultClientBuilderHook: ClientBuilderHookProtocol {
func configure(_ builder: ClientBuilder) -> ClientBuilder { builder }
}
3 changes: 2 additions & 1 deletion ElementX/Sources/Application/AppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
windowManager = WindowManager(appDelegate: appDelegate)
appMediator = AppMediator(windowManager: windowManager)

let appSettings = appHooks.runAppSettingsHook(AppSettings())
let appSettings = appHooks.appSettingsHook.configure(AppSettings())

MXLog.configure(logLevel: appSettings.logLevel)

Expand Down Expand Up @@ -534,6 +534,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
roomTimelineControllerFactory: RoomTimelineControllerFactory(),
appMediator: appMediator,
appSettings: appSettings,
appHooks: appHooks,
analytics: ServiceLocator.shared.analytics,
notificationManager: notificationManager,
isNewLogin: isNewLogin)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
private let elementCallService: ElementCallServiceProtocol
private let appMediator: AppMediatorProtocol
private let appSettings: AppSettings
private let appHooks: AppHooks
private let analytics: AnalyticsService
private let notificationManager: NotificationManagerProtocol

Expand Down Expand Up @@ -74,6 +75,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol,
appMediator: AppMediatorProtocol,
appSettings: AppSettings,
appHooks: AppHooks,
analytics: AnalyticsService,
notificationManager: NotificationManagerProtocol,
isNewLogin: Bool) {
Expand All @@ -85,6 +87,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
self.roomTimelineControllerFactory = roomTimelineControllerFactory
self.appMediator = appMediator
self.appSettings = appSettings
self.appHooks = appHooks
self.analytics = analytics
self.notificationManager = notificationManager

Expand Down Expand Up @@ -561,7 +564,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
clientID: InfoPlistReader.main.bundleIdentifier,
elementCallBaseURL: appSettings.elementCallBaseURL,
elementCallBaseURLOverride: appSettings.elementCallBaseURLOverride,
colorScheme: colorScheme))
colorScheme: colorScheme,
appHooks: appHooks))

callScreenCoordinator.actions
.sink { [weak self] action in
Expand Down
78 changes: 0 additions & 78 deletions ElementX/Sources/Hooks/AppHooks.swift

This file was deleted.

2 changes: 1 addition & 1 deletion ElementX/Sources/Other/Extensions/ClientBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@ extension ClientBuilder {
builder = builder.proxy(url: httpProxy)
}

return appHooks.runClientBuilderHook(builder)
return appHooks.clientBuilderHook.configure(builder)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ struct CallScreenCoordinatorParameters {
let elementCallBaseURL: URL
let elementCallBaseURLOverride: URL?
let colorScheme: ColorScheme
let appHooks: AppHooks
}

enum CallScreenCoordinatorAction {
Expand All @@ -47,7 +48,8 @@ final class CallScreenCoordinator: CoordinatorProtocol {
clientID: parameters.clientID,
elementCallBaseURL: parameters.elementCallBaseURL,
elementCallBaseURLOverride: parameters.elementCallBaseURLOverride,
colorScheme: parameters.colorScheme)
colorScheme: parameters.colorScheme,
appHooks: parameters.appHooks)
}

func start() {
Expand Down
3 changes: 3 additions & 0 deletions ElementX/Sources/Screens/CallScreen/CallScreenModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ struct CallScreenViewState: BindableState {
let messageHandler: String
let script: String?
var url: URL?

let certificateValidator: CertificateValidatorHookProtocol

var bindings = Bindings()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
clientID: String,
elementCallBaseURL: URL,
elementCallBaseURLOverride: URL?,
colorScheme: ColorScheme) {
colorScheme: ColorScheme,
appHooks: AppHooks) {
guard let deviceID = clientProxy.deviceID else { fatalError("Missing device ID for the call.") }

self.elementCallService = elementCallService
Expand All @@ -52,7 +53,8 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
widgetDriver = roomProxy.elementCallWidgetDriver(deviceID: deviceID)

super.init(initialViewState: CallScreenViewState(messageHandler: Self.eventHandlerName,
script: Self.eventHandlerInjectionScript))
script: Self.eventHandlerInjectionScript,
certificateValidator: appHooks.certificateValidatorHook))

state.bindings.javaScriptMessageHandler = { [weak self] message in
guard let self,
Expand Down
9 changes: 8 additions & 1 deletion ElementX/Sources/Screens/CallScreen/View/CallScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,15 @@ private struct WebView: UIViewRepresentable {
@MainActor
class Coordinator: NSObject, WKUIDelegate, WKNavigationDelegate {
private weak var viewModelContext: CallScreenViewModel.Context?
private let certificateValidator: CertificateValidatorHookProtocol

private(set) var webView: WKWebView!

var url: URL!

init(viewModelContext: CallScreenViewModel.Context) {
self.viewModelContext = viewModelContext
certificateValidator = viewModelContext.viewState.certificateValidator

super.init()

Expand Down Expand Up @@ -131,6 +133,10 @@ private struct WebView: UIViewRepresentable {

// MARK: - WKNavigationDelegate

func webView(_ webView: WKWebView, respondTo challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {
await certificateValidator.respondTo(challenge)
}

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy {
// Allow any content from the main URL.
if navigationAction.request.url?.host == url.host {
Expand Down Expand Up @@ -193,7 +199,8 @@ struct CallScreen_Previews: PreviewProvider {
clientID: "io.element.elementx",
elementCallBaseURL: "https://call.element.io",
elementCallBaseURLOverride: nil,
colorScheme: .light)
colorScheme: .light,
appHooks: AppHooks())
}()

static var previews: some View {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class BugReportService: NSObject, BugReportServiceProtocol {
// swiftlint:disable:next cyclomatic_complexity
func submitBugReport(_ bugReport: BugReport,
progressListener: CurrentValueSubject<Double, Never>) async -> Result<SubmitBugReportResponse, BugReportServiceError> {
let bugReport = appHooks.runBugReportHook(bugReport)
let bugReport = appHooks.bugReportHook.update(bugReport)

var params = [
MultipartFormData(key: "text", type: .text(value: bugReport.text)),
Expand Down
Loading
Loading