SafeSetupPasswordViewController.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  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. class SafeSetupPasswordViewController: ThemedTableViewController {
  22. @IBOutlet weak var passwordField: UITextField!
  23. @IBOutlet weak var passwordAgainField: UITextField!
  24. @IBOutlet weak var serverSwitchLabel: UILabel!
  25. @IBOutlet weak var serverSwitch: UISwitch!
  26. @IBOutlet weak var serverField: UITextField!
  27. @IBOutlet weak var serverUserNameField: UITextField!
  28. @IBOutlet weak var serverPasswordField: UITextField!
  29. private var safeStore: SafeStore
  30. private var safeManager: SafeManager
  31. private var mdmSetup: MDMSetup
  32. var customServer: String?
  33. var server: String?
  34. var maxBackupBytes: Int?
  35. var retentionDays: Int?
  36. var isOpenedFromIntro: Bool = false
  37. @objc var isForcedBackup: Bool = false
  38. required init?(coder aDecoder: NSCoder) {
  39. let safeConfigManager = SafeConfigManager()
  40. self.safeStore = SafeStore(safeConfigManager: safeConfigManager, serverApiConnector: ServerAPIConnector())
  41. self.safeManager = SafeManager(safeConfigManager: safeConfigManager, safeStore: self.safeStore, safeApiService: SafeApiService())
  42. self.mdmSetup = MDMSetup(setup: false)
  43. super.init(coder: aDecoder)
  44. }
  45. override func viewDidLoad() {
  46. super.viewDidLoad()
  47. self.hideKeyboardWhenTappedAround()
  48. self.passwordField.placeholder = BundleUtil.localizedString(forKey: "Password")
  49. self.passwordAgainField.placeholder = BundleUtil.localizedString(forKey: "password_again")
  50. self.serverSwitchLabel.text = BundleUtil.localizedString(forKey: "safe_use_default_server")
  51. self.serverField.placeholder = "https://server.example.com"
  52. self.serverUserNameField.placeholder = BundleUtil.localizedString(forKey: "username")
  53. self.serverPasswordField.placeholder = BundleUtil.localizedString(forKey: "Password")
  54. if self.safeManager.isActivated || self.mdmSetup.isSafeBackupServerPreset() || isForcedBackup {
  55. // is already activated means is in change password mode or server is given by MDM
  56. // hide server config elements
  57. self.serverSwitchLabel.isHidden = true
  58. self.serverSwitch.isHidden = true
  59. self.serverField.isHidden = true
  60. }
  61. if self.safeManager.isActivated {
  62. // is in change paasword mode, use existing server
  63. let safeConfigManager = SafeConfigManager()
  64. self.customServer = safeConfigManager.getCustomServer()
  65. self.server = safeConfigManager.getServer()
  66. self.maxBackupBytes = safeConfigManager.getMaxBackupBytes()
  67. self.retentionDays = safeConfigManager.getRetentionDays()
  68. }
  69. if isForcedBackup == false {
  70. self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(self.cancel))
  71. }
  72. self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(self.done))
  73. }
  74. //MARK: - Navigation
  75. @objc private func cancel() {
  76. if self.view.isUserInteractionEnabled {
  77. self.dismiss(animated: true, completion: nil)
  78. }
  79. }
  80. @objc private func done() {
  81. // is already activated means is in change password mode and server validation is not necessary
  82. if (!self.safeManager.isActivated && validateServer()) || self.safeManager.isActivated {
  83. if let password = validatedPassword() {
  84. if self.safeManager.isPasswordBad(password: password) {
  85. UIAlertTemplate.showConfirm(owner: self, popOverSource: self.passwordField, title: BundleUtil.localizedString(forKey: "password_bad"), message: BundleUtil.localizedString(forKey: "password_bad_explain"), titleOk: BundleUtil.localizedString(forKey: "continue_anyway"), actionOk: { (action) in
  86. self.activate(password: password)
  87. }, titleCancel: BundleUtil.localizedString(forKey:"try_again"), actionCancel: { (action) in
  88. self.passwordField.becomeFirstResponder()
  89. })
  90. } else {
  91. self.activate(password: password)
  92. }
  93. }
  94. }
  95. }
  96. private func activate(password: String) {
  97. self.view.isUserInteractionEnabled = false
  98. MBProgressHUD.showAdded(to: self.view, animated: true)
  99. let queue = DispatchQueue.global(qos: .userInitiated)
  100. queue.async {
  101. do {
  102. // is already activated means is in change password mode, deactivate safe and activate with new password
  103. if self.safeManager.isActivated {
  104. self.safeManager.deactivate()
  105. }
  106. try self.safeManager.activate(identity: MyIdentityStore.shared().identity, password: password, customServer: self.customServer, server: self.server, maxBackupBytes: self.maxBackupBytes != nil ? NSNumber(integerLiteral: self.maxBackupBytes!) : nil, retentionDays: self.retentionDays != nil ? NSNumber(integerLiteral:self.retentionDays!) : nil)
  107. DispatchQueue.main.async {
  108. if self.isForcedBackup {
  109. self.dismiss(animated: true, completion: nil)
  110. } else {
  111. self.performSegue(withIdentifier: self.isOpenedFromIntro ? "SafeIntroPasswordDone" : "SafeSetupPasswordDone", sender: self)
  112. }
  113. }
  114. } catch let error {
  115. DispatchQueue.main.async {
  116. // isFordedBackup maybe we dismiss the view and show it again at the next app start
  117. UIAlertTemplate.showAlert(owner: self, title: BundleUtil.localizedString(forKey: "safe_error_preparing"), message: error.localizedDescription)
  118. }
  119. }
  120. DispatchQueue.main.async {
  121. MBProgressHUD.hide(for: self.view, animated: true)
  122. self.view.isUserInteractionEnabled = true
  123. }
  124. }
  125. }
  126. private func validatedPassword() -> String? {
  127. if let password = self.passwordField.text {
  128. if let regExPattern = self.mdmSetup.safePasswordPattern() {
  129. do {
  130. if try !SafeManager.isPasswordPatternValid(password: password, regExPattern: regExPattern) {
  131. if let message = self.mdmSetup.safePasswordMessage() {
  132. UIAlertTemplate.showAlert(owner: self, title: BundleUtil.localizedString(forKey: "Password"), message: message)
  133. } else {
  134. UIAlertTemplate.showAlert(owner: self, title: BundleUtil.localizedString(forKey: "Password"), message: BundleUtil.localizedString(forKey: "password_bad_guidelines"))
  135. }
  136. return nil
  137. }
  138. }
  139. catch {
  140. ValidationLogger.shared()?.logString("Threema Safe: Can't check safe password because regex is invalid")
  141. UIAlertTemplate.showAlert(owner: self, title: BundleUtil.localizedString(forKey: "Password"), message: BundleUtil.localizedString(forKey: "password_bad_regex"))
  142. return nil
  143. }
  144. } else {
  145. if password.count < kMinimumPasswordLength {
  146. UIAlertTemplate.showAlert(owner: self, title: BundleUtil.localizedString(forKey: "password_too_short_title"), message: BundleUtil.localizedString(forKey: "password_too_short_message"))
  147. return nil
  148. }
  149. }
  150. if let passwordAgain = self.passwordAgainField.text,
  151. !password.elementsEqual(passwordAgain) {
  152. UIAlertTemplate.showAlert(owner: self, title: BundleUtil.localizedString(forKey: "password_mismatch_title"), message: BundleUtil.localizedString(forKey: "password_mismatch_message"))
  153. return nil
  154. } else {
  155. return password
  156. }
  157. }
  158. return nil
  159. }
  160. private func validateServer() -> Bool {
  161. if self.mdmSetup.isSafeBackupServerPreset() {
  162. // server is given by MDM
  163. let mdmSetup = MDMSetup(setup: false)
  164. self.customServer = mdmSetup?.safeServerUrl()
  165. self.server = self.safeStore.composeSafeServerAuth(server: mdmSetup?.safeServerUrl(), user: mdmSetup?.safeServerUsername(), password: mdmSetup?.safeServerPassword())?.absoluteString
  166. }
  167. else if self.serverSwitch.isOn {
  168. // server is standard (Threema)
  169. self.customServer = nil
  170. self.server = nil
  171. self.maxBackupBytes = nil
  172. self.retentionDays = nil
  173. } else {
  174. // server is WebDAV
  175. let safeConfigManager = SafeConfigManager()
  176. let safeStore = SafeStore(safeConfigManager: SafeConfigManager(), serverApiConnector: ServerAPIConnector())
  177. if let customServer = self.serverField.text,
  178. let customServerUrl = safeStore.composeSafeServerAuth(server: customServer, user: self.serverUserNameField.text, password: self.serverPasswordField.text) {
  179. let safeManager = SafeManager(safeConfigManager: safeConfigManager, safeStore: safeStore, safeApiService: SafeApiService())
  180. let result = safeManager.testServer(serverUrl: customServerUrl)
  181. if let errorMessage = result.errorMessage {
  182. UIAlertTemplate.showAlert(owner: self, title: BundleUtil.localizedString(forKey: "safe_test_server"), message: errorMessage)
  183. return false
  184. } else {
  185. self.customServer = customServer
  186. self.server = customServerUrl.absoluteString
  187. self.maxBackupBytes = result.maxBackupBytes
  188. self.retentionDays = result.retentionDays
  189. }
  190. } else {
  191. UIAlertTemplate.showAlert(owner: self, title: BundleUtil.localizedString(forKey: "safe_test_server"), message: BundleUtil.localizedString(forKey: "safe_test_server_invalid_url"))
  192. return false
  193. }
  194. }
  195. return true
  196. }
  197. // MARK: - Table view data source
  198. override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  199. if self.safeManager.isActivated || self.mdmSetup.isSafeBackupServerPreset() || isForcedBackup {
  200. return indexPath.section != 0 ? 0.0 : UITableView.automaticDimension
  201. } else {
  202. switch indexPath.section {
  203. case 1:
  204. if indexPath.row != 0 {
  205. return self.serverSwitch.isOn ? 0.0 : UITableView.automaticDimension
  206. }
  207. case 2:
  208. return self.serverSwitch.isOn ? 0.0 : UITableView.automaticDimension
  209. default:
  210. return UITableView.automaticDimension
  211. }
  212. }
  213. return UITableView.automaticDimension
  214. }
  215. override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
  216. return UITableView.automaticDimension
  217. }
  218. override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
  219. return UITableView.automaticDimension
  220. }
  221. override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  222. switch section {
  223. case 0:
  224. return BundleUtil.localizedString(forKey: "safe_configure_choose_password_title")
  225. case 1:
  226. return !self.safeManager.isActivated && !self.mdmSetup.isSafeBackupServerPreset() && !isForcedBackup ? BundleUtil.localizedString(forKey: "safe_server_name") : nil
  227. case 2:
  228. if self.safeManager.isActivated {
  229. return nil
  230. } else {
  231. return !serverSwitch.isOn ? BundleUtil.localizedString(forKey: "safe_server_authentication") : nil
  232. }
  233. default:
  234. return nil
  235. }
  236. }
  237. override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
  238. switch section {
  239. case 0:
  240. if isForcedBackup {
  241. return BundleUtil.localizedString(forKey: "safe_configure_choose_password_mdm") + "\n\n" + BundleUtil.localizedString(forKey: "safe_configure_choose_password")
  242. } else {
  243. return BundleUtil.localizedString(forKey: "safe_configure_choose_password")
  244. }
  245. case 1:
  246. return !self.safeManager.isActivated && !self.mdmSetup.isSafeBackupServerPreset() && !isForcedBackup ? BundleUtil.localizedString(forKey: "safe_configure_server_explain") : nil
  247. case 2:
  248. return nil
  249. default:
  250. return nil
  251. }
  252. }
  253. }
  254. extension SafeSetupPasswordViewController {
  255. @IBAction func primaryActionTriggered(_ sender: UITextField, forEvent event: UIEvent) {
  256. if sender == self.passwordField {
  257. self.passwordAgainField.becomeFirstResponder()
  258. }
  259. else if sender == self.passwordAgainField && !self.serverSwitch.isOn {
  260. self.serverField.becomeFirstResponder()
  261. }
  262. else if sender == self.passwordAgainField && self.serverSwitch.isOn {
  263. done()
  264. }
  265. else if sender == self.serverField {
  266. self.serverUserNameField.becomeFirstResponder()
  267. }
  268. else if sender == self.serverUserNameField {
  269. self.serverPasswordField.becomeFirstResponder()
  270. }
  271. else if sender == self.serverPasswordField {
  272. done()
  273. }
  274. }
  275. @IBAction func changedServerSwitch(_ sender: UISwitch) {
  276. self.serverField.isEnabled = !sender.isOn
  277. self.tableView.reloadData()
  278. if let currentText = self.serverField.text, currentText.count == 0 {
  279. self.serverField.text = "https://"
  280. }
  281. self.serverField.becomeFirstResponder()
  282. }
  283. }