diff --git a/CHANGES.rst b/CHANGES.rst index a1e5f33b2..0bae4ae8e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,6 +10,9 @@ Changes to be released in next version * Integrated FLEX for debug builds. * VoIP: Add dial pad for PSTN capable servers to menu on homescreen. * VoIP: Replace call bar with PiP tiles for every type of calls. + * Security settings: Display the cross-signing section (#4430). + * Security settings: The Secure backup section has been updated to match element-web UX (#4430). + * Wording: Replace Recovery Passphrase and Recovery Key by Security Phrase and Security Key (#4268). * Room directory: Join room by alias or id (#4429). 🐛 Bugfix diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 986bd6a22..234022cf2 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -568,7 +568,7 @@ Tap the + to start adding people."; "settings_key_backup_info" = "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages."; "settings_key_backup_info_checking" = "Checking…"; "settings_key_backup_info_none" = "Your keys are not being backed up from this session."; -"settings_key_backup_info_signout_warning" = "Connect this session to key backup before signing out to avoid losing any keys that may only be on this device."; +"settings_key_backup_info_signout_warning" = "Back up your keys before signing out to avoid losing them."; "settings_key_backup_info_version" = "Key Backup Version: %@"; "settings_key_backup_info_algorithm" = "Algorithm: %@"; "settings_key_backup_info_valid" = "This session is backing up your keys."; @@ -622,10 +622,13 @@ Tap the + to start adding people."; "security_settings_crypto_sessions_description_2" = "If you don’t recognise a login, change your password and reset Secure Backup."; "security_settings_secure_backup" = "SECURE BACKUP"; -"security_settings_secure_backup_description" = "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server."; +"security_settings_secure_backup_description" = "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key."; +"security_settings_secure_backup_info_checking" = "Checking…"; +"security_settings_secure_backup_info_valid" = "This session is backing up your keys."; "security_settings_secure_backup_setup" = "Set up"; -"security_settings_secure_backup_synchronise" = "Synchronise"; -"security_settings_secure_backup_delete" = "Delete"; +"security_settings_secure_backup_reset" = "Reset"; +"security_settings_secure_backup_restore" = "Restore from Backup"; +"security_settings_secure_backup_delete" = "Delete Backup"; "security_settings_backup" = "MESSAGE BACKUP"; @@ -633,9 +636,9 @@ Tap the + to start adding people."; "security_settings_crosssigning_info_not_bootstrapped" = "Cross-signing is not yet set up."; "security_settings_crosssigning_info_exists" = "Your account has a cross-signing identity, but it is not yet trusted by this session. Complete security of this session."; "security_settings_crosssigning_info_trusted" = "Cross-signing is enabled. You can trust other users and your other sessions based on cross-signing but you cannot cross-sign from this session because it does not have cross-signing private keys. Complete security of this session."; -"security_settings_crosssigning_info_ok" = "Cross-signing is enabled."; -"security_settings_crosssigning_bootstrap" = "Bootstrap cross-signing"; -"security_settings_crosssigning_reset" = "Reset cross-signing"; +"security_settings_crosssigning_info_ok" = "Cross-signing is ready for use."; +"security_settings_crosssigning_bootstrap" = "Set up"; +"security_settings_crosssigning_reset" = "Reset"; "security_settings_crosssigning_complete_security" = "Complete security"; "security_settings_cryptography" = "CRYPTOGRAPHY"; @@ -899,7 +902,7 @@ Tap the + to start adding people."; // Key backup wrong version "e2e_key_backup_wrong_version_title" = "New Key Backup"; -"e2e_key_backup_wrong_version" = "A new secure message key backup has been detected.\n\nIf this wasn’t you, set a new passphrase in Settings."; +"e2e_key_backup_wrong_version" = "A new secure message key backup has been detected.\n\nIf this wasn’t you, set a new Security Phrase in Settings."; "e2e_key_backup_wrong_version_button_settings" = "Settings"; "e2e_key_backup_wrong_version_button_wasme" = "It was me"; @@ -1025,7 +1028,7 @@ Tap the + to start adding people."; "secure_key_backup_setup_intro_use_security_key_title" = "Use a Security Key"; "secure_key_backup_setup_intro_use_security_key_info" = "Generate a security key to store somewhere safe like a password manager or a safe."; -"secure_key_backup_setup_intro_use_security_passphrase_title" = "Use a Security Passphrase"; +"secure_key_backup_setup_intro_use_security_passphrase_title" = "Use a Security Phrase"; "secure_key_backup_setup_intro_use_security_passphrase_info" = "Enter a secret phrase only you know, and generate a key for backup."; "secure_key_backup_setup_existing_backup_error_title" = "A backup for messages already exists"; @@ -1064,66 +1067,69 @@ Tap the + to start adding people."; // Passphrase -"key_backup_setup_passphrase_title" = "Secure your backup with a Passphrase"; -"key_backup_setup_passphrase_info" = "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.\n\nFor maximum security, this should be different from your account password."; +"key_backup_setup_passphrase_title" = "Secure your backup with a Security Phrase"; +"key_backup_setup_passphrase_info" = "We'll store an encrypted copy of your keys on our server. Protect your backup with a phrase to keep it secure.\n\nFor maximum security, this should be different from your account password."; "key_backup_setup_passphrase_passphrase_title" = "Enter"; -"key_backup_setup_passphrase_passphrase_placeholder" = "Enter passphrase"; +"key_backup_setup_passphrase_passphrase_placeholder" = "Enter phrase"; "key_backup_setup_passphrase_passphrase_valid" = "Great!"; "key_backup_setup_passphrase_passphrase_invalid" = "Try adding a word"; "key_backup_setup_passphrase_confirm_passphrase_title" = "Confirm"; -"key_backup_setup_passphrase_confirm_passphrase_placeholder" = "Confirm passphrase"; +"key_backup_setup_passphrase_confirm_passphrase_placeholder" = "Confirm phrase"; "key_backup_setup_passphrase_confirm_passphrase_valid" = "Great!"; -"key_backup_setup_passphrase_confirm_passphrase_invalid" = "Passphrase doesn’t match"; -"key_backup_setup_passphrase_set_passphrase_action" = "Set Passphrase"; -"key_backup_setup_passphrase_setup_recovery_key_info" = "Or, secure your backup with a Recovery Key, saving it somewhere safe."; -"key_backup_setup_passphrase_setup_recovery_key_action" = "(Advanced) Set up with Recovery Key"; +"key_backup_setup_passphrase_confirm_passphrase_invalid" = "phrase doesn’t match"; +"key_backup_setup_passphrase_set_passphrase_action" = "Set Phrase"; +"key_backup_setup_passphrase_setup_recovery_key_info" = "Or, secure your backup with a Security Key, saving it somewhere safe."; +"key_backup_setup_passphrase_setup_recovery_key_action" = "(Advanced) Set up with Security Key"; // Success "key_backup_setup_success_title" = "Success!"; // Success from passphrase -"key_backup_setup_success_from_passphrase_info" = "Your keys are being backed up.\n\nYour recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.\n\nKeep your recovery key somewhere very secure, like a password manager (or a safe)."; -"key_backup_setup_success_from_passphrase_save_recovery_key_action" = "Save Recovery Key"; +"key_backup_setup_success_from_passphrase_info" = "Your keys are being backed up.\n\nYour Security Key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.\n\nKeep your Security Key somewhere very secure, like a password manager (or a safe)."; +"key_backup_setup_success_from_passphrase_save_recovery_key_action" = "Save Security Key"; "key_backup_setup_success_from_passphrase_done_action" = "Done"; // Success from recovery key -"key_backup_setup_success_from_recovery_key_info" = "Your keys are being backed up.\n\nMake a copy of this recovery key and keep it safe."; -"key_backup_setup_success_from_recovery_key_recovery_key_title" = "Recovery Key"; +"key_backup_setup_success_from_recovery_key_info" = "Your keys are being backed up.\n\nMake a copy of this Security Key and keep it safe."; +"key_backup_setup_success_from_recovery_key_recovery_key_title" = "Security Key"; "key_backup_setup_success_from_recovery_key_make_copy_action" = "Make a Copy"; "key_backup_setup_success_from_recovery_key_made_copy_action" = "I've made a copy"; +// Success from secure backup +"key_backup_setup_success_from_secure_backup_info" = "Your keys are being backed up."; + // MARK: Key backup recover "key_backup_recover_title" = "Secure Messages"; -"key_backup_recover_invalid_passphrase_title" = "Incorrect Recovery Passphrase"; -"key_backup_recover_invalid_passphrase" = "Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase."; -"key_backup_recover_invalid_recovery_key_title" = "Recovery Key Mismatch"; -"key_backup_recover_invalid_recovery_key" = "Backup could not be decrypted with this key: please verify that you entered the correct recovery key."; +"key_backup_recover_invalid_passphrase_title" = "Incorrect Security Phrase"; +"key_backup_recover_invalid_passphrase" = "Backup could not be decrypted with this phrase: please verify that you entered the correct Security Phrase."; +"key_backup_recover_invalid_recovery_key_title" = "Security Key Mismatch"; +"key_backup_recover_invalid_recovery_key" = "Backup could not be decrypted with this key: please verify that you entered the correct Security Key."; // Recover from private key "key_backup_recover_from_private_key_info" = "Restoring backup…"; // Recover from passphrase -"key_backup_recover_from_passphrase_info" = "Use your recovery passphrase to unlock your secure message history"; +"key_backup_recover_from_passphrase_info" = "Use your Security Phrase to unlock your secure message history"; "key_backup_recover_from_passphrase_passphrase_title" = "Enter"; -"key_backup_recover_from_passphrase_passphrase_placeholder" = "Enter Passphrase"; +"key_backup_recover_from_passphrase_passphrase_placeholder" = "Enter Phrase"; "key_backup_recover_from_passphrase_recover_action" = "Unlock History"; -"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Don’t know your recovery passphrase? You can "; -"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "use your recovery key"; +"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Don’t know your Security Phrase? You can "; +"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "use your Security Key"; "key_backup_recover_from_passphrase_lost_passphrase_action_part3" = "."; // Recover from recovery key -"key_backup_recover_from_recovery_key_info" = "Use your recovery key to unlock your secure message history"; +"key_backup_recover_from_recovery_key_info" = "Use your Security Key to unlock your secure message history"; "key_backup_recover_from_recovery_key_recovery_key_title" = "Enter"; -"key_backup_recover_from_recovery_key_recovery_key_placeholder" = "Enter Recovery Key"; +"key_backup_recover_from_recovery_key_recovery_key_placeholder" = "Enter Security Key"; "key_backup_recover_from_recovery_key_recover_action" = "Unlock History"; -"key_backup_recover_from_recovery_key_lost_recovery_key_action" = "Lost your recovery key? You can set up a new one in settings."; +"key_backup_recover_from_recovery_key_lost_recovery_key_action" = "Lost your Security Key You can set up a new one in settings."; // Success @@ -1200,8 +1206,8 @@ Tap the + to start adding people."; "device_verification_self_verify_wait_new_sign_in_title" = "Verify this login"; "device_verification_self_verify_wait_information" = "Verify this session from one of your other sessions, granting it access to encrypted messages.\n\nUse the latest Element on your other devices:"; "device_verification_self_verify_wait_additional_information" = "This works with Element and other cross-signing capable Matrix clients."; -"device_verification_self_verify_wait_recover_secrets_without_passphrase" = "Use Recovery Key"; -"device_verification_self_verify_wait_recover_secrets_with_passphrase" = "Use Recovery Passphrase or Key"; +"device_verification_self_verify_wait_recover_secrets_without_passphrase" = "Use Security Key"; +"device_verification_self_verify_wait_recover_secrets_with_passphrase" = "Use Security Phrase or Key"; "device_verification_self_verify_wait_recover_secrets_additional_information" = "If you can't access an existing session"; "device_verification_self_verify_wait_recover_secrets_checking_availability" = "Checking for other verification capabilities ..."; @@ -1435,38 +1441,40 @@ Tap the + to start adding people."; // Recover with passphrase -"secrets_recovery_with_passphrase_title" = "Recovery Passphrase"; -"secrets_recovery_with_passphrase_information_default" = "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery passphrase."; -"secrets_recovery_with_passphrase_information_verify_device" = "Use your Recovery Passphrase to verify this device."; +"secrets_recovery_with_passphrase_title" = "Security Phrase"; +"secrets_recovery_with_passphrase_information_default" = "Access your secure message history and your cross-signing identity for verifying other sessions by entering your Security Phrase."; +"secrets_recovery_with_passphrase_information_verify_device" = "Use your Security Phrase to verify this device."; "secrets_recovery_with_passphrase_passphrase_title" = "Enter"; -"secrets_recovery_with_passphrase_passphrase_placeholder" = "Enter Recovery Passphrase"; -"secrets_recovery_with_passphrase_recover_action" = "Use Passphrase"; +"secrets_recovery_with_passphrase_passphrase_placeholder" = "Enter Security Phrase"; +"secrets_recovery_with_passphrase_recover_action" = "Use Phrase"; -"secrets_recovery_with_passphrase_lost_passphrase_action_part1" = "Don’t know your recovery passphrase? You can "; -"secrets_recovery_with_passphrase_lost_passphrase_action_part2" = "use your recovery key"; +"secrets_recovery_with_passphrase_lost_passphrase_action_part1" = "Don’t know your Security Phrase? You can "; +"secrets_recovery_with_passphrase_lost_passphrase_action_part2" = "use your Security Key"; "secrets_recovery_with_passphrase_lost_passphrase_action_part3" = "."; "secrets_recovery_with_passphrase_invalid_passphrase_title" = "Unable to access secret storage"; -"secrets_recovery_with_passphrase_invalid_passphrase_message" = "Please verify that you entered the correct recovery passphrase."; +"secrets_recovery_with_passphrase_invalid_passphrase_message" = "Please verify that you entered the correct Security Phrase."; // Recover with key -"secrets_recovery_with_key_title" = "Recovery Key"; -"secrets_recovery_with_key_information_default" = "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery key."; -"secrets_recovery_with_key_information_verify_device" = "Use your Recovery Key to verify this device."; +"secrets_recovery_with_key_title" = "Security Key"; +"secrets_recovery_with_key_information_default" = "Access your secure message history and your cross-signing identity for verifying other sessions by entering your Security Key."; +"secrets_recovery_with_key_information_verify_device" = "Use your Security Key to verify this device."; +"secrets_recovery_with_key_information_unlock_secure_backup_with_phrase" = "Enter your Security Phrase to continue."; +"secrets_recovery_with_key_information_unlock_secure_backup_with_key" = "Enter your Security Key to continue."; "secrets_recovery_with_key_recovery_key_title" = "Enter"; -"secrets_recovery_with_key_recovery_key_placeholder" = "Enter Recovery Key"; +"secrets_recovery_with_key_recovery_key_placeholder" = "Enter Security Key"; "secrets_recovery_with_key_recover_action" = "Use Key"; "secrets_recovery_with_key_invalid_recovery_key_title" = "Unable to access secret storage"; -"secrets_recovery_with_key_invalid_recovery_key_message" = "Please verify that you entered the correct recovery key."; +"secrets_recovery_with_key_invalid_recovery_key_message" = "Please verify that you entered the correct Security Key."; // MARK: - Secrets set up // Recovery Key "secrets_setup_recovery_key_title" = "Save your Security Key"; -"secrets_setup_recovery_key_information" = "Store your Recovery Key somewhere safe. It can be used to unlock your encrypted messages & data."; +"secrets_setup_recovery_key_information" = "Store your Security Key somewhere safe. It can be used to unlock your encrypted messages & data."; "secrets_setup_recovery_key_loading" = "Loading…"; "secrets_setup_recovery_key_export_action" = "Save"; "secrets_setup_recovery_key_done_action" = "Done"; @@ -1483,10 +1491,10 @@ Tap the + to start adding people."; "secrets_setup_recovery_passphrase_confirm_information" = "Enter your Security Phrase again to confirm it."; "secrets_setup_recovery_passphrase_confirm_passphrase_title" = "Confirm"; -"secrets_setup_recovery_passphrase_confirm_passphrase_placeholder" = "Confirm passphrase"; +"secrets_setup_recovery_passphrase_confirm_passphrase_placeholder" = "Confirm phrase"; "key_backup_setup_passphrase_confirm_passphrase_valid" = "Great!"; -"key_backup_setup_passphrase_confirm_passphrase_invalid" = "Passphrase doesn’t match"; +"key_backup_setup_passphrase_confirm_passphrase_invalid" = "Phrase doesn’t match"; "secrets_setup_recovery_passphrase_summary_title" = "Save your Security Phrase"; "secrets_setup_recovery_passphrase_summary_information" = "Remember your Security Phrase. It can be used to unlock your encrypted messages & data."; diff --git a/Riot/Generated/Storyboards.swift b/Riot/Generated/Storyboards.swift index d592e4f76..c66d40f6a 100644 --- a/Riot/Generated/Storyboards.swift +++ b/Riot/Generated/Storyboards.swift @@ -97,6 +97,11 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType(storyboard: KeyBackupSetupSuccessFromRecoveryKeyViewController.self) } + internal enum KeyBackupSetupSuccessFromSecureBackupViewController: StoryboardType { + internal static let storyboardName = "KeyBackupSetupSuccessFromSecureBackupViewController" + + internal static let initialScene = InitialSceneType(storyboard: KeyBackupSetupSuccessFromSecureBackupViewController.self) + } internal enum KeyVerificationDataLoadingViewController: StoryboardType { internal static let storyboardName = "KeyVerificationDataLoadingViewController" diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index c2e0b8beb..ba05c0233 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -1034,11 +1034,11 @@ internal enum VectorL10n { internal static var deviceVerificationSelfVerifyWaitRecoverSecretsCheckingAvailability: String { return VectorL10n.tr("Vector", "device_verification_self_verify_wait_recover_secrets_checking_availability") } - /// Use Recovery Passphrase or Key + /// Use Security Phrase or Key internal static var deviceVerificationSelfVerifyWaitRecoverSecretsWithPassphrase: String { return VectorL10n.tr("Vector", "device_verification_self_verify_wait_recover_secrets_with_passphrase") } - /// Use Recovery Key + /// Use Security Key internal static var deviceVerificationSelfVerifyWaitRecoverSecretsWithoutPassphrase: String { return VectorL10n.tr("Vector", "device_verification_self_verify_wait_recover_secrets_without_passphrase") } @@ -1142,7 +1142,7 @@ internal enum VectorL10n { internal static var e2eEnablingOnAppUpdate: String { return VectorL10n.tr("Vector", "e2e_enabling_on_app_update") } - /// A new secure message key backup has been detected.\n\nIf this wasn’t you, set a new passphrase in Settings. + /// A new secure message key backup has been detected.\n\nIf this wasn’t you, set a new Security Phrase in Settings. internal static var e2eKeyBackupWrongVersion: String { return VectorL10n.tr("Vector", "e2e_key_backup_wrong_version") } @@ -1614,15 +1614,15 @@ internal enum VectorL10n { internal static var keyBackupRecoverDoneAction: String { return VectorL10n.tr("Vector", "key_backup_recover_done_action") } - /// Use your recovery passphrase to unlock your secure message history + /// Use your Security Phrase to unlock your secure message history internal static var keyBackupRecoverFromPassphraseInfo: String { return VectorL10n.tr("Vector", "key_backup_recover_from_passphrase_info") } - /// Don’t know your recovery passphrase? You can + /// Don’t know your Security Phrase? You can internal static var keyBackupRecoverFromPassphraseLostPassphraseActionPart1: String { return VectorL10n.tr("Vector", "key_backup_recover_from_passphrase_lost_passphrase_action_part1") } - /// use your recovery key + /// use your Security Key internal static var keyBackupRecoverFromPassphraseLostPassphraseActionPart2: String { return VectorL10n.tr("Vector", "key_backup_recover_from_passphrase_lost_passphrase_action_part2") } @@ -1630,7 +1630,7 @@ internal enum VectorL10n { internal static var keyBackupRecoverFromPassphraseLostPassphraseActionPart3: String { return VectorL10n.tr("Vector", "key_backup_recover_from_passphrase_lost_passphrase_action_part3") } - /// Enter Passphrase + /// Enter Phrase internal static var keyBackupRecoverFromPassphrasePassphrasePlaceholder: String { return VectorL10n.tr("Vector", "key_backup_recover_from_passphrase_passphrase_placeholder") } @@ -1646,11 +1646,11 @@ internal enum VectorL10n { internal static var keyBackupRecoverFromPrivateKeyInfo: String { return VectorL10n.tr("Vector", "key_backup_recover_from_private_key_info") } - /// Use your recovery key to unlock your secure message history + /// Use your Security Key to unlock your secure message history internal static var keyBackupRecoverFromRecoveryKeyInfo: String { return VectorL10n.tr("Vector", "key_backup_recover_from_recovery_key_info") } - /// Lost your recovery key? You can set up a new one in settings. + /// Lost your Security Key You can set up a new one in settings. internal static var keyBackupRecoverFromRecoveryKeyLostRecoveryKeyAction: String { return VectorL10n.tr("Vector", "key_backup_recover_from_recovery_key_lost_recovery_key_action") } @@ -1658,7 +1658,7 @@ internal enum VectorL10n { internal static var keyBackupRecoverFromRecoveryKeyRecoverAction: String { return VectorL10n.tr("Vector", "key_backup_recover_from_recovery_key_recover_action") } - /// Enter Recovery Key + /// Enter Security Key internal static var keyBackupRecoverFromRecoveryKeyRecoveryKeyPlaceholder: String { return VectorL10n.tr("Vector", "key_backup_recover_from_recovery_key_recovery_key_placeholder") } @@ -1666,19 +1666,19 @@ internal enum VectorL10n { internal static var keyBackupRecoverFromRecoveryKeyRecoveryKeyTitle: String { return VectorL10n.tr("Vector", "key_backup_recover_from_recovery_key_recovery_key_title") } - /// Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase. + /// Backup could not be decrypted with this phrase: please verify that you entered the correct Security Phrase. internal static var keyBackupRecoverInvalidPassphrase: String { return VectorL10n.tr("Vector", "key_backup_recover_invalid_passphrase") } - /// Incorrect Recovery Passphrase + /// Incorrect Security Phrase internal static var keyBackupRecoverInvalidPassphraseTitle: String { return VectorL10n.tr("Vector", "key_backup_recover_invalid_passphrase_title") } - /// Backup could not be decrypted with this key: please verify that you entered the correct recovery key. + /// Backup could not be decrypted with this key: please verify that you entered the correct Security Key. internal static var keyBackupRecoverInvalidRecoveryKey: String { return VectorL10n.tr("Vector", "key_backup_recover_invalid_recovery_key") } - /// Recovery Key Mismatch + /// Security Key Mismatch internal static var keyBackupRecoverInvalidRecoveryKeyTitle: String { return VectorL10n.tr("Vector", "key_backup_recover_invalid_recovery_key_title") } @@ -1714,11 +1714,11 @@ internal enum VectorL10n { internal static var keyBackupSetupIntroTitle: String { return VectorL10n.tr("Vector", "key_backup_setup_intro_title") } - /// Passphrase doesn’t match + /// Phrase doesn’t match internal static var keyBackupSetupPassphraseConfirmPassphraseInvalid: String { return VectorL10n.tr("Vector", "key_backup_setup_passphrase_confirm_passphrase_invalid") } - /// Confirm passphrase + /// Confirm phrase internal static var keyBackupSetupPassphraseConfirmPassphrasePlaceholder: String { return VectorL10n.tr("Vector", "key_backup_setup_passphrase_confirm_passphrase_placeholder") } @@ -1730,7 +1730,7 @@ internal enum VectorL10n { internal static var keyBackupSetupPassphraseConfirmPassphraseValid: String { return VectorL10n.tr("Vector", "key_backup_setup_passphrase_confirm_passphrase_valid") } - /// We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.\n\nFor maximum security, this should be different from your account password. + /// We'll store an encrypted copy of your keys on our server. Protect your backup with a phrase to keep it secure.\n\nFor maximum security, this should be different from your account password. internal static var keyBackupSetupPassphraseInfo: String { return VectorL10n.tr("Vector", "key_backup_setup_passphrase_info") } @@ -1738,7 +1738,7 @@ internal enum VectorL10n { internal static var keyBackupSetupPassphrasePassphraseInvalid: String { return VectorL10n.tr("Vector", "key_backup_setup_passphrase_passphrase_invalid") } - /// Enter passphrase + /// Enter phrase internal static var keyBackupSetupPassphrasePassphrasePlaceholder: String { return VectorL10n.tr("Vector", "key_backup_setup_passphrase_passphrase_placeholder") } @@ -1750,19 +1750,19 @@ internal enum VectorL10n { internal static var keyBackupSetupPassphrasePassphraseValid: String { return VectorL10n.tr("Vector", "key_backup_setup_passphrase_passphrase_valid") } - /// Set Passphrase + /// Set Phrase internal static var keyBackupSetupPassphraseSetPassphraseAction: String { return VectorL10n.tr("Vector", "key_backup_setup_passphrase_set_passphrase_action") } - /// (Advanced) Set up with Recovery Key + /// (Advanced) Set up with Security Key internal static var keyBackupSetupPassphraseSetupRecoveryKeyAction: String { return VectorL10n.tr("Vector", "key_backup_setup_passphrase_setup_recovery_key_action") } - /// Or, secure your backup with a Recovery Key, saving it somewhere safe. + /// Or, secure your backup with a Security Key, saving it somewhere safe. internal static var keyBackupSetupPassphraseSetupRecoveryKeyInfo: String { return VectorL10n.tr("Vector", "key_backup_setup_passphrase_setup_recovery_key_info") } - /// Secure your backup with a Passphrase + /// Secure your backup with a Security Phrase internal static var keyBackupSetupPassphraseTitle: String { return VectorL10n.tr("Vector", "key_backup_setup_passphrase_title") } @@ -1782,15 +1782,15 @@ internal enum VectorL10n { internal static var keyBackupSetupSuccessFromPassphraseDoneAction: String { return VectorL10n.tr("Vector", "key_backup_setup_success_from_passphrase_done_action") } - /// Your keys are being backed up.\n\nYour recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.\n\nKeep your recovery key somewhere very secure, like a password manager (or a safe). + /// Your keys are being backed up.\n\nYour Security Key is a safety net - you can use it to restore access to your encrypted messages if you forget your passphrase.\n\nKeep your Security Key somewhere very secure, like a password manager (or a safe). internal static var keyBackupSetupSuccessFromPassphraseInfo: String { return VectorL10n.tr("Vector", "key_backup_setup_success_from_passphrase_info") } - /// Save Recovery Key + /// Save Security Key internal static var keyBackupSetupSuccessFromPassphraseSaveRecoveryKeyAction: String { return VectorL10n.tr("Vector", "key_backup_setup_success_from_passphrase_save_recovery_key_action") } - /// Your keys are being backed up.\n\nMake a copy of this recovery key and keep it safe. + /// Your keys are being backed up.\n\nMake a copy of this Security Key and keep it safe. internal static var keyBackupSetupSuccessFromRecoveryKeyInfo: String { return VectorL10n.tr("Vector", "key_backup_setup_success_from_recovery_key_info") } @@ -1802,10 +1802,14 @@ internal enum VectorL10n { internal static var keyBackupSetupSuccessFromRecoveryKeyMakeCopyAction: String { return VectorL10n.tr("Vector", "key_backup_setup_success_from_recovery_key_make_copy_action") } - /// Recovery Key + /// Security Key internal static var keyBackupSetupSuccessFromRecoveryKeyRecoveryKeyTitle: String { return VectorL10n.tr("Vector", "key_backup_setup_success_from_recovery_key_recovery_key_title") } + /// Your keys are being backed up. + internal static var keyBackupSetupSuccessFromSecureBackupInfo: String { + return VectorL10n.tr("Vector", "key_backup_setup_success_from_secure_backup_info") + } /// Success! internal static var keyBackupSetupSuccessTitle: String { return VectorL10n.tr("Vector", "key_backup_setup_success_title") @@ -3554,15 +3558,23 @@ internal enum VectorL10n { internal static var secretsRecoveryResetActionPart2: String { return VectorL10n.tr("Vector", "secrets_recovery_reset_action_part_2") } - /// Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery key. + /// Access your secure message history and your cross-signing identity for verifying other sessions by entering your Security Key. internal static var secretsRecoveryWithKeyInformationDefault: String { return VectorL10n.tr("Vector", "secrets_recovery_with_key_information_default") } - /// Use your Recovery Key to verify this device. + /// Enter your Security Key to continue. + internal static var secretsRecoveryWithKeyInformationUnlockSecureBackupWithKey: String { + return VectorL10n.tr("Vector", "secrets_recovery_with_key_information_unlock_secure_backup_with_key") + } + /// Enter your Security Phrase to continue. + internal static var secretsRecoveryWithKeyInformationUnlockSecureBackupWithPhrase: String { + return VectorL10n.tr("Vector", "secrets_recovery_with_key_information_unlock_secure_backup_with_phrase") + } + /// Use your Security Key to verify this device. internal static var secretsRecoveryWithKeyInformationVerifyDevice: String { return VectorL10n.tr("Vector", "secrets_recovery_with_key_information_verify_device") } - /// Please verify that you entered the correct recovery key. + /// Please verify that you entered the correct Security Key. internal static var secretsRecoveryWithKeyInvalidRecoveryKeyMessage: String { return VectorL10n.tr("Vector", "secrets_recovery_with_key_invalid_recovery_key_message") } @@ -3574,7 +3586,7 @@ internal enum VectorL10n { internal static var secretsRecoveryWithKeyRecoverAction: String { return VectorL10n.tr("Vector", "secrets_recovery_with_key_recover_action") } - /// Enter Recovery Key + /// Enter Security Key internal static var secretsRecoveryWithKeyRecoveryKeyPlaceholder: String { return VectorL10n.tr("Vector", "secrets_recovery_with_key_recovery_key_placeholder") } @@ -3582,19 +3594,19 @@ internal enum VectorL10n { internal static var secretsRecoveryWithKeyRecoveryKeyTitle: String { return VectorL10n.tr("Vector", "secrets_recovery_with_key_recovery_key_title") } - /// Recovery Key + /// Security Key internal static var secretsRecoveryWithKeyTitle: String { return VectorL10n.tr("Vector", "secrets_recovery_with_key_title") } - /// Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery passphrase. + /// Access your secure message history and your cross-signing identity for verifying other sessions by entering your Security Phrase. internal static var secretsRecoveryWithPassphraseInformationDefault: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_information_default") } - /// Use your Recovery Passphrase to verify this device. + /// Use your Security Phrase to verify this device. internal static var secretsRecoveryWithPassphraseInformationVerifyDevice: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_information_verify_device") } - /// Please verify that you entered the correct recovery passphrase. + /// Please verify that you entered the correct Security Phrase. internal static var secretsRecoveryWithPassphraseInvalidPassphraseMessage: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_invalid_passphrase_message") } @@ -3602,11 +3614,11 @@ internal enum VectorL10n { internal static var secretsRecoveryWithPassphraseInvalidPassphraseTitle: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_invalid_passphrase_title") } - /// Don’t know your recovery passphrase? You can + /// Don’t know your Security Phrase? You can internal static var secretsRecoveryWithPassphraseLostPassphraseActionPart1: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_lost_passphrase_action_part1") } - /// use your recovery key + /// use your Security Key internal static var secretsRecoveryWithPassphraseLostPassphraseActionPart2: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_lost_passphrase_action_part2") } @@ -3614,7 +3626,7 @@ internal enum VectorL10n { internal static var secretsRecoveryWithPassphraseLostPassphraseActionPart3: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_lost_passphrase_action_part3") } - /// Enter Recovery Passphrase + /// Enter Security Phrase internal static var secretsRecoveryWithPassphrasePassphrasePlaceholder: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_passphrase_placeholder") } @@ -3622,11 +3634,11 @@ internal enum VectorL10n { internal static var secretsRecoveryWithPassphrasePassphraseTitle: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_passphrase_title") } - /// Use Passphrase + /// Use Phrase internal static var secretsRecoveryWithPassphraseRecoverAction: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_recover_action") } - /// Recovery Passphrase + /// Security Phrase internal static var secretsRecoveryWithPassphraseTitle: String { return VectorL10n.tr("Vector", "secrets_recovery_with_passphrase_title") } @@ -3662,7 +3674,7 @@ internal enum VectorL10n { internal static var secretsSetupRecoveryKeyExportAction: String { return VectorL10n.tr("Vector", "secrets_setup_recovery_key_export_action") } - /// Store your Recovery Key somewhere safe. It can be used to unlock your encrypted messages & data. + /// Store your Security Key somewhere safe. It can be used to unlock your encrypted messages & data. internal static var secretsSetupRecoveryKeyInformation: String { return VectorL10n.tr("Vector", "secrets_setup_recovery_key_information") } @@ -3690,7 +3702,7 @@ internal enum VectorL10n { internal static var secretsSetupRecoveryPassphraseConfirmInformation: String { return VectorL10n.tr("Vector", "secrets_setup_recovery_passphrase_confirm_information") } - /// Confirm passphrase + /// Confirm phrase internal static var secretsSetupRecoveryPassphraseConfirmPassphrasePlaceholder: String { return VectorL10n.tr("Vector", "secrets_setup_recovery_passphrase_confirm_passphrase_placeholder") } @@ -3770,7 +3782,7 @@ internal enum VectorL10n { internal static var secureKeyBackupSetupIntroUseSecurityPassphraseInfo: String { return VectorL10n.tr("Vector", "secure_key_backup_setup_intro_use_security_passphrase_info") } - /// Use a Security Passphrase + /// Use a Security Phrase internal static var secureKeyBackupSetupIntroUseSecurityPassphraseTitle: String { return VectorL10n.tr("Vector", "secure_key_backup_setup_intro_use_security_passphrase_title") } @@ -3806,7 +3818,7 @@ internal enum VectorL10n { internal static var securitySettingsCrosssigning: String { return VectorL10n.tr("Vector", "security_settings_crosssigning") } - /// Bootstrap cross-signing + /// Set up internal static var securitySettingsCrosssigningBootstrap: String { return VectorL10n.tr("Vector", "security_settings_crosssigning_bootstrap") } @@ -3822,7 +3834,7 @@ internal enum VectorL10n { internal static var securitySettingsCrosssigningInfoNotBootstrapped: String { return VectorL10n.tr("Vector", "security_settings_crosssigning_info_not_bootstrapped") } - /// Cross-signing is enabled. + /// Cross-signing is ready for use. internal static var securitySettingsCrosssigningInfoOk: String { return VectorL10n.tr("Vector", "security_settings_crosssigning_info_ok") } @@ -3830,7 +3842,7 @@ internal enum VectorL10n { internal static var securitySettingsCrosssigningInfoTrusted: String { return VectorL10n.tr("Vector", "security_settings_crosssigning_info_trusted") } - /// Reset cross-signing + /// Reset internal static var securitySettingsCrosssigningReset: String { return VectorL10n.tr("Vector", "security_settings_crosssigning_reset") } @@ -3858,22 +3870,34 @@ internal enum VectorL10n { internal static var securitySettingsSecureBackup: String { return VectorL10n.tr("Vector", "security_settings_secure_backup") } - /// Delete + /// Delete Backup internal static var securitySettingsSecureBackupDelete: String { return VectorL10n.tr("Vector", "security_settings_secure_backup_delete") } - /// Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server. + /// Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key. internal static var securitySettingsSecureBackupDescription: String { return VectorL10n.tr("Vector", "security_settings_secure_backup_description") } + /// Checking… + internal static var securitySettingsSecureBackupInfoChecking: String { + return VectorL10n.tr("Vector", "security_settings_secure_backup_info_checking") + } + /// This session is backing up your keys. + internal static var securitySettingsSecureBackupInfoValid: String { + return VectorL10n.tr("Vector", "security_settings_secure_backup_info_valid") + } + /// Reset + internal static var securitySettingsSecureBackupReset: String { + return VectorL10n.tr("Vector", "security_settings_secure_backup_reset") + } + /// Restore from Backup + internal static var securitySettingsSecureBackupRestore: String { + return VectorL10n.tr("Vector", "security_settings_secure_backup_restore") + } /// Set up internal static var securitySettingsSecureBackupSetup: String { return VectorL10n.tr("Vector", "security_settings_secure_backup_setup") } - /// Synchronise - internal static var securitySettingsSecureBackupSynchronise: String { - return VectorL10n.tr("Vector", "security_settings_secure_backup_synchronise") - } /// Security internal static var securitySettingsTitle: String { return VectorL10n.tr("Vector", "security_settings_title") @@ -4250,7 +4274,7 @@ internal enum VectorL10n { internal static var settingsKeyBackupInfoProgressDone: String { return VectorL10n.tr("Vector", "settings_key_backup_info_progress_done") } - /// Connect this session to key backup before signing out to avoid losing any keys that may only be on this device. + /// Back up your keys before signing out to avoid losing them. internal static var settingsKeyBackupInfoSignoutWarning: String { return VectorL10n.tr("Vector", "settings_key_backup_info_signout_warning") } diff --git a/Riot/Modules/Home/HomeViewController.m b/Riot/Modules/Home/HomeViewController.m index 553f6e6ce..d8d5382fc 100644 --- a/Riot/Modules/Home/HomeViewController.m +++ b/Riot/Modules/Home/HomeViewController.m @@ -167,7 +167,7 @@ - (void)presentSecureBackupSetup { - SecureBackupSetupCoordinatorBridgePresenter *keyBackupSetupCoordinatorBridgePresenter = [[SecureBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession]; + SecureBackupSetupCoordinatorBridgePresenter *keyBackupSetupCoordinatorBridgePresenter = [[SecureBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession allowOverwrite:NO]; keyBackupSetupCoordinatorBridgePresenter.delegate = self; [keyBackupSetupCoordinatorBridgePresenter presentFrom:self animated:YES]; diff --git a/Riot/Modules/KeyBackup/Setup/KeyBackupSetupCoordinator.swift b/Riot/Modules/KeyBackup/Setup/KeyBackupSetupCoordinator.swift index e5dd5561b..3fce9171d 100644 --- a/Riot/Modules/KeyBackup/Setup/KeyBackupSetupCoordinator.swift +++ b/Riot/Modules/KeyBackup/Setup/KeyBackupSetupCoordinator.swift @@ -44,11 +44,11 @@ final class KeyBackupSetupCoordinator: KeyBackupSetupCoordinatorType { // MARK: - Public methods func start() { - - // Set key backup setup intro as root controller - let keyBackupSetupIntroViewController = self.createSetupIntroViewController() - keyBackupSetupIntroViewController.delegate = self - self.navigationRouter.setRootModule(keyBackupSetupIntroViewController) + if self.session.crypto.recoveryService.hasRecovery() { + showUnlockSecureBackup() + } else { + showSetupIntro() + } } func toPresentable() -> UIViewController { @@ -57,6 +57,13 @@ final class KeyBackupSetupCoordinator: KeyBackupSetupCoordinatorType { // MARK: - Private methods + private func showSetupIntro() { + // Set key backup setup intro as root controller + let keyBackupSetupIntroViewController = self.createSetupIntroViewController() + keyBackupSetupIntroViewController.delegate = self + self.navigationRouter.setRootModule(keyBackupSetupIntroViewController) + } + private func createSetupIntroViewController() -> KeyBackupSetupIntroViewController { let backupState = self.session.crypto.backup?.state ?? MXKeyBackupStateUnknown @@ -80,6 +87,17 @@ final class KeyBackupSetupCoordinator: KeyBackupSetupCoordinatorType { return KeyBackupSetupIntroViewController.instantiate(isABackupAlreadyExists: isABackupAlreadyExists, encryptionKeysExportPresenter: encryptionKeysExportPresenter) } + private func showUnlockSecureBackup() { + let recoveryGoal: SecretsRecoveryGoal = .unlockSecureBackup { (privateKey, completion) in + self.createKeyBackupUsingSecureBackup(privateKey: privateKey, completion: completion) + } + + let coordinator = SecretsRecoveryCoordinator(session: self.session, recoveryMode: .passphraseOrKey, recoveryGoal: recoveryGoal, navigationRouter: self.navigationRouter) + coordinator.delegate = self + coordinator.start() + self.add(childCoordinator: coordinator) + } + private func showSetupPassphrase(animated: Bool) { let keyBackupSetupPassphraseCoordinator = KeyBackupSetupPassphraseCoordinator(session: self.session) keyBackupSetupPassphraseCoordinator.delegate = self @@ -104,6 +122,33 @@ final class KeyBackupSetupCoordinator: KeyBackupSetupCoordinatorType { viewController.delegate = self self.navigationRouter.push(viewController, animated: animated, popCompletion: nil) } + + private func showSetupWithSecureBackupSuccess(animated: Bool) { + let viewController = KeyBackupSetupSuccessFromSecureBackupViewController.instantiate() + viewController.delegate = self + self.navigationRouter.push(viewController, animated: animated, popCompletion: nil) + } + + private func createKeyBackupUsingSecureBackup(privateKey: Data, completion: @escaping (Result) -> Void) { + guard let keyBackup = session.crypto.backup, let recoveryService = session.crypto.recoveryService else { + return + } + + keyBackup.prepareKeyBackupVersion(withPassword: nil, success: { megolmBackupCreationInfo in + keyBackup.createKeyBackupVersion(megolmBackupCreationInfo, success: { _ in + recoveryService.updateRecovery(forSecrets: [MXSecretId.keyBackup.takeUnretainedValue() as String], withPrivateKey: privateKey) { + completion(.success(Void())) + } failure: { error in + completion(.failure(error)) + } + + }, failure: { error in + completion(.failure(error)) + }) + }, failure: { error in + completion(.failure(error)) + }) + } } // MARK: - KeyBackupSetupIntroViewControllerDelegate @@ -133,6 +178,17 @@ extension KeyBackupSetupCoordinator: KeyBackupSetupPassphraseCoordinatorDelegate } } +// MARK: - SecretsRecoveryCoordinatorDelegate +extension KeyBackupSetupCoordinator: SecretsRecoveryCoordinatorDelegate { + func secretsRecoveryCoordinatorDidRecover(_ coordinator: SecretsRecoveryCoordinatorType) { + self.showSetupWithSecureBackupSuccess(animated: true) + } + + func secretsRecoveryCoordinatorDidCancel(_ coordinator: SecretsRecoveryCoordinatorType) { + self.delegate?.keyBackupSetupCoordinatorDidCancel(self) + } +} + // MARK: - KeyBackupSetupSuccessFromPassphraseViewControllerDelegate extension KeyBackupSetupCoordinator: KeyBackupSetupSuccessFromPassphraseViewControllerDelegate { func keyBackupSetupSuccessFromPassphraseViewControllerDidTapDoneAction(_ viewController: KeyBackupSetupSuccessFromPassphraseViewController) { @@ -146,3 +202,10 @@ extension KeyBackupSetupCoordinator: KeyBackupSetupSuccessFromRecoveryKeyViewCon self.delegate?.keyBackupSetupCoordinatorDidSetupRecoveryKey(self) } } + +// MARK: - KeyBackupSetupSuccessFromSecureBackupViewControllerDelegate +extension KeyBackupSetupCoordinator: KeyBackupSetupSuccessFromSecureBackupViewControllerDelegate { + func keyBackupSetupSuccessFromSecureBackupViewControllerDidTapDoneAction(_ viewController: KeyBackupSetupSuccessFromSecureBackupViewController) { + self.delegate?.keyBackupSetupCoordinatorDidSetupRecoveryKey(self) + } +} diff --git a/Riot/Modules/KeyBackup/Setup/Success/KeyBackupSetupSuccessFromSecureBackupViewController.storyboard b/Riot/Modules/KeyBackup/Setup/Success/KeyBackupSetupSuccessFromSecureBackupViewController.storyboard new file mode 100644 index 000000000..1020489c2 --- /dev/null +++ b/Riot/Modules/KeyBackup/Setup/Success/KeyBackupSetupSuccessFromSecureBackupViewController.storyboard @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/KeyBackup/Setup/Success/KeyBackupSetupSuccessFromSecureBackupViewController.swift b/Riot/Modules/KeyBackup/Setup/Success/KeyBackupSetupSuccessFromSecureBackupViewController.swift new file mode 100644 index 000000000..c369cb5e9 --- /dev/null +++ b/Riot/Modules/KeyBackup/Setup/Success/KeyBackupSetupSuccessFromSecureBackupViewController.swift @@ -0,0 +1,120 @@ +/* + Copyright 2021 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 UIKit + +protocol KeyBackupSetupSuccessFromSecureBackupViewControllerDelegate: class { + func keyBackupSetupSuccessFromSecureBackupViewControllerDidTapDoneAction(_ viewController: KeyBackupSetupSuccessFromSecureBackupViewController) +} + +final class KeyBackupSetupSuccessFromSecureBackupViewController: UIViewController { + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var keyBackupLogoImageView: UIImageView! + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var informationLabel: UILabel! + + @IBOutlet private weak var doneButtonBackgroundView: UIView! + @IBOutlet private weak var doneButton: UIButton! + + // MARK: Private + + private var theme: Theme! + + // MARK: Public + + weak var delegate: KeyBackupSetupSuccessFromSecureBackupViewControllerDelegate? + + // MARK: - Setup + + class func instantiate() -> KeyBackupSetupSuccessFromSecureBackupViewController { + let viewController = StoryboardScene.KeyBackupSetupSuccessFromSecureBackupViewController.initialScene.instantiate() + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.title = VectorL10n.keyBackupSetupTitle + + self.setupViews() + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + // Hide back button + self.navigationItem.setHidesBackButton(true, animated: animated) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + // MARK: - Private + + private func setupViews() { + + let keybackupLogoImage = Asset.Images.keyBackupLogo.image.withRenderingMode(.alwaysTemplate) + self.keyBackupLogoImageView.image = keybackupLogoImage + + self.titleLabel.text = VectorL10n.keyBackupSetupSuccessTitle + self.informationLabel.text = VectorL10n.keyBackupSetupSuccessFromSecureBackupInfo + + self.doneButton.setTitle(VectorL10n.keyBackupSetupSuccessFromPassphraseDoneAction, for: .normal) + } + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.headerBackgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + + self.keyBackupLogoImageView.tintColor = theme.textPrimaryColor + self.titleLabel.textColor = theme.textPrimaryColor + self.informationLabel.textColor = theme.textPrimaryColor + + self.doneButtonBackgroundView.backgroundColor = theme.backgroundColor + theme.applyStyle(onButton: self.doneButton) + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + // MARK: - Actions + + @IBAction private func doneButtonAction(_ sender: Any) { + self.delegate?.keyBackupSetupSuccessFromSecureBackupViewControllerDidTapDoneAction(self) + } +} diff --git a/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewController.swift b/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewController.swift index 306c3bcb8..a0cc96d35 100644 --- a/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewController.swift +++ b/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewController.swift @@ -103,6 +103,8 @@ final class SecretsRecoveryWithKeyViewController: UIViewController { switch self.viewModel.recoveryGoal { case .default, .keyBackup, .restoreSecureBackup: informationText = VectorL10n.secretsRecoveryWithKeyInformationDefault + case .unlockSecureBackup(_): + informationText = VectorL10n.secretsRecoveryWithKeyInformationUnlockSecureBackupWithKey case .verifyDevice: informationText = VectorL10n.secretsRecoveryWithKeyInformationVerifyDevice } diff --git a/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewController.swift b/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewController.swift index cf42a77a7..c1fcaaec4 100644 --- a/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewController.swift +++ b/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewController.swift @@ -106,6 +106,8 @@ final class SecretsRecoveryWithPassphraseViewController: UIViewController { switch self.viewModel.recoveryGoal { case .default, .keyBackup, .restoreSecureBackup: informationText = VectorL10n.secretsRecoveryWithPassphraseInformationDefault + case .unlockSecureBackup(_): + informationText = VectorL10n.secretsRecoveryWithKeyInformationUnlockSecureBackupWithPhrase case .verifyDevice: informationText = VectorL10n.secretsRecoveryWithPassphraseInformationVerifyDevice } diff --git a/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewModel.swift b/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewModel.swift index 77cb22489..71e51e270 100644 --- a/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewModel.swift +++ b/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewModel.swift @@ -73,27 +73,13 @@ final class SecretsRecoveryWithPassphraseViewModel: SecretsRecoveryWithPassphras return } - let secretIds: [String]? - - if case SecretsRecoveryGoal.keyBackup = self.recoveryGoal { - secretIds = [MXSecretId.keyBackup.takeUnretainedValue() as String] - } else { - secretIds = nil + switch self.recoveryGoal { + case .unlockSecureBackup(let block): + self.execute(block: block, privateKey: privateKey) + default: + self.recoverSecrets(privateKey: privateKey) } - self.recoveryService.recoverSecrets(secretIds, withPrivateKey: privateKey, recoverServices: true, success: { [weak self] _ in - guard let self = self else { - return - } - self.update(viewState: .loaded) - self.coordinatorDelegate?.secretsRecoveryWithPassphraseViewModelDidRecover(self) - }, failure: { [weak self] error in - guard let self = self else { - return - } - self.update(viewState: .error(error)) - }) - }, failure: { [weak self] error in guard let self = self else { return @@ -102,6 +88,54 @@ final class SecretsRecoveryWithPassphraseViewModel: SecretsRecoveryWithPassphras }) } + private func recoverSecrets(privateKey: Data) { + let secretIds: [String]? + + if case SecretsRecoveryGoal.keyBackup = self.recoveryGoal { + secretIds = [MXSecretId.keyBackup.takeUnretainedValue() as String] + } else { + secretIds = nil + } + + self.recoveryService.recoverSecrets(secretIds, withPrivateKey: privateKey, recoverServices: true, success: { [weak self] _ in + guard let self = self else { + return + } + self.update(viewState: .loaded) + self.coordinatorDelegate?.secretsRecoveryWithPassphraseViewModelDidRecover(self) + }, failure: { [weak self] error in + guard let self = self else { + return + } + self.update(viewState: .error(error)) + }) + } + + private func execute(block: @escaping (_ privateKey: Data, _ completion: @escaping (Result) -> Void) -> Void, privateKey: Data) { + // Check the private key is valid before using it + self.recoveryService.checkPrivateKey(privateKey) { match in + guard match else { + // Reuse already managed error + let error = NSError(domain: MXRecoveryServiceErrorDomain, code: Int(MXRecoveryServiceErrorCode.badRecoveryKeyErrorCode.rawValue), userInfo: nil) + self.update(viewState: .error(error)) + return + } + + // Run the extenal code while the view state is .loading + block(privateKey) { result in + MXLog.debug("[SecretsRecoveryWithPassphraseViewModel] execute: Block returned: \(result)") + + switch result { + case .success: + self.update(viewState: .loaded) + self.coordinatorDelegate?.secretsRecoveryWithPassphraseViewModelDidRecover(self) + case .failure(let error): + self.update(viewState: .error(error)) + } + } + } + } + private func update(viewState: SecretsRecoveryWithPassphraseViewState) { self.viewDelegate?.secretsRecoveryWithPassphraseViewModel(self, didUpdateViewState: viewState) } diff --git a/Riot/Modules/Secrets/Recover/SecretsRecoveryCoordinatorBridgePresenter.swift b/Riot/Modules/Secrets/Recover/SecretsRecoveryCoordinatorBridgePresenter.swift index 9ec041e61..a5a1ea31a 100644 --- a/Riot/Modules/Secrets/Recover/SecretsRecoveryCoordinatorBridgePresenter.swift +++ b/Riot/Modules/Secrets/Recover/SecretsRecoveryCoordinatorBridgePresenter.swift @@ -32,7 +32,7 @@ final class SecretsRecoveryCoordinatorBridgePresenter: NSObject { private let session: MXSession private let recoveryMode: SecretsRecoveryMode - let recoveryGoal: SecretsRecoveryGoal + let recoveryGoal: SecretsRecoveryGoalBridge private var coordinator: SecretsRecoveryCoordinator? @@ -46,14 +46,14 @@ final class SecretsRecoveryCoordinatorBridgePresenter: NSObject { // MARK: - Setup - init(session: MXSession, recoveryMode: SecretsRecoveryMode, recoveryGoal: SecretsRecoveryGoal) { + init(session: MXSession, recoveryMode: SecretsRecoveryMode, recoveryGoal: SecretsRecoveryGoalBridge) { self.session = session self.recoveryMode = recoveryMode self.recoveryGoal = recoveryGoal super.init() } - init(session: MXSession, recoveryGoal: SecretsRecoveryGoal) { + init(session: MXSession, recoveryGoal: SecretsRecoveryGoalBridge) { self.session = session if case SecretsRecoveryAvailability.available(let secretMode) = session.crypto.recoveryService.vc_availability { @@ -74,7 +74,7 @@ final class SecretsRecoveryCoordinatorBridgePresenter: NSObject { func present(from viewController: UIViewController, animated: Bool) { - let coordinator = SecretsRecoveryCoordinator(session: self.session, recoveryMode: self.recoveryMode, recoveryGoal: self.recoveryGoal) + let coordinator = SecretsRecoveryCoordinator(session: self.session, recoveryMode: self.recoveryMode, recoveryGoal: self.recoveryGoal.goal) coordinator.delegate = self let presentable = coordinator.toPresentable() diff --git a/Riot/Modules/Secrets/Recover/SecretsRecoveryGoal.swift b/Riot/Modules/Secrets/Recover/SecretsRecoveryGoal.swift index 0017d2b75..62b293b9f 100644 --- a/Riot/Modules/Secrets/Recover/SecretsRecoveryGoal.swift +++ b/Riot/Modules/Secrets/Recover/SecretsRecoveryGoal.swift @@ -16,10 +16,28 @@ import Foundation +enum SecretsRecoveryGoal { + case `default` + case keyBackup + /// Unlock the secure backup (4S) to get the private key and execute a closure during the flow + case unlockSecureBackup ((_ privateKey: Data, _ completion: @escaping (Result) -> Void) -> Void) + case verifyDevice + case restoreSecureBackup +} + @objc -enum SecretsRecoveryGoal: Int { +enum SecretsRecoveryGoalBridge: Int { case `default` case keyBackup case verifyDevice case restoreSecureBackup + + var goal: SecretsRecoveryGoal { + switch self { + case .default: return .default + case .keyBackup: return .keyBackup + case .verifyDevice: return .verifyDevice + case .restoreSecureBackup: return .restoreSecureBackup + } + } } diff --git a/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyCoordinator.swift b/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyCoordinator.swift index d11b3e619..f08f88869 100644 --- a/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyCoordinator.swift +++ b/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyCoordinator.swift @@ -39,8 +39,9 @@ final class SecretsSetupRecoveryKeyCoordinator: SecretsSetupRecoveryKeyCoordinat init(recoveryService: MXRecoveryService, passphrase: String?, - passphraseOnly: Bool) { - let secretsSetupRecoveryKeyViewModel = SecretsSetupRecoveryKeyViewModel(recoveryService: recoveryService, passphrase: passphrase, passphraseOnly: passphraseOnly) + passphraseOnly: Bool, + allowOverwrite: Bool = false) { + let secretsSetupRecoveryKeyViewModel = SecretsSetupRecoveryKeyViewModel(recoveryService: recoveryService, passphrase: passphrase, passphraseOnly: passphraseOnly, allowOverwrite: allowOverwrite) let secretsSetupRecoveryKeyViewController = SecretsSetupRecoveryKeyViewController.instantiate(with: secretsSetupRecoveryKeyViewModel) self.secretsSetupRecoveryKeyViewModel = secretsSetupRecoveryKeyViewModel self.secretsSetupRecoveryKeyViewController = secretsSetupRecoveryKeyViewController diff --git a/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewModel.swift b/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewModel.swift index 8ad575dca..af85b7eb7 100644 --- a/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewModel.swift +++ b/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewModel.swift @@ -27,6 +27,7 @@ final class SecretsSetupRecoveryKeyViewModel: SecretsSetupRecoveryKeyViewModelTy private let recoveryService: MXRecoveryService private let passphrase: String? private let passphraseOnly: Bool + private let allowOverwrite: Bool // MARK: Public @@ -35,10 +36,11 @@ final class SecretsSetupRecoveryKeyViewModel: SecretsSetupRecoveryKeyViewModelTy // MARK: - Setup - init(recoveryService: MXRecoveryService, passphrase: String?, passphraseOnly: Bool) { + init(recoveryService: MXRecoveryService, passphrase: String?, passphraseOnly: Bool, allowOverwrite: Bool = false) { self.recoveryService = recoveryService self.passphrase = passphrase self.passphraseOnly = passphraseOnly + self.allowOverwrite = allowOverwrite } // MARK: - Public @@ -61,6 +63,16 @@ final class SecretsSetupRecoveryKeyViewModel: SecretsSetupRecoveryKeyViewModelTy private func createSecureKey() { self.update(viewState: .loading) + + if allowOverwrite && self.recoveryService.hasRecovery() { + MXLog.debug("[SecretsSetupRecoveryKeyViewModel] createSecureKey: Overwrite existing secure backup") + self.recoveryService.deleteRecovery(withDeleteServicesBackups: true) { + self.createSecureKey() + } failure: { error in + self.update(viewState: .error(error)) + } + return + } self.recoveryService.createRecovery(forSecrets: nil, withPassphrase: self.passphrase, createServicesBackups: true, success: { secretStorageKeyCreationInfo in self.update(viewState: .recoveryCreated(secretStorageKeyCreationInfo.recoveryKey)) diff --git a/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinator.swift b/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinator.swift index 0ddb3416c..543616377 100644 --- a/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinator.swift +++ b/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinator.swift @@ -30,6 +30,7 @@ final class SecureBackupSetupCoordinator: SecureBackupSetupCoordinatorType { private let recoveryService: MXRecoveryService private let keyBackup: MXKeyBackup? private let checkKeyBackup: Bool + private let allowOverwrite: Bool // MARK: Public @@ -45,11 +46,12 @@ final class SecureBackupSetupCoordinator: SecureBackupSetupCoordinatorType { /// - session: The MXSession. /// - checkKeyBackup: Indicate false to ignore existing key backup. /// - navigationRouter: Use existing navigation router to plug this flow or let nil to use new one. - init(session: MXSession, checkKeyBackup: Bool = true, navigationRouter: NavigationRouterType? = nil) { + init(session: MXSession, checkKeyBackup: Bool = true, allowOverwrite: Bool = false, navigationRouter: NavigationRouterType? = nil) { self.session = session self.recoveryService = session.crypto.recoveryService self.keyBackup = session.crypto.backup self.checkKeyBackup = checkKeyBackup + self.allowOverwrite = allowOverwrite if let navigationRouter = navigationRouter { self.navigationRouter = navigationRouter @@ -85,7 +87,7 @@ final class SecureBackupSetupCoordinator: SecureBackupSetupCoordinatorType { } private func showSetupKey(passphraseOnly: Bool, passphrase: String? = nil) { - let coordinator = SecretsSetupRecoveryKeyCoordinator(recoveryService: self.recoveryService, passphrase: passphrase, passphraseOnly: passphraseOnly) + let coordinator = SecretsSetupRecoveryKeyCoordinator(recoveryService: self.recoveryService, passphrase: passphrase, passphraseOnly: passphraseOnly, allowOverwrite: allowOverwrite) coordinator.delegate = self coordinator.start() diff --git a/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinatorBridgePresenter.swift b/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinatorBridgePresenter.swift index 9748dbc51..3db379e8f 100644 --- a/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinatorBridgePresenter.swift +++ b/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinatorBridgePresenter.swift @@ -33,6 +33,7 @@ final class SecureBackupSetupCoordinatorBridgePresenter: NSObject { // MARK: Private private let session: MXSession + private let allowOverwrite: Bool private var coordinator: SecureBackupSetupCoordinator? // MARK: Public @@ -40,9 +41,10 @@ final class SecureBackupSetupCoordinatorBridgePresenter: NSObject { weak var delegate: SecureBackupSetupCoordinatorBridgePresenterDelegate? // MARK: - Setup - - init(session: MXSession) { + + init(session: MXSession, allowOverwrite: Bool) { self.session = session + self.allowOverwrite = allowOverwrite super.init() } @@ -54,7 +56,7 @@ final class SecureBackupSetupCoordinatorBridgePresenter: NSObject { // } func present(from viewController: UIViewController, animated: Bool) { - let secureBackupSetupCoordinator = SecureBackupSetupCoordinator(session: self.session) + let secureBackupSetupCoordinator = SecureBackupSetupCoordinator(session: self.session, allowOverwrite: self.allowOverwrite) secureBackupSetupCoordinator.delegate = self viewController.present(secureBackupSetupCoordinator.toPresentable(), animated: animated, completion: nil) secureBackupSetupCoordinator.start() diff --git a/Riot/Modules/Settings/Security/SecureBackup/SettingsSecureBackupTableViewSection.swift b/Riot/Modules/Settings/Security/SecureBackup/SettingsSecureBackupTableViewSection.swift new file mode 100644 index 000000000..6ef387a20 --- /dev/null +++ b/Riot/Modules/Settings/Security/SecureBackup/SettingsSecureBackupTableViewSection.swift @@ -0,0 +1,336 @@ +/* + Copyright 2021 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 UIKit + +@objc protocol SettingsSecureBackupTableViewSectionDelegate: class { + // Table view rendering + func settingsSecureBackupTableViewSectionDidUpdate(_ settingsSecureBackupTableViewSection: SettingsSecureBackupTableViewSection) + + func settingsSecureBackupTableViewSection(_ settingsSecureBackupTableViewSection: SettingsSecureBackupTableViewSection, textCellForRow: Int) -> MXKTableViewCellWithTextView + func settingsSecureBackupTableViewSection(_ settingsSecureBackupTableViewSection: SettingsSecureBackupTableViewSection, descriptionCellForRow: Int) -> MXKTableViewCell + func settingsSecureBackupTableViewSection(_ settingsSecureBackupTableViewSection: SettingsSecureBackupTableViewSection, buttonCellForRow: Int) -> MXKTableViewCellWithButton + + // Secure backup + func settingsSecureBackupTableViewSectionShowSecureBackupReset(_ settingsSecureBackupTableViewSection: SettingsSecureBackupTableViewSection) + + // Key backup + func settingsSecureBackupTableViewSectionShowKeyBackupCreate(_ settingsSecureBackupTableViewSection: SettingsSecureBackupTableViewSection) + func settingsSecureBackupTableViewSection(_ settingsSecureBackupTableViewSection: SettingsSecureBackupTableViewSection, showKeyBackupRecover keyBackupVersion: MXKeyBackupVersion) + func settingsSecureBackupTableViewSection(_ settingsSecureBackupTableViewSection: SettingsSecureBackupTableViewSection, showKeyBackupDeleteConfirm keyBackupVersion: MXKeyBackupVersion) + + // Life cycle + func settingsSecureBackupTableViewSection(_ settingsSecureBackupTableViewSection: SettingsSecureBackupTableViewSection, showActivityIndicator show: Bool) + func settingsSecureBackupTableViewSection(_ settingsSecureBackupTableViewSection: SettingsSecureBackupTableViewSection, showError error: Error) +} + +private enum BackupRows { + case info(text: String) + case description(text: String) + case createSecureBackupAction + case resetSecureBackupAction + case createKeyBackupAction + case restoreFromKeyBackupAction(keyBackupVersion: MXKeyBackupVersion, title: String) + case deleteKeyBackupAction(keyBackupVersion: MXKeyBackupVersion) +} + +/// SettingsSecureBackupTableViewSection provides UITableViewCells to manage secure backup and key backup. +/// +/// All states are described in SettingsSecureBackupViewState. +/// All actions in SettingsSecureBackupViewAction. +@objc final class SettingsSecureBackupTableViewSection: NSObject { + + // MARK: - Properties + + @objc weak var delegate: SettingsSecureBackupTableViewSectionDelegate? + + // MARK: Private + + // This view class holds the model because the model is in pure Swift + // whereas this class can be used from objC + private var viewModel: SettingsSecureBackupViewModelType! + + // Need to know the state to make `cellForRow` deliver cells accordingly + private var viewState: SettingsSecureBackupViewState = .loading { + didSet { + self.updateBackupRows() + } + } + + private var userDevice: MXDeviceInfo + + private var backupRows: [BackupRows] = [] + + // MARK: - Public + + @objc init(withRecoveryService recoveryService: MXRecoveryService, keyBackup: MXKeyBackup, userDevice: MXDeviceInfo) { + self.viewModel = SettingsSecureBackupViewModel(recoveryService: recoveryService, keyBackup: keyBackup) + self.userDevice = userDevice + super.init() + self.viewModel.viewDelegate = self + + self.viewModel.process(viewAction: .load) + } + + @objc func numberOfRows() -> Int { + return self.backupRows.count + } + + @objc func cellForRow(atRow row: Int) -> UITableViewCell { + let backupRow = self.backupRows[row] + + var cell: UITableViewCell + switch backupRow { + case .info(let text): + cell = self.textCell(atRow: row, text: text) + case .description(let text): + cell = self.descriptionCell(atRow: row, text: text) + case .createSecureBackupAction: + cell = self.buttonCellForCreateSecureBackup(atRow: row) + case .resetSecureBackupAction: + cell = self.buttonCellForResetSecureBackup(atRow: row) + case .createKeyBackupAction: + cell = self.buttonCellForCreateKeyBackup(atRow: row) + case .restoreFromKeyBackupAction(keyBackupVersion: let keyBackupVersion, let title): + cell = self.buttonCellForRestoreFromKeyBackup(keyBackupVersion: keyBackupVersion, title: title, atRow: row) + case .deleteKeyBackupAction(keyBackupVersion: let keyBackupVersion): + cell = self.buttonCellForDeleteKeyBackup(keyBackupVersion: keyBackupVersion, atRow: row) + } + + return cell + } + + @objc func reload() { + self.viewModel.process(viewAction: .load) + } + + @objc func deleteKeyBackup(keyBackupVersion: MXKeyBackupVersion) { + self.viewModel.process(viewAction: .deleteKeyBackup(keyBackupVersion)) + } + + // MARK: - Data Computing + + private func updateBackupRows() { + + let backupRows: [BackupRows] + + switch self.viewState { + case .loading: + backupRows = [ + .info(text: VectorL10n.securitySettingsSecureBackupInfoChecking), + .description(text: VectorL10n.securitySettingsSecureBackupDescription) + ] + + case .noSecureBackup(let keyBackupState): + switch keyBackupState { + case .noKeyBackup: + let noBackup = VectorL10n.settingsKeyBackupInfoNone + let signoutWarning = VectorL10n.settingsKeyBackupInfoSignoutWarning + let infoText = [noBackup, signoutWarning].joined(separator: "\n") + + backupRows = [ + .info(text: infoText), + .createSecureBackupAction, + .description(text: VectorL10n.securitySettingsSecureBackupDescription) + ] + case .keyBackup(let keyBackupVersion, _, _), + .keyBackupNotTrusted(let keyBackupVersion, _): // Manage the key backup in the same way for the moment + backupRows = [ + .info(text: VectorL10n.securitySettingsSecureBackupInfoValid), + .restoreFromKeyBackupAction(keyBackupVersion: keyBackupVersion, title: VectorL10n.securitySettingsSecureBackupRestore), + .deleteKeyBackupAction(keyBackupVersion: keyBackupVersion), + .description(text: VectorL10n.securitySettingsSecureBackupDescription) + ] + } + case .secureBackup(let keyBackupState): + switch keyBackupState { + case .noKeyBackup: + let noBackup = VectorL10n.settingsKeyBackupInfoNone + let signoutWarning = VectorL10n.settingsKeyBackupInfoSignoutWarning + let infoText = [noBackup, signoutWarning].joined(separator: "\n") + + backupRows = [ + .info(text: infoText), + .createKeyBackupAction, + .resetSecureBackupAction, + .description(text: VectorL10n.securitySettingsSecureBackupDescription) + ] + case .keyBackup(let keyBackupVersion, _, _), + .keyBackupNotTrusted(let keyBackupVersion, _): // Manage the key backup in the same way for the moment + backupRows = [ + .info(text: VectorL10n.securitySettingsSecureBackupInfoValid), + .restoreFromKeyBackupAction(keyBackupVersion: keyBackupVersion, title: VectorL10n.securitySettingsSecureBackupRestore), + .deleteKeyBackupAction(keyBackupVersion: keyBackupVersion), + .resetSecureBackupAction, + .description(text: VectorL10n.securitySettingsSecureBackupDescription) + ] + } + } + self.backupRows = backupRows + } + + // MARK: - Cells - + + private func textCell(atRow row: Int, text: String) -> UITableViewCell { + guard let delegate = self.delegate else { + return UITableViewCell() + } + + let cell = delegate.settingsSecureBackupTableViewSection(self, textCellForRow: row) + cell.mxkTextView.text = text + return cell + } + + private func descriptionCell(atRow row: Int, text: String) -> UITableViewCell { + guard let delegate = self.delegate else { + return UITableViewCell() + } + + let cell = delegate.settingsSecureBackupTableViewSection(self, descriptionCellForRow: row) + cell.textLabel?.text = text + return cell + } + + // MARK: - Button cells + + private func buttonCellForCreateSecureBackup(atRow row: Int) -> UITableViewCell { + + guard let delegate = self.delegate else { + return UITableViewCell() + } + + let cell: MXKTableViewCellWithButton = delegate.settingsSecureBackupTableViewSection(self, buttonCellForRow: row) + + let btnTitle = VectorL10n.securitySettingsSecureBackupSetup + cell.mxkButton.setTitle(btnTitle, for: .normal) + cell.mxkButton.setTitle(btnTitle, for: .highlighted) + + cell.mxkButton.vc_addAction { + self.viewModel.process(viewAction: .createSecureBackup) + } + + return cell + } + + private func buttonCellForResetSecureBackup(atRow row: Int) -> UITableViewCell { + + guard let delegate = self.delegate else { + return UITableViewCell() + } + + let cell: MXKTableViewCellWithButton = delegate.settingsSecureBackupTableViewSection(self, buttonCellForRow: row) + + let btnTitle = VectorL10n.securitySettingsSecureBackupReset + cell.mxkButton.setTitle(btnTitle, for: .normal) + cell.mxkButton.setTitle(btnTitle, for: .highlighted) + cell.mxkButton.tintColor = ThemeService.shared().theme.warningColor + + cell.mxkButton.vc_addAction { + self.viewModel.process(viewAction: .resetSecureBackup) + } + + return cell + } + + private func buttonCellForCreateKeyBackup(atRow row: Int) -> UITableViewCell { + + guard let delegate = self.delegate else { + return UITableViewCell() + } + + let cell: MXKTableViewCellWithButton = delegate.settingsSecureBackupTableViewSection(self, buttonCellForRow: row) + + let btnTitle = VectorL10n.securitySettingsSecureBackupSetup + cell.mxkButton.setTitle(btnTitle, for: .normal) + cell.mxkButton.setTitle(btnTitle, for: .highlighted) + + cell.mxkButton.vc_addAction { + self.viewModel.process(viewAction: .createKeyBackup) + } + + return cell + } + + private func buttonCellForRestoreFromKeyBackup(keyBackupVersion: MXKeyBackupVersion, title: String, atRow row: Int) -> UITableViewCell { + guard let delegate = self.delegate else { + return UITableViewCell() + } + + let cell: MXKTableViewCellWithButton = delegate.settingsSecureBackupTableViewSection(self, buttonCellForRow: row) + cell.mxkButton.setTitle(title, for: .normal) + cell.mxkButton.setTitle(title, for: .highlighted) + cell.mxkButton.vc_addAction { + self.viewModel.process(viewAction: .restoreFromKeyBackup(keyBackupVersion)) + } + return cell + } + + private func buttonCellForDeleteKeyBackup(keyBackupVersion: MXKeyBackupVersion, atRow row: Int) -> UITableViewCell { + guard let delegate = self.delegate else { + return UITableViewCell() + } + + let cell: MXKTableViewCellWithButton = delegate.settingsSecureBackupTableViewSection(self, buttonCellForRow: row) + let btnTitle = VectorL10n.securitySettingsSecureBackupDelete + cell.mxkButton.setTitle(btnTitle, for: .normal) + cell.mxkButton.setTitle(btnTitle, for: .highlighted) + cell.mxkButton.tintColor = ThemeService.shared().theme.warningColor + cell.mxkButton.vc_addAction { + self.viewModel.process(viewAction: .confirmDeleteKeyBackup(keyBackupVersion)) + } + + return cell + } +} + + +// MARK: - KeyBackupSetupRecoveryKeyViewModelViewDelegate +extension SettingsSecureBackupTableViewSection: SettingsSecureBackupViewModelViewDelegate { + + func settingsSecureBackupViewModel(_ viewModel: SettingsSecureBackupViewModelType, didUpdateViewState viewState: SettingsSecureBackupViewState) { + self.viewState = viewState + + // The tableview datasource will call `self.cellForRow()` + self.delegate?.settingsSecureBackupTableViewSectionDidUpdate(self) + } + + func settingsSecureBackupViewModel(_ viewModel: SettingsSecureBackupViewModelType, didUpdateNetworkRequestViewState networkRequestViewSate: SettingsSecureBackupNetworkRequestViewState) { + switch networkRequestViewSate { + case .loading: + self.delegate?.settingsSecureBackupTableViewSection(self, showActivityIndicator: true) + case .loaded: + self.delegate?.settingsSecureBackupTableViewSection(self, showActivityIndicator: false) + case .error(let error): + self.delegate?.settingsSecureBackupTableViewSection(self, showError: error) + } + } + + func settingsSecureBackupViewModelShowSecureBackupReset(_ viewModel: SettingsSecureBackupViewModelType) { + self.delegate?.settingsSecureBackupTableViewSectionShowSecureBackupReset(self) + } + + func settingsSecureBackupViewModelShowKeyBackupCreate(_ viewModel: SettingsSecureBackupViewModelType) { + self.delegate?.settingsSecureBackupTableViewSectionShowKeyBackupCreate(self) + } + + func settingsSecureBackupViewModel(_ viewModel: SettingsSecureBackupViewModelType, showKeyBackupRecover keyBackupVersion: MXKeyBackupVersion) { + self.delegate?.settingsSecureBackupTableViewSection(self, showKeyBackupRecover: keyBackupVersion) + } + + func settingsSecureBackupViewModel(_ viewModel: SettingsSecureBackupViewModelType, showKeyBackupDeleteConfirm keyBackupVersion: MXKeyBackupVersion) { + self.delegate?.settingsSecureBackupTableViewSection(self, showKeyBackupDeleteConfirm: keyBackupVersion) + } +} diff --git a/Riot/Modules/Settings/Security/SecureBackup/SettingsSecureBackupViewAction.swift b/Riot/Modules/Settings/Security/SecureBackup/SettingsSecureBackupViewAction.swift new file mode 100644 index 000000000..2616d25cb --- /dev/null +++ b/Riot/Modules/Settings/Security/SecureBackup/SettingsSecureBackupViewAction.swift @@ -0,0 +1,27 @@ +/* + Copyright 2021 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 UIKit + +enum SettingsSecureBackupViewAction { + case load + case createSecureBackup + case resetSecureBackup + case createKeyBackup + case restoreFromKeyBackup(MXKeyBackupVersion) + case confirmDeleteKeyBackup(MXKeyBackupVersion) + case deleteKeyBackup(MXKeyBackupVersion) +} diff --git a/Riot/Modules/Settings/Security/SecureBackup/SettingsSecureBackupViewModel.swift b/Riot/Modules/Settings/Security/SecureBackup/SettingsSecureBackupViewModel.swift new file mode 100644 index 000000000..66b720861 --- /dev/null +++ b/Riot/Modules/Settings/Security/SecureBackup/SettingsSecureBackupViewModel.swift @@ -0,0 +1,154 @@ +/* + Copyright 2021 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 UIKit + +final class SettingsSecureBackupViewModel: SettingsSecureBackupViewModelType { + + // MARK: - Properties + weak var viewDelegate: SettingsSecureBackupViewModelViewDelegate? + + // MARK: Private + private let recoveryService: MXRecoveryService + private let keyBackup: MXKeyBackup + + init(recoveryService: MXRecoveryService, keyBackup: MXKeyBackup) { + self.recoveryService = recoveryService + self.keyBackup = keyBackup + self.registerKeyBackupVersionDidChangeStateNotification() + } + + private func registerKeyBackupVersionDidChangeStateNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(keyBackupDidStateChange), name: NSNotification.Name.mxKeyBackupDidStateChange, object: self.keyBackup) + } + + @objc private func keyBackupDidStateChange() { + self.checkKeyBackupState() + } + + func process(viewAction: SettingsSecureBackupViewAction) { + guard let viewDelegate = self.viewDelegate else { + return + } + + switch viewAction { + case .load: + viewDelegate.settingsSecureBackupViewModel(self, didUpdateViewState: .loading) + self.checkKeyBackupState() + case .resetSecureBackup, + .createSecureBackup: // The implement supports both + viewDelegate.settingsSecureBackupViewModelShowSecureBackupReset(self) + case .createKeyBackup: + viewDelegate.settingsSecureBackupViewModelShowKeyBackupCreate(self) + case .restoreFromKeyBackup(let keyBackupVersion): + viewDelegate.settingsSecureBackupViewModel(self, showKeyBackupRecover: keyBackupVersion) + case .confirmDeleteKeyBackup(let keyBackupVersion): + viewDelegate.settingsSecureBackupViewModel(self, showKeyBackupDeleteConfirm: keyBackupVersion) + case .deleteKeyBackup(let keyBackupVersion): + self.deleteKeyBackupVersion(keyBackupVersion) + } + } + + // MARK: - Private + private func checkKeyBackupState() { + + // Check homeserver update in background + self.keyBackup.forceRefresh(nil, failure: nil) + + if let keyBackupVersion = self.keyBackup.keyBackupVersion { + + self.keyBackup.trust(for: keyBackupVersion, onComplete: { [weak self] (keyBackupVersionTrust) in + + guard let sself = self else { + return + } + + sself.computeState(withBackupVersionTrust: keyBackupVersionTrust) + }) + } else { + computeState() + } + } + + private func computeState(withBackupVersionTrust keyBackupVersionTrust: MXKeyBackupVersionTrust? = nil) { + + var viewState: SettingsSecureBackupViewState? + var keyBackupState: SettingsSecureBackupViewState.KeyBackupState? + switch self.keyBackup.state { + + case MXKeyBackupStateUnknown, + MXKeyBackupStateCheckingBackUpOnHomeserver: + viewState = .loading + + case MXKeyBackupStateDisabled, MXKeyBackupStateEnabling: + keyBackupState = .noKeyBackup + + case MXKeyBackupStateNotTrusted: + guard let keyBackupVersion = self.keyBackup.keyBackupVersion, let keyBackupVersionTrust = keyBackupVersionTrust else { + return + } + keyBackupState = .keyBackupNotTrusted(keyBackupVersion, keyBackupVersionTrust) + + case MXKeyBackupStateReadyToBackUp, MXKeyBackupStateWillBackUp, MXKeyBackupStateBackingUp: + guard let keyBackupVersion = self.keyBackup.keyBackupVersion, let keyBackupVersionTrust = keyBackupVersionTrust else { + return + } + + // Get the backup progress before updating the state + self.keyBackup.backupProgress { [weak self] (progress) in + guard let self = self else { + return + } + + let keyBackupState: SettingsSecureBackupViewState.KeyBackupState = .keyBackup(keyBackupVersion, keyBackupVersionTrust, progress) + let viewState: SettingsSecureBackupViewState = self.recoveryService.hasRecovery() ? .secureBackup(keyBackupState) : .noSecureBackup(keyBackupState) + self.viewDelegate?.settingsSecureBackupViewModel(self, didUpdateViewState: viewState) + } + default: + break + } + + // Turn secure backup and key back states into view state + if let keyBackupState = keyBackupState { + viewState = recoveryService.hasRecovery() ? .secureBackup(keyBackupState) : .noSecureBackup(keyBackupState) + } + + if let viewState = viewState { + self.viewDelegate?.settingsSecureBackupViewModel(self, didUpdateViewState: viewState) + } + } + + private func deleteKeyBackupVersion(_ keyBackupVersion: MXKeyBackupVersion) { + guard let keyBackupVersionVersion = keyBackupVersion.version else { + return + } + + self.viewDelegate?.settingsSecureBackupViewModel(self, didUpdateNetworkRequestViewState: .loading) + + self.keyBackup.deleteVersion(keyBackupVersionVersion, success: { [weak self] () in + guard let sself = self else { + return + } + sself.viewDelegate?.settingsSecureBackupViewModel(sself, didUpdateNetworkRequestViewState: .loaded) + + }, failure: { [weak self] error in + guard let sself = self else { + return + } + sself.viewDelegate?.settingsSecureBackupViewModel(sself, didUpdateNetworkRequestViewState: .error(error)) + }) + } +} diff --git a/Riot/Modules/Settings/Security/SecureBackup/SettingsSecureBackupViewModelType.swift b/Riot/Modules/Settings/Security/SecureBackup/SettingsSecureBackupViewModelType.swift new file mode 100644 index 000000000..4f83b5ccc --- /dev/null +++ b/Riot/Modules/Settings/Security/SecureBackup/SettingsSecureBackupViewModelType.swift @@ -0,0 +1,35 @@ +/* + Copyright 2021 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 UIKit + +protocol SettingsSecureBackupViewModelViewDelegate: class { + func settingsSecureBackupViewModel(_ viewModel: SettingsSecureBackupViewModelType, didUpdateViewState viewState: SettingsSecureBackupViewState) + func settingsSecureBackupViewModel(_ viewModel: SettingsSecureBackupViewModelType, didUpdateNetworkRequestViewState networkRequestViewSate: SettingsSecureBackupNetworkRequestViewState) + + func settingsSecureBackupViewModelShowSecureBackupReset(_ viewModel: SettingsSecureBackupViewModelType) + + func settingsSecureBackupViewModelShowKeyBackupCreate(_ viewModel: SettingsSecureBackupViewModelType) + func settingsSecureBackupViewModel(_ viewModel: SettingsSecureBackupViewModelType, showKeyBackupRecover keyBackupVersion: MXKeyBackupVersion) + func settingsSecureBackupViewModel(_ viewModel: SettingsSecureBackupViewModelType, showKeyBackupDeleteConfirm keyBackupVersion: MXKeyBackupVersion) +} + +protocol SettingsSecureBackupViewModelType { + + var viewDelegate: SettingsSecureBackupViewModelViewDelegate? { get set } + + func process(viewAction: SettingsSecureBackupViewAction) +} diff --git a/Riot/Modules/Settings/Security/SecureBackup/SettingsSecureBackupViewState.swift b/Riot/Modules/Settings/Security/SecureBackup/SettingsSecureBackupViewState.swift new file mode 100644 index 000000000..1390f56ba --- /dev/null +++ b/Riot/Modules/Settings/Security/SecureBackup/SettingsSecureBackupViewState.swift @@ -0,0 +1,49 @@ +/* + Copyright 2021 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 UIKit + +/// State of the Secure Backup section in securtiy settings. +/// +/// It is a mixed of the state of the Secure Backup(4S) and the state of the Key Backup. +/// +/// - loading: Load current state +/// - noSecureBackup: The account has no secure backup +/// - secureBackup: The account has a secure backup +enum SettingsSecureBackupViewState { + case loading + case noSecureBackup(KeyBackupState) + case secureBackup(KeyBackupState) + + /// Internal key backup state. It is independent from the secure backup state. + /// + /// - noKeyBackup: There is no backup on the homeserver + /// - keyBackup: There is a valid running backup on the homeserver. Keys are being sent to it + /// - keyBackupNotTrusted: There is a backup on the homeserver but it is not trusted + enum KeyBackupState { + case noKeyBackup + case keyBackup(MXKeyBackupVersion, MXKeyBackupVersionTrust, Progress) + case keyBackupNotTrusted(MXKeyBackupVersion, MXKeyBackupVersionTrust) + } +} + +/// State representing a network request made by the module +/// Only SettingsSecureBackupViewAction.delete generates such states +enum SettingsSecureBackupNetworkRequestViewState { + case loading + case loaded + case error(Error) +} diff --git a/Riot/Modules/Settings/Security/SecurityViewController.m b/Riot/Modules/Settings/Security/SecurityViewController.m index 5817a6816..b0191ae74 100644 --- a/Riot/Modules/Settings/Security/SecurityViewController.m +++ b/Riot/Modules/Settings/Security/SecurityViewController.m @@ -36,9 +36,9 @@ enum SECTION_PIN_CODE, SECTION_CRYPTO_SESSIONS, SECTION_SECURE_BACKUP, + SECTION_CROSSSIGNING, SECTION_CRYPTOGRAPHY, #ifdef CROSS_SIGNING_AND_BACKUP_DEV - SECTION_CROSSSIGNING, SECTION_KEYBACKUP, #endif SECTION_ADVANCED, @@ -51,23 +51,6 @@ enum { CROSSSIGNING_SECOND_ACTION, // Reset }; -enum { - SECURE_BACKUP_DESCRIPTION, - // TODO: We can display the state of 4S both locally and on the server. Then, provide actions according to all combinations. - // - Does the 4S contains all the 4 keys server side? - // - Advice the user to do a recovery if there is less keys locally - // - Advice them to do a recovery if local keys are obsolete -> We cannot know now - // - Advice them to fix a secure backup if there is 4S but no key backup - // - Warm them if there is no 4S and they do not have all 3 signing keys locally. They will set up a not complete secure backup -#ifdef CROSS_SIGNING_AND_BACKUP_DEV - SECURE_BACKUP_INFO, -#endif - SECURE_BACKUP_SETUP, - SECURE_BACKUP_RESTORE, - SECURE_BACKUP_DELETE, - SECURE_BACKUP_MANAGE_MANUALLY, // TODO: What to do with that? -}; - enum { PIN_CODE_SETTING, PIN_CODE_DESCRIPTION, @@ -90,9 +73,10 @@ enum { @interface SecurityViewController () < +SettingsSecureBackupTableViewSectionDelegate, +KeyBackupSetupCoordinatorBridgePresenterDelegate, #ifdef CROSS_SIGNING_AND_BACKUP_DEV SettingsKeyBackupTableViewSectionDelegate, -KeyBackupSetupCoordinatorBridgePresenterDelegate, KeyBackupRecoverCoordinatorBridgePresenterDelegate, #endif UIDocumentInteractionControllerDelegate, @@ -107,9 +91,6 @@ TableViewSectionsDelegate> // Devices NSMutableArray *devicesArray; - // SECURE_BACKUP_* rows to display - NSArray *secureBackupSectionState; - // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. id kThemeServiceDidChangeThemeNotificationObserver; @@ -123,13 +104,15 @@ TableViewSectionsDelegate> // The current pushed view controller UIViewController *pushedViewController; - + + SettingsSecureBackupTableViewSection *secureBackupSection; + #ifdef CROSS_SIGNING_AND_BACKUP_DEV SettingsKeyBackupTableViewSection *keyBackupSection; - KeyBackupSetupCoordinatorBridgePresenter *keyBackupSetupCoordinatorBridgePresenter; #endif + + KeyBackupSetupCoordinatorBridgePresenter *keyBackupSetupCoordinatorBridgePresenter; KeyBackupRecoverCoordinatorBridgePresenter *keyBackupRecoverCoordinatorBridgePresenter; - SecretsRecoveryCoordinatorBridgePresenter *secretsRecoveryCoordinatorBridgePresenter; } @@ -183,7 +166,6 @@ TableViewSectionsDelegate> self.tableView.rowHeight = UITableViewAutomaticDimension; self.tableView.estimatedRowHeight = 50; -#ifdef CROSS_SIGNING_AND_BACKUP_DEV if (self.mainSession.crypto.backup) { MXDeviceInfo *deviceInfo = [self.mainSession.crypto.deviceList storedDevice:self.mainSession.matrixRestClient.credentials.userId @@ -191,11 +173,15 @@ TableViewSectionsDelegate> if (deviceInfo) { + secureBackupSection = [[SettingsSecureBackupTableViewSection alloc] initWithRecoveryService:self.mainSession.crypto.recoveryService keyBackup:self.mainSession.crypto.backup userDevice:deviceInfo]; + secureBackupSection.delegate = self; + +#ifdef CROSS_SIGNING_AND_BACKUP_DEV keyBackupSection = [[SettingsKeyBackupTableViewSection alloc] initWithKeyBackup:self.mainSession.crypto.backup userDevice:deviceInfo]; keyBackupSection.delegate = self; +#endif } } -#endif // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { @@ -258,11 +244,8 @@ TableViewSectionsDelegate> kThemeServiceDidChangeThemeNotificationObserver = nil; } -#ifdef CROSS_SIGNING_AND_BACKUP_DEV keyBackupSetupCoordinatorBridgePresenter = nil; -#endif keyBackupRecoverCoordinatorBridgePresenter = nil; - } - (void)viewWillAppear:(BOOL)animated @@ -355,13 +338,19 @@ TableViewSectionsDelegate> Section *secureBackupSection = [Section sectionWithTag:SECTION_SECURE_BACKUP]; secureBackupSection.headerTitle = NSLocalizedStringFromTable(@"security_settings_secure_backup", @"Vector", nil); - - [secureBackupSection addRowsWithCount:[self numberOfRowsInSecureBackupSection]]; - - if (secureBackupSection.rows.count) - { - [sections addObject:secureBackupSection]; - } + + [secureBackupSection addRowsWithCount:self->secureBackupSection.numberOfRows]; + + [sections addObject:secureBackupSection]; + + // Cross-Signing + + Section *crossSigningSection = [Section sectionWithTag:SECTION_CROSSSIGNING]; + crossSigningSection.headerTitle = NSLocalizedStringFromTable(@"security_settings_crosssigning", @"Vector", nil); + + [crossSigningSection addRowsWithCount:[self numberOfRowsInCrossSigningSection]]; + + [sections addObject:crossSigningSection]; // Cryptograhpy @@ -385,15 +374,6 @@ TableViewSectionsDelegate> #ifdef CROSS_SIGNING_AND_BACKUP_DEV - // Cross-Signing - - Section *crossSigningSection = [Section sectionWithTag:SECTION_CROSSSIGNING]; - crossSigningSection.headerTitle = NSLocalizedStringFromTable(@"security_settings_crosssigning", @"Vector", nil); - - [crossSigningSection addRowsWithCount:[self numberOfRowsInCrossSigningSection]]; - - [sections addObject:crossSigningSection]; - // Keybackup Section *keybackupSection = [Section sectionWithTag:SECTION_KEYBACKUP]; @@ -582,8 +562,6 @@ TableViewSectionsDelegate> - (void)reloadData { - [self refreshSecureBackupSectionData]; - // Update table view sections and trigger a tableView reloadData [self updateSections]; } @@ -874,56 +852,6 @@ TableViewSectionsDelegate> #pragma mark - SSSS -- (void)refreshSecureBackupSectionData -{ - MXRecoveryService *recoveryService = self.mainSession.crypto.recoveryService; - NSMutableArray *secureBackupSectionState = [NSMutableArray new]; - if (recoveryService.hasRecovery) - { - if (RiotSettings.shared.settingsSecurityScreenShowRestoreBackup) - { - [secureBackupSectionState addObject:@(SECURE_BACKUP_RESTORE)]; - } - if (RiotSettings.shared.settingsSecurityScreenShowDeleteBackup) - { - [secureBackupSectionState addObject:@(SECURE_BACKUP_DELETE)]; - } - } - else - { - if (RiotSettings.shared.settingsSecurityScreenShowSetupBackup) - { - [secureBackupSectionState addObject:@(SECURE_BACKUP_SETUP)]; - } - } - - if (secureBackupSectionState.count) - { - [secureBackupSectionState addObject:@(SECURE_BACKUP_DESCRIPTION)]; - } - -#ifdef CROSS_SIGNING_AND_BACKUP_DEV - [secureBackupSectionState addObject:@(SECURE_BACKUP_INFO)]; -#endif - - self->secureBackupSectionState = secureBackupSectionState; -} - -- (NSUInteger)secureBackupSectionEnumForRow:(NSUInteger)row -{ - if (row < secureBackupSectionState.count) - { - return secureBackupSectionState[row].unsignedIntegerValue; - } - - return SECURE_BACKUP_DESCRIPTION; -} - -- (NSUInteger)numberOfRowsInSecureBackupSection -{ - return secureBackupSectionState.count; -} - - (NSString*)secureBackupInformation { NSString *secureBackupInformation; @@ -1060,7 +988,7 @@ TableViewSectionsDelegate> - (void)setupSecureBackup2 { - SecureBackupSetupCoordinatorBridgePresenter *secureBackupSetupCoordinatorBridgePresenter = [[SecureBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession]; + SecureBackupSetupCoordinatorBridgePresenter *secureBackupSetupCoordinatorBridgePresenter = [[SecureBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession allowOverwrite:YES]; secureBackupSetupCoordinatorBridgePresenter.delegate = self; [secureBackupSetupCoordinatorBridgePresenter presentFrom:self animated:YES]; @@ -1068,32 +996,6 @@ TableViewSectionsDelegate> self.secureBackupSetupCoordinatorBridgePresenter = secureBackupSetupCoordinatorBridgePresenter; } -- (void)restoreFromSecureBackup -{ - secretsRecoveryCoordinatorBridgePresenter = [[SecretsRecoveryCoordinatorBridgePresenter alloc] initWithSession:self.mainSession recoveryGoal:SecretsRecoveryGoalRestoreSecureBackup]; - - [secretsRecoveryCoordinatorBridgePresenter presentFrom:self animated:true]; - secretsRecoveryCoordinatorBridgePresenter.delegate = self; -} - -- (void)deleteSecureBackup -{ - MXRecoveryService *recoveryService = self.mainSession.crypto.recoveryService; - if (recoveryService) - { - [self startActivityIndicator]; - [recoveryService deleteRecoveryWithDeleteServicesBackups:YES success:^{ - [self stopActivityIndicator]; - [self reloadData]; - } failure:^(NSError * _Nonnull error) { - [self stopActivityIndicator]; - [self reloadData]; - - [[AppDelegate theDelegate] showErrorAsAlert:error]; - }]; - } -} - #pragma mark - Segues @@ -1228,6 +1130,17 @@ TableViewSectionsDelegate> return textViewCell; } +- (MXKTableViewCell*)descriptionCellForTableView:(UITableView*)tableView atIndexPath:(NSIndexPath *)indexPath +{ + MXKTableViewCell *cell = [self getDefaultTableViewCell:tableView]; + cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + cell.textLabel.numberOfLines = 0; + cell.contentView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + + return cell; +} + - (MXKTableViewCellWithButton *)buttonCellForTableView:(UITableView*)tableView atIndexPath:(NSIndexPath *)indexPath { MXKTableViewCellWithButton *cell = [self.tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier] forIndexPath:indexPath]; @@ -1360,71 +1273,14 @@ TableViewSectionsDelegate> } else if (section == SECTION_SECURE_BACKUP) { - switch ([self secureBackupSectionEnumForRow:row]) - { - case SECURE_BACKUP_DESCRIPTION: - { - cell = [self descriptionCellForTableView:tableView - withText:NSLocalizedStringFromTable(@"security_settings_secure_backup_description", @"Vector", nil)]; - break; - } -#ifdef CROSS_SIGNING_AND_BACKUP_DEV - case SECURE_BACKUP_INFO: - { - cell = [self descriptionCellForTableView:tableView - withText:self.secureBackupInformation]; - break; - } -#endif - case SECURE_BACKUP_SETUP: - { - MXKTableViewCellWithButton *buttonCell = [self buttonCellWithTitle:NSLocalizedStringFromTable(@"security_settings_secure_backup_setup", @"Vector", nil) - action:@selector(setupSecureBackup) - forTableView:tableView - atIndexPath:indexPath]; - - cell = buttonCell; - break; - } - case SECURE_BACKUP_RESTORE: - { - MXKTableViewCellWithButton *buttonCell = [self buttonCellWithTitle:NSLocalizedStringFromTable(@"security_settings_secure_backup_synchronise", @"Vector", nil) - action:@selector(restoreFromSecureBackup) - forTableView:tableView - atIndexPath:indexPath]; - - cell = buttonCell; - break; - } - case SECURE_BACKUP_DELETE: - { - MXKTableViewCellWithButton *buttonCell = [self buttonCellWithTitle:NSLocalizedStringFromTable(@"security_settings_secure_backup_delete", @"Vector", nil) - action:@selector(deleteSecureBackup) - forTableView:tableView - atIndexPath:indexPath]; - buttonCell.mxkButton.tintColor = ThemeService.shared.theme.warningColor; - - cell = buttonCell; - break; - } - - case SECURE_BACKUP_MANAGE_MANUALLY: - { - MXKTableViewCellWithTextView *textCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; - textCell.mxkTextView.text = @"Advanced: Manually manage keys"; // TODO - [textCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; - - cell = textCell; - break; - } - } - + cell = [secureBackupSection cellForRowAtRow:row]; } #ifdef CROSS_SIGNING_AND_BACKUP_DEV else if (section == SECTION_KEYBACKUP) { cell = [keyBackupSection cellForRowAtRow:row]; } +#endif else if (section == SECTION_CROSSSIGNING) { switch (row) @@ -1444,7 +1300,6 @@ TableViewSectionsDelegate> break; } } -#endif else if (section == SECTION_CRYPTOGRAPHY) { switch (row) @@ -1739,6 +1594,155 @@ TableViewSectionsDelegate> [self.setPinCoordinatorBridgePresenter presentFrom:self animated:YES]; } + +#pragma mark - SettingsSecureBackupTableViewSectionDelegate + +- (void)settingsSecureBackupTableViewSectionDidUpdate:(SettingsSecureBackupTableViewSection *)settingsSecureBackupTableViewSection +{ + [self reloadData]; +} + +- (MXKTableViewCellWithTextView *)settingsSecureBackupTableViewSection:(SettingsSecureBackupTableViewSection *)settingsSecureBackupTableViewSection textCellForRow:(NSInteger)textCellForRow +{ + MXKTableViewCellWithTextView *cell; + + NSIndexPath *indexPath = [self.tableViewSections exactIndexPathForRowTag:textCellForRow sectionTag:SECTION_SECURE_BACKUP]; + + if (indexPath) + { + cell = [self textViewCellForTableView:self.tableView atIndexPath:indexPath]; + } + + return cell; +} + +- (MXKTableViewCell *)settingsSecureBackupTableViewSection:(SettingsSecureBackupTableViewSection *)settingsSecureBackupTableViewSection descriptionCellForRow:(NSInteger)textCellForRow +{ + MXKTableViewCell *cell; + + NSIndexPath *indexPath = [self.tableViewSections exactIndexPathForRowTag:textCellForRow sectionTag:SECTION_SECURE_BACKUP]; + + if (indexPath) + { + cell = [self descriptionCellForTableView:self.tableView atIndexPath:indexPath]; + } + + return cell; +} + +- (MXKTableViewCellWithButton *)settingsSecureBackupTableViewSection:(SettingsSecureBackupTableViewSection *)settingsSecureBackupTableViewSection buttonCellForRow:(NSInteger)buttonCellForRow +{ + MXKTableViewCellWithButton *cell; + + NSIndexPath *indexPath = [self.tableViewSections exactIndexPathForRowTag:buttonCellForRow sectionTag:SECTION_SECURE_BACKUP]; + + if (indexPath) + { + cell = [self buttonCellForTableView:self.tableView atIndexPath:indexPath]; + } + + return cell; +} + +- (void)settingsSecureBackupTableViewSectionShowSecureBackupReset:(SettingsSecureBackupTableViewSection *)settingsSecureBackupTableViewSection +{ + [self setupSecureBackup]; +} + +- (void)settingsSecureBackupTableViewSectionShowKeyBackupCreate:(SettingsSecureBackupTableViewSection *)settingsSecureBackupTableViewSection +{ + [self showKeyBackupSetupFromSignOutFlow:NO]; +} + +- (void)settingsSecureBackupTableViewSection:(SettingsSecureBackupTableViewSection *)settingsSecureBackupTableViewSection showKeyBackupRecover:(MXKeyBackupVersion *)keyBackupVersion +{ + self.currentkeyBackupVersion = keyBackupVersion; + + // If key backup key is stored in SSSS, ask for secrets recovery before restoring key backup. + if (!self.mainSession.crypto.backup.hasPrivateKeyInCryptoStore + && self.mainSession.crypto.recoveryService.hasRecovery + && [self.mainSession.crypto.recoveryService hasSecretWithSecretId:MXSecretId.keyBackup]) + { + [self showSecretsRecovery]; + } + else + { + [self showKeyBackupRecover:keyBackupVersion fromViewController:self]; + } +} + +- (void)settingsSecureBackupTableViewSection:(SettingsSecureBackupTableViewSection *)settingsSecureBackupTableViewSection showKeyBackupDeleteConfirm:(MXKeyBackupVersion *)keyBackupVersion +{ + MXWeakify(self); + [currentAlert dismissViewControllerAnimated:NO completion:nil]; + + currentAlert = + [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"settings_key_backup_delete_confirmation_prompt_title", @"Vector", nil) + message:NSLocalizedStringFromTable(@"settings_key_backup_delete_confirmation_prompt_msg", @"Vector", nil) + preferredStyle:UIAlertControllerStyleAlert]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + }]]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"settings_key_backup_button_delete", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + + [self->secureBackupSection deleteKeyBackupWithKeyBackupVersion:keyBackupVersion]; + }]]; + + [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCDeleteKeyBackup"]; + [self presentViewController:currentAlert animated:YES completion:nil]; +} + +- (void)settingsSecureBackupTableViewSection:(SettingsSecureBackupTableViewSection *)settingsSecureBackupTableViewSection showActivityIndicator:(BOOL)show +{ + if (show) + { + [self startActivityIndicator]; + } + else + { + [self stopActivityIndicator]; + } +} + +- (void)settingsSecureBackupTableViewSection:(SettingsSecureBackupTableViewSection *)settingsSecureBackupTableViewSection showError:(NSError *)error +{ + [[AppDelegate theDelegate] showErrorAsAlert:error]; +} + +#pragma mark - KeyBackupRecoverCoordinatorBridgePresenter + +- (void)showKeyBackupSetupFromSignOutFlow:(BOOL)showFromSignOutFlow +{ + keyBackupSetupCoordinatorBridgePresenter = [[KeyBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession]; + + [keyBackupSetupCoordinatorBridgePresenter presentFrom:self + isStartedFromSignOut:showFromSignOutFlow + animated:true]; + + keyBackupSetupCoordinatorBridgePresenter.delegate = self; +} + +- (void)keyBackupSetupCoordinatorBridgePresenterDelegateDidCancel:(KeyBackupSetupCoordinatorBridgePresenter *)bridgePresenter { + [keyBackupSetupCoordinatorBridgePresenter dismissWithAnimated:true]; + keyBackupSetupCoordinatorBridgePresenter = nil; +} + +- (void)keyBackupSetupCoordinatorBridgePresenterDelegateDidSetupRecoveryKey:(KeyBackupSetupCoordinatorBridgePresenter *)bridgePresenter { + [keyBackupSetupCoordinatorBridgePresenter dismissWithAnimated:true]; + keyBackupSetupCoordinatorBridgePresenter = nil; + + [secureBackupSection reload]; +} + #pragma mark - SettingsKeyBackupTableViewSectionDelegate #ifdef CROSS_SIGNING_AND_BACKUP_DEV - (void)settingsKeyBackupTableViewSectionDidUpdate:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection @@ -1768,7 +1772,7 @@ TableViewSectionsDelegate> if (indexPath) { - [self buttonCellForTableView:self.tableView atIndexPath:indexPath]; + cell = [self buttonCellForTableView:self.tableView atIndexPath:indexPath]; } return cell; @@ -1843,31 +1847,6 @@ TableViewSectionsDelegate> [[AppDelegate theDelegate] showErrorAsAlert:error]; } -#pragma mark - KeyBackupRecoverCoordinatorBridgePresenter - -- (void)showKeyBackupSetupFromSignOutFlow:(BOOL)showFromSignOutFlow -{ - keyBackupSetupCoordinatorBridgePresenter = [[KeyBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession]; - - [keyBackupSetupCoordinatorBridgePresenter presentFrom:self - isStartedFromSignOut:showFromSignOutFlow - animated:true]; - - keyBackupSetupCoordinatorBridgePresenter.delegate = self; -} - -- (void)keyBackupSetupCoordinatorBridgePresenterDelegateDidCancel:(KeyBackupSetupCoordinatorBridgePresenter *)bridgePresenter { - [keyBackupSetupCoordinatorBridgePresenter dismissWithAnimated:true]; - keyBackupSetupCoordinatorBridgePresenter = nil; -} - -- (void)keyBackupSetupCoordinatorBridgePresenterDelegateDidSetupRecoveryKey:(KeyBackupSetupCoordinatorBridgePresenter *)bridgePresenter { - [keyBackupSetupCoordinatorBridgePresenter dismissWithAnimated:true]; - keyBackupSetupCoordinatorBridgePresenter = nil; - - [keyBackupSection reload]; -} - #endif #pragma mark - KeyBackupRecoverCoordinatorBridgePresenter @@ -1904,7 +1883,7 @@ TableViewSectionsDelegate> - (void)showSecretsRecovery { - secretsRecoveryCoordinatorBridgePresenter = [[SecretsRecoveryCoordinatorBridgePresenter alloc] initWithSession:self.mainSession recoveryGoal:SecretsRecoveryGoalKeyBackup]; + secretsRecoveryCoordinatorBridgePresenter = [[SecretsRecoveryCoordinatorBridgePresenter alloc] initWithSession:self.mainSession recoveryGoal:SecretsRecoveryGoalBridgeKeyBackup]; [secretsRecoveryCoordinatorBridgePresenter presentFrom:self animated:true]; secretsRecoveryCoordinatorBridgePresenter.delegate = self; @@ -1920,7 +1899,7 @@ TableViewSectionsDelegate> { UIViewController *presentedViewController = [coordinatorBridgePresenter toPresentable]; - if (coordinatorBridgePresenter.recoveryGoal == SecretsRecoveryGoalKeyBackup) + if (coordinatorBridgePresenter.recoveryGoal == SecretsRecoveryGoalBridgeKeyBackup) { // Go to the true key backup recovery screen if ([presentedViewController isKindOfClass:UINavigationController.class]) diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 7381bfb2a..a6dad221f 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -4023,7 +4023,7 @@ TableViewSectionsDelegate> - (void)setupSecureBackup2 { - SecureBackupSetupCoordinatorBridgePresenter *secureBackupSetupCoordinatorBridgePresenter = [[SecureBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession]; + SecureBackupSetupCoordinatorBridgePresenter *secureBackupSetupCoordinatorBridgePresenter = [[SecureBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession allowOverwrite:YES]; secureBackupSetupCoordinatorBridgePresenter.delegate = self; [secureBackupSetupCoordinatorBridgePresenter presentFrom:self animated:YES];