diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningCoordinator.swift b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningCoordinator.swift new file mode 100644 index 000000000..344c1be44 --- /dev/null +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningCoordinator.swift @@ -0,0 +1,82 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + 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 +import UIKit + +final class KeyVerificationVerifyByScanningCoordinator: KeyVerificationVerifyByScanningCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let keyVerificationRequest: MXKeyVerificationRequest + + private var keyVerificationVerifyByScanningViewModel: KeyVerificationVerifyByScanningViewModelType + private let keyVerificationVerifyByScanningViewController: KeyVerificationVerifyByScanningViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: KeyVerificationVerifyByScanningCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, keyVerificationRequest: MXKeyVerificationRequest) { + self.session = session + self.keyVerificationRequest = keyVerificationRequest + + let keyVerificationVerifyByScanningViewModel = KeyVerificationVerifyByScanningViewModel(session: self.session, keyVerificationRequest: keyVerificationRequest) + let keyVerificationVerifyByScanningViewController = KeyVerificationVerifyByScanningViewController.instantiate(with: keyVerificationVerifyByScanningViewModel) + self.keyVerificationVerifyByScanningViewModel = keyVerificationVerifyByScanningViewModel + self.keyVerificationVerifyByScanningViewController = keyVerificationVerifyByScanningViewController + } + + // MARK: - Public methods + + func start() { + self.keyVerificationVerifyByScanningViewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.keyVerificationVerifyByScanningViewController + } +} + +// MARK: - KeyVerificationVerifyByScanningViewModelCoordinatorDelegate +extension KeyVerificationVerifyByScanningCoordinator: KeyVerificationVerifyByScanningViewModelCoordinatorDelegate { + + func keyVerificationVerifyByScanningViewModelDidCancel(_ viewModel: KeyVerificationVerifyByScanningViewModelType) { + self.delegate?.keyVerificationVerifyByScanningCoordinatorDidCancel(self) + } + + func keyVerificationVerifyByScanningViewModelCannotScan(_ viewModel: KeyVerificationVerifyByScanningViewModelType) { + self.delegate?.keyVerificationVerifyByScanningCoordinatorCannotScan(self) + } + + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didStartSASVerificationWithTransaction transaction: MXSASTransaction) { + self.delegate?.keyVerificationVerifyByScanningCoordinator(self, didCompleteWithSASTransaction: transaction) + } + + func keyVerificationVerifyByScanningViewModelDidCompleteQRCodeVerification(_ viewModel: KeyVerificationVerifyByScanningViewModelType) { + self.delegate?.keyVerificationVerifyByScanningCoordinatorDidCompleteQRCodeVerification(self) + } +} diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningCoordinatorType.swift b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningCoordinatorType.swift new file mode 100644 index 000000000..50cb3a380 --- /dev/null +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningCoordinatorType.swift @@ -0,0 +1,31 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + 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 + +protocol KeyVerificationVerifyByScanningCoordinatorDelegate: class { + func keyVerificationVerifyByScanningCoordinatorDidCancel(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType) + func keyVerificationVerifyByScanningCoordinatorCannotScan(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType) + func keyVerificationVerifyByScanningCoordinatorDidCompleteQRCodeVerification(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType) + func keyVerificationVerifyByScanningCoordinator(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType, didCompleteWithSASTransaction transaction: MXSASTransaction) +} + +/// `KeyVerificationVerifyByScanningCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. +protocol KeyVerificationVerifyByScanningCoordinatorType: Coordinator, Presentable { + var delegate: KeyVerificationVerifyByScanningCoordinatorDelegate? { get } +} diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewAction.swift b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewAction.swift new file mode 100644 index 000000000..617dd202c --- /dev/null +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewAction.swift @@ -0,0 +1,29 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + 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 + +/// KeyVerificationVerifyByScanningViewController view actions exposed to view model +enum KeyVerificationVerifyByScanningViewAction { + case loadData + case cancel + case scannedCode(payloadData: Data) + case cannotScan + case acknowledgeOtherScannedMyCode(Bool) + case acknowledgeMyUserScannedOtherCode +} diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift new file mode 100644 index 000000000..b4cfc8855 --- /dev/null +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift @@ -0,0 +1,248 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + 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 + +enum KeyVerificationVerifyByScanningViewModelError: Error { + case unknown +} + +final class KeyVerificationVerifyByScanningViewModel: KeyVerificationVerifyByScanningViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let keyVerificationRequest: MXKeyVerificationRequest + private let qrCodeDataCoder: MXQRCodeDataCoder + private let keyVerificationManager: MXKeyVerificationManager + + private var qrCodeTransaction: MXQRCodeTransaction? + private var scannedQRCodeData: MXQRCodeData? + + // MARK: Public + + weak var viewDelegate: KeyVerificationVerifyByScanningViewModelViewDelegate? + weak var coordinatorDelegate: KeyVerificationVerifyByScanningViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, keyVerificationRequest: MXKeyVerificationRequest) { + self.session = session + self.keyVerificationManager = self.session.crypto.keyVerificationManager + self.keyVerificationRequest = keyVerificationRequest + self.qrCodeDataCoder = MXQRCodeDataCoder() + } + + deinit { + // TODO: Remove QR code transaction if needed + } + + // MARK: - Public + + func process(viewAction: KeyVerificationVerifyByScanningViewAction) { + switch viewAction { + case .loadData: + self.loadData() + case .scannedCode(payloadData: let payloadData): + self.scannedQRCode(payloadData: payloadData) + case .cannotScan: + self.startSASVerification() + case .acknowledgeOtherScannedMyCode(let acknowledgeOtherScannedMyCode): + self.acknowledgeOtherScannedMyCode(acknowledgeOtherScannedMyCode) + case .cancel: + self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModelDidCancel(self) + case .acknowledgeMyUserScannedOtherCode: + self.acknowledgeScanOtherCode() + } + } + + // MARK: - Private + + private func loadData() { + + let qrCodePlayloadData: Data? + let canShowScanAction: Bool + + // TODO: QRcode fetch existing Qr code transaction +// let qrCodeTransaction: MXQRCodeTransaction? = nil + self.qrCodeTransaction = self.keyVerificationManager.qrCodeTransaction(withTransactionId: self.keyVerificationRequest.requestId) + + if let supportedVerificationMethods = self.keyVerificationRequest.myMethods { + + if let qrCodeData = self.qrCodeTransaction?.qrCodeData { + qrCodePlayloadData = self.qrCodeDataCoder.encode(qrCodeData) + } else { + qrCodePlayloadData = nil + } + + canShowScanAction = self.canShowScanAction(from: supportedVerificationMethods) + } else { + qrCodePlayloadData = nil + canShowScanAction = false + } + + let viewData = KeyVerificationVerifyByScanningViewData(qrCodeData: qrCodePlayloadData, + showScanAction: canShowScanAction) + + self.update(viewState: .loaded(viewData: viewData)) + + self.registerTransactionDidStateChangeNotification() + } + + private func canShowScanAction(from verificationMethods: [String]) -> Bool { + return verificationMethods.contains(MXKeyVerificationMethodQRCodeScan) + } + + private func update(viewState: KeyVerificationVerifyByScanningViewState) { + self.viewDelegate?.keyVerificationVerifyByScanningViewModel(self, didUpdateViewState: viewState) + } + + // MARK: QR code + + private func scannedQRCode(payloadData: Data) { + self.scannedQRCodeData = self.qrCodeDataCoder.decode(payloadData) + + let isQRCodeValid = self.scannedQRCodeData != nil + + self.update(viewState: .scannedCodeValidated(isValid: isQRCodeValid)) + } + + private func acknowledgeScanOtherCode() { + guard let scannedQRCodeData = self.scannedQRCodeData else { + return + } + + guard let qrCodeTransaction = self.qrCodeTransaction else { + return + } + + qrCodeTransaction.userHasScannedOtherQrCodeData(scannedQRCodeData) + self.update(viewState: .loading) + } + + private func acknowledgeOtherScannedMyCode(_ acknowledgeOtherScannedMyCode: Bool) { + guard let qrCodeTransaction = self.qrCodeTransaction else { + return + } + self.update(viewState: .loading) + qrCodeTransaction.otherUserScannedMyQrCode(acknowledgeOtherScannedMyCode) + } + + // MARK: SAS + + private func startSASVerification() { + + self.update(viewState: .loading) + + self.session.crypto.keyVerificationManager.beginKeyVerification(from: self.keyVerificationRequest, method: MXKeyVerificationMethodSAS, success: { [weak self] (deviceVerificationTransaction) in + guard let self = self else { + return + } + + // TODO: Remove QR code transaction + + if deviceVerificationTransaction is MXOutgoingSASTransaction == false { + NSLog("[KeyVerificationVerifyByScanningViewModel] SAS transaction should be outgoing") + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .error(KeyVerificationVerifyByScanningViewModelError.unknown)) + } + + }, failure: { [weak self] (error) in + guard let self = self else { + return + } + self.update(viewState: .error(error)) + } + ) + } + + // MARK: - MXKeyVerificationTransactionDidChange + + private func registerTransactionDidStateChangeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: .MXKeyVerificationTransactionDidChange, object: nil) + } + + private func unregisterTransactionDidStateChangeNotification() { + NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationTransactionDidChange, object: nil) + } + + @objc private func transactionDidStateChange(notification: Notification) { + guard let transaction = notification.object as? MXKeyVerificationTransaction else { + return + } + + guard let transactionDMEventId = transaction.dmEventId, + self.keyVerificationRequest.requestId == transactionDMEventId else { + return + } + + if let sasTransaction = transaction as? MXSASTransaction { + self.sasTransactionDidStateChange(sasTransaction) + } else if let qrCodeTransaction = transaction as? MXQRCodeTransaction { + self.qrCodeTransactionDidStateChange(qrCodeTransaction) + } + } + + private func sasTransactionDidStateChange(_ transaction: MXSASTransaction) { + switch transaction.state { + case MXSASTransactionStateShowSAS: + self.unregisterTransactionDidStateChangeNotification() + self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModel(self, didStartSASVerificationWithTransaction: transaction) + case MXSASTransactionStateCancelled: + guard let reason = transaction.reasonCancelCode else { + return + } + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .cancelled(reason)) + case MXSASTransactionStateCancelledByMe: + guard let reason = transaction.reasonCancelCode else { + return + } + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .cancelledByMe(reason)) + default: + break + } + } + + private func qrCodeTransactionDidStateChange(_ transaction: MXQRCodeTransaction) { + switch transaction.state { + case .verified: + self.unregisterTransactionDidStateChangeNotification() + self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModelDidCompleteQRCodeVerification(self) + case .qrScannedByOther: + self.update(viewState: .otherUserScannedMyCode) + case .cancelled: + guard let reason = transaction.reasonCancelCode else { + return + } + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .cancelled(reason)) + case .cancelledByMe: + guard let reason = transaction.reasonCancelCode else { + return + } + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .cancelledByMe(reason)) + default: + break + } + } +} diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewModelType.swift b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewModelType.swift new file mode 100644 index 000000000..561de5277 --- /dev/null +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewModelType.swift @@ -0,0 +1,38 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + 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 + +protocol KeyVerificationVerifyByScanningViewModelViewDelegate: class { + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didUpdateViewState viewSate: KeyVerificationVerifyByScanningViewState) +} + +protocol KeyVerificationVerifyByScanningViewModelCoordinatorDelegate: class { + func keyVerificationVerifyByScanningViewModelDidCancel(_ viewModel: KeyVerificationVerifyByScanningViewModelType) + func keyVerificationVerifyByScanningViewModelDidCompleteQRCodeVerification(_ viewModel: KeyVerificationVerifyByScanningViewModelType) + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didStartSASVerificationWithTransaction transaction: MXSASTransaction) +} + +/// Protocol describing the view model used by `KeyVerificationVerifyByScanningViewController` +protocol KeyVerificationVerifyByScanningViewModelType { + + var viewDelegate: KeyVerificationVerifyByScanningViewModelViewDelegate? { get set } + var coordinatorDelegate: KeyVerificationVerifyByScanningViewModelCoordinatorDelegate? { get set } + + func process(viewAction: KeyVerificationVerifyByScanningViewAction) +} diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewState.swift b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewState.swift new file mode 100644 index 000000000..df48f6e91 --- /dev/null +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewState.swift @@ -0,0 +1,35 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + 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 + +struct KeyVerificationVerifyByScanningViewData { + let qrCodeData: Data? + let showScanAction: Bool +} + +/// KeyVerificationVerifyByScanningViewController view state +enum KeyVerificationVerifyByScanningViewState { + case loading + case loaded(viewData: KeyVerificationVerifyByScanningViewData) + case scannedCodeValidated(isValid: Bool) + case otherUserScannedMyCode + case cancelled(MXTransactionCancelCode) + case cancelledByMe(MXTransactionCancelCode) + case error(Error) +} diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/Reader/QRCodeReaderViewController.swift b/Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/Reader/QRCodeReaderViewController.swift index 47f032ef0..c3bb92fde 100644 --- a/Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/Reader/QRCodeReaderViewController.swift +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/Reader/QRCodeReaderViewController.swift @@ -262,7 +262,7 @@ extension QRCodeReaderViewController: ZXCaptureDelegate { if let bytes = result.resultMetadata.object(forKey: kResultMetadataTypeByteSegments.rawValue) as? NSArray, let byteArray = bytes.firstObject as? ZXByteArray { - let data = Data(bytes: UnsafeRawPointer(byteArray.array), count: Int(byteArray.length)) + let data = Data(bytes: UnsafeRawPointer(byteArray.array), count: Int(byteArray.length)) self.delegate?.qrCodeReaderViewController(self, didFound: data) }