Skip to content

Commit

Permalink
Add a certificate validator hook. (#3069)
Browse files Browse the repository at this point in the history
* Add a certificate validator hook.

* General tidy up of AppHooks.

* Don't worry about the generic call links.
  • Loading branch information
pixlwave authored Jul 19, 2024
1 parent 74907d3 commit cdd763e
Show file tree
Hide file tree
Showing 21 changed files with 227 additions and 104 deletions.
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
6 changes: 4 additions & 2 deletions ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift
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
2 changes: 1 addition & 1 deletion ElementX/Sources/Services/BugReport/BugReportService.swift
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

0 comments on commit cdd763e

Please sign in to comment.