Secure backup: Add possibility to not expose recovery key when creating a secure backup.

This commit is contained in:
SBiOSoftWhare
2020-10-15 15:27:26 +02:00
parent b9f173f398
commit 66e80e37f1
8 changed files with 123 additions and 62 deletions
@@ -37,8 +37,10 @@ final class SecretsSetupRecoveryKeyCoordinator: SecretsSetupRecoveryKeyCoordinat
// MARK: - Setup
init(recoveryService: MXRecoveryService, passphrase: String?) {
let secretsSetupRecoveryKeyViewModel = SecretsSetupRecoveryKeyViewModel(recoveryService: recoveryService, passphrase: passphrase)
init(recoveryService: MXRecoveryService,
passphrase: String?,
passphraseOnly: Bool) {
let secretsSetupRecoveryKeyViewModel = SecretsSetupRecoveryKeyViewModel(recoveryService: recoveryService, passphrase: passphrase, passphraseOnly: passphraseOnly)
let secretsSetupRecoveryKeyViewController = SecretsSetupRecoveryKeyViewController.instantiate(with: secretsSetupRecoveryKeyViewModel)
self.secretsSetupRecoveryKeyViewModel = secretsSetupRecoveryKeyViewModel
self.secretsSetupRecoveryKeyViewController = secretsSetupRecoveryKeyViewController
@@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EoE-Pl-I63">
<device id="retina6_1" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.3" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EoE-Pl-I63">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@@ -49,52 +47,58 @@
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="zgt-AN-9EH">
<rect key="frame" x="0.0" y="242" width="414" height="120"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="cju-9A-6Ns" customClass="RoundedButton" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="20" y="0.0" width="374" height="50"/>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="Nbm-U7-u0J">
<rect key="frame" x="20" y="0.0" width="374" height="120"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="cju-9A-6Ns" customClass="RoundedButton" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="374" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="ywR-tU-0tp"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<inset key="contentEdgeInsets" minX="20" minY="0.0" maxX="20" maxY="0.0"/>
<state key="normal" title="Export">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="1" colorSpace="calibratedRGB"/>
</state>
<state key="disabled">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="0.5" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="exportButtonAction:" destination="EoE-Pl-I63" eventType="touchUpInside" id="8Sv-N4-mhU"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="v3h-dA-xvr" userLabel="Continue" customClass="RoundedButton" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="70" width="374" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="efg-jS-vMe"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<inset key="contentEdgeInsets" minX="20" minY="0.0" maxX="20" maxY="0.0"/>
<state key="normal" title="Continue">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="1" colorSpace="calibratedRGB"/>
</state>
<state key="disabled">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="0.5" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="doneButtonAction:" destination="EoE-Pl-I63" eventType="touchUpInside" id="NO6-2J-vWo"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="50" id="ywR-tU-0tp"/>
<constraint firstItem="cju-9A-6Ns" firstAttribute="width" secondItem="Nbm-U7-u0J" secondAttribute="width" id="3Q0-zU-kSa"/>
<constraint firstItem="v3h-dA-xvr" firstAttribute="width" secondItem="Nbm-U7-u0J" secondAttribute="width" id="zpJ-fc-yEu"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<inset key="contentEdgeInsets" minX="20" minY="0.0" maxX="20" maxY="0.0"/>
<state key="normal" title="Export">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="1" colorSpace="calibratedRGB"/>
</state>
<state key="disabled">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="0.5" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="exportButtonAction:" destination="EoE-Pl-I63" eventType="touchUpInside" id="8Sv-N4-mhU"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="v3h-dA-xvr" userLabel="Continue" customClass="RoundedButton" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="20" y="70" width="374" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="efg-jS-vMe"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<inset key="contentEdgeInsets" minX="20" minY="0.0" maxX="20" maxY="0.0"/>
<state key="normal" title="Continue">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="1" colorSpace="calibratedRGB"/>
</state>
<state key="disabled">
<color key="titleColor" red="0.47843137250000001" green="0.78823529410000004" blue="0.63137254899999995" alpha="0.5" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="doneButtonAction:" destination="EoE-Pl-I63" eventType="touchUpInside" id="NO6-2J-vWo"/>
</connections>
</button>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="v3h-dA-xvr" firstAttribute="top" secondItem="cju-9A-6Ns" secondAttribute="bottom" constant="20" id="CVl-bR-ZyK"/>
<constraint firstAttribute="bottom" secondItem="v3h-dA-xvr" secondAttribute="bottom" id="PPw-gM-JID"/>
<constraint firstItem="v3h-dA-xvr" firstAttribute="leading" secondItem="cju-9A-6Ns" secondAttribute="leading" id="THe-E7-SvT"/>
<constraint firstItem="v3h-dA-xvr" firstAttribute="trailing" secondItem="cju-9A-6Ns" secondAttribute="trailing" id="W5O-7M-cF3"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="cju-9A-6Ns" secondAttribute="trailing" constant="20" id="X0c-Z0-KGn"/>
<constraint firstItem="cju-9A-6Ns" firstAttribute="top" secondItem="zgt-AN-9EH" secondAttribute="top" id="dPj-uh-WWa"/>
<constraint firstItem="cju-9A-6Ns" firstAttribute="centerX" secondItem="zgt-AN-9EH" secondAttribute="centerX" id="iuh-Zf-BZM"/>
<constraint firstItem="cju-9A-6Ns" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="zgt-AN-9EH" secondAttribute="leading" constant="20" id="iyq-Bv-kvf"/>
<constraint firstItem="cju-9A-6Ns" firstAttribute="width" secondItem="zgt-AN-9EH" secondAttribute="width" priority="750" id="t3e-c6-wSf"/>
<constraint firstItem="Nbm-U7-u0J" firstAttribute="width" secondItem="zgt-AN-9EH" secondAttribute="width" priority="750" id="IVm-Um-rVO"/>
<constraint firstItem="Nbm-U7-u0J" firstAttribute="centerX" secondItem="zgt-AN-9EH" secondAttribute="centerX" id="Loa-hj-EGO"/>
<constraint firstAttribute="bottom" secondItem="Nbm-U7-u0J" secondAttribute="bottom" id="XYI-0q-4F1"/>
<constraint firstItem="Nbm-U7-u0J" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="zgt-AN-9EH" secondAttribute="leading" constant="20" id="aNG-Pe-yCg"/>
<constraint firstItem="Nbm-U7-u0J" firstAttribute="top" secondItem="zgt-AN-9EH" secondAttribute="top" id="pWA-9P-KBl"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Nbm-U7-u0J" secondAttribute="trailing" constant="20" id="vLC-EA-fxi"/>
</constraints>
</view>
</subviews>
@@ -33,6 +33,7 @@ final class SecretsSetupRecoveryKeyViewController: UIViewController {
// MARK: Private
private var viewModel: SecretsSetupRecoveryKeyViewModelType!
private var isPassphraseOnly: Bool = true
private var theme: Theme!
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
@@ -132,22 +133,56 @@ final class SecretsSetupRecoveryKeyViewController: UIViewController {
switch viewState {
case .loading:
self.renderLoading()
case .loaded(let recoveryKey):
self.renderLoaded(recoveryKey: recoveryKey)
case .loaded(let passphraseOnly):
self.renderLoaded(passphraseOnly: passphraseOnly)
case .recoveryCreated(let recoveryKey):
self.renderRecoveryCreated(recoveryKey: recoveryKey)
case .error(let error):
self.render(error: error)
}
}
private func renderLoaded(passphraseOnly: Bool) {
self.isPassphraseOnly = passphraseOnly
let title: String
let secretsLogoImage: UIImage
let informationText: String
let recoveryKeyText: String?
if passphraseOnly {
title = VectorL10n.secretsSetupRecoveryPassphraseSummaryTitle
secretsLogoImage = Asset.Images.secretsSetupPassphrase.image
informationText = VectorL10n.secretsSetupRecoveryPassphraseSummaryInformation
recoveryKeyText = nil
} else {
title = VectorL10n.secretsSetupRecoveryKeyTitle
secretsLogoImage = Asset.Images.secretsSetupKey.image
informationText = VectorL10n.secretsSetupRecoveryKeyInformation
recoveryKeyText = VectorL10n.secretsSetupRecoveryKeyLoading
}
self.title = title
self.secureKeyImageView.image = secretsLogoImage
self.informationLabel.text = informationText
self.exportButton.isHidden = passphraseOnly
self.recoveryKeyLabel.text = recoveryKeyText
}
private func renderLoading() {
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
}
private func renderLoaded(recoveryKey: String) {
private func renderRecoveryCreated(recoveryKey: String) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.exportButton.isEnabled = true
self.recoveryKey = recoveryKey
self.recoveryKeyLabel.text = recoveryKey
self.exportButton.isEnabled = !self.isPassphraseOnly
self.doneButton.isEnabled = self.isPassphraseOnly
if !self.isPassphraseOnly {
self.recoveryKey = recoveryKey
self.recoveryKeyLabel.text = recoveryKey
}
}
private func render(error: Error) {
@@ -208,7 +243,12 @@ final class SecretsSetupRecoveryKeyViewController: UIViewController {
}
@IBAction private func doneButtonAction(_ sender: Any) {
self.presentKeepSafeAlert()
if self.isPassphraseOnly {
self.viewModel.process(viewAction: .done)
} else {
self.presentKeepSafeAlert()
}
}
private func cancelButtonAction() {
@@ -26,6 +26,7 @@ final class SecretsSetupRecoveryKeyViewModel: SecretsSetupRecoveryKeyViewModelTy
private let recoveryService: MXRecoveryService
private let passphrase: String?
private let passphraseOnly: Bool
// MARK: Public
@@ -34,9 +35,10 @@ final class SecretsSetupRecoveryKeyViewModel: SecretsSetupRecoveryKeyViewModelTy
// MARK: - Setup
init(recoveryService: MXRecoveryService, passphrase: String?) {
init(recoveryService: MXRecoveryService, passphrase: String?, passphraseOnly: Bool) {
self.recoveryService = recoveryService
self.passphrase = passphrase
self.passphraseOnly = passphraseOnly
}
// MARK: - Public
@@ -44,6 +46,7 @@ final class SecretsSetupRecoveryKeyViewModel: SecretsSetupRecoveryKeyViewModelTy
func process(viewAction: SecretsSetupRecoveryKeyViewAction) {
switch viewAction {
case .loadData:
self.update(viewState: .loaded(self.passphraseOnly))
self.createSecureKey()
case .done:
self.coordinatorDelegate?.secretsSetupRecoveryKeyViewModelDidComplete(self)
@@ -60,7 +63,7 @@ final class SecretsSetupRecoveryKeyViewModel: SecretsSetupRecoveryKeyViewModelTy
self.update(viewState: .loading)
self.recoveryService.createRecovery(forSecrets: nil, withPassphrase: self.passphrase, createServicesBackups: true, success: { secretStorageKeyCreationInfo in
self.update(viewState: .loaded(secretStorageKeyCreationInfo.recoveryKey))
self.update(viewState: .recoveryCreated(secretStorageKeyCreationInfo.recoveryKey))
}, failure: { error in
self.update(viewState: .error(error))
})
@@ -20,7 +20,8 @@ import Foundation
/// SecretsSetupRecoveryKeyViewController view state
enum SecretsSetupRecoveryKeyViewState {
case loaded(_ passphraseOnly: Bool)
case loading
case loaded(_ recoveryKey: String)
case recoveryCreated(_ recoveryKey: String)
case error(Error)
}