diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 9fff98705..7beb30eb2 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -34,11 +34,14 @@ 21FEE26626FDD91A00E3783F /* ComposeMessageAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21FEE26526FDD91A00E3783F /* ComposeMessageAttachment.swift */; }; 2C08F6BE273FA7B900EE1610 /* Version5SchemaMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C08F6BD273FA7B900EE1610 /* Version5SchemaMigration.swift */; }; 2C124DB42728809100A2EFA6 /* ApiCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C124DB32728809100A2EFA6 /* ApiCall.swift */; }; + 2C141B2C274572D50038A3F8 /* Recipient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C141B2B274572D50038A3F8 /* Recipient.swift */; }; + 2C141B2F274578C20038A3F8 /* KeyInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C141B2E274578C20038A3F8 /* KeyInfo.swift */; }; 2C2A3B4B2719EE6100B7F27B /* KeyServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2A3B4A2719EE6100B7F27B /* KeyServiceTests.swift */; }; 2C2A3B4D2719EF7300B7F27B /* PassPhraseServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2A3B4C2719EF7300B7F27B /* PassPhraseServiceMock.swift */; }; - 2C595764273AC47400C7C055 /* RealmExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C595763273AC47400C7C055 /* RealmExtension.swift */; }; 2C60AB0C272564D40040D7F2 /* InvalidStorageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C60AB0B272564D40040D7F2 /* InvalidStorageViewController.swift */; }; 2CC12C3F273571B80021DDDF /* AttachmentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC12C3E273571B80021DDDF /* AttachmentManager.swift */; }; + 2CC50FAF27440B2C0051629A /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC50FAE27440B2C0051629A /* Session.swift */; }; + 2CC50FB12744167A0051629A /* Folder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC50FB02744167A0051629A /* Folder.swift */; }; 32DCA00224982EDA88D69C6E /* AppErr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA4B11D4531B3B04D01D1 /* AppErr.swift */; }; 32DCA04CA0DAB79C39514782 /* CoreTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCAC732B988D9704658812 /* CoreTypes.swift */; }; 32DCA1414EEA727B86C337D5 /* Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA0C3D34A69851A238E87 /* Core.swift */; }; @@ -445,11 +448,14 @@ 21FEE26526FDD91A00E3783F /* ComposeMessageAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeMessageAttachment.swift; sourceTree = ""; }; 2C08F6BD273FA7B900EE1610 /* Version5SchemaMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Version5SchemaMigration.swift; sourceTree = ""; }; 2C124DB32728809100A2EFA6 /* ApiCall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiCall.swift; sourceTree = ""; }; + 2C141B2B274572D50038A3F8 /* Recipient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Recipient.swift; sourceTree = ""; }; + 2C141B2E274578C20038A3F8 /* KeyInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyInfo.swift; sourceTree = ""; }; 2C2A3B4A2719EE6100B7F27B /* KeyServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyServiceTests.swift; sourceTree = ""; }; 2C2A3B4C2719EF7300B7F27B /* PassPhraseServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassPhraseServiceMock.swift; sourceTree = ""; }; - 2C595763273AC47400C7C055 /* RealmExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmExtension.swift; sourceTree = ""; }; 2C60AB0B272564D40040D7F2 /* InvalidStorageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvalidStorageViewController.swift; sourceTree = ""; }; 2CC12C3E273571B80021DDDF /* AttachmentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentManager.swift; sourceTree = ""; }; + 2CC50FAE27440B2C0051629A /* Session.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Session.swift; sourceTree = ""; }; + 2CC50FB02744167A0051629A /* Folder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Folder.swift; sourceTree = ""; }; 32DCA058652FD4616FB04FB6 /* SequenceExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SequenceExtensions.swift; sourceTree = ""; }; 32DCA0C3D34A69851A238E87 /* Core.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Core.swift; sourceTree = ""; }; 32DCA0E63F2F0473D0A8EDB0 /* StringExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; }; @@ -836,11 +842,10 @@ 04B4728A1ECE29D200B8266F /* Models */ = { isa = PBXGroup; children = ( - 5180CB9227357B67001FC7EF /* RawClientConfiguration.swift */, + 2C141B2D274572E40038A3F8 /* Common */, + 9F9AAFFC2383E216000A00F1 /* Document.swift */, 9FD22A20230FF9BC005067A6 /* Inbox Models */, 9F1B49E02624E19D00420472 /* Realm Models */, - 9F0C3C132316E69300299985 /* User.swift */, - 9F9AAFFC2383E216000A00F1 /* Document.swift */, ); path = Models; sourceTree = ""; @@ -942,6 +947,19 @@ path = Data; sourceTree = ""; }; + 2C141B2D274572E40038A3F8 /* Common */ = { + isa = PBXGroup; + children = ( + 2CC50FB02744167A0051629A /* Folder.swift */, + 2C141B2E274578C20038A3F8 /* KeyInfo.swift */, + 5180CB9227357B67001FC7EF /* RawClientConfiguration.swift */, + 2C141B2B274572D50038A3F8 /* Recipient.swift */, + 2CC50FAE27440B2C0051629A /* Session.swift */, + 9F0C3C132316E69300299985 /* User.swift */, + ); + path = Common; + sourceTree = ""; + }; 2C2A3B492719EE4D00B7F27B /* Key Services */ = { isa = PBXGroup; children = ( @@ -994,7 +1012,6 @@ D2D27B78248A8694007346FA /* BigIntExtension.swift */, 9F2F217226B3269D0044E144 /* CombineExtensions.swift */, 51E4F0B427348E310017DABB /* Error+Extension.swift */, - 2C595763273AC47400C7C055 /* RealmExtension.swift */, 51B0C7702729861C00124663 /* String+Extension.swift */, 518389C92726D8F700131B2C /* UIApplicationExtension.swift */, 9F31AB9D232BF2A600CF87EA /* UIColorExtension.swift */, @@ -2551,6 +2568,7 @@ 9F5C2A99257E94E900DE9B4B /* Gmail+MessageOperations.swift in Sources */, 9F31AB932329950800CF87EA /* Imap+Backup.swift in Sources */, 9F589F15238C8249007FD759 /* KeyChainService.swift in Sources */, + 2CC50FB12744167A0051629A /* Folder.swift in Sources */, 9F2F217326B3269D0044E144 /* CombineExtensions.swift in Sources */, D2F41373243CC7990066AFB5 /* UserRealmObject.swift in Sources */, F80E95362720B6640093F243 /* DraftsListProvider.swift in Sources */, @@ -2559,7 +2577,6 @@ 21489B80267CC39E00BDE4AC /* ClientConfigurationService.swift in Sources */, D28655932423B4EE0066F52E /* MyMenuViewDecorator.swift in Sources */, 04B4728D1ECE29D200B8266F /* KeyInfoRealmObject.swift in Sources */, - 2C595764273AC47400C7C055 /* RealmExtension.swift in Sources */, 04B4728D1ECE29D200B8266F /* KeyInfoRealmObject.swift in Sources */, 9F3EF32F23B172D300FA0CEF /* SearchViewController.swift in Sources */, F191F621272511790053833E /* BlurViewController.swift in Sources */, @@ -2570,6 +2587,7 @@ 9FE1B3802563F85400D6D086 /* MessagesListProvider.swift in Sources */, 32DCA1B95DDC04D671F662F8 /* URLSessionExtension.swift in Sources */, 214A023A26A3029700C24066 /* EmailKeyManagerApi.swift in Sources */, + 2C141B2F274578C20038A3F8 /* KeyInfo.swift in Sources */, D2E26F7424F2705B00612AF1 /* ContactDetailDecorator.swift in Sources */, 2C60AB0C272564D40040D7F2 /* InvalidStorageViewController.swift in Sources */, 2C124DB42728809100A2EFA6 /* ApiCall.swift in Sources */, @@ -2616,6 +2634,7 @@ 9F5C2A92257E94DF00DE9B4B /* Imap+MessageOperations.swift in Sources */, D27B911F24EFE828002DF0A1 /* RecipientWithSortedPubKeys.swift in Sources */, 32DCA1414EEA727B86C337D5 /* Core.swift in Sources */, + 2CC50FAF27440B2C0051629A /* Session.swift in Sources */, 9FB22CF725715DC50026EE64 /* KeyServiceErrorHandler.swift in Sources */, 9F0C3C1A231819C500299985 /* MessageKindProviderType.swift in Sources */, 21C7DF09266C0D8F00C44800 /* EnterpriseServerApi.swift in Sources */, @@ -2634,6 +2653,7 @@ D2891AC224C59EFA008918E3 /* KeyService.swift in Sources */, D269E02724103A20000495C3 /* ComposeViewControllerInput.swift in Sources */, 9FAFD75D2714A06400321FA4 /* InboxProviders.swift in Sources */, + 2C141B2C274572D50038A3F8 /* Recipient.swift in Sources */, 9F0C3C142316E69300299985 /* User.swift in Sources */, 2155E9EF26E3628C008FB033 /* Refreshable.swift in Sources */, 9F3EF32523B15C1400FA0CEF /* ImapHelper.swift in Sources */, diff --git a/FlowCrypt/Controllers/SetupImap/SetupImapViewController.swift b/FlowCrypt/Controllers/SetupImap/SetupImapViewController.swift index 0e50ca8d6..39e94247a 100644 --- a/FlowCrypt/Controllers/SetupImap/SetupImapViewController.swift +++ b/FlowCrypt/Controllers/SetupImap/SetupImapViewController.swift @@ -30,7 +30,7 @@ final class SetupImapViewController: TableNodeViewController { private let decorator: SetupImapViewDecorator private let sessionCredentials: SessionCredentialsProvider private let imap: Imap - private var user = UserRealmObject.empty + private var user = User.empty init( globalRouter: GlobalRouterType = GlobalRouter(), @@ -360,7 +360,7 @@ extension SetupImapViewController { private func updateForEmailChanges(with text: String?) { guard let email = text, email.isNotEmpty else { - user = UserRealmObject.empty + user = User.empty node.reloadData() return } @@ -412,7 +412,8 @@ extension SetupImapViewController { switch settings { case let .failure(.notFound(defaultPort)): - user.imap?.port = user.imap?.port ?? defaultPort + let port = user.imap?.port ?? defaultPort + user.imap?.port = port case let .success(imapSetting): updateUser(imap: imapSetting) } @@ -427,7 +428,8 @@ extension SetupImapViewController { switch settings { case let .failure(.notFound(defaultPort)): - user.smtp?.port = user.smtp?.port ?? defaultPort + let port = user.smtp?.port ?? defaultPort + user.smtp?.port = port case let .success(imapSetting): updateUser(smtp: imapSetting) } @@ -500,8 +502,9 @@ extension SetupImapViewController { private func checkImapSession() { showSpinner() - guard let imapSessionToCheck = IMAPSession(userObject: user), - let smtpSession = SMTPSession(userObject: user) + guard + let imapSessionToCheck = IMAPSession(user: user), + let smtpSession = SMTPSession(user: user) else { fatalError("Should be able to create session at this momment") } @@ -529,8 +532,8 @@ extension SetupImapViewController { globalRouter.signIn(with: .other(.session(user))) } - private func checkCurrentUser() -> Result { - guard user != UserRealmObject.empty, user.email != UserRealmObject.empty.email else { + private func checkCurrentUser() -> Result { + guard user != User.empty, user.email != User.empty.email else { return .failure(.empty) } diff --git a/FlowCrypt/Controllers/SetupImap/SetupImapViewDecorator.swift b/FlowCrypt/Controllers/SetupImap/SetupImapViewDecorator.swift index 024773abb..63ef496e6 100644 --- a/FlowCrypt/Controllers/SetupImap/SetupImapViewDecorator.swift +++ b/FlowCrypt/Controllers/SetupImap/SetupImapViewDecorator.swift @@ -149,7 +149,7 @@ struct SetupImapViewDecorator { ) } - func stringFor(user: UserRealmObject, for section: SetupImapViewController.Section) -> NSAttributedString? { + func stringFor(user: User, for section: SetupImapViewController.Section) -> NSAttributedString? { switch section { case let .account(part): switch part { @@ -165,7 +165,7 @@ struct SetupImapViewDecorator { case let .imap(part): switch part { case .port: - guard let port = user.imap?.port, port != UserRealmObject.empty.imap?.port else { + guard let port = user.imap?.port, port != User.empty.imap?.port else { return nil } return "\(port)".attributed() @@ -185,7 +185,7 @@ struct SetupImapViewDecorator { case let .smtp(part): switch part { case .port: - guard let port = user.smtp?.port, port != UserRealmObject.empty.smtp?.port else { + guard let port = user.smtp?.port, port != User.empty.smtp?.port else { return nil } return "\(port)".attributed() diff --git a/FlowCrypt/Core/Models/PrvKeyInfo.swift b/FlowCrypt/Core/Models/PrvKeyInfo.swift index bd46d4272..ed4b19e34 100644 --- a/FlowCrypt/Core/Models/PrvKeyInfo.swift +++ b/FlowCrypt/Core/Models/PrvKeyInfo.swift @@ -16,11 +16,11 @@ struct PrvKeyInfo: Encodable, Equatable { } extension PrvKeyInfo { - init(keyInfo: KeyInfoRealmObject, passphrase: String?) { + init(keyInfo: KeyInfo, passphrase: String?) { self.private = keyInfo.private self.longid = keyInfo.primaryLongid self.passphrase = keyInfo.passphrase ?? passphrase - self.fingerprints = Array(keyInfo.allFingerprints) + self.fingerprints = keyInfo.allFingerprints } func copy(with passphrase: String) -> PrvKeyInfo { diff --git a/FlowCrypt/Extensions/RealmExtension.swift b/FlowCrypt/Extensions/RealmExtension.swift deleted file mode 100644 index 0c6f91bdc..000000000 --- a/FlowCrypt/Extensions/RealmExtension.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// RealmExtension.swift -// FlowCrypt -// -// Created by  Ivan Ushakov on 09.11.2021 -// Copyright © 2017-present FlowCrypt a. s. All rights reserved. -// - -import Realm -import RealmSwift - -protocol RealmListDetachable { - func detached() -> Self -} - -extension List: RealmListDetachable where Element: Object { - func detached() -> List { - let detached = self.detached - let result = List() - result.append(objectsIn: detached) - return result - } -} - -extension Object { - // TODO Temporary solution from StackOverflow for https://github.com/FlowCrypt/flowcrypt-ios/issues/877 - func detached() -> Self { - let detached = type(of: self).init() - for property in objectSchema.properties { - guard - property != objectSchema.primaryKeyProperty, - let value = value(forKey: property.name) - else { continue } - - if let detachable = value as? Object { - detached.setValue(detachable.detached(), forKey: property.name) - } else if let list = value as? RealmListDetachable { - detached.setValue(list.detached(), forKey: property.name) - } else { - detached.setValue(value, forKey: property.name) - } - } - return detached - } -} - -extension Sequence where Iterator.Element: Object { - var detached: [Element] { - return self.map({ $0.detached() }) - } -} diff --git a/FlowCrypt/Functionality/DataManager/DataService.swift b/FlowCrypt/Functionality/DataManager/DataService.swift index a2cbe5bdf..ceacbec1e 100644 --- a/FlowCrypt/Functionality/DataManager/DataService.swift +++ b/FlowCrypt/Functionality/DataManager/DataService.swift @@ -34,7 +34,7 @@ protocol ImapSessionProvider { enum SessionType: CustomStringConvertible { case google(_ email: String, name: String, token: String) - case session(_ userObject: UserRealmObject) + case session(_ user: User) var description: String { switch self { @@ -66,10 +66,6 @@ final class DataService { // MARK: - DataServiceType extension DataService: DataServiceType { - var storage: Realm { - encryptedStorage.storage - } - var isSetupFinished: Bool { isLoggedIn && doesAnyKeyExistForCurrentUser } @@ -90,21 +86,20 @@ extension DataService: DataServiceType { } // helper to get current user object from DB - private var currentUserObject: UserRealmObject? { + private var activeUser: User? { encryptedStorage.activeUser } var users: [User] { encryptedStorage.getAllUsers() - .map(User.init) } var currentUser: User? { - users.first(where: \.isActive) + encryptedStorage.getAllUsers().first(where: \.isActive) } var currentAuthType: AuthType? { - currentUserObject?.authType + activeUser?.authType } var token: String? { @@ -120,7 +115,6 @@ extension DataService: DataServiceType { encryptedStorage.getAllUsers() .filter { encryptedStorage.doesAnyKeyExist(for: $0.email) } .filter { $0.email != currentUser?.email } - .map(User.init) } } @@ -135,12 +129,12 @@ extension DataService: DBMigration { // MARK: - SessionProvider extension DataService: ImapSessionProvider { func imapSession() -> IMAPSession? { - guard let user = currentUserObject else { + guard let user = activeUser else { assertionFailure("Can't get IMAP Session without user data") return nil } - guard let imapSession = IMAPSession(userObject: user) else { + guard let imapSession = IMAPSession(user: user) else { assertionFailure("couldn't create IMAP Session with this parameters") return nil } @@ -149,12 +143,12 @@ extension DataService: ImapSessionProvider { } func smtpSession() -> SMTPSession? { - guard let user = currentUserObject else { + guard let user = activeUser else { assertionFailure("Can't get SMTP Session without user data") return nil } - guard let smtpSession = SMTPSession(userObject: user) else { + guard let smtpSession = SMTPSession(user: user) else { assertionFailure("couldn't create SMTP Session with this parameters") return nil } diff --git a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift index d9c8363be..bc00d026c 100644 --- a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift +++ b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift @@ -14,9 +14,9 @@ import RealmSwift protocol EncryptedStorageType: KeyStorageType { var storage: Realm { get } - func getAllUsers() -> [UserRealmObject] - func saveActiveUser(with user: UserRealmObject) - var activeUser: UserRealmObject? { get } + func getAllUsers() -> [User] + func saveActiveUser(with user: User) + var activeUser: User? { get } func doesAnyKeyExist(for email: String) -> Bool func validate() throws @@ -154,7 +154,7 @@ extension EncryptedStorage { // MARK: - Keys extension EncryptedStorage { func addKeys(keyDetails: [KeyDetails], passPhrase: String?, source: KeySource, for email: String) { - guard let user = storage.objects(UserRealmObject.self).first(where: { $0.email == email }) else { + guard let user = getUserObject(for: email) else { logger.logError("Can't find user with given email to add keys. User should be already saved") return } @@ -230,22 +230,26 @@ extension EncryptedStorage: PassPhraseStorageType { // MARK: - User extension EncryptedStorage { - var activeUser: UserRealmObject? { - getAllUsers().first(where: \.isActive) + var activeUser: User? { + storage.objects(UserRealmObject.self) + .first(where: \.isActive) + .flatMap(User.init) } - func getAllUsers() -> [UserRealmObject] { - Array(storage.objects(UserRealmObject.self)) + func getAllUsers() -> [User] { + storage.objects(UserRealmObject.self).map(User.init) } - func saveActiveUser(with user: UserRealmObject) { + func saveActiveUser(with user: User) { try! storage.write { // Mark all users as inactive - self.getAllUsers().forEach { + storage.objects(UserRealmObject.self).forEach { $0.isActive = false } - user.isActive = true - self.storage.add(user, update: .all) + + let object = UserRealmObject(user) + object.isActive = true + storage.add(object, update: .all) } } } diff --git a/FlowCrypt/Functionality/DataManager/UserAccountService.swift b/FlowCrypt/Functionality/DataManager/UserAccountService.swift index ac7effa8c..153210611 100644 --- a/FlowCrypt/Functionality/DataManager/UserAccountService.swift +++ b/FlowCrypt/Functionality/DataManager/UserAccountService.swift @@ -56,7 +56,7 @@ extension UserAccountService: UserAccountServiceType { switch type { case let .google(email, name, token): // save new user data - let user = UserRealmObject.googleUser( + let user = User.googleUser( name: name, email: email, token: token @@ -82,16 +82,16 @@ extension UserAccountService: UserAccountServiceType { } func switchActiveSessionFor(user: User) -> SessionType? { - let userObj = self.encryptedStorage + let actualUser = encryptedStorage .getAllUsers() .first(where: { $0.email == user.email }) - guard let userObject = userObj else { + guard let actualUser = actualUser else { logger.logWarning("UserObject should be persisted to encrypted storage in case of switching accounts") return nil } - let session = switchActiveSession(for: userObject) + let session = switchActiveSession(for: actualUser) return session } @@ -117,15 +117,15 @@ extension UserAccountService: UserAccountServiceType { } @discardableResult - private func switchActiveSession(for userObject: UserRealmObject) -> SessionType? { - logger.logInfo("Try to switch session for \(userObject.email)") + private func switchActiveSession(for user: User) -> SessionType? { + logger.logInfo("Try to switch session for \(user.email)") let sessionType: SessionType - switch userObject.authType { + switch user.authType { case .oAuthGmail(let token): - sessionType = .google(userObject.email, name: userObject.name, token: token) + sessionType = .google(user.email, name: user.name, token: token) case .password: - sessionType = .session(userObject) + sessionType = .session(user) case .none: logger.logWarning("authType is not defined in switchActiveSession") return nil diff --git a/FlowCrypt/Functionality/Mail Provider/Mail Sessions Providers/IMAPConnectionParameters.swift b/FlowCrypt/Functionality/Mail Provider/Mail Sessions Providers/IMAPConnectionParameters.swift index 8518860f4..e20ee5f41 100644 --- a/FlowCrypt/Functionality/Mail Provider/Mail Sessions Providers/IMAPConnectionParameters.swift +++ b/FlowCrypt/Functionality/Mail Provider/Mail Sessions Providers/IMAPConnectionParameters.swift @@ -18,7 +18,7 @@ struct IMAPSession { } extension IMAPSession { - init?(userObject user: UserRealmObject) { + init?(user: User) { guard let imap = user.imap else { assertionFailure("Can't get IMAP Session without user data") return nil diff --git a/FlowCrypt/Functionality/Mail Provider/Mail Sessions Providers/SMTPSession.swift b/FlowCrypt/Functionality/Mail Provider/Mail Sessions Providers/SMTPSession.swift index 48624c845..5d1aa7b77 100644 --- a/FlowCrypt/Functionality/Mail Provider/Mail Sessions Providers/SMTPSession.swift +++ b/FlowCrypt/Functionality/Mail Provider/Mail Sessions Providers/SMTPSession.swift @@ -18,7 +18,7 @@ struct SMTPSession { } extension SMTPSession { - init?(userObject user: UserRealmObject) { + init?(user: User) { guard let smtp = user.smtp else { assertionFailure("Can't get SMTP Session without user data") return nil diff --git a/FlowCrypt/Functionality/Services/Client Configuration Service/LocalClientConfiguration.swift b/FlowCrypt/Functionality/Services/Client Configuration Service/LocalClientConfiguration.swift index 84d640bcc..9e2472c3a 100644 --- a/FlowCrypt/Functionality/Services/Client Configuration Service/LocalClientConfiguration.swift +++ b/FlowCrypt/Functionality/Services/Client Configuration Service/LocalClientConfiguration.swift @@ -41,6 +41,6 @@ extension LocalClientConfiguration: LocalClientConfigurationType { guard let user = cache.encryptedStorage.activeUser else { fatalError("Internal inconsistency, no active user when saving client configuration") } - cache.save(ClientConfigurationRealmObject(raw, user: user)) + cache.save(ClientConfigurationRealmObject(configuration: raw, user: user)) } } diff --git a/FlowCrypt/Functionality/Services/Folders Services/LocalFoldersProvider.swift b/FlowCrypt/Functionality/Services/Folders Services/LocalFoldersProvider.swift index 9864d6c0d..705f1b47d 100644 --- a/FlowCrypt/Functionality/Services/Folders Services/LocalFoldersProvider.swift +++ b/FlowCrypt/Functionality/Services/Folders Services/LocalFoldersProvider.swift @@ -33,7 +33,7 @@ struct LocalFoldersProvider: LocalFoldersProviderType { return } - folders.map { FolderRealmObject(folder: $0, currentUser: currentUser) } + folders.map { FolderRealmObject(folder: $0, user: currentUser) } .forEach(folderCache.save) } @@ -41,14 +41,3 @@ struct LocalFoldersProvider: LocalFoldersProviderType { folderCache.removeAllForActiveUser() } } - -private extension FolderRealmObject { - convenience init(folder: Folder, currentUser: UserRealmObject) { - self.init( - name: folder.name, - path: folder.path, - image: folder.image, - user: currentUser - ) - } -} diff --git a/FlowCrypt/Functionality/Services/Folders Services/RemoteFoldersProviderType/GmailService+folders.swift b/FlowCrypt/Functionality/Services/Folders Services/RemoteFoldersProviderType/GmailService+folders.swift index 060f71ee9..a2e6f08ee 100644 --- a/FlowCrypt/Functionality/Services/Folders Services/RemoteFoldersProviderType/GmailService+folders.swift +++ b/FlowCrypt/Functionality/Services/Folders Services/RemoteFoldersProviderType/GmailService+folders.swift @@ -11,7 +11,7 @@ import GoogleAPIClientForREST_Gmail extension GmailService: RemoteFoldersProviderType { enum Constants { - static let allMailFolder = Folder(name: "All Mail", path: "", image: nil) + static let allMailFolder = Folder(path: "", name: "All Mail", image: nil) } func fetchFolders() async throws -> [Folder] { @@ -61,8 +61,8 @@ private extension Folder { } self.init( - name: name, path: path, + name: name, image: nil ) } diff --git a/FlowCrypt/Functionality/Services/Folders Services/RemoteFoldersProviderType/Imap+folders.swift b/FlowCrypt/Functionality/Services/Folders Services/RemoteFoldersProviderType/Imap+folders.swift index bd0a39ba0..a8ecafd29 100644 --- a/FlowCrypt/Functionality/Services/Folders Services/RemoteFoldersProviderType/Imap+folders.swift +++ b/FlowCrypt/Functionality/Services/Folders Services/RemoteFoldersProviderType/Imap+folders.swift @@ -24,8 +24,8 @@ extension Imap: RemoteFoldersProviderType { private extension Folder { init(with folder: MCOIMAPFolder) { self.init( - name: folder.name ?? folder.path, path: folder.path, + name: folder.name ?? folder.path, image: nil ) } diff --git a/FlowCrypt/Functionality/Services/Folders Services/RemoteFoldersProviderType/RemoteFoldersProvider.swift b/FlowCrypt/Functionality/Services/Folders Services/RemoteFoldersProviderType/RemoteFoldersProvider.swift index 73a065c2c..c289585de 100644 --- a/FlowCrypt/Functionality/Services/Folders Services/RemoteFoldersProviderType/RemoteFoldersProvider.swift +++ b/FlowCrypt/Functionality/Services/Folders Services/RemoteFoldersProviderType/RemoteFoldersProvider.swift @@ -11,9 +11,3 @@ import Foundation protocol RemoteFoldersProviderType { func fetchFolders() async throws -> [Folder] } - -struct Folder { - let name: String - let path: String - let image: Data? -} diff --git a/FlowCrypt/Functionality/Services/Local Private Key Services/KeyService.swift b/FlowCrypt/Functionality/Services/Local Private Key Services/KeyService.swift index fc0bdacf6..598416b8e 100644 --- a/FlowCrypt/Functionality/Services/Local Private Key Services/KeyService.swift +++ b/FlowCrypt/Functionality/Services/Local Private Key Services/KeyService.swift @@ -60,8 +60,11 @@ final class KeyService: KeyServiceType { guard let email = currentUserEmail() else { throw KeyServiceError.missingCurrentUserEmail } + let keysInfo = storage.keysInfo() .filter { $0.account == email } + .map(KeyInfo.init) + let storedPassPhrases = passPhraseService.getPassPhrases() let privateKeys = keysInfo .map { keyInfo -> PrvKeyInfo in @@ -79,11 +82,8 @@ final class KeyService: KeyServiceType { logger.logError("no current user email") throw AppErr.noCurrentUser } - // get keys associated with this account, freeze them to pass across threads - let keysInfo = storage.keysInfo().filter { $0.account == email }.map { object -> KeyInfoRealmObject in - guard object.realm != nil else { return object } - return object.detached() - } + + let keysInfo = storage.keysInfo().filter { $0.account == email }.map(KeyInfo.init) guard let foundKey = try await findKeyByUserEmail(keysInfo: keysInfo, email: email) else { return nil } @@ -97,10 +97,10 @@ final class KeyService: KeyServiceType { return PrvKeyInfo(keyInfo: foundKey, passphrase: passphrase) } - private func findKeyByUserEmail(keysInfo: [KeyInfoRealmObject], email: String) async throws -> KeyInfoRealmObject? { + private func findKeyByUserEmail(keysInfo: [KeyInfo], email: String) async throws -> KeyInfo? { // todo - should be refactored with https://github.com/FlowCrypt/flowcrypt-ios/issues/812 logger.logDebug("findKeyByUserEmail: found \(keysInfo.count) candidate prvs in storage, searching by:\(email)") - var keys: [(KeyInfoRealmObject, KeyDetails)] = [] + var keys: [(KeyInfo, KeyDetails)] = [] for keyInfo in keysInfo { let parsedKeys = try await coreService.parseKeys( armoredOrBinary: keyInfo.`private`.data() diff --git a/FlowCrypt/Functionality/Services/Local Pub Key Services/LocalContactsProvider.swift b/FlowCrypt/Functionality/Services/Local Pub Key Services/LocalContactsProvider.swift index a4c7061fc..52379e5e9 100644 --- a/FlowCrypt/Functionality/Services/Local Pub Key Services/LocalContactsProvider.swift +++ b/FlowCrypt/Functionality/Services/Local Pub Key Services/LocalContactsProvider.swift @@ -73,8 +73,8 @@ extension LocalContactsProvider: LocalContactsProviderType { } func searchRecipient(with email: String) async throws -> RecipientWithSortedPubKeys? { - guard let recipientObject = find(with: email) else { return nil } - return try await parseRecipient(from: recipientObject.detached()) + guard let recipient = find(with: email).map(Recipient.init) else { return nil } + return try await parseRecipient(from: recipient) } func searchEmails(query: String) -> [String] { @@ -85,7 +85,8 @@ extension LocalContactsProvider: LocalContactsProviderType { } func getAllRecipients() async throws -> [RecipientWithSortedPubKeys] { - let objects = localContactsCache.realm.objects(RecipientRealmObject.self).detached + let objects: [Recipient] = localContactsCache.realm.objects(RecipientRealmObject.self) + .map(Recipient.init) var recipients: [RecipientWithSortedPubKeys] = [] for object in objects { recipients.append(try await parseRecipient(from: object)) @@ -107,16 +108,18 @@ extension LocalContactsProvider: LocalContactsProviderType { extension LocalContactsProvider { private func find(with email: String) -> RecipientRealmObject? { - localContactsCache.realm.object(ofType: RecipientRealmObject.self, - forPrimaryKey: email) + localContactsCache.realm.object( + ofType: RecipientRealmObject.self, + forPrimaryKey: email + ) } - private func parseRecipient(from object: RecipientRealmObject) async throws -> RecipientWithSortedPubKeys { - let armoredToParse = object.pubKeys + private func parseRecipient(from recipient: Recipient) async throws -> RecipientWithSortedPubKeys { + let armoredToParse = recipient.pubKeys .map { $0.armored } .joined(separator: "\n") let parsed = try await core.parseKeys(armoredOrBinary: armoredToParse.data()) - return RecipientWithSortedPubKeys(object, keyDetails: parsed.keyDetails) + return RecipientWithSortedPubKeys(recipient, keyDetails: parsed.keyDetails) } private func add(pubKey: PubKey, to recipient: RecipientRealmObject) { diff --git a/FlowCrypt/Functionality/Services/Local Pub Key Services/Models/PubKey.swift b/FlowCrypt/Functionality/Services/Local Pub Key Services/Models/PubKey.swift index a8ee7373d..d8577ff2c 100644 --- a/FlowCrypt/Functionality/Services/Local Pub Key Services/Models/PubKey.swift +++ b/FlowCrypt/Functionality/Services/Local Pub Key Services/Models/PubKey.swift @@ -9,6 +9,7 @@ import Foundation struct PubKey { + let primaryFingerprint: String let armored: String /// will be provided later let lastSig: Date? @@ -53,16 +54,36 @@ extension PubKey { let longids = keyIds.map(\.longid) let fingerprints = keyIds.map(\.fingerprint) - self.init(armored: keyDetails.public, - lastSig: keyDetails.lastModified.map { Date(timeIntervalSince1970: TimeInterval($0)) }, - lastChecked: Date(), - expiresOn: keyDetails.expiration.map { Date(timeIntervalSince1970: TimeInterval($0)) }, - longids: longids, - fingerprints: fingerprints, - created: Date(timeIntervalSince1970: Double(keyDetails.created)), - algo: keyDetails.algo, - isRevoked: keyDetails.revoked, - emails: keyDetails.pgpUserEmails) + self.init( + primaryFingerprint: keyDetails.primaryFingerprint, + armored: keyDetails.public, + lastSig: keyDetails.lastModified.map { Date(timeIntervalSince1970: TimeInterval($0)) }, + lastChecked: Date(), + expiresOn: keyDetails.expiration.map { Date(timeIntervalSince1970: TimeInterval($0)) }, + longids: longids, + fingerprints: fingerprints, + created: Date(timeIntervalSince1970: Double(keyDetails.created)), + algo: keyDetails.algo, + isRevoked: keyDetails.revoked, + emails: keyDetails.pgpUserEmails + ) + } +} + +extension PubKey { + init(_ object: PubKeyRealmObject) { + self.primaryFingerprint = object.primaryFingerprint + self.armored = object.armored + self.lastSig = object.lastSig + self.lastChecked = object.lastChecked + self.expiresOn = object.expiresOn + self.longids = object.longids.map { $0 } + self.fingerprints = object.fingerprints.map { $0 } + self.created = object.created + + self.algo = nil + self.isRevoked = false + self.emails = [] } } diff --git a/FlowCrypt/Functionality/Services/Local Pub Key Services/Models/RecipientWithSortedPubKeys.swift b/FlowCrypt/Functionality/Services/Local Pub Key Services/Models/RecipientWithSortedPubKeys.swift index 50d641a39..0135483aa 100644 --- a/FlowCrypt/Functionality/Services/Local Pub Key Services/Models/RecipientWithSortedPubKeys.swift +++ b/FlowCrypt/Functionality/Services/Local Pub Key Services/Models/RecipientWithSortedPubKeys.swift @@ -24,10 +24,10 @@ struct RecipientWithSortedPubKeys { } extension RecipientWithSortedPubKeys { - init(_ recipientObject: RecipientRealmObject, keyDetails: [KeyDetails] = []) { - self.email = recipientObject.email - self.name = recipientObject.name.nilIfEmpty - self.lastUsed = recipientObject.lastUsed + init(_ recipient: Recipient, keyDetails: [KeyDetails] = []) { + self.email = recipient.email + self.name = recipient.name + self.lastUsed = recipient.lastUsed self._pubKeys = keyDetails.map(PubKey.init) } } diff --git a/FlowCrypt/Models/Common/Folder.swift b/FlowCrypt/Models/Common/Folder.swift new file mode 100644 index 000000000..57ca440d4 --- /dev/null +++ b/FlowCrypt/Models/Common/Folder.swift @@ -0,0 +1,16 @@ +// +// Folder.swift +// FlowCrypt +// +// Created by  Ivan Ushakov on 16.11.2021 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import Foundation + +struct Folder { + var path: String + var name: String + var image: Data? + var itemType: String = FolderViewModel.ItemType.folder.rawValue +} diff --git a/FlowCrypt/Models/Common/KeyInfo.swift b/FlowCrypt/Models/Common/KeyInfo.swift new file mode 100644 index 000000000..d4e67e7e8 --- /dev/null +++ b/FlowCrypt/Models/Common/KeyInfo.swift @@ -0,0 +1,35 @@ +// +// KeyInfo.swift +// FlowCrypt +// +// Created by  Ivan Ushakov on 17.11.2021 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import Foundation + +struct KeyInfo { + var primaryFingerprint: String + var `private`: String + var `public`: String + var passphrase: String? + var source: String + var allFingerprints: [String] + var allLongids: [String] + + var primaryLongid: String { + allLongids[0] + } +} + +extension KeyInfo { + init(_ object: KeyInfoRealmObject) { + self.primaryFingerprint = object.primaryFingerprint + self.private = object.private + self.public = object.public + self.passphrase = object.passphrase + self.source = object.source + self.allFingerprints = object.allFingerprints.map { $0 } + self.allLongids = object.allLongids.map { $0 } + } +} diff --git a/FlowCrypt/Models/RawClientConfiguration.swift b/FlowCrypt/Models/Common/RawClientConfiguration.swift similarity index 100% rename from FlowCrypt/Models/RawClientConfiguration.swift rename to FlowCrypt/Models/Common/RawClientConfiguration.swift diff --git a/FlowCrypt/Models/Common/Recipient.swift b/FlowCrypt/Models/Common/Recipient.swift new file mode 100644 index 000000000..d4ebf5f34 --- /dev/null +++ b/FlowCrypt/Models/Common/Recipient.swift @@ -0,0 +1,25 @@ +// +// Recipient.swift +// FlowCrypt +// +// Created by  Ivan Ushakov on 17.11.2021 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import Foundation + +struct Recipient { + var email: String + var name: String? + var lastUsed: Date? + var pubKeys: [PubKey] +} + +extension Recipient { + init(_ recipientObject: RecipientRealmObject) { + self.email = recipientObject.email + self.name = recipientObject.name + self.lastUsed = recipientObject.lastUsed + self.pubKeys = recipientObject.pubKeys.map(PubKey.init) + } +} diff --git a/FlowCrypt/Models/Common/Session.swift b/FlowCrypt/Models/Common/Session.swift new file mode 100644 index 000000000..246f469a3 --- /dev/null +++ b/FlowCrypt/Models/Common/Session.swift @@ -0,0 +1,71 @@ +// +// Session.swift +// FlowCrypt +// +// Created by  Ivan Ushakov on 16.11.2021 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import Foundation + +struct Session: Codable, Equatable { + var hostname: String + var port: Int + var username: String + var password: String? + var oAuth2Token: String? + var connectionType: String + var email: String? +} + +extension Session { + static func googleIMAP(with token: String, username: String, email: String) -> Session { + Session( + hostname: "imap.gmail.com", + port: 993, + username: username, + password: nil, + oAuth2Token: token, + connectionType: ConnectionType.tls.rawValue, + email: email + ) + } + + static func googleSMTP(with token: String, username: String, email: String) -> Session { + Session( + hostname: "smtp.gmail.com", + port: 465, + username: username, + password: nil, + oAuth2Token: token, + connectionType: ConnectionType.tls.rawValue, + email: email + ) + } +} + +extension Session { + init(_ object: SessionRealmObject) { + self.hostname = object.hostname + self.port = object.port + self.username = object.username + self.password = object.password + self.oAuth2Token = object.oAuth2Token + self.connectionType = object.connectionType + self.email = object.email + } +} + +extension Session { + static var empty: Session { + Session( + hostname: "", + port: 0, + username: "", + password: nil, + oAuth2Token: nil, + connectionType: "", + email: "" + ) + } +} diff --git a/FlowCrypt/Models/Common/User.swift b/FlowCrypt/Models/Common/User.swift new file mode 100644 index 000000000..504904e20 --- /dev/null +++ b/FlowCrypt/Models/Common/User.swift @@ -0,0 +1,72 @@ +// +// User.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 8/28/19. +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import Foundation + +struct User: Codable, Equatable { + var email: String + var isActive: Bool + var name: String { + didSet { + imap?.username = name + smtp?.username = name + } + } + var imap: Session? + var smtp: Session? + + var password: String? { + imap?.password + } +} + +extension User { + static func googleUser(name: String, email: String, token: String) -> User { + User( + email: email, + isActive: true, + name: name, + imap: Session.googleIMAP(with: token, username: name, email: email), + smtp: Session.googleSMTP(with: token, username: name, email: email) + ) + } +} + +extension User { + var authType: AuthType? { + if let password = password { + return .password(password) + } + if let token = smtp?.oAuth2Token { + return .oAuthGmail(token) + } + return nil + } +} + +extension User { + init(_ userObject: UserRealmObject) { + self.name = userObject.name + self.email = userObject.email + self.isActive = userObject.isActive + self.imap = userObject.imap.flatMap(Session.init) + self.smtp = userObject.smtp.flatMap(Session.init) + } +} + +extension User { + static var empty: User { + User( + email: "", + isActive: true, + name: "", + imap: .empty, + smtp: .empty + ) + } +} diff --git a/FlowCrypt/Models/Realm Models/ClientConfigurationRealmObject.swift b/FlowCrypt/Models/Realm Models/ClientConfigurationRealmObject.swift index 0c9f739f9..55467bb37 100644 --- a/FlowCrypt/Models/Realm Models/ClientConfigurationRealmObject.swift +++ b/FlowCrypt/Models/Realm Models/ClientConfigurationRealmObject.swift @@ -16,8 +16,8 @@ final class ClientConfigurationRealmObject: Object { @Persisted var keyManagerUrl: String? @Persisted var disallowAttesterSearchForDomains: Data? @Persisted var enforceKeygenAlgo: String? - @Persisted var enforceKeygenExpireMonths: Int = -1 - @Persisted var user: UserRealmObject! + @Persisted var enforceKeygenExpireMonths: Int + @Persisted var user: UserRealmObject? convenience init( flags: [String]?, @@ -42,19 +42,18 @@ final class ClientConfigurationRealmObject: Object { self.user = user self.userEmail = user.email } +} - convenience init( - _ clientConfiguration: RawClientConfiguration, - user: UserRealmObject - ) { +extension ClientConfigurationRealmObject { + convenience init(configuration: RawClientConfiguration, user: User) { self.init( - flags: clientConfiguration.flags?.map(\.rawValue), - customKeyserverUrl: clientConfiguration.customKeyserverUrl, - keyManagerUrl: clientConfiguration.keyManagerUrl, - disallowAttesterSearchForDomains: clientConfiguration.disallowAttesterSearchForDomains, - enforceKeygenAlgo: clientConfiguration.enforceKeygenAlgo, - enforceKeygenExpireMonths: clientConfiguration.enforceKeygenExpireMonths, - user: user + flags: configuration.flags?.map(\.rawValue), + customKeyserverUrl: configuration.customKeyserverUrl, + keyManagerUrl: configuration.keyManagerUrl, + disallowAttesterSearchForDomains: configuration.disallowAttesterSearchForDomains, + enforceKeygenAlgo: configuration.enforceKeygenAlgo, + enforceKeygenExpireMonths: configuration.enforceKeygenExpireMonths, + user: UserRealmObject(user) ) } } diff --git a/FlowCrypt/Models/Realm Models/FolderRealmObject.swift b/FlowCrypt/Models/Realm Models/FolderRealmObject.swift index 598e32637..a3da8d873 100644 --- a/FlowCrypt/Models/Realm Models/FolderRealmObject.swift +++ b/FlowCrypt/Models/Realm Models/FolderRealmObject.swift @@ -10,23 +10,21 @@ import Foundation import RealmSwift final class FolderRealmObject: Object { - @Persisted(primaryKey: true) var path: String = "" - @Persisted var name: String = "" + @Persisted(primaryKey: true) var path: String + @Persisted var name: String @Persisted var image: Data? - @Persisted var itemType: String = FolderViewModel.ItemType.folder.rawValue - @Persisted var user: UserRealmObject! + @Persisted var itemType: String + @Persisted var user: UserRealmObject? +} - convenience init( - name: String, - path: String, - image: Data?, - user: UserRealmObject - ) { +extension FolderRealmObject { + convenience init(folder: Folder, user: User) { self.init() - self.name = name - self.path = path - self.image = image - self.user = user + self.path = folder.path + self.name = folder.name + self.image = folder.image + self.itemType = folder.itemType + self.user = UserRealmObject(user) } } diff --git a/FlowCrypt/Models/Realm Models/KeyInfoRealmObject.swift b/FlowCrypt/Models/Realm Models/KeyInfoRealmObject.swift index 6ec690df3..ee19560cb 100644 --- a/FlowCrypt/Models/Realm Models/KeyInfoRealmObject.swift +++ b/FlowCrypt/Models/Realm Models/KeyInfoRealmObject.swift @@ -20,12 +20,12 @@ enum KeyInfoError: Error { } final class KeyInfoRealmObject: Object { - @Persisted(primaryKey: true) var primaryFingerprint = "" - @Persisted var `private`: String = "" - @Persisted var `public`: String = "" + @Persisted(primaryKey: true) var primaryFingerprint: String + @Persisted var `private`: String + @Persisted var `public`: String @Persisted var passphrase: String? - @Persisted var source: String = "" - @Persisted var user: UserRealmObject! + @Persisted var source: String + @Persisted var user: UserRealmObject? @Persisted var allFingerprints: List @Persisted var allLongids: List @@ -33,6 +33,12 @@ final class KeyInfoRealmObject: Object { allLongids[0] } + override var description: String { + "account = \(user?.email ?? "N/A") ####### longid = \(primaryLongid)" + } +} + +extension KeyInfoRealmObject { convenience init(_ keyDetails: KeyDetails, passphrase: String?, source: KeySource, user: UserRealmObject) throws { self.init() @@ -60,15 +66,11 @@ final class KeyInfoRealmObject: Object { self.source = source.rawValue self.user = user } - - override var description: String { - "account = \(user?.email ?? "N/A") ####### longid = \(primaryLongid)" - } } extension KeyInfoRealmObject { /// associated user email - var account: String { - user.email + var account: String? { + user?.email } } diff --git a/FlowCrypt/Models/Realm Models/PubKeyRealmObject.swift b/FlowCrypt/Models/Realm Models/PubKeyRealmObject.swift index d07b7b07f..db1344936 100644 --- a/FlowCrypt/Models/Realm Models/PubKeyRealmObject.swift +++ b/FlowCrypt/Models/Realm Models/PubKeyRealmObject.swift @@ -14,15 +14,17 @@ enum PubKeyObjectError: Error { } final class PubKeyRealmObject: Object { - @Persisted(primaryKey: true) var primaryFingerprint: String = "" - @Persisted var armored: String = "" + @Persisted(primaryKey: true) var primaryFingerprint: String + @Persisted var armored: String @Persisted var lastSig: Date? @Persisted var lastChecked: Date? @Persisted var expiresOn: Date? @Persisted var longids: List @Persisted var fingerprints: List @Persisted var created: Date? +} +extension PubKeyRealmObject { convenience init(armored: String, lastSig: Date? = nil, lastChecked: Date? = nil, diff --git a/FlowCrypt/Models/Realm Models/RecipientRealmObject.swift b/FlowCrypt/Models/Realm Models/RecipientRealmObject.swift index b45697e5c..51986345f 100644 --- a/FlowCrypt/Models/Realm Models/RecipientRealmObject.swift +++ b/FlowCrypt/Models/Realm Models/RecipientRealmObject.swift @@ -10,11 +10,13 @@ import Foundation import RealmSwift final class RecipientRealmObject: Object { - @Persisted(primaryKey: true) var email: String = "" + @Persisted(primaryKey: true) var email: String @Persisted var name: String? @Persisted var lastUsed: Date? @Persisted var pubKeys: List +} +extension RecipientRealmObject { convenience init( email: String, name: String?, diff --git a/FlowCrypt/Models/Realm Models/SessionRealmObject.swift b/FlowCrypt/Models/Realm Models/SessionRealmObject.swift index 123925c3b..ab845f765 100644 --- a/FlowCrypt/Models/Realm Models/SessionRealmObject.swift +++ b/FlowCrypt/Models/Realm Models/SessionRealmObject.swift @@ -6,74 +6,27 @@ // Copyright © 2017-present FlowCrypt a. s. All rights reserved. // -import Foundation import RealmSwift final class SessionRealmObject: Object { - @Persisted var hostname: String = "" - @Persisted var port: Int = 0 - @Persisted var username: String = "" + @Persisted var hostname: String + @Persisted var port: Int + @Persisted var username: String @Persisted var password: String? @Persisted var oAuth2Token: String? - @Persisted var connectionType: String = "" + @Persisted var connectionType: String @Persisted var email: String? - - convenience init( - hostname: String, - port: Int, - username: String, - password: String?, - oAuth2Token: String?, - connectionType: String, - email: String - ) { - self.init() - self.hostname = hostname - self.port = port - self.username = username - self.password = password - self.oAuth2Token = oAuth2Token - self.connectionType = connectionType - self.email = email - } -} - -extension SessionRealmObject { - static func googleIMAP(with token: String, username: String, email: String) -> SessionRealmObject { - SessionRealmObject( - hostname: "imap.gmail.com", - port: 993, - username: username, - password: nil, - oAuth2Token: token, - connectionType: ConnectionType.tls.rawValue, - email: email - ) - } - - static func googleSMTP(with token: String, username: String, email: String) -> SessionRealmObject { - SessionRealmObject( - hostname: "smtp.gmail.com", - port: 465, - username: username, - password: nil, - oAuth2Token: token, - connectionType: ConnectionType.tls.rawValue, - email: email - ) - } } extension SessionRealmObject { - static var empty: SessionRealmObject { - SessionRealmObject( - hostname: "", - port: 0, - username: "", - password: nil, - oAuth2Token: nil, - connectionType: "", - email: "" - ) + convenience init(_ session: Session) { + self.init() + self.hostname = session.hostname + self.port = session.port + self.username = session.username + self.password = session.password + self.oAuth2Token = session.oAuth2Token + self.connectionType = session.connectionType + self.email = session.email } } diff --git a/FlowCrypt/Models/Realm Models/UserRealmObject.swift b/FlowCrypt/Models/Realm Models/UserRealmObject.swift index 2e6b969fe..100d897b8 100644 --- a/FlowCrypt/Models/Realm Models/UserRealmObject.swift +++ b/FlowCrypt/Models/Realm Models/UserRealmObject.swift @@ -10,77 +10,31 @@ import Foundation import RealmSwift final class UserRealmObject: Object { - @Persisted(primaryKey: true) var email: String = "" - @Persisted var isActive = true - @Persisted var name: String = "" { - didSet { - imap?.username = name - smtp?.username = name - } - } + @Persisted(primaryKey: true) var email: String + @Persisted var isActive: Bool + @Persisted var name: String @Persisted var imap: SessionRealmObject? @Persisted var smtp: SessionRealmObject? +} - var password: String? { - imap?.password - } - - convenience init( - name: String, - email: String, - imap: SessionRealmObject?, - smtp: SessionRealmObject? - ) { +extension UserRealmObject { + convenience init(name: String, email: String, imap: SessionRealmObject?, smtp: SessionRealmObject?) { self.init() - self.name = name self.email = email + self.isActive = true + self.name = name self.imap = imap self.smtp = smtp } - - override var description: String { - email - } } extension UserRealmObject { - static func googleUser(name: String, email: String, token: String) -> UserRealmObject { - UserRealmObject( - name: name, - email: email, - imap: SessionRealmObject.googleIMAP(with: token, username: name, email: email), - smtp: SessionRealmObject.googleSMTP(with: token, username: name, email: email) - ) - } -} - -extension UserRealmObject { - var authType: AuthType? { - if let password = password { - return .password(password) - } - if let token = smtp?.oAuth2Token { - return .oAuthGmail(token) - } - return nil - } -} - -extension User { - init(_ userObject: UserRealmObject) { - self.name = userObject.name - self.email = userObject.email - self.isActive = userObject.isActive - } -} - -extension UserRealmObject { - static var empty: UserRealmObject { - UserRealmObject( - name: "", - email: "", - imap: .empty, - smtp: .empty - ) + convenience init(_ user: User) { + self.init() + self.email = user.email + self.isActive = user.isActive + self.name = user.name + self.imap = user.imap.flatMap(SessionRealmObject.init) + self.smtp = user.smtp.flatMap(SessionRealmObject.init) } } diff --git a/FlowCrypt/Models/User.swift b/FlowCrypt/Models/User.swift deleted file mode 100644 index 6b04425bf..000000000 --- a/FlowCrypt/Models/User.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// User.swift -// FlowCrypt -// -// Created by Anton Kharchevskyi on 8/28/19. -// Copyright © 2017-present FlowCrypt a. s. All rights reserved. -// - -import Foundation - -struct User: Codable, Equatable { - let email: String - let name: String - let isActive: Bool -} diff --git a/FlowCryptAppTests/Core/Models/PrvKeyInfoTests.swift b/FlowCryptAppTests/Core/Models/PrvKeyInfoTests.swift index 13e9fbc3e..5b2e93d41 100644 --- a/FlowCryptAppTests/Core/Models/PrvKeyInfoTests.swift +++ b/FlowCryptAppTests/Core/Models/PrvKeyInfoTests.swift @@ -30,8 +30,12 @@ class PrvKeyInfoTests: XCTestCase { private let user = UserRealmObject(name: "name", email: "email", imap: nil, smtp: nil) func testInitFromKeyInfo() { - let keyInfo = try! KeyInfoRealmObject(keyDetail, passphrase: "123", source: .backup, user: user) - let keyInfoWithoutPassphrase = try! KeyInfoRealmObject(keyDetail, passphrase: nil, source: .backup, user: user) + let keyInfo = KeyInfo( + try! KeyInfoRealmObject(keyDetail, passphrase: "123", source: .backup, user: user) + ) + let keyInfoWithoutPassphrase = KeyInfo( + try! KeyInfoRealmObject(keyDetail, passphrase: nil, source: .backup, user: user) + ) let privateKey1 = PrvKeyInfo(keyInfo: keyInfo, passphrase: nil) XCTAssertEqual(privateKey1.passphrase, "123") diff --git a/appium/tests/helpers/TouchHelper.ts b/appium/tests/helpers/TouchHelper.ts index e122fc2dd..b16dfa46a 100644 --- a/appium/tests/helpers/TouchHelper.ts +++ b/appium/tests/helpers/TouchHelper.ts @@ -4,15 +4,15 @@ class TouchHelper { /** * scroll down */ - static scrollDown = async() => { - await driver.execute('mobile: scroll', {direction: 'down'}); + static scrollDown = async () => { + await driver.execute('mobile: scroll', { direction: 'down' }); } /** * scroll up */ - static scrollUp = async() => { - await driver.execute('mobile: scroll', {direction: 'up'}); + static scrollUp = async () => { + await driver.execute('mobile: scroll', { direction: 'up' }); } } diff --git a/appium/tests/screenobjects/new-message.screen.ts b/appium/tests/screenobjects/new-message.screen.ts index 1e4d15a00..e50874e41 100644 --- a/appium/tests/screenobjects/new-message.screen.ts +++ b/appium/tests/screenobjects/new-message.screen.ts @@ -45,11 +45,11 @@ class NewMessageScreen extends BaseScreen { return $(SELECTORS.SENT_BUTTON); } - get errorHeader () { + get errorHeader() { return $(SELECTORS.ERROR_HEADER) } - get okButton () { + get okButton() { return $(SELECTORS.OK_BUTTON); } @@ -93,7 +93,6 @@ class NewMessageScreen extends BaseScreen { checkAddedRecipient = async (recipient: string) => { const addedRecipientEl = await this.addedRecipientEmail; const value = await addedRecipientEl.getValue(); - console.log(`addedRecipientEl value: ${value}`); expect(value).toEqual(` ${recipient} `); }; @@ -107,7 +106,7 @@ class NewMessageScreen extends BaseScreen { checkError = async (errorText: string) => { const message = '-ios class chain:**/XCUIElementTypeAlert/XCUIElementTypeOther/XCUIElementTypeOther/' + - 'XCUIElementTypeOther[2]/XCUIElementTypeScrollView[1]/XCUIElementTypeOther[1]/XCUIElementTypeStaticText[2]';//it works only with this selector + 'XCUIElementTypeOther[2]/XCUIElementTypeScrollView[1]/XCUIElementTypeOther[1]/XCUIElementTypeStaticText[2]';//it works only with this selector await expect(await this.errorHeader).toBeDisplayed(); await expect(await $(message)).toHaveAttribute('value', `${errorText}`); await expect(await this.okButton).toBeDisplayed(); diff --git a/appium/tests/screenobjects/trash.screen.ts b/appium/tests/screenobjects/trash.screen.ts index d3b605f78..92a780700 100644 --- a/appium/tests/screenobjects/trash.screen.ts +++ b/appium/tests/screenobjects/trash.screen.ts @@ -17,11 +17,11 @@ class TrashScreen extends BaseScreen { } get helpIcon() { - return $(SELECTORS.HELP_ICON); + return $(SELECTORS.HELP_ICON); } get trashHeader() { - return $(SELECTORS.TRASH_HEADER) + return $(SELECTORS.TRASH_HEADER) } checkTrashScreen = async () => { @@ -31,7 +31,7 @@ class TrashScreen extends BaseScreen { } checkEmailIsNotDisplayed = async (subject: string) => { - await (await $(`~${subject}`)).waitForDisplayed({reverse: true}); + await (await $(`~${subject}`)).waitForDisplayed({ reverse: true }); } refreshTrashList = async () => { diff --git a/appium/tests/specs/composeEmail/CheckComposeEmailAfterReopening.spec.ts b/appium/tests/specs/composeEmail/CheckComposeEmailAfterReopening.spec.ts index 625f44eaf..f46978253 100644 --- a/appium/tests/specs/composeEmail/CheckComposeEmailAfterReopening.spec.ts +++ b/appium/tests/specs/composeEmail/CheckComposeEmailAfterReopening.spec.ts @@ -17,6 +17,7 @@ describe('COMPOSE EMAIL: ', () => { await SplashScreen.login(); await SetupKeyScreen.setPassPhrase(); + await InboxScreen.checkInboxScreen(); await InboxScreen.clickCreateEmail(); await NewMessageScreen.composeEmail(recipientEmail, emailSubject, emailText); diff --git a/appium/tests/specs/composeEmail/SelectRecipientByName.spec.ts b/appium/tests/specs/composeEmail/SelectRecipientByName.spec.ts index 7f2a419f5..540d33b98 100644 --- a/appium/tests/specs/composeEmail/SelectRecipientByName.spec.ts +++ b/appium/tests/specs/composeEmail/SelectRecipientByName.spec.ts @@ -25,6 +25,7 @@ describe('COMPOSE EMAIL: ', () => { await SplashScreen.login(); await SetupKeyScreen.setPassPhrase(); + await InboxScreen.checkInboxScreen(); // Go to Contacts screen await MenuBarScreen.clickMenuIcon(); diff --git a/appium/tests/specs/composeEmail/SendEncryptedEmailAfterWrongPassPhrase.spec.ts b/appium/tests/specs/composeEmail/SendEncryptedEmailAfterPassPhraseSessionEndedAndTrashIt.spec.ts similarity index 84% rename from appium/tests/specs/composeEmail/SendEncryptedEmailAfterWrongPassPhrase.spec.ts rename to appium/tests/specs/composeEmail/SendEncryptedEmailAfterPassPhraseSessionEndedAndTrashIt.spec.ts index a38e1d650..89d128daf 100644 --- a/appium/tests/specs/composeEmail/SendEncryptedEmailAfterWrongPassPhrase.spec.ts +++ b/appium/tests/specs/composeEmail/SendEncryptedEmailAfterPassPhraseSessionEndedAndTrashIt.spec.ts @@ -14,7 +14,7 @@ import DataHelper from "../../helpers/DataHelper"; describe('COMPOSE EMAIL: ', () => { - it('user is able to send encrypted email after resetting pass phrase + move to trash, delete', async () => { + it('user is able to send encrypted email when pass phrase session ended + move to trash, delete', async () => { const contactEmail = CommonData.secondContact.email; const emailSubject = CommonData.simpleEmail.subject + DataHelper.uniqueValue(); @@ -23,9 +23,15 @@ describe('COMPOSE EMAIL: ', () => { const wrongPassPhraseError = CommonData.errors.wrongPassPhrase; const wrongPassPhrase = "wrong"; const senderEmail = CommonData.account.email; + const bundleId = CommonData.bundleId.id; await SplashScreen.login(); await SetupKeyScreen.setPassPhrase(); + await InboxScreen.checkInboxScreen(); + + //Restart app to reset pass phrase memory cache + await driver.terminateApp(bundleId); + await driver.activateApp(bundleId); await InboxScreen.clickCreateEmail(); await NewMessageScreen.composeEmail(contactEmail, emailSubject, emailText); @@ -53,6 +59,7 @@ describe('COMPOSE EMAIL: ', () => { await EmailScreen.clickDeleteButton(); await SentScreen.checkSentScreen(); await SentScreen.checkEmailIsNotDisplayed(emailSubject); + await browser.pause(2000); // give Google API time to process the deletion await SentScreen.refreshSentList(); await SentScreen.checkSentScreen(); await SentScreen.checkEmailIsNotDisplayed(emailSubject); @@ -65,6 +72,7 @@ describe('COMPOSE EMAIL: ', () => { await EmailScreen.clickDeleteButton(); await EmailScreen.confirmDelete(); await TrashScreen.checkTrashScreen(); + await browser.pause(2000); // give Google API time to process the deletion await TrashScreen.refreshTrashList(); await TrashScreen.checkTrashScreen(); await TrashScreen.checkEmailIsNotDisplayed(emailSubject); diff --git a/appium/tests/specs/composeEmail/SentEmailToRecipientWithoutPublicKey.spec.ts b/appium/tests/specs/composeEmail/SentEmailToRecipientWithoutPublicKey.spec.ts index f6b5c88d6..9992a1a12 100644 --- a/appium/tests/specs/composeEmail/SentEmailToRecipientWithoutPublicKey.spec.ts +++ b/appium/tests/specs/composeEmail/SentEmailToRecipientWithoutPublicKey.spec.ts @@ -2,8 +2,7 @@ import { SplashScreen, SetupKeyScreen, InboxScreen, - NewMessageScreen, - EmailScreen + NewMessageScreen } from '../../screenobjects/all-screens'; import { CommonData } from '../../data'; @@ -15,20 +14,17 @@ describe('COMPOSE EMAIL: ', () => { const noPublicKeyRecipient = CommonData.recipientWithoutPublicKey.email; const emailSubject = CommonData.simpleEmail.subject; const emailText = CommonData.simpleEmail.message; - const passPhrase = CommonData.account.passPhrase; const noPublicKeyError = CommonData.errors.noPublicKey; await SplashScreen.login(); await SetupKeyScreen.setPassPhrase(); + await InboxScreen.checkInboxScreen(); await InboxScreen.clickCreateEmail(); await NewMessageScreen.composeEmail(noPublicKeyRecipient, emailSubject, emailText); await NewMessageScreen.checkFilledComposeEmailInfo(noPublicKeyRecipient, emailSubject, emailText); await NewMessageScreen.clickSentButton(); - await EmailScreen.enterPassPhrase(passPhrase); - await EmailScreen.clickOkButton(); - await NewMessageScreen.checkError(noPublicKeyError); }); }); diff --git a/appium/tests/specs/inbox/CheckEncryptedEmailAfterRestartApp.spec.ts b/appium/tests/specs/inbox/CheckEncryptedEmailAfterRestartApp.spec.ts index 82be1cac5..4cbaeb640 100644 --- a/appium/tests/specs/inbox/CheckEncryptedEmailAfterRestartApp.spec.ts +++ b/appium/tests/specs/inbox/CheckEncryptedEmailAfterRestartApp.spec.ts @@ -22,6 +22,7 @@ describe('INBOX: ', () => { await SplashScreen.login(); await SetupKeyScreen.setPassPhrase(); + await InboxScreen.checkInboxScreen(); await InboxScreen.clickOnEmailBySubject(emailSubject); await EmailScreen.checkOpenedEmail(senderEmail, emailSubject, emailText); diff --git a/appium/tests/specs/inbox/CheckReplyForEncryptedEmail.spec.ts b/appium/tests/specs/inbox/CheckReplyForEncryptedEmail.spec.ts index f574f8ca9..4950c81e8 100644 --- a/appium/tests/specs/inbox/CheckReplyForEncryptedEmail.spec.ts +++ b/appium/tests/specs/inbox/CheckReplyForEncryptedEmail.spec.ts @@ -6,7 +6,7 @@ import { NewMessageScreen } from '../../screenobjects/all-screens'; -import {CommonData} from '../../data'; +import { CommonData } from '../../data'; describe('INBOX: ', () => { @@ -21,6 +21,7 @@ describe('INBOX: ', () => { await SplashScreen.login(); await SetupKeyScreen.setPassPhrase(); + await InboxScreen.checkInboxScreen(); await InboxScreen.clickOnEmailBySubject(emailSubject); await EmailScreen.checkOpenedEmail(senderEmail, emailSubject, emailText); diff --git a/appium/tests/specs/inbox/ReadAttachmentEmail.spec.ts b/appium/tests/specs/inbox/ReadAttachmentEmail.spec.ts index d516a9cf0..c5deed954 100644 --- a/appium/tests/specs/inbox/ReadAttachmentEmail.spec.ts +++ b/appium/tests/specs/inbox/ReadAttachmentEmail.spec.ts @@ -23,6 +23,7 @@ describe('INBOX: ', () => { await SplashScreen.login(); await SetupKeyScreen.setPassPhrase(); + await InboxScreen.checkInboxScreen(); await InboxScreen.clickOnEmailBySubject(emailSubject); await EmailScreen.checkOpenedEmail(senderEmail, emailSubject, emailText); diff --git a/appium/tests/specs/inbox/ReadEmailAfterRestartApp.spec.ts b/appium/tests/specs/inbox/ReadEmailAfterRestartApp.spec.ts index 56061be4a..59bbc71b7 100644 --- a/appium/tests/specs/inbox/ReadEmailAfterRestartApp.spec.ts +++ b/appium/tests/specs/inbox/ReadEmailAfterRestartApp.spec.ts @@ -17,6 +17,7 @@ describe('INBOX: ', () => { await SplashScreen.login(); await SetupKeyScreen.setPassPhrase(); + await InboxScreen.checkInboxScreen(); await InboxScreen.clickOnEmailBySubject(emailSubject); await EmailScreen.checkOpenedEmail(senderEmail, emailSubject, emailText); diff --git a/appium/tests/specs/inbox/ReadTextEmail.spec.ts b/appium/tests/specs/inbox/ReadTextEmail.spec.ts index cbe43d889..6be0b3edb 100644 --- a/appium/tests/specs/inbox/ReadTextEmail.spec.ts +++ b/appium/tests/specs/inbox/ReadTextEmail.spec.ts @@ -17,6 +17,7 @@ describe('INBOX: ', () => { await SplashScreen.login(); await SetupKeyScreen.setPassPhrase(); + await InboxScreen.checkInboxScreen(); await InboxScreen.clickOnEmailBySubject(emailSubject); await EmailScreen.checkOpenedEmail(senderEmail, emailSubject, emailText); diff --git a/appium/tests/specs/login/GmailLogin.spec.ts b/appium/tests/specs/login/GmailLogin.spec.ts index b99bde0da..d5d2987a6 100644 --- a/appium/tests/specs/login/GmailLogin.spec.ts +++ b/appium/tests/specs/login/GmailLogin.spec.ts @@ -2,7 +2,8 @@ import { MockApi } from 'api-mocks/mock'; import { SplashScreen, SetupKeyScreen, - MenuBarScreen + MenuBarScreen, + InboxScreen } from '../../screenobjects/all-screens'; @@ -18,6 +19,7 @@ describe('LOGIN: ', () => { await SplashScreen.login(); await SetupKeyScreen.setPassPhrase(); + await InboxScreen.checkInboxScreen(); await MenuBarScreen.clickMenuIcon(); await MenuBarScreen.checkUserEmail(); diff --git a/appium/tests/specs/settings/CheckSettingsForLoggedUser.spec.ts b/appium/tests/specs/settings/CheckSettingsForLoggedUser.spec.ts index 9b9818de5..b77d2a1fb 100644 --- a/appium/tests/specs/settings/CheckSettingsForLoggedUser.spec.ts +++ b/appium/tests/specs/settings/CheckSettingsForLoggedUser.spec.ts @@ -4,7 +4,8 @@ import { MenuBarScreen, SettingsScreen, KeysScreen, - PublicKeyScreen + PublicKeyScreen, + InboxScreen } from '../../screenobjects/all-screens'; @@ -14,6 +15,7 @@ describe('SETTINGS: ', () => { await SplashScreen.login(); await SetupKeyScreen.setPassPhrase(); + await InboxScreen.checkInboxScreen(); await MenuBarScreen.clickMenuIcon(); await MenuBarScreen.checkUserEmail();