pEpForiOS/UI/Settings/Setting/AccountSettings/ViewModel/AccountSettingsViewModel.swift
author Dirk Zimmermann <dz@pep.security>
Thu, 25 Apr 2019 16:25:40 +0200
branchIOS-1542
changeset 8376 f517a983ae7c
parent 8375 81ef9257d9b5
child 8381 907e287ead66
permissions -rw-r--r--
IOS-1542 When OAUTH2, pass it on to verifier.
andreas@3697
     1
//
andreas@3697
     2
//  AccountSettingsViewModel.swift
andreas@3697
     3
//  pEpForiOS
andreas@3697
     4
//
andreas@3697
     5
//  Created by Xavier Algarra on 13/06/2017.
andreas@3697
     6
//  Copyright © 2017 p≡p Security S.A. All rights reserved.
andreas@3697
     7
//
andreas@3697
     8
andreas@3697
     9
import Foundation
andreas@3697
    10
import MessageModel
xavier@7644
    11
import pEpIOSToolbox
andreas@3697
    12
dz@8358
    13
import PantomimeFramework
dz@8358
    14
andreas@3697
    15
public class AccountSettingsViewModel {
andreas@3697
    16
    public struct ServerViewModel {
andreas@3697
    17
        var address: String?
andreas@3697
    18
        var port: String?
andreas@3697
    19
        var transport: String?
andreas@3697
    20
    }
andreas@3697
    21
andreas@3697
    22
    public struct SecurityViewModel {
andreas@3697
    23
        var options = Server.Transport.toArray()
andreas@3697
    24
        var size : Int {
andreas@3697
    25
            get {
andreas@3697
    26
                return options.count
andreas@3697
    27
            }
andreas@3697
    28
        }
andreas@3697
    29
andreas@3697
    30
        subscript(option: Int) -> String {
andreas@3697
    31
            get {
andreas@3697
    32
                return options[option].asString()
andreas@3697
    33
            }
andreas@3697
    34
        }
andreas@3697
    35
    }
andreas@3697
    36
andreas@3697
    37
    private let headers = [
andreas@3697
    38
        NSLocalizedString("Account", comment: "Account settings"),
andreas@3697
    39
        NSLocalizedString("IMAP Settings", comment: "Account settings title IMAP"),
andreas@3697
    40
        NSLocalizedString("SMTP Settings", comment: "Account settings title SMTP")
andreas@3697
    41
    ]
andreas@3697
    42
    private var controlWord = "noRealPassword"
andreas@3697
    43
andreas@3697
    44
    public let svm = SecurityViewModel()
dirk@4019
    45
    public let isOAuth2: Bool
andreas@3697
    46
andreas@3697
    47
    public init(account: Account) {
dz@8354
    48
        // We are using a copy of the data here.
dz@8354
    49
        // The outside world must not know changed settings until they have been verified.
dirk@4019
    50
        isOAuth2 = account.server(with: .imap)?.authMethod == AuthMethod.saslXoauth2.rawValue
dz@8354
    51
        self.email = account.user.address
dz@8354
    52
        self.loginName = account.server(with: .imap)?.credentials.loginName ?? ""
dz@8354
    53
        self.name = account.user.userName ?? ""
andreas@3697
    54
dz@8373
    55
        if let server = account.imapServer {
dz@8374
    56
            self.originalPassword = server.credentials.password
dz@8373
    57
            self.imapServer = ServerViewModel(
dz@8373
    58
                address: server.address,
dz@8373
    59
                port: "\(server.port)",
dz@8373
    60
                transport: server.transport?.asString())
dz@8373
    61
        } else {
dz@8373
    62
            self.imapServer = ServerViewModel()
dz@8373
    63
        }
dz@8373
    64
dz@8354
    65
        if let server = account.smtpServer {
dz@8374
    66
            self.originalPassword = self.originalPassword ?? server.credentials.password
dz@8354
    67
            self.smtpServer = ServerViewModel(
dz@8354
    68
                address: server.address,
dz@8354
    69
                port: "\(server.port)",
dz@8354
    70
                transport: server.transport?.asString())
dz@8354
    71
        } else {
dz@8354
    72
            self.smtpServer = ServerViewModel()
dz@8354
    73
        }
andreas@3697
    74
    }
andreas@3697
    75
dz@8356
    76
    private(set) var email: String
dz@8354
    77
dz@8353
    78
    /// - Note: The email model is based on the assumption that imap.loginName == smtp.loginName
dz@8356
    79
    private(set) var loginName: String
andreas@3697
    80
dz@8356
    81
    private(set) var name: String
andreas@3697
    82
dz@8356
    83
    private(set) var smtpServer: ServerViewModel
andreas@3697
    84
dz@8356
    85
    private(set) var imapServer: ServerViewModel
andreas@3697
    86
andreas@3697
    87
    weak var delegate: AccountVerificationResultDelegate?
andreas@3697
    88
dz@8357
    89
    /// Holding both the data of the current account in verification,
dz@8357
    90
    /// and also the implementation of the verification.
dz@8357
    91
    private var verifiableAccount: VerifiableAccountProtocol?
dz@8357
    92
dz@8375
    93
    /// If the credentials have either an IMAP or SMTP password,
dz@8375
    94
    /// it gets stored here.
dz@8374
    95
    private var originalPassword: String?
dz@8374
    96
dz@8375
    97
    /// If there was OAUTH2 for this account, here is a current token.
dz@8375
    98
    /// This trumps both the `originalPassword` and a password given by the user
dz@8375
    99
    /// via the UI.
dz@8375
   100
    private var accessToken: OAuth2AccessTokenProtocol?
dz@8375
   101
dz@8354
   102
    // Currently we assume imap and smtp servers exist already (update).
dz@8354
   103
    // If we run into problems here modify to updateOrCreate.
andreas@3697
   104
    func update(loginName: String, name: String, password: String? = nil, imap: ServerViewModel,
andreas@3697
   105
                smtp: ServerViewModel) {
dz@8358
   106
        var theVerifier = verifiableAccount ?? VerifiableAccount()
dz@8358
   107
        verifiableAccount = theVerifier
dz@8358
   108
dz@8358
   109
        theVerifier.address = email
dz@8358
   110
        theVerifier.userName = name
dz@8361
   111
dz@8358
   112
        if loginName != email {
dz@8358
   113
            theVerifier.loginName = loginName
dz@8358
   114
        }
dz@8358
   115
dz@8358
   116
        if isOAuth2 {
dz@8376
   117
            if self.accessToken == nil {
dz@8376
   118
                Logger.frontendLogger.errorAndCrash("Have to do OAUTH2, but lacking current token")
dz@8376
   119
            }
dz@8376
   120
            theVerifier.authMethod = .saslXoauth2
dz@8376
   121
            theVerifier.accessToken = accessToken
dz@8376
   122
            // OAUTH2 trumps any password
dz@8376
   123
            theVerifier.password = nil
dz@8376
   124
        } else {
dz@8376
   125
            theVerifier.password = originalPassword
dz@8376
   126
            if password != nil {
dz@8376
   127
                theVerifier.password = password
dz@8376
   128
            }
dz@8358
   129
        }
dz@8358
   130
dz@8360
   131
        // IMAP
dz@8358
   132
        theVerifier.serverIMAP = imap.address
dz@8358
   133
        if let portString = imap.port, let port = UInt16(portString) {
dz@8358
   134
            theVerifier.portIMAP = port
dz@8358
   135
        }
dz@8360
   136
        if let transport = Server.Transport(fromString: imap.transport) {
dz@8360
   137
            theVerifier.transportIMAP = ConnectionTransport.init(transport: transport)
dz@8360
   138
        }
dz@8360
   139
dz@8360
   140
        // SMTP
dz@8360
   141
        theVerifier.serverSMTP = smtp.address
dz@8360
   142
        if let portString = smtp.port, let port = UInt16(portString) {
dz@8360
   143
            theVerifier.portSMTP = port
dz@8360
   144
        }
dz@8360
   145
        if let transport = Server.Transport(fromString: smtp.transport) {
dz@8360
   146
            theVerifier.transportSMTP = ConnectionTransport.init(transport: transport)
dz@8360
   147
        }
dz@8358
   148
dz@8363
   149
        theVerifier.verifiableAccountDelegate = self
dz@8361
   150
dz@8361
   151
        do {
dz@8361
   152
            try theVerifier.verify()
dz@8361
   153
        } catch {
dz@8361
   154
            delegate?.didVerify(result: .noImapConnectData, accountInput: theVerifier)
dz@8361
   155
        }
andreas@3697
   156
    }
andreas@3697
   157
andreas@3697
   158
    func sectionIsValid(section: Int) -> Bool {
borja@6875
   159
        return section >= 0 && section < headers.count
andreas@3697
   160
    }
andreas@3697
   161
andreas@3697
   162
    var count: Int {
andreas@3697
   163
        get {
andreas@3697
   164
            return headers.count
andreas@3697
   165
        }
andreas@3697
   166
    }
andreas@3697
   167
andreas@3697
   168
    subscript(section: Int) -> String {
andreas@3697
   169
        get {
andreas@3697
   170
            assert(sectionIsValid(section: section), "Section out of range")
andreas@3697
   171
            return headers[section]
andreas@3697
   172
        }
andreas@3697
   173
    }
andreas@3697
   174
andreas@3697
   175
    private func server(from viewModel:ServerViewModel, serverType:Server.ServerType,
andreas@3697
   176
                        loginName: String, password: String?, key: String? = nil) -> Server? {
andreas@3697
   177
        guard let viewModelPort = viewModel.port,
andreas@3697
   178
            let port = UInt16(viewModelPort),
andreas@3697
   179
            let address = viewModel.address else {
dz@7517
   180
                Logger.frontendLogger.errorAndCrash("viewModel misses required data.")
andreas@3697
   181
                return nil
andreas@3697
   182
        }
andreas@5601
   183
        let transport = Server.Transport(fromString: viewModel.transport)
andreas@3697
   184
andreas@3697
   185
        let credentials = ServerCredentials.create(loginName: loginName, key: key)
andreas@3697
   186
        if password != nil && password != "" {
andreas@3697
   187
            credentials.password = password
andreas@3697
   188
        }
andreas@3697
   189
andreas@3697
   190
        let server = Server.create(serverType: serverType, port: port, address: address,
andreas@3697
   191
                                   transport: transport, credentials: credentials)
andreas@3697
   192
andreas@3697
   193
        return server
andreas@3697
   194
    }
andreas@3697
   195
dirk@4052
   196
    func updateToken(accessToken: OAuth2AccessTokenProtocol) {
dz@8375
   197
        self.accessToken = accessToken
dirk@4052
   198
    }
andreas@3697
   199
}
andreas@3697
   200
andreas@5601
   201
// MARK: - AccountVerificationServiceDelegate
andreas@5601
   202
andreas@3697
   203
extension AccountSettingsViewModel: AccountVerificationServiceDelegate {
dz@7999
   204
    public func verified(account: Account,
andreas@5601
   205
                  service: AccountVerificationServiceProtocol,
andreas@3697
   206
                  result: AccountVerificationResult) {
andreas@5601
   207
        if result == .ok {
dz@7914
   208
            MessageModelUtil.performAndWait {
andreas@5601
   209
                account.save()
andreas@5601
   210
            }
andreas@5601
   211
        }
andreas@5601
   212
        GCD.onMainWait { [weak self] in
andreas@5601
   213
            guard let me = self else {
dz@7517
   214
                Logger.frontendLogger.lostMySelf()
andreas@5601
   215
                return
andreas@5601
   216
            }
andreas@5601
   217
            me.delegate?.didVerify(result: result, accountInput: nil)
andreas@3697
   218
        }
andreas@3697
   219
    }
andreas@3697
   220
}
dz@8363
   221
dz@8363
   222
// MARK: - VerifiableAccountDelegate
dz@8363
   223
dz@8363
   224
extension AccountSettingsViewModel: VerifiableAccountDelegate {
dz@8363
   225
    public func didEndVerification(result: Result<Void, Error>) {
dz@8364
   226
        switch result {
dz@8364
   227
        case .success(()):
dz@8364
   228
            do {
dz@8364
   229
                try verifiableAccount?.save()
dz@8364
   230
            } catch {
dz@8364
   231
                Logger.frontendLogger.log(error: error)
dz@8364
   232
                Logger.frontendLogger.errorAndCrash("Unexpected error on saving the account")
dz@8364
   233
            }
dz@8364
   234
        case .failure(let error):
dz@8364
   235
            if let imapError = error as? ImapSyncError {
dz@8364
   236
                delegate?.didVerify(
dz@8364
   237
                    result: .imapError(imapError), accountInput: verifiableAccount)
dz@8364
   238
            } else if let smtpError = error as? SmtpSendError {
dz@8364
   239
                delegate?.didVerify(
dz@8364
   240
                    result: .smtpError(smtpError), accountInput: verifiableAccount)
dz@8364
   241
            } else {
dz@8364
   242
                Logger.frontendLogger.log(error: error)
dz@8364
   243
                Logger.frontendLogger.errorAndCrash("Unexpected error")
dz@8364
   244
            }
dz@8364
   245
        }
dz@8363
   246
    }
dz@8363
   247
}