SafeViewController.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2018-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. import UIKit
  21. @objc class SafeViewController: IDCreationPageViewController, IntroQuestionDelegate {
  22. @IBOutlet weak var titleLabel: UILabel!
  23. @IBOutlet weak var descriptionLabel: UILabel!
  24. @IBOutlet weak var passwordField: SetupTextField!
  25. @IBOutlet weak var passwordAgainField: SetupTextField!
  26. @IBOutlet weak var advancedOptionsButton: UIButton!
  27. var didShowAlert: Bool = false
  28. var didShowConfirm: Bool = false
  29. var customServer: String?
  30. var server: String?
  31. var serverUsername: String?
  32. var serverPassword: String?
  33. var maxBackupBytes: Int?
  34. var retentionDays: Int?
  35. var passwordAgainOffset: CGFloat = 0
  36. var mdmSetup: MDMSetup
  37. required init?(coder aDecoder: NSCoder) {
  38. self.mdmSetup = MDMSetup(setup: true)
  39. super.init(coder: aDecoder)
  40. }
  41. override func viewDidLoad() {
  42. super.viewDidLoad()
  43. self.hideKeyboardWhenTappedAround()
  44. self.titleLabel.text = NSLocalizedString("safe_setup_backup_title", comment: "")
  45. self.descriptionLabel.text = NSLocalizedString("safe_setup_backup_description", comment: "")
  46. self.passwordField.delegate = self
  47. self.passwordField.placeholder = NSLocalizedString("Password", comment: "")
  48. self.passwordAgainField.delegate = self
  49. self.passwordAgainField.placeholder = NSLocalizedString("password_again", comment: "")
  50. self.advancedOptionsButton.setTitle(NSLocalizedString("safe_advanced_options", comment: ""), for: .normal)
  51. self.advancedOptionsButton.isHidden = self.mdmSetup.isSafeBackupForce()
  52. self.moreView.mainView = self.mainContentView
  53. self.moreView.moreButtonTitle = NSLocalizedString("more_information", comment: "")
  54. self.moreView.moreMessageText = NSLocalizedString("safe_enable_explain", comment: "")
  55. passwordAgainOffset = passwordAgainField.frame.origin.y
  56. }
  57. override func viewDidAppear(_ animated: Bool) {
  58. super.viewDidAppear(animated)
  59. registerForKeyboardNotifications()
  60. }
  61. override func viewDidDisappear(_ animated: Bool) {
  62. super.viewDidDisappear(animated)
  63. unregisterForKeyboardNotifications()
  64. }
  65. //MARK: - Navigation
  66. override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  67. guard let safeServerViewController = segue.destination as? SafeServerViewController else {
  68. return
  69. }
  70. self.mainContentView.isHidden = true
  71. self.moreView.isHidden = true
  72. self.containerDelegate.hideControls(true)
  73. safeServerViewController.customServer = self.customServer
  74. safeServerViewController.server = self.server
  75. safeServerViewController.serverUsername = self.serverUsername
  76. safeServerViewController.serverPassword = self.serverPassword
  77. }
  78. private func validatedPassword() -> String? {
  79. if let password = self.passwordField.text {
  80. if let regExPattern = self.mdmSetup.safePasswordPattern() {
  81. do {
  82. if try !SafeManager.isPasswordPatternValid(password: password, regExPattern: regExPattern) {
  83. if let message = self.mdmSetup.safePasswordMessage() {
  84. let alert = IntroQuestionViewHelper(parent: self, onAnswer: nil)
  85. alert.showAlert(message, title: BundleUtil.localizedString(forKey: "Password"))
  86. } else {
  87. let alert = IntroQuestionViewHelper(parent: self, onAnswer: nil)
  88. alert.showAlert(BundleUtil.localizedString(forKey: "password_bad_guidelines"), title: BundleUtil.localizedString(forKey: "Password"))
  89. }
  90. return nil
  91. }
  92. }
  93. catch {
  94. ValidationLogger.shared()?.logString("Threema Safe: Can't check safe password because regex is invalid")
  95. let alert = IntroQuestionViewHelper(parent: self, onAnswer: nil)
  96. alert.showAlert(BundleUtil.localizedString(forKey: "password_bad_regex"), title: BundleUtil.localizedString(forKey: "Password"))
  97. return nil
  98. }
  99. } else {
  100. if password.count < kMinimumPasswordLength {
  101. let alert = IntroQuestionViewHelper(parent: self, onAnswer: nil)
  102. alert.showAlert(BundleUtil.localizedString(forKey: "password_too_short_message"), title: BundleUtil.localizedString(forKey: "password_too_short_title"))
  103. return nil
  104. }
  105. }
  106. if let passwordAgain = self.passwordAgainField.text,
  107. !password.elementsEqual(passwordAgain) {
  108. let alert = IntroQuestionViewHelper(parent: self, onAnswer: nil)
  109. alert.showAlert(BundleUtil.localizedString(forKey: "password_mismatch_message"), title: BundleUtil.localizedString(forKey: "password_mismatch_title"))
  110. return nil
  111. } else {
  112. return password
  113. }
  114. }
  115. return nil
  116. }
  117. override func isInputValid() -> Bool {
  118. if self.moreView.isShown() {
  119. return false
  120. }
  121. if let password = self.passwordField.text,
  122. password.count <= 0 && !self.mdmSetup.isSafeBackupForce() && self.didShowAlert {
  123. return true
  124. } else if let password = self.passwordField.text,
  125. password.count > 0 && self.didShowConfirm {
  126. return true
  127. } else if let password = self.passwordField.text,
  128. password.count > 0 {
  129. //store custom safe backup server
  130. let safeConfigManager = SafeConfigManager()
  131. if let server = self.server {
  132. safeConfigManager.setServer(server)
  133. safeConfigManager.setCustomServer(self.customServer)
  134. safeConfigManager.setMaxBackupBytes(self.maxBackupBytes)
  135. safeConfigManager.setRetentionDays(self.retentionDays)
  136. } else {
  137. safeConfigManager.setServer(nil)
  138. safeConfigManager.setCustomServer(nil)
  139. safeConfigManager.setMaxBackupBytes(nil)
  140. safeConfigManager.setRetentionDays(nil)
  141. }
  142. if let validPassword = self.validatedPassword() {
  143. let safeConfigManager = SafeConfigManager()
  144. let safeStore = SafeStore(safeConfigManager: safeConfigManager, serverApiConnector: ServerAPIConnector())
  145. let safeManager = SafeManager(safeConfigManager: safeConfigManager, safeStore: safeStore, safeApiService: SafeApiService())
  146. if safeManager.isPasswordBad(password:validPassword) {
  147. let alert = IntroQuestionViewHelper(parent: self) { (sender, answer) in
  148. if answer == .yes {
  149. self.didShowConfirm = true
  150. //store password temp. just in memory for setup completion process
  151. MyIdentityStore.shared()?.tempSafePassword = self.passwordField.text
  152. self.containerDelegate.pageLeft()
  153. } else {
  154. self.passwordField.becomeFirstResponder()
  155. }
  156. }
  157. alert.showConfirm(BundleUtil.localizedString(forKey: "password_bad_explain"), noButtonLabel: BundleUtil.localizedString(forKey: "try_again"), yesButtonLabel: BundleUtil.localizedString(forKey: "continue_anyway"))
  158. return false
  159. }
  160. //store password temp. just in memory for setup completion process
  161. MyIdentityStore.shared()?.tempSafePassword = validPassword
  162. return true;
  163. }
  164. return false
  165. } else if !self.mdmSetup.isSafeBackupForce() {
  166. let alert = IntroQuestionViewHelper(parent: self) { (sender, answer) in
  167. if answer == .yes {
  168. self.didShowAlert = true;
  169. self.containerDelegate.pageLeft()
  170. } else {
  171. self.passwordField.becomeFirstResponder()
  172. }
  173. }
  174. alert.showConfirm(BundleUtil.localizedString(forKey: "safe_disable_confirm"))
  175. }
  176. return false
  177. }
  178. }
  179. extension SafeViewController {
  180. // private functions
  181. private func registerForKeyboardNotifications() {
  182. NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
  183. NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
  184. }
  185. private func unregisterForKeyboardNotifications() {
  186. NotificationCenter.default.removeObserver(self)
  187. }
  188. @objc private func keyboardWillShow(notification: Notification) {
  189. if self.passwordField.isFirstResponder {
  190. if let info = notification.userInfo {
  191. let keyboardScreenEndFrame = (info[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
  192. let keyboardRectConverted = self.view.convert(keyboardScreenEndFrame, from: view.window)
  193. let diff = keyboardRectConverted.minY - passwordField.frame.midY - 32.0
  194. if diff < 21.0 {
  195. passwordField.isHidden = false
  196. passwordAgainField.isHidden = true
  197. }
  198. }
  199. }
  200. else if self.passwordAgainField.isFirstResponder {
  201. if let info = notification.userInfo {
  202. let keyboardScreenEndFrame = (info[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
  203. let keyboardRectConverted = self.view.convert(keyboardScreenEndFrame, from: view.window)
  204. let diff = keyboardRectConverted.minY - passwordAgainField.frame.midY - 32.0
  205. if diff < 0.0 {
  206. passwordField.isHidden = true
  207. passwordAgainField.isHidden = false
  208. var animationDuration = TimeInterval()
  209. let options = Utils.animationOptions(for: notification, animationDuration: &animationDuration)
  210. UIView.animate(withDuration: animationDuration, delay: 0, options: options, animations: {
  211. self.passwordAgainField.frame = RectUtil.offsetRect(self.passwordAgainField.frame, byX: 0.0, byY: diff)
  212. }) { (finished) in
  213. }
  214. }
  215. }
  216. }
  217. }
  218. @objc private func keyboardWillHide(notification: Notification) {
  219. var animationDuration = TimeInterval()
  220. let options = Utils.animationOptions(for: notification, animationDuration: &animationDuration)
  221. UIView.animate(withDuration: animationDuration, delay: 0, options: options, animations: {
  222. self.passwordAgainField.frame = RectUtil.setYPositionOf(self.passwordAgainField.frame, y: self.passwordAgainOffset)
  223. }) { (finished) in
  224. if self.passwordAgainField.isFirstResponder == false {
  225. self.passwordField.isHidden = false
  226. self.passwordAgainField.isHidden = false
  227. } else {
  228. if let info = notification.userInfo {
  229. let keyboardScreenEndFrame = (info[UIResponder.keyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
  230. let keyboardRectConverted = self.view.convert(keyboardScreenEndFrame, from: self.view.window)
  231. let diff = keyboardRectConverted.minY - self.passwordAgainField.frame.midY - 32.0
  232. self.passwordField.isHidden = diff <= 0.0 ? true : false
  233. } else {
  234. self.passwordField.isHidden = false
  235. }
  236. self.passwordAgainField.isHidden = false
  237. }
  238. }
  239. }
  240. }
  241. extension SafeViewController : SetupTextFieldDelegate {
  242. func editingChangedTextField(_ sender: SetupTextField, forEvent event: UIEvent) {
  243. self.didShowConfirm = false
  244. }
  245. func primaryActionTriggered(_ sender: SetupTextField, forEvent event: UIEvent) {
  246. if sender == self.passwordField {
  247. sender.resignFirstResponder()
  248. passwordAgainField.becomeFirstResponder()
  249. } else if sender == self.passwordAgainField {
  250. sender.resignFirstResponder()
  251. }
  252. }
  253. }
  254. extension SafeViewController {
  255. @IBAction func cancelSafeServer(_ segue: UIStoryboardSegue) {
  256. self.mainContentView.isHidden = false
  257. self.moreView.isHidden = false
  258. self.containerDelegate.hideControls(false)
  259. }
  260. @IBAction func okSafeServer(_ segue: UIStoryboardSegue) {
  261. guard let safeServerViewController = segue.source as? SafeServerViewController else {
  262. return
  263. }
  264. self.didShowConfirm = false
  265. self.mainContentView.isHidden = false
  266. self.moreView.isHidden = false
  267. self.containerDelegate.hideControls(false)
  268. self.customServer = safeServerViewController.customServer
  269. self.server = safeServerViewController.server
  270. self.serverUsername = safeServerViewController.serverUsername
  271. self.serverPassword = safeServerViewController.serverPassword
  272. self.maxBackupBytes = safeServerViewController.maxBackupBytes
  273. self.retentionDays = safeServerViewController.retentionDays
  274. }
  275. }
  276. extension UIViewController {
  277. func hideKeyboardWhenTappedAround() {
  278. let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
  279. tap.cancelsTouchesInView = false
  280. view.addGestureRecognizer(tap)
  281. }
  282. @objc func dismissKeyboard() {
  283. view.resignFirstResponder()
  284. }
  285. }