diff --git a/enzevalos_iphone.xcodeproj/project.pbxproj b/enzevalos_iphone.xcodeproj/project.pbxproj index 07d4b24ca65ac5685d65dedf638457a4dbe54394..4b007fa5071b84027b37d2b08d3122a1aff62929 100644 --- a/enzevalos_iphone.xcodeproj/project.pbxproj +++ b/enzevalos_iphone.xcodeproj/project.pbxproj @@ -151,6 +151,8 @@ 8428A8711F436A1E007649A5 /* GamificationStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8428A86D1F436A1E007649A5 /* GamificationStatusViewController.swift */; }; 8428A8831F436AC9007649A5 /* GamificationDataUnitTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8428A8561F4369EA007649A5 /* GamificationDataUnitTest.swift */; }; 8428A8841F436ACC007649A5 /* GamificationElements.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8428A8541F4369CF007649A5 /* GamificationElements.xcassets */; }; + 976EEBED240D47C3006FE574 /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 976EEBEC240D47C3006FE574 /* AuthenticationViewModel.swift */; }; + 97B2F4D6240D321B000DB34E /* AuthenticationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97B2F4D5240D321B000DB34E /* AuthenticationScreen.swift */; }; 988C9C5D240D507A006213F0 /* PhishingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988C9C5C240D507A006213F0 /* PhishingTests.swift */; }; A102AA8A1EDDB4F40024B457 /* videoOnboarding2.m4v in Resources */ = {isa = PBXBuildFile; fileRef = A102AA891EDDB4E80024B457 /* videoOnboarding2.m4v */; }; A1083A541E8BFEA6003666B7 /* Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1083A531E8BFEA6003666B7 /* Onboarding.swift */; }; @@ -424,6 +426,8 @@ 8B87EFB6CEAA31452F744015 /* Pods-enzevalos_iphoneUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-enzevalos_iphoneUITests.release.xcconfig"; path = "../enzevalos_iphone_workspace/Pods/Target Support Files/Pods-enzevalos_iphoneUITests/Pods-enzevalos_iphoneUITests.release.xcconfig"; sourceTree = "<group>"; }; 91B6C9020C660BEA78FAEF28 /* Pods-enzevalos_iphone.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-enzevalos_iphone.debug.xcconfig"; path = "../enzevalos_iphone_workspace/Pods/Target Support Files/Pods-enzevalos_iphone/Pods-enzevalos_iphone.debug.xcconfig"; sourceTree = "<group>"; }; 94EE54279AB591E0CAB8EFD8 /* Pods_enzevalos_iphone.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_enzevalos_iphone.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 976EEBEC240D47C3006FE574 /* AuthenticationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewModel.swift; sourceTree = "<group>"; }; + 97B2F4D5240D321B000DB34E /* AuthenticationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationScreen.swift; sourceTree = "<group>"; }; 988C9C5C240D507A006213F0 /* PhishingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhishingTests.swift; sourceTree = "<group>"; }; 9A132EDE8BCA06ACDB505C22 /* Pods-enzevalos_iphoneUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-enzevalos_iphoneUITests.debug.xcconfig"; path = "../enzevalos_iphone_workspace/Pods/Target Support Files/Pods-enzevalos_iphoneUITests/Pods-enzevalos_iphoneUITests.debug.xcconfig"; sourceTree = "<group>"; }; 9B3D62838C729BAC6832270A /* Pods-enzevalos_iphone-AdHoc.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-enzevalos_iphone-AdHoc.debug.xcconfig"; path = "../enzevalos_iphone_workspace/Pods/Target Support Files/Pods-enzevalos_iphone-AdHoc/Pods-enzevalos_iphone-AdHoc.debug.xcconfig"; sourceTree = "<group>"; }; @@ -855,6 +859,15 @@ name = Data; sourceTree = "<group>"; }; + 97B2F4D4240D31E6000DB34E /* authentication */ = { + isa = PBXGroup; + children = ( + 97B2F4D5240D321B000DB34E /* AuthenticationScreen.swift */, + 976EEBEC240D47C3006FE574 /* AuthenticationViewModel.swift */, + ); + name = authentication; + sourceTree = "<group>"; + }; A10DE41E1EFAA140005E8189 /* folders */ = { isa = PBXGroup; children = ( @@ -921,6 +934,7 @@ A13526771D955BDF00D3BFE1 /* enzevalos_iphone */ = { isa = PBXGroup; children = ( + 97B2F4D4240D31E6000DB34E /* authentication */, A1B9999D21DE7CD2002563F6 /* Travel */, 477548DC21F5DA46000B22A8 /* mail */, F1866C84201F703200B72453 /* OAuth */, @@ -1582,10 +1596,12 @@ F14239C11F30A99C00998A83 /* QRCodeGenerator.swift in Sources */, 478154A921FF3FF400A931EC /* Invitation.swift in Sources */, 47A2A56E2350A4EF0013883D /* MoreInformationViewController.swift in Sources */, + 97B2F4D6240D321B000DB34E /* AuthenticationScreen.swift in Sources */, F1AF938F1E2D04BA00755128 /* CustomCells.swift in Sources */, 8428A8711F436A1E007649A5 /* GamificationStatusViewController.swift in Sources */, F1866C86201F707200B72453 /* EmailHelper.m in Sources */, 47F79241203492E3005E7DB6 /* KeyRecord+CoreDataProperties.swift in Sources */, + 976EEBED240D47C3006FE574 /* AuthenticationViewModel.swift in Sources */, A10DE4201EFAA2CE005E8189 /* FolderViewController.swift in Sources */, 3EB4FA9F2012007C001D0625 /* DialogViewController.swift in Sources */, 476142081E07E52B00FD5E4F /* Theme.swift in Sources */, diff --git a/enzevalos_iphone/AppDelegate.swift b/enzevalos_iphone/AppDelegate.swift index fa2e3ec793f82c7f9a1ea87fb2b03366e67fcc2d..5b06669feaf96aca1b0b20fe45a2d42995001efb 100644 --- a/enzevalos_iphone/AppDelegate.swift +++ b/enzevalos_iphone/AppDelegate.swift @@ -18,6 +18,7 @@ // import UIKit +import SwiftUI import Contacts import CoreData import Onboard @@ -55,7 +56,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { UserDefaults.standard.register(defaults: ["Signature.Text": "Verfasst mit Letterbox. Mehr Informationen: http://letterbox-app.org"]) self.window = UIWindow(frame: UIScreen.main.bounds) - self.window?.rootViewController = Onboarding.onboarding() + self.window?.rootViewController = UIHostingController(rootView: AuthenticationScreen()) self.window?.makeKeyAndVisible() diff --git a/enzevalos_iphone/AuthenticationScreen.swift b/enzevalos_iphone/AuthenticationScreen.swift new file mode 100644 index 0000000000000000000000000000000000000000..3ba5aee4cc42a261e52e4c42085e25ceccb7e613 --- /dev/null +++ b/enzevalos_iphone/AuthenticationScreen.swift @@ -0,0 +1,102 @@ +// +// AuthenticationScreen.swift +// enzevalos_iphone +// +// Created by Cezary Pilaszewicz on 02.03.20. +// Copyright © 2020 fu-berlin. All rights reserved. +// + +import SwiftUI + +struct AuthenticationScreen: View { + @State private var login: String = "" + @State private var password: String = "" + @State private var username: String = "" + @State private var imapServer: String = "" + @State private var imapPort: String = "" + @State private var smtpServer: String = "" + @State private var smtpPort: String = "" + @State private var imapEncryption = 0 + @State private var smtpEncryption = 0 + + @ObservedObject private var viewModel = AuthenticationViewModel() + var encryptionOptions = ["Plaintext", "StartTLS", "TLS/SSL"] + + var body: some View { + ScrollView{ + ZStack { + Color.white.edgesIgnoringSafeArea(.all) + + VStack { + Text("Please enter Your credentials").padding().foregroundColor(Color.yellow) + Text("Login") + TextField("Please enter your login", text: $login).textFieldStyle(RoundedBorderTextFieldStyle()) + Text("Password") + SecureField("Please enter your password", text: $password).textFieldStyle(RoundedBorderTextFieldStyle()) + + HStack { + Toggle(isOn: self.$viewModel.isDetailedAuthentication) { + Text("Advanced options") + } + Spacer() + Button(action: {self.viewModel.isDetailedAuthentication ? + self.viewModel.validate(self.login, self.password, self.username, self.imapServer, self.imapPort, self.imapEncryption, self.smtpServer, self.smtpPort, self.smtpEncryption) : + self.viewModel.validate(self.login, self.password) + }) { + Text("Login") + } + } + + if self.viewModel.isDetailedAuthentication { + Text("Username") + TextField("Please enter your username", text: $username).textFieldStyle(RoundedBorderTextFieldStyle()) + + HStack { + Text("Imap server") + TextField("e.g. imap.web.de", text: $imapServer) + } + HStack { + Text("Imap port") + TextField("e.g. 993", text:$imapPort).keyboardType(.numberPad) + } + Picker(selection: $imapEncryption, label: Text("IMAP-Transportencryption")) { + ForEach(0..<encryptionOptions.count) { + Text(self.encryptionOptions[$0]) + } + } + HStack { + Text("Smtp server") + TextField("e.g. smtp.web.de", text: $smtpServer) + } + HStack { + Text("Smtp port") + TextField("e.g. 587", text: $smtpPort).keyboardType(.numberPad) + } + + Picker(selection: $smtpEncryption, label: Text("SMTP-Transportencryption")) { + ForEach(0..<encryptionOptions.count) { + Text(self.encryptionOptions[$0]) + } + } + } + + Button(action: { self.viewModel.oauth() }) { + Text("Google login") + } + + }.padding() + + //TODO: once SWIFTUI supports optionals improve this if statement + if self.viewModel.errorMessage != nil && !self.viewModel.errorMessage!.isEmpty { + VStack { + Text(self.viewModel.errorMessage!) + .foregroundColor(Color.white) + .background(Color.red) + Spacer() + } + } + } + } + } +} + diff --git a/enzevalos_iphone/AuthenticationViewModel.swift b/enzevalos_iphone/AuthenticationViewModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..6b06149e52006a31d5a4b8c0ea5249e4345c0c97 --- /dev/null +++ b/enzevalos_iphone/AuthenticationViewModel.swift @@ -0,0 +1,242 @@ +// +// AuthenticationViewModel.swift +// enzevalos_iphone +// +// Created by Cezary Pilaszewicz on 02.03.20. +// Copyright © 2020 fu-berlin. All rights reserved. +// + +import Foundation + +class AuthenticationViewModel : ObservableObject { + + static let DEFAULT_IMAP_PORT = 993 + static let DEFAULT_SMTP_PORT = 587 + + @Published var errorMessage: String? + @Published var isDetailedAuthentication: Bool = false + + var login:String = "" + + @Published var imapServer: String = "imap.example.com" + @Published var imapPort: String = String(DEFAULT_IMAP_PORT) + @Published var imapTransportEncryption = 2 + + @Published var smtpServer: String = "smtp.example.com" + @Published var smtpPort: String = String(DEFAULT_SMTP_PORT) + @Published var smtpTransportEncryption = 1 + + var isGoogleAuth = false + + var imapConfigurationSuccessful = false + var smtpConfigurationSuccessful = false + var startTimeIMAPCheck: Date? + + var startTimeView = Date() + var transportRows: [Int: String] = [MCOConnectionType.clear.rawValue: NSLocalizedString("Plaintext", comment: ""), MCOConnectionType.startTLS.rawValue: "StartTLS", MCOConnectionType.TLS.rawValue: "TLS/SSL"] + + private var previousIMAP: MailSession? + private var previousSMTP: MailSession? + private var currentIMAP: MailSession? + private var currentSMTP: MailSession? + + func validate(_ login: String, _ password: String) -> Void { + checkIMAPConfig(login, password) + } + + func validate(_ login: String, _ password: String, _ username: String, _ imapServer: String, _ imapPort: String, _ imapEncryption: Int, _ smtpServer: String, _ smtpPort: String, _ smtpEncryption: Int) -> Void { + self.login = login + self.imapServer = imapServer + self.imapPort = imapPort + self.imapTransportEncryption = imapEncryption + self.smtpServer = smtpServer + self.smtpPort = smtpPort + self.smtpTransportEncryption = smtpEncryption + checkDetailConfig(imap: true, login, password, username: username, imapServer, Int(imapPort)!, imapEncryption, smtpServer, Int(smtpPort)!, smtpEncryption) + } + + func checkIMAPConfig(_ login: String, _ password: String) { + var mailSession: MailSession + if isGoogleAuth, let session = currentIMAP, session.startTestingServerConfig() { + mailSession = session + imapConfigurationSuccessful = true + startTimeIMAPCheck = Date() + } else { + mailSession = setupIMAPSession(login, password) + if isDetailedAuthentication { + checkDetailConfig(imap: true, login, password, imapServer, Int(imapPort)!, imapTransportEncryption, smtpServer, Int(smtpPort)!, smtpTransportEncryption) + return + } else if !mailSession.hasJsonFile && mailSession.startLongSearchOfServerConfig(hostFromAdr: true) { + imapConfigurationSuccessful = true + startTimeIMAPCheck = Date() + } else { + if mailSession.startTestingServerConfigFromList() || mailSession.startLongSearchOfServerConfig(hostFromAdr: true){ + imapConfigurationSuccessful = true + startTimeIMAPCheck = Date() + } + } + } + currentIMAP = mailSession + } + + func checkSMTPConfig(_ login: String, _ password: String) { + var mailSession: MailSession + if isGoogleAuth, let session = currentSMTP, session.startTestingServerConfig() { + mailSession = session + smtpConfigurationSuccessful = true + } else { + startTimeIMAPCheck = nil + mailSession = setupSMTPSession(login, password) + if isDetailedAuthentication { + checkDetailConfig(imap: false, login, password, imapServer, Int(imapPort)!, imapTransportEncryption, smtpServer, Int(smtpPort)!, smtpTransportEncryption) + return + } else if !mailSession.hasJsonFile && mailSession.startLongSearchOfServerConfig(hostFromAdr: !isDetailedAuthentication) { + smtpConfigurationSuccessful = true + } else { + if mailSession.startTestingServerConfigFromList() || mailSession.startLongSearchOfServerConfig(hostFromAdr: true){ + smtpConfigurationSuccessful = true + } + } + } + currentSMTP = mailSession + } + + func oauth() { + isGoogleAuth = true + googleLogin() + } + + func googleLogin() { + guard let vc = AppDelegate.getAppDelegate().window?.rootViewController else { + print("No view controller!") + return + } + Logger.log(onboardingState: .GoogleLogIn, duration: 0) + + EmailHelper.singleton().doEmailLoginIfRequired(onVC: vc, completionBlock: { + guard let userEmail = EmailHelper.singleton().authorization?.userEmail, EmailHelper.singleton().authorization?.canAuthorize() ?? false else { + print("Google authetication failed") + self.isGoogleAuth = false + return + } + self.currentIMAP = MailSession.init(configSession: .IMAP, mailAddress: userEmail.lowercased(), password: "", username: userEmail.lowercased()) + let listenerIMAP = Listener(type: SessionType.IMAP, callback: self.onImapCompleted, login: userEmail.lowercased(), password: "") + self.currentIMAP?.addListener(listener: listenerIMAP) + self.currentIMAP?.setServer(hostname: "imap.gmail.com", port: 993, connType: MCOConnectionType.TLS.rawValue, authType: MCOAuthType.xoAuth2.rawValue) + self.currentSMTP = MailSession.init(configSession: .SMTP, mailAddress: userEmail.lowercased(), password: "", username: userEmail.lowercased()) + self.currentSMTP?.setServer(hostname: "smtp.gmail.com", port: 587, connType: MCOConnectionType.startTLS.rawValue, authType: MCOAuthType.xoAuth2.rawValue) + let listenerSMTP = Listener(type: SessionType.SMTP, callback: self.onSmtpCompleted, login: userEmail.lowercased(), password: "") + self.currentSMTP?.addListener(listener: listenerSMTP) + self.checkIMAPConfig(userEmail.lowercased(), "") + }) + } + + private func setupIMAPSession(_ login: String, _ password: String, username:String? = nil) -> MailSession { + var name = login + if let n = username { + name = n + } + let mailSession = MailSession(configSession: SessionType.IMAP, mailAddress: login, password: password, username: name) + let listenerIMAP = Listener(type: SessionType.IMAP, callback: onImapCompleted, login: login, password: password) + mailSession.addListener(listener: listenerIMAP) + return mailSession + } + + private func setupSMTPSession(_ login: String, _ password: String, username:String? = nil) -> MailSession{ + var name = login + if let n = username { + name = n + } + let mailSession = MailSession(configSession: SessionType.SMTP, mailAddress: login, password: password, username: name) + let listenerSMTP = Listener(type: SessionType.SMTP, callback: onSmtpCompleted, login: login, password: password) + mailSession.addListener(listener: listenerSMTP) + return mailSession + } + + func authenticationFailed() { + previousSMTP = currentSMTP + previousIMAP = currentIMAP + currentSMTP = nil + currentIMAP = nil + + var error = MailServerConnectionError.AuthenticationError + if let imap = previousIMAP, let e = MailServerConnectionError.findPrioError(errors: imap.errors) { + error = e + } + errorMessage = error.localizedDescription + } + + private func checkDetailConfig(imap: Bool, _ login: String, _ password: String, username:String? = nil, _ imapServer:String, _ imapPort:Int, _ imapEncryption:Int, _ smtpServer:String, _ smtpPort:Int, _ smtpEncryption:Int) { + + let imapSession = setupIMAPSession(login, password) + let smtpSession = setupSMTPSession(login, password) + + let imapConnValue = 1 << imapEncryption + let smtpConnValue = 1 << smtpEncryption + + smtpSession.setServer(hostname: smtpServer, port: UInt32(smtpPort), connType: smtpConnValue, authType: nil) + imapSession.setServer(hostname: imapServer, port: UInt32(imapPort), connType: imapConnValue, authType: nil) + + if imap { + previousSMTP = currentSMTP + previousIMAP = currentIMAP + currentSMTP = smtpSession + currentIMAP = imapSession + if imapSession.startTestingServerConfig() { + imapConfigurationSuccessful = true + } + } else { + if smtpSession.startTestingServerConfig() { + smtpConfigurationSuccessful = true + currentSMTP = smtpSession + } + } + } + + func onImapCompleted(imapWorks: Bool, _ login: String, _ password: String) { + if imapWorks { + _ = currentIMAP?.storeToUserDefaults() + checkSMTPConfig(login, password) + return + + } else if let start = startTimeIMAPCheck { + startTimeIMAPCheck = nil + let duration = abs(start.timeIntervalSinceNow) + if duration > TimeInterval(10) { + isDetailedAuthentication = true + } + } + imapConfigurationSuccessful = false + authenticationFailed() + } + + func onSmtpCompleted(smtpWorks: Bool, _ login: String, _ password: String) { + if smtpWorks { + _ = currentSMTP?.storeToUserDefaults() + AppDelegate.getAppDelegate().credentialsWork() + return + } + smtpConfigurationSuccessful = false + authenticationFailed() + } + + class Listener: MailSessionListener { + let type: SessionType + let callback: (Bool, String, String) -> Void + let login: String + let password: String + + init(type: SessionType, callback: @escaping (Bool, String, String) -> Void, login: String , password: String) { + self.type = type + self.callback = callback + self.login = login + self.password = password + } + + func testFinish(result: Bool) { + DispatchQueue.main.async(execute: { + self.callback(result, self.login, self.password) + }) + } + } +}