From d3460065e013b755d019ee39ccecb1c49104020c Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Thu, 17 Jun 2021 23:29:59 +0300 Subject: [PATCH 01/15] fix wasteful backup search in inbox on Gmail API --- FlowCrypt.xcodeproj/project.pbxproj | 10 +++++++ .../Backup Provider/Gmail+Backup.swift | 23 ++++++-------- .../Mail Provider/Gmail/GmailService.swift | 7 ++++- .../GmailSearchExpressionGenerator.swift | 22 ++++++++++++++ .../GmailSearchExpressionGeneratorTests.swift | 30 +++++++++++++++++++ 5 files changed, 77 insertions(+), 15 deletions(-) create mode 100644 FlowCrypt/Functionality/Mail Provider/SearchMessage Provider/GmailSearchExpressionGenerator.swift create mode 100644 FlowCryptTests/GmailSearchExpressionGeneratorTests.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 40c124992..3e98f4ea1 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -82,6 +82,9 @@ 9F23EA50237217140017DFED /* ComposeViewDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F23EA4F237217140017DFED /* ComposeViewDecorator.swift */; }; 9F268891237DC55600428A94 /* ImportKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F268890237DC55600428A94 /* ImportKeyViewController.swift */; }; 9F268894237DD98900428A94 /* EnterPassPhraseViewDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F268893237DD98900428A94 /* EnterPassPhraseViewDecorator.swift */; }; + 9F2AC59F267BDE0700F6149B /* GmailSearchExpressionGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2AC59E267BDE0700F6149B /* GmailSearchExpressionGeneratorTests.swift */; }; + 9F2AC5B1267BDED100F6149B /* GmailSearchExpressionGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2AC5B0267BDED100F6149B /* GmailSearchExpressionGenerator.swift */; }; + 9F2AC5BC267BDEF500F6149B /* GmailSearchExpressionGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2AC5B0267BDED100F6149B /* GmailSearchExpressionGenerator.swift */; }; 9F31AB8C23298B3F00CF87EA /* Imap+retry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F31AB8B23298B3F00CF87EA /* Imap+retry.swift */; }; 9F31AB8E23298BCF00CF87EA /* Imap+folders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F31AB8D23298BCF00CF87EA /* Imap+folders.swift */; }; 9F31AB91232993F500CF87EA /* Imap+session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F31AB90232993F500CF87EA /* Imap+session.swift */; }; @@ -445,6 +448,8 @@ 9F23EA4F237217140017DFED /* ComposeViewDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewDecorator.swift; sourceTree = ""; }; 9F268890237DC55600428A94 /* ImportKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportKeyViewController.swift; sourceTree = ""; }; 9F268893237DD98900428A94 /* EnterPassPhraseViewDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterPassPhraseViewDecorator.swift; sourceTree = ""; }; + 9F2AC59E267BDE0700F6149B /* GmailSearchExpressionGeneratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GmailSearchExpressionGeneratorTests.swift; sourceTree = ""; }; + 9F2AC5B0267BDED100F6149B /* GmailSearchExpressionGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GmailSearchExpressionGenerator.swift; sourceTree = ""; }; 9F31AB8B23298B3F00CF87EA /* Imap+retry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Imap+retry.swift"; sourceTree = ""; }; 9F31AB8D23298BCF00CF87EA /* Imap+folders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Imap+folders.swift"; sourceTree = ""; }; 9F31AB90232993F500CF87EA /* Imap+session.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Imap+session.swift"; sourceTree = ""; }; @@ -1165,6 +1170,7 @@ 9FC411202595EA12001180A8 /* MessageSearchProvider.swift */, 9FC4112D2595EA8B001180A8 /* Gmail+Search.swift */, 9FC411342595EA94001180A8 /* Imap+Search.swift */, + 9F2AC5B0267BDED100F6149B /* GmailSearchExpressionGenerator.swift */, ); path = "SearchMessage Provider"; sourceTree = ""; @@ -1288,6 +1294,7 @@ A3A680EF22EEF0BF00905813 /* FlowCryptTests-Bridging-Header.h */, D2A9CA44242622F800E1D898 /* GeneralConstantsTest.swift */, 9F003D9D25EA910B00EB38C0 /* LocalStorageTests.swift */, + 9F2AC59E267BDE0700F6149B /* GmailSearchExpressionGeneratorTests.swift */, ); path = FlowCryptTests; sourceTree = ""; @@ -2295,6 +2302,7 @@ D2A9CA45242622F800E1D898 /* GeneralConstantsTest.swift in Sources */, A3DAD60B22E458C300F2C4CD /* DataExtensions.swift in Sources */, 21EA3B532656611C00691848 /* OrganisationalRule.swift in Sources */, + 9F2AC5BC267BDEF500F6149B /* GmailSearchExpressionGenerator.swift in Sources */, A3DAD60822E4588800F2C4CD /* TestData.swift in Sources */, D212D36124C1AC0D00035991 /* KeyDetails.swift in Sources */, 9F6EE18C25A8AF970059BA51 /* GeneralConstants.swift in Sources */, @@ -2307,6 +2315,7 @@ D212D36524C1AC4800035991 /* KeyId.swift in Sources */, 9F003DBC25EA92D000EB38C0 /* LogOutHandler.swift in Sources */, 21F836A02652A19A00B2448C /* WKDURLsConstructor.swift in Sources */, + 9F2AC59F267BDE0700F6149B /* GmailSearchExpressionGeneratorTests.swift in Sources */, 9F7DE8C5232029BD00F10B3E /* Core.swift in Sources */, 32DCAD6360C9EFF4FDD8EF6F /* DispatchTimeExtension.swift in Sources */, 9F7DE8C723202A0200F10B3E /* CoreHost.swift in Sources */, @@ -2477,6 +2486,7 @@ 9F17976D2368EEBD002BF770 /* SetupViewDecorator.swift in Sources */, 5ADEDCC023A43B0800EC495E /* KeyDetailInfoViewDecorator.swift in Sources */, D227C0E6250538780070F805 /* RemoteFoldersProvider.swift in Sources */, + 9F2AC5B1267BDED100F6149B /* GmailSearchExpressionGenerator.swift in Sources */, 9F953E09238310D500AEB98B /* KeyMethods.swift in Sources */, 9F92EE72236F165E009BE0D7 /* EncryptedStorage.swift in Sources */, 32DCA00224982EDA88D69C6E /* AppErr.swift in Sources */, diff --git a/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift b/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift index 90d15d85b..41a267681 100644 --- a/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift +++ b/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift @@ -12,31 +12,26 @@ import Promises extension GmailService: BackupProvider { func searchBackups(for email: String) -> Promise { - Logger.logVerbose("[GmailService] will begin searching for backups") - return Promise { resolve, _ in - let backupSearchExpressions = GeneralConstants.EmailConstant - .recoverAccountSearchSubject - .map { searchExpression(using: MessageSearchContext(expression: $0)) } - - Logger.logVerbose("[GmailService] searching with \(backupSearchExpressions.count) search expressions") - let backupMessages = try awaitPromise(all(backupSearchExpressions)) - .flatMap { $0 } - Logger.logVerbose("[GmailService] searching done, found \(backupMessages.count) backup messages") + Promise { resolve, _ in + logger.logVerbose("will begin searching for backups") + let query = backupGenerator.makeBackupQuery(with: GeneralConstants.EmailConstant.recoverAccountSearchSubject) + let backupMessages = try awaitPromise(searchExpression(using: MessageSearchContext(expression: query))) + logger.logVerbose("searching done, found \(backupMessages.count) backup messages") let uniqueMessages = Set(backupMessages) let attachments = uniqueMessages .compactMap { message -> [(String, String)]? in - Logger.logVerbose("[GmailService] processing backup '\(message.subject ?? "-")' with \(message.attachmentIds.count) attachments") + logger.logVerbose("processing backup '\(message.subject ?? "-")' with \(message.attachmentIds.count) attachments") guard let identifier = message.identifier.stringId else { - Logger.logVerbose("[GmailService] skipping this last backup?") + logger.logVerbose("skipping this last backup?") return nil } return message.attachmentIds.map { (identifier, $0) } } .flatMap { $0 } .map(findAttachment) - Logger.logVerbose("[GmailService] downloading \(attachments.count) attachments with possible backups in them") + logger.logVerbose("downloading \(attachments.count) attachments with possible backups in them") let data = try awaitPromise(all(attachments)).joined - Logger.logVerbose("[GmailService] downloaded \(attachments.count) attachments that contain \(data.count / 1024)kB of data") + logger.logVerbose("downloaded \(attachments.count) attachments that contain \(data.count / 1024)kB of data") resolve(data) } } diff --git a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift index ec271ff57..43bda372b 100644 --- a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift @@ -12,6 +12,7 @@ import GoogleAPIClientForREST struct GmailService: MailServiceProvider { let mailServiceProviderType = MailServiceProviderType.gmail let userService: GoogleUserService + let backupGenerator: GmailSearchBackupGenerator let logger = Logger.nested("GmailService") var gmailService: GTLRService { @@ -25,8 +26,12 @@ struct GmailService: MailServiceProvider { return service } - init(userService: GoogleUserService = GoogleUserService()) { + init( + userService: GoogleUserService = GoogleUserService(), + backupGenerator: GmailSearchBackupGenerator = GmailSearchExpressionGenerator() + ) { self.userService = userService + self.backupGenerator = backupGenerator } } diff --git a/FlowCrypt/Functionality/Mail Provider/SearchMessage Provider/GmailSearchExpressionGenerator.swift b/FlowCrypt/Functionality/Mail Provider/SearchMessage Provider/GmailSearchExpressionGenerator.swift new file mode 100644 index 000000000..5328f2a9f --- /dev/null +++ b/FlowCrypt/Functionality/Mail Provider/SearchMessage Provider/GmailSearchExpressionGenerator.swift @@ -0,0 +1,22 @@ +// +// GmailSearchExpressionGenerator.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 17.06.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import Foundation + +protocol GmailSearchBackupGenerator { + func makeBackupQuery(with expressions: [String]) -> String +} + +final class GmailSearchExpressionGenerator: GmailSearchBackupGenerator { + func makeBackupQuery(with expressions: [String]) -> String { + let folderQuery = "in:anywhere" + let search = expressions.map { "\"\($0)\"" } + let searchExpressionQuery = search.joined(separator: " OR ") + return folderQuery + " " + searchExpressionQuery + " has:attachment" + } +} diff --git a/FlowCryptTests/GmailSearchExpressionGeneratorTests.swift b/FlowCryptTests/GmailSearchExpressionGeneratorTests.swift new file mode 100644 index 000000000..c0d29930a --- /dev/null +++ b/FlowCryptTests/GmailSearchExpressionGeneratorTests.swift @@ -0,0 +1,30 @@ +// +// GmailSearchExpressionGeneratorTests.swift +// FlowCryptTests +// +// Created by Anton Kharchevskyi on 17.06.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import XCTest + +class GmailSearchExpressionGeneratorTests: XCTestCase { + + var sut: GmailSearchExpressionGenerator! + + override func setUp() { + sut = GmailSearchExpressionGenerator() + } + + func testBackupExpressions() { + let expressions = GeneralConstants.EmailConstant.recoverAccountSearchSubject + let result = sut.makeBackupQuery(with: expressions) + + XCTAssertTrue(result.contains("in:anywhere ")) + XCTAssertTrue(result.contains("\"Your FlowCrypt Backup\"")) + XCTAssertTrue(result.contains("\"Your CryptUp Backup\"")) + XCTAssertTrue(result.contains("\"CryptUP Account Backup\"")) + XCTAssertTrue(result.contains("\"All you need to know about CryptUP (contains a backup)\"")) + XCTAssertTrue(result.contains(" has:attachment")) + } +} From e7e57ddb46cef91db98116a49c06f27b9f7e6379 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Tue, 22 Jun 2021 18:28:49 +0300 Subject: [PATCH 02/15] Use Core to generate backup search query --- FlowCrypt/Core/Core.swift | 28 +++++++++++++------ .../GmailServiceErrorHandler.swift | 2 ++ .../Backup Provider/Gmail+Backup.swift | 7 +++-- .../Mail Provider/Gmail/GmailService.swift | 6 ++-- .../Gmail/GmailServiceError.swift | 2 ++ .../GmailSearchExpressionGenerator.swift | 19 +++++++------ 6 files changed, 42 insertions(+), 22 deletions(-) diff --git a/FlowCrypt/Core/Core.swift b/FlowCrypt/Core/Core.swift index dcc87cf1b..11410410f 100644 --- a/FlowCrypt/Core/Core.swift +++ b/FlowCrypt/Core/Core.swift @@ -31,22 +31,22 @@ final class Core: KeyDecrypter { private init() {} - public func version() throws -> CoreRes.Version { + func version() throws -> CoreRes.Version { let r = try call("version", jsonDict: nil, data: nil) return try r.json.decodeJson(as: CoreRes.Version.self) } - public func parseKeys(armoredOrBinary: Data) throws -> CoreRes.ParseKeys { + func parseKeys(armoredOrBinary: Data) throws -> CoreRes.ParseKeys { let r = try call("parseKeys", jsonDict: [String: String](), data: armoredOrBinary) return try r.json.decodeJson(as: CoreRes.ParseKeys.self) } - public func decryptKey(armoredPrv: String, passphrase: String) throws -> CoreRes.DecryptKey { + func decryptKey(armoredPrv: String, passphrase: String) throws -> CoreRes.DecryptKey { let r = try call("decryptKey", jsonDict: ["armored": armoredPrv, "passphrases": [passphrase]], data: nil) return try r.json.decodeJson(as: CoreRes.DecryptKey.self) } - public func parseDecryptMsg(encrypted: Data, keys: [PrvKeyInfo], msgPwd: String?, isEmail: Bool) throws -> CoreRes.ParseDecryptMsg { + func parseDecryptMsg(encrypted: Data, keys: [PrvKeyInfo], msgPwd: String?, isEmail: Bool) throws -> CoreRes.ParseDecryptMsg { let json: [String : Any?]? = [ "keys": try keys.map { try $0.toJsonEncodedDict() }, "isEmail": isEmail, @@ -77,7 +77,7 @@ final class Core: KeyDecrypter { ) } - public func composeEmail(msg: SendableMsg, fmt: MsgFmt, pubKeys: [String]?) throws -> CoreRes.ComposeEmail { + func composeEmail(msg: SendableMsg, fmt: MsgFmt, pubKeys: [String]?) throws -> CoreRes.ComposeEmail { let r = try call("composeEmail", jsonDict: [ "text": msg.text, "to": msg.to, @@ -94,18 +94,18 @@ final class Core: KeyDecrypter { return CoreRes.ComposeEmail(mimeEncoded: r.data) } - public func generateKey(passphrase: String, variant: KeyVariant, userIds: [UserId]) throws -> CoreRes.GenerateKey { + func generateKey(passphrase: String, variant: KeyVariant, userIds: [UserId]) throws -> CoreRes.GenerateKey { let request: [String: Any] = ["passphrase": passphrase, "variant": String(variant.rawValue), "userIds": try userIds.map { try $0.toJsonEncodedDict() }] let r = try call("generateKey", jsonDict: request, data: nil) return try r.json.decodeJson(as: CoreRes.GenerateKey.self) } - public func zxcvbnStrengthBar(passPhrase: String) throws -> CoreRes.ZxcvbnStrengthBar { + func zxcvbnStrengthBar(passPhrase: String) throws -> CoreRes.ZxcvbnStrengthBar { let r = try call("zxcvbnStrengthBar", jsonDict: ["value": passPhrase, "purpose": "passphrase"], data: nil) return try r.json.decodeJson(as: CoreRes.ZxcvbnStrengthBar.self) } - public func startInBackgroundIfNotAlreadyRunning(_ completion: @escaping (() -> Void)) { + func startInBackgroundIfNotAlreadyRunning(_ completion: @escaping (() -> Void)) { if self.ready { completion() } @@ -135,7 +135,7 @@ final class Core: KeyDecrypter { } } - public func blockUntilReadyOrThrow() throws { + func blockUntilReadyOrThrow() throws { // This will block the thread for up to 1000ms if the app was just started and Core method was called before JSContext is ready // It should only affect the user if Core method was called within 500-800ms of starting the app let start = DispatchTime.now() @@ -147,6 +147,12 @@ final class Core: KeyDecrypter { } } + func gmailBackupSearch(for email: String) -> String? { + let response = try? call("gmailBackupSearch", jsonDict: ["acctEmail": email], data: nil) + let result = try? response?.json.decodeJson(as: GmailBackupSearchResponse.self) + return result?.query + } + // private private func call(_ endpoint: String, jsonDict: [String: Any?]?, data: Data?) throws -> RawRes { @@ -179,3 +185,7 @@ final class Core: KeyDecrypter { let data: Data } } + +private struct GmailBackupSearchResponse: Decodable { + let query: String +} diff --git a/FlowCrypt/Functionality/Error Handling/GmailServiceErrorHandler.swift b/FlowCrypt/Functionality/Error Handling/GmailServiceErrorHandler.swift index 57c375308..4fa48a6d4 100644 --- a/FlowCrypt/Functionality/Error Handling/GmailServiceErrorHandler.swift +++ b/FlowCrypt/Functionality/Error Handling/GmailServiceErrorHandler.swift @@ -25,6 +25,8 @@ struct GmailServiceErrorHandler: ErrorHandler { errorMessage = "Failed to parse Gmail API Response. Missed message payload" case .providerError(let error): errorMessage = "Provider error \(error)" + case .missedBackupQuery: + errorMessage = "Failed to get backup search query" } default: errorMessage = nil diff --git a/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift b/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift index 41a267681..947736710 100644 --- a/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift +++ b/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift @@ -12,9 +12,12 @@ import Promises extension GmailService: BackupProvider { func searchBackups(for email: String) -> Promise { - Promise { resolve, _ in + Promise { resolve, reject in logger.logVerbose("will begin searching for backups") - let query = backupGenerator.makeBackupQuery(with: GeneralConstants.EmailConstant.recoverAccountSearchSubject) + guard let query = backupSearchQueryProvider.makeBackupQuery(for: email) else { + return reject(GmailServiceError.missedBackupQuery) + } + let backupMessages = try awaitPromise(searchExpression(using: MessageSearchContext(expression: query))) logger.logVerbose("searching done, found \(backupMessages.count) backup messages") let uniqueMessages = Set(backupMessages) diff --git a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift index 43bda372b..95371b089 100644 --- a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift @@ -12,7 +12,7 @@ import GoogleAPIClientForREST struct GmailService: MailServiceProvider { let mailServiceProviderType = MailServiceProviderType.gmail let userService: GoogleUserService - let backupGenerator: GmailSearchBackupGenerator + let backupSearchQueryProvider: GmailBackupSearchQueryProviderType let logger = Logger.nested("GmailService") var gmailService: GTLRService { @@ -28,10 +28,10 @@ struct GmailService: MailServiceProvider { init( userService: GoogleUserService = GoogleUserService(), - backupGenerator: GmailSearchBackupGenerator = GmailSearchExpressionGenerator() + backupSearchQueryProvider: GmailBackupSearchQueryProviderType = GmailBackupSearchQueryProvider() ) { self.userService = userService - self.backupGenerator = backupGenerator + self.backupSearchQueryProvider = backupSearchQueryProvider } } diff --git a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailServiceError.swift b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailServiceError.swift index da4258d1d..7848b8746 100644 --- a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailServiceError.swift +++ b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailServiceError.swift @@ -19,4 +19,6 @@ enum GmailServiceError: Error { case missedMessageInfo(String) /// Provider Error case providerError(Error) + /// Empty or invalid backup search query + case missedBackupQuery } diff --git a/FlowCrypt/Functionality/Mail Provider/SearchMessage Provider/GmailSearchExpressionGenerator.swift b/FlowCrypt/Functionality/Mail Provider/SearchMessage Provider/GmailSearchExpressionGenerator.swift index 5328f2a9f..fdb6a8869 100644 --- a/FlowCrypt/Functionality/Mail Provider/SearchMessage Provider/GmailSearchExpressionGenerator.swift +++ b/FlowCrypt/Functionality/Mail Provider/SearchMessage Provider/GmailSearchExpressionGenerator.swift @@ -8,15 +8,18 @@ import Foundation -protocol GmailSearchBackupGenerator { - func makeBackupQuery(with expressions: [String]) -> String +protocol GmailBackupSearchQueryProviderType { + func makeBackupQuery(for email: String) -> String? } -final class GmailSearchExpressionGenerator: GmailSearchBackupGenerator { - func makeBackupQuery(with expressions: [String]) -> String { - let folderQuery = "in:anywhere" - let search = expressions.map { "\"\($0)\"" } - let searchExpressionQuery = search.joined(separator: " OR ") - return folderQuery + " " + searchExpressionQuery + " has:attachment" +final class GmailBackupSearchQueryProvider: GmailBackupSearchQueryProviderType { + let core: Core + + init(core: Core = .shared) { + self.core = core + } + + func makeBackupQuery(for email: String) -> String? { + core.gmailBackupSearch(for: email) } } From ef8a5529ea40e6d0aafa86b2eefcf8623aa2d8f9 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Wed, 23 Jun 2021 22:54:11 +0300 Subject: [PATCH 03/15] Update Search backup functionality --- .../Setup/SetupBackupsViewController.swift | 2 +- FlowCrypt/Core/Core.swift | 8 ++-- .../Backup Provider/Gmail+Backup.swift | 45 ++++++++++--------- .../Gmail/GmailServiceError.swift | 2 +- .../GmailSearchExpressionGenerator.swift | 6 +-- 5 files changed, 32 insertions(+), 31 deletions(-) diff --git a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift index 9c6772f94..d9d5de698 100644 --- a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift @@ -198,7 +198,7 @@ extension SetupBackupsViewController: ASTableDelegate, ASTableDataSource { case .description: return SetupTitleNode( SetupTitleNode.Input( - title: self.decorator.subtitle(for: .common), + title: self.decorator.subtitle(for: .fetchedKeys(self.fetchedEncryptedKeys.count)), insets: self.decorator.insets.subTitleInset, backgroundColor: .backgroundColor ) diff --git a/FlowCrypt/Core/Core.swift b/FlowCrypt/Core/Core.swift index 11410410f..4d7cd2a47 100644 --- a/FlowCrypt/Core/Core.swift +++ b/FlowCrypt/Core/Core.swift @@ -147,10 +147,10 @@ final class Core: KeyDecrypter { } } - func gmailBackupSearch(for email: String) -> String? { - let response = try? call("gmailBackupSearch", jsonDict: ["acctEmail": email], data: nil) - let result = try? response?.json.decodeJson(as: GmailBackupSearchResponse.self) - return result?.query + func gmailBackupSearch(for email: String) throws -> String { + let response = try call("gmailBackupSearch", jsonDict: ["acctEmail": email], data: nil) + let result = try response.json.decodeJson(as: GmailBackupSearchResponse.self) + return result.query } // private diff --git a/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift b/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift index 947736710..8287f2c6e 100644 --- a/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift +++ b/FlowCrypt/Functionality/Mail Provider/Backup Provider/Gmail+Backup.swift @@ -13,29 +13,30 @@ import Promises extension GmailService: BackupProvider { func searchBackups(for email: String) -> Promise { Promise { resolve, reject in - logger.logVerbose("will begin searching for backups") - guard let query = backupSearchQueryProvider.makeBackupQuery(for: email) else { - return reject(GmailServiceError.missedBackupQuery) - } - - let backupMessages = try awaitPromise(searchExpression(using: MessageSearchContext(expression: query))) - logger.logVerbose("searching done, found \(backupMessages.count) backup messages") - let uniqueMessages = Set(backupMessages) - let attachments = uniqueMessages - .compactMap { message -> [(String, String)]? in - logger.logVerbose("processing backup '\(message.subject ?? "-")' with \(message.attachmentIds.count) attachments") - guard let identifier = message.identifier.stringId else { - logger.logVerbose("skipping this last backup?") - return nil + do { + logger.logVerbose("will begin searching for backups") + let query = try backupSearchQueryProvider.makeBackupQuery(for: email) + let backupMessages = try awaitPromise(searchExpression(using: MessageSearchContext(expression: query))) + logger.logVerbose("searching done, found \(backupMessages.count) backup messages") + let uniqueMessages = Set(backupMessages) + let attachments = uniqueMessages + .compactMap { message -> [(String, String)]? in + logger.logVerbose("processing backup '\(message.subject ?? "-")' with \(message.attachmentIds.count) attachments") + guard let identifier = message.identifier.stringId else { + logger.logVerbose("skipping this last backup?") + return nil + } + return message.attachmentIds.map { (identifier, $0) } } - return message.attachmentIds.map { (identifier, $0) } - } - .flatMap { $0 } - .map(findAttachment) - logger.logVerbose("downloading \(attachments.count) attachments with possible backups in them") - let data = try awaitPromise(all(attachments)).joined - logger.logVerbose("downloaded \(attachments.count) attachments that contain \(data.count / 1024)kB of data") - resolve(data) + .flatMap { $0 } + .map(findAttachment) + logger.logVerbose("downloading \(attachments.count) attachments with possible backups in them") + let data = try awaitPromise(all(attachments)).joined + logger.logVerbose("downloaded \(attachments.count) attachments that contain \(data.count / 1024)kB of data") + resolve(data) + } catch { + reject(GmailServiceError.missedBackupQuery(error)) + } } } diff --git a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailServiceError.swift b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailServiceError.swift index 7848b8746..e50939f4a 100644 --- a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailServiceError.swift +++ b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailServiceError.swift @@ -20,5 +20,5 @@ enum GmailServiceError: Error { /// Provider Error case providerError(Error) /// Empty or invalid backup search query - case missedBackupQuery + case missedBackupQuery(Error) } diff --git a/FlowCrypt/Functionality/Mail Provider/SearchMessage Provider/GmailSearchExpressionGenerator.swift b/FlowCrypt/Functionality/Mail Provider/SearchMessage Provider/GmailSearchExpressionGenerator.swift index fdb6a8869..5564fcfcf 100644 --- a/FlowCrypt/Functionality/Mail Provider/SearchMessage Provider/GmailSearchExpressionGenerator.swift +++ b/FlowCrypt/Functionality/Mail Provider/SearchMessage Provider/GmailSearchExpressionGenerator.swift @@ -9,7 +9,7 @@ import Foundation protocol GmailBackupSearchQueryProviderType { - func makeBackupQuery(for email: String) -> String? + func makeBackupQuery(for email: String) throws -> String } final class GmailBackupSearchQueryProvider: GmailBackupSearchQueryProviderType { @@ -19,7 +19,7 @@ final class GmailBackupSearchQueryProvider: GmailBackupSearchQueryProviderType { self.core = core } - func makeBackupQuery(for email: String) -> String? { - core.gmailBackupSearch(for: email) + func makeBackupQuery(for email: String) throws -> String { + try core.gmailBackupSearch(for: email) } } From 32f49f40278067ec2c4c30ce13b237cb55a362ec Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Wed, 23 Jun 2021 23:13:28 +0300 Subject: [PATCH 04/15] Start working on GmailService tests --- FlowCrypt.xcodeproj/project.pbxproj | 83 ++++--------------- .../Mail Provider/Gmail/GmailService.swift | 4 +- .../Services/GoogleUserService.swift | 7 +- .../Mail Provider/GmailServiceTest.swift | 46 ++++++++++ Podfile | 1 + Podfile.lock | 2 +- 6 files changed, 70 insertions(+), 73 deletions(-) create mode 100644 FlowCryptAppTests/Functionallity/Mail Provider/GmailServiceTest.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index d51e0f4ee..b331254f5 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -64,12 +64,8 @@ 9F228BA923C67587005D2CB6 /* UserCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F228BA823C67587005D2CB6 /* UserCredentials.swift */; }; 9F228BAA23C67729005D2CB6 /* DataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCAEFF16F5D91A35791730 /* DataExtensions.swift */; }; 9F23EA50237217140017DFED /* ComposeViewDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F23EA4F237217140017DFED /* ComposeViewDecorator.swift */; }; - 9F268891237DC55600428A94 /* ImportKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F268890237DC55600428A94 /* ImportKeyViewController.swift */; }; - 9F268894237DD98900428A94 /* EnterPassPhraseViewDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F268893237DD98900428A94 /* EnterPassPhraseViewDecorator.swift */; }; - 9F2AC59F267BDE0700F6149B /* GmailSearchExpressionGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2AC59E267BDE0700F6149B /* GmailSearchExpressionGeneratorTests.swift */; }; - 9F2AC5B1267BDED100F6149B /* GmailSearchExpressionGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2AC5B0267BDED100F6149B /* GmailSearchExpressionGenerator.swift */; }; - 9F2AC5BC267BDEF500F6149B /* GmailSearchExpressionGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2AC5B0267BDED100F6149B /* GmailSearchExpressionGenerator.swift */; }; 9F268891237DC55600428A94 /* SetupManuallyImportKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F268890237DC55600428A94 /* SetupManuallyImportKeyViewController.swift */; }; + 9F2AC5B1267BDED100F6149B /* GmailSearchExpressionGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2AC5B0267BDED100F6149B /* GmailSearchExpressionGenerator.swift */; }; 9F31AB8C23298B3F00CF87EA /* Imap+retry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F31AB8B23298B3F00CF87EA /* Imap+retry.swift */; }; 9F31AB8E23298BCF00CF87EA /* Imap+folders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F31AB8D23298BCF00CF87EA /* Imap+folders.swift */; }; 9F31AB91232993F500CF87EA /* Imap+session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F31AB90232993F500CF87EA /* Imap+session.swift */; }; @@ -148,6 +144,7 @@ 9FC4117D268118AE004C0A69 /* EncryptedPassPhraseStorageMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBCF266EBE1D00F3BF5D /* EncryptedPassPhraseStorageMock.swift */; }; 9FC41183268118B1004C0A69 /* EmailProviderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBC1266EBE0100F3BF5D /* EmailProviderMock.swift */; }; 9FC4120926811D00004C0A69 /* PromiseExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC411892681191D004C0A69 /* PromiseExtensions.swift */; }; + 9FC413442683C912004C0A69 /* GmailServiceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC413432683C912004C0A69 /* GmailServiceTest.swift */; }; 9FC7EAB3266A404D00F3BF5D /* PassPhrase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EAB2266A404D00F3BF5D /* PassPhrase.swift */; }; 9FC7EB76266EB67B00F3BF5D /* EncryptedStorageProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EB75266EB67B00F3BF5D /* EncryptedStorageProtocols.swift */; }; 9FC7EBAA266EBD3700F3BF5D /* InMemoryPassPhraseStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC7EBA9266EBD3700F3BF5D /* InMemoryPassPhraseStorage.swift */; }; @@ -441,11 +438,8 @@ 9F228BA823C67587005D2CB6 /* UserCredentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserCredentials.swift; sourceTree = ""; }; 9F23EA4D237216FA0017DFED /* TextViewCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewCellNode.swift; sourceTree = ""; }; 9F23EA4F237217140017DFED /* ComposeViewDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewDecorator.swift; sourceTree = ""; }; - 9F268890237DC55600428A94 /* ImportKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportKeyViewController.swift; sourceTree = ""; }; - 9F268893237DD98900428A94 /* EnterPassPhraseViewDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterPassPhraseViewDecorator.swift; sourceTree = ""; }; - 9F2AC59E267BDE0700F6149B /* GmailSearchExpressionGeneratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GmailSearchExpressionGeneratorTests.swift; sourceTree = ""; }; - 9F2AC5B0267BDED100F6149B /* GmailSearchExpressionGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GmailSearchExpressionGenerator.swift; sourceTree = ""; }; 9F268890237DC55600428A94 /* SetupManuallyImportKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupManuallyImportKeyViewController.swift; sourceTree = ""; }; + 9F2AC5B0267BDED100F6149B /* GmailSearchExpressionGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GmailSearchExpressionGenerator.swift; sourceTree = ""; }; 9F2AC5C6267BE99E00F6149B /* FlowCryptAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FlowCryptAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 9F2AC5CA267BE99E00F6149B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9F31AB8B23298B3F00CF87EA /* Imap+retry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Imap+retry.swift"; sourceTree = ""; }; @@ -532,6 +526,7 @@ 9FC411342595EA94001180A8 /* Imap+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Imap+Search.swift"; sourceTree = ""; }; 9FC4114B25961CEA001180A8 /* MailServiceProviderType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailServiceProviderType.swift; sourceTree = ""; }; 9FC411892681191D004C0A69 /* PromiseExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PromiseExtensions.swift; sourceTree = ""; }; + 9FC413432683C912004C0A69 /* GmailServiceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GmailServiceTest.swift; sourceTree = ""; }; 9FC7EAB2266A404D00F3BF5D /* PassPhrase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassPhrase.swift; sourceTree = ""; }; 9FC7EB75266EB67B00F3BF5D /* EncryptedStorageProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedStorageProtocols.swift; sourceTree = ""; }; 9FC7EBA2266EB95300F3BF5D /* PassPhraseStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassPhraseStorageTests.swift; sourceTree = ""; }; @@ -786,6 +781,7 @@ 21F836A62652A1B700B2448C /* Functionallity */ = { isa = PBXGroup; children = ( + 9FC4136B2683CBBD004C0A69 /* Mail Provider */, 9F4164162665757700106194 /* PGP */, 21F836A72652A1CD00B2448C /* WKDURLs */, 9F4163F3266574CF00106194 /* Services */, @@ -1253,6 +1249,14 @@ path = "Mail Provider"; sourceTree = ""; }; + 9FC4136B2683CBBD004C0A69 /* Mail Provider */ = { + isa = PBXGroup; + children = ( + 9FC413432683C912004C0A69 /* GmailServiceTest.swift */, + ); + path = "Mail Provider"; + sourceTree = ""; + }; 9FC7EBB6266EBDF000F3BF5D /* PassPhraseStorageTests */ = { isa = PBXGroup; children = ( @@ -1351,24 +1355,6 @@ path = Pgp; sourceTree = ""; }; - A3DAD5FC22E4574B00F2C4CD /* FlowCryptTests */ = { - isa = PBXGroup; - children = ( - 21EA3B2126565B4100691848 /* Models Parsing */, - 21F836A62652A1B700B2448C /* Functionallity */, - 9F3EF32923B15C9500FA0CEF /* ImapHelperTest.swift */, - 5A39F438239ECDD0001F4607 /* ExtensionTests.swift */, - A3DAD5FD22E4574B00F2C4CD /* FlowCryptCoreTests.swift */, - A3DAD5FF22E4574B00F2C4CD /* Info.plist */, - A3DAD60722E4588800F2C4CD /* TestData.swift */, - A3A680EF22EEF0BF00905813 /* FlowCryptTests-Bridging-Header.h */, - D2A9CA44242622F800E1D898 /* GeneralConstantsTest.swift */, - 9F003D9D25EA910B00EB38C0 /* LocalStorageTests.swift */, - 9F2AC59E267BDE0700F6149B /* GmailSearchExpressionGeneratorTests.swift */, - ); - path = FlowCryptTests; - sourceTree = ""; - }; A3DAD60C22E469E400F2C4CD /* Resources */ = { isa = PBXGroup; children = ( @@ -2344,6 +2330,7 @@ 9F97653D267E17C90058419D /* LocalStorageTests.swift in Sources */, 9FC41177268118AD004C0A69 /* LocalPassPhraseStorageMock.swift in Sources */, 9F9764F4267E15CC0058419D /* ExtensionTests.swift in Sources */, + 9FC413442683C912004C0A69 /* GmailServiceTest.swift in Sources */, 9F976556267E186D0058419D /* DomainRulesTests.swift in Sources */, 9FC41171268118A7004C0A69 /* PassPhraseStorageTests.swift in Sources */, ); @@ -2367,48 +2354,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - A3DAD5F722E4574A00F2C4CD /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - D2D27B7A248A874C007346FA /* BigIntExtension.swift in Sources */, - 9F3EF32B23B16ADE00FA0CEF /* CommonExtensions.swift in Sources */, - A357699622EA2BC8009242C4 /* KeyInfo.swift in Sources */, - 9FC411902596229D001180A8 /* AppErr.swift in Sources */, - D212D35E24C1AACF00035991 /* PrvKeyInfo.swift in Sources */, - 9F1D5769263B540100477938 /* Logger.swift in Sources */, - A3DAD5FE22E4574B00F2C4CD /* FlowCryptCoreTests.swift in Sources */, - 21EA3B2326565B5D00691848 /* DomainRulesTests.swift in Sources */, - 21F836CC2652A38700B2448C /* ZBase32EncodingTests.swift in Sources */, - D2A9CA45242622F800E1D898 /* GeneralConstantsTest.swift in Sources */, - A3DAD60B22E458C300F2C4CD /* DataExtensions.swift in Sources */, - 21EA3B532656611C00691848 /* OrganisationalRule.swift in Sources */, - 9F2AC5BC267BDEF500F6149B /* GmailSearchExpressionGenerator.swift in Sources */, - A3DAD60822E4588800F2C4CD /* TestData.swift in Sources */, - D212D36124C1AC0D00035991 /* KeyDetails.swift in Sources */, - 9F6EE18C25A8AF970059BA51 /* GeneralConstants.swift in Sources */, - A3DAD60A22E458C100F2C4CD /* CodableExntensions.swift in Sources */, - D254733424C597CD00DEE698 /* CoreTypesTest.swift in Sources */, - 9F003DA425EA928200EB38C0 /* LocalStorage.swift in Sources */, - 9F7DE8C6232029D000F10B3E /* CoreTypes.swift in Sources */, - 9F3EF32A23B15C9600FA0CEF /* ImapHelperTest.swift in Sources */, - 5A39F439239ECDD0001F4607 /* ExtensionTests.swift in Sources */, - D212D36524C1AC4800035991 /* KeyId.swift in Sources */, - 9F003DBC25EA92D000EB38C0 /* LogOutHandler.swift in Sources */, - 21F836A02652A19A00B2448C /* WKDURLsConstructor.swift in Sources */, - 9F2AC59F267BDE0700F6149B /* GmailSearchExpressionGeneratorTests.swift in Sources */, - 9F7DE8C5232029BD00F10B3E /* Core.swift in Sources */, - 32DCAD6360C9EFF4FDD8EF6F /* DispatchTimeExtension.swift in Sources */, - 9F7DE8C723202A0200F10B3E /* CoreHost.swift in Sources */, - 9F3EF32823B15C8400FA0CEF /* ImapHelper.swift in Sources */, - 9F003D9E25EA910B00EB38C0 /* LocalStorageTests.swift in Sources */, - 32DCAF683D87EA6221F71335 /* SequenceExtensions.swift in Sources */, - 9F589F11238C7DDC007FD759 /* User.swift in Sources */, - 21F836D32652A46E00B2448C /* WKDURLsConstructorTests.swift in Sources */, - D2E26F6D24F263B600612AF1 /* KeyAlgo.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; C132B9AC1EC2DBD800763715 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; diff --git a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift index 95371b089..1c5e7cf84 100644 --- a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift @@ -11,7 +11,7 @@ import GoogleAPIClientForREST struct GmailService: MailServiceProvider { let mailServiceProviderType = MailServiceProviderType.gmail - let userService: GoogleUserService + let userService: GoogleUserServiceType let backupSearchQueryProvider: GmailBackupSearchQueryProviderType let logger = Logger.nested("GmailService") @@ -27,7 +27,7 @@ struct GmailService: MailServiceProvider { } init( - userService: GoogleUserService = GoogleUserService(), + userService: GoogleUserServiceType = GoogleUserService(), backupSearchQueryProvider: GmailBackupSearchQueryProviderType = GmailBackupSearchQueryProvider() ) { self.userService = userService diff --git a/FlowCrypt/Functionality/Services/GoogleUserService.swift b/FlowCrypt/Functionality/Services/GoogleUserService.swift index c1d4f2580..cc9ef413a 100644 --- a/FlowCrypt/Functionality/Services/GoogleUserService.swift +++ b/FlowCrypt/Functionality/Services/GoogleUserService.swift @@ -31,7 +31,12 @@ struct GoogleUser: Codable { let picture: URL? } -final class GoogleUserService: NSObject { +protocol GoogleUserServiceType { + var authorization: GTMAppAuthFetcherAuthorization? { get } + func renewSession() -> Promise +} + +final class GoogleUserService: NSObject, GoogleUserServiceType { private enum Constants { static let index = "GTMAppAuthAuthorizerIndex" } diff --git a/FlowCryptAppTests/Functionallity/Mail Provider/GmailServiceTest.swift b/FlowCryptAppTests/Functionallity/Mail Provider/GmailServiceTest.swift new file mode 100644 index 000000000..8ed79c797 --- /dev/null +++ b/FlowCryptAppTests/Functionallity/Mail Provider/GmailServiceTest.swift @@ -0,0 +1,46 @@ +// +// GmailServiceTest.swift +// FlowCryptAppTests +// +// Created by Anton Kharchevskyi on 23.06.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import XCTest +import Promises +import GTMAppAuth +@testable import FlowCrypt + +class GmailServiceTest: XCTestCase { + + var sut: GmailService! + var userService: GoogleUserServiceMock! + var backupSearchQueryProvider: GmailBackupSearchQueryProviderMock! + + override func setUp() { + userService = GoogleUserServiceMock() + backupSearchQueryProvider = GmailBackupSearchQueryProviderMock() + sut = GmailService(userService: userService, backupSearchQueryProvider: backupSearchQueryProvider) + } + + func testSearchBackups() { + + } +} + +// MARK: - Mock +class GoogleUserServiceMock: GoogleUserServiceType { + var authorization: GTMAppAuthFetcherAuthorization? = nil + + var renewSessionResult: Result = .success(()) + func renewSession() -> Promise { + Promise.resolveAfter(timeout: 1, with: renewSessionResult) + } +} + +class GmailBackupSearchQueryProviderMock: GmailBackupSearchQueryProviderType { + var makeBackupQueryResult: Result = .success("query") + func makeBackupQuery(for email: String) throws -> String { + try makeBackupQueryResult.get() + } +} diff --git a/Podfile b/Podfile index e59d160b6..ad908f5a1 100644 --- a/Podfile +++ b/Podfile @@ -59,6 +59,7 @@ target 'FlowCryptAppTests' do pod 'mailcore2-ios' pod 'IDZSwiftCommonCrypto' pod 'PromisesSwift' + pod 'GTMAppAuth' end ## Set IPHONEOS_DEPLOYMENT_TARGET for all pods diff --git a/Podfile.lock b/Podfile.lock index 229265844..bd1cdb69a 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -142,6 +142,6 @@ SPEC CHECKSUMS: Texture: 2f109e937850d94d1d07232041c9c7313ccddb81 Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 -PODFILE CHECKSUM: d0ba5e2b8f96be098d5624c44930008b96f6fea7 +PODFILE CHECKSUM: 7a526c976e3f30d14e1a652b68f7b95b30c1c7c3 COCOAPODS: 1.10.1 From 1f4d82bd103f3512c8eabd0dd14b7f1ab0c4dc2d Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Wed, 23 Jun 2021 23:22:47 +0300 Subject: [PATCH 05/15] Add testSearchBackupsWhenErrorInQuery --- .../Mail Provider/GmailServiceTest.swift | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/FlowCryptAppTests/Functionallity/Mail Provider/GmailServiceTest.swift b/FlowCryptAppTests/Functionallity/Mail Provider/GmailServiceTest.swift index 8ed79c797..aee634824 100644 --- a/FlowCryptAppTests/Functionallity/Mail Provider/GmailServiceTest.swift +++ b/FlowCryptAppTests/Functionallity/Mail Provider/GmailServiceTest.swift @@ -23,8 +23,25 @@ class GmailServiceTest: XCTestCase { sut = GmailService(userService: userService, backupSearchQueryProvider: backupSearchQueryProvider) } - func testSearchBackups() { + func testSearchBackupsWhenErrorInQuery() { + backupSearchQueryProvider.makeBackupQueryResult = .failure(.some) + let expectation = XCTestExpectation() + sut.searchBackups(for: "james.bond@gmail.com") + .then(on: .main) { data in + + } + .catch(on: .main) { error in + switch error as? GmailServiceError { + case .missedBackupQuery(let underliningError): + if underliningError is MockError { + expectation.fulfill() + } + default: + break + } + } + wait(for: [expectation], timeout: 3, enforceOrder: true) } } From 1771be23f845c1288f4b6b7f0f13fd473a75c0b9 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Thu, 24 Jun 2021 18:31:08 +0300 Subject: [PATCH 06/15] Add logs to AppStart --- .../Setup/SetupBackupsViewController.swift | 4 +- FlowCrypt/Functionality/Pgp/KeyMethods.swift | 2 +- .../Functionality/Services/AppStartup.swift | 40 +++++++++++++------ 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift index d9d5de698..664db7140 100644 --- a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift @@ -11,6 +11,7 @@ final class SetupBackupsViewController: TableNodeViewController, PassPhraseSavea case title, description, passPhrase, divider, saveLocally, saveInMemory, action, optionalAction } + private lazy var logger = Logger.nested(in: Self.self, with: .setup) private let router: GlobalRouterType private let decorator: SetupViewDecorator private let core: Core @@ -21,7 +22,6 @@ final class SetupBackupsViewController: TableNodeViewController, PassPhraseSavea let passPhraseStorage: PassPhraseStorageType private var passPhrase: String? - private lazy var logger = Logger.nested(in: Self.self, with: .setup) var shouldSaveLocally = true { didSet { @@ -127,8 +127,10 @@ extension SetupBackupsViewController { } private func recoverAccount(with backups: [KeyDetails], and passPhrase: String) { + logger.logInfo("Start recoverAccount with \(backups.count)") let matchingKeyBackups = Set(keyMethods.filterByPassPhraseMatch(keys: backups, passPhrase: passPhrase)) + logger.logInfo("matchingKeyBackups = \(matchingKeyBackups.count)") guard matchingKeyBackups.isNotEmpty else { showAlert(message: "setup_wrong_pass_phrase_retry".localized) return diff --git a/FlowCrypt/Functionality/Pgp/KeyMethods.swift b/FlowCrypt/Functionality/Pgp/KeyMethods.swift index 6892d6fa8..525420762 100644 --- a/FlowCrypt/Functionality/Pgp/KeyMethods.swift +++ b/FlowCrypt/Functionality/Pgp/KeyMethods.swift @@ -40,7 +40,7 @@ final class KeyMethods: KeyMethodsType { } guard decrypted.decryptedKey != nil else { - logger.logInfo("Filtered. decryptedKey = nil") + logger.logInfo("Filtered. decryptedKey = nil for key \(key.longid)") return nil } diff --git a/FlowCrypt/Functionality/Services/AppStartup.swift b/FlowCrypt/Functionality/Services/AppStartup.swift index a433a3ba3..8bd2b0696 100644 --- a/FlowCrypt/Functionality/Services/AppStartup.swift +++ b/FlowCrypt/Functionality/Services/AppStartup.swift @@ -9,28 +9,36 @@ import Foundation import Promises -struct AppStartup { +final class AppStartup { private enum EntryPoint { case signIn, setupFlow(UserId), mainFlow } + private lazy var logger = Logger.nested(in: Self.self, with: .userAppStart) + func initializeApp(window: UIWindow, session: SessionType?) { + logger.logInfo("Initialize application with session \(session.debugDescription)") + DispatchQueue.promises = .global() window.rootViewController = BootstrapViewController() window.makeKeyAndVisible() - Promise { + + Promise { [weak self] in + guard let self = self else { return } + try awaitPromise(self.setupCore()) try self.setupMigrationIfNeeded() try self.setupSession() - }.then(on: .main) { - self.chooseView(for: window, session: session) - }.catch(on: .main) { error in - self.showErrorAlert(with: error, on: window, session: session) + }.then(on: .main) { [weak self] in + self?.chooseView(for: window, session: session) + }.catch(on: .main) { [weak self] error in + self?.showErrorAlert(with: error, on: window, session: session) } } private func setupCore() -> Promise { - Promise { resolve, _ in + Promise { [logger] resolve, _ in + logger.logInfo("Core") Core.shared.startInBackgroundIfNotAlreadyRunning { resolve(()) } @@ -38,10 +46,12 @@ struct AppStartup { } private func setupMigrationIfNeeded() throws { + logger.logInfo("Migration") try awaitPromise(DataService.shared.performMigrationIfNeeded()) } private func setupSession() throws { + logger.logInfo("Session") try awaitPromise(renewSessionIfValid()) } @@ -53,10 +63,7 @@ struct AppStartup { } private func chooseView(for window: UIWindow, session: SessionType?) { - guard let entryPoint = entryPointForUser(session: session) else { - assertionFailure("Internal error, can't choose desired entry point") - return - } + let entryPoint = entryPointForUser(session: session) let viewController: UIViewController @@ -73,20 +80,25 @@ struct AppStartup { window.rootViewController = viewController } - private func entryPointForUser(session: SessionType?) -> EntryPoint? { + private func entryPointForUser(session: SessionType?) -> EntryPoint { if !DataService.shared.isLoggedIn { + logger.logInfo("User is not logged in -> signIn") return .signIn } else if DataService.shared.isSetupFinished { + logger.logInfo("Setup finished -> mainFlow") return .mainFlow } else if let session = session, let userId = makeUserIdForSetup(session: session) { + logger.logInfo("User with session \(session) -> setupFlow") return .setupFlow(userId) } else { + logger.logInfo("User us not signed in -> mainFlow") return .signIn } } private func makeUserIdForSetup(session: SessionType) -> UserId? { guard let currentUser = DataService.shared.currentUser else { + logger.logInfo("Can't create user id for setup") return nil } @@ -95,13 +107,17 @@ struct AppStartup { switch session { case let .google(email, name, _): guard currentUser.email != email else { + logger.logInfo("UserId = current user id") return userId } + logger.logInfo("UserId = google user id") userId = UserId(email: email, name: name) case let .session(userObject): guard userObject.email != currentUser.email else { + logger.logInfo("UserId = current user id") return userId } + logger.logInfo("UserId = session user id") userId = UserId(email: userObject.email, name: userObject.name) } From f7971ca450c6434b0d334b273da68c7148b74a6e Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Thu, 24 Jun 2021 18:56:39 +0300 Subject: [PATCH 07/15] Fix KeyDetails longid --- FlowCrypt.xcodeproj/xcshareddata/xcschemes/FlowCrypt.xcscheme | 2 +- FlowCrypt/Core/Models/KeyDetails.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/FlowCrypt.xcodeproj/xcshareddata/xcschemes/FlowCrypt.xcscheme b/FlowCrypt.xcodeproj/xcshareddata/xcschemes/FlowCrypt.xcscheme index d92b235e8..4e86d244d 100644 --- a/FlowCrypt.xcodeproj/xcshareddata/xcschemes/FlowCrypt.xcscheme +++ b/FlowCrypt.xcodeproj/xcshareddata/xcschemes/FlowCrypt.xcscheme @@ -7,7 +7,7 @@ buildImplicitDependencies = "YES"> Date: Thu, 24 Jun 2021 20:18:43 +0300 Subject: [PATCH 08/15] Change back AppStartup to be a struct --- .../Functionality/Services/AppStartup.swift | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/FlowCrypt/Functionality/Services/AppStartup.swift b/FlowCrypt/Functionality/Services/AppStartup.swift index 867c50599..569f38c49 100644 --- a/FlowCrypt/Functionality/Services/AppStartup.swift +++ b/FlowCrypt/Functionality/Services/AppStartup.swift @@ -9,13 +9,13 @@ import Foundation import Promises -final class AppStartup { +private let logger = Logger.nested("AppStart") + +struct AppStartup { private enum EntryPoint { case signIn, setupFlow(UserId), mainFlow } - private lazy var logger = Logger.nested(in: Self.self, with: .userAppStart) - func initializeApp(window: UIWindow, session: SessionType?) { logger.logInfo("Initialize application with session \(session.debugDescription)") @@ -23,24 +23,22 @@ final class AppStartup { window.rootViewController = BootstrapViewController() window.makeKeyAndVisible() - Promise { [weak self] in - guard let self = self else { return } - + Promise { try awaitPromise(self.setupCore()) try self.setupMigrationIfNeeded() try self.setupSession() // Fetching of org rules is being called async in purpose we don't need to wait until it's fetched self.getUserOrgRulesIfNeeded() - }.then(on: .main) { [weak self] in - self?.chooseView(for: window, session: session) - }.catch(on: .main) { [weak self] error in - self?.showErrorAlert(with: error, on: window, session: session) + }.then(on: .main) { + self.chooseView(for: window, session: session) + }.catch(on: .main) { error in + self.showErrorAlert(with: error, on: window, session: session) } } private func setupCore() -> Promise { - Promise { [logger] resolve, _ in - logger.logInfo("Core") + Promise { resolve, _ in + logger.logInfo("Setup Core") Core.shared.startInBackgroundIfNotAlreadyRunning { resolve(()) } @@ -48,12 +46,12 @@ final class AppStartup { } private func setupMigrationIfNeeded() throws { - logger.logInfo("Migration") + logger.logInfo("Setup Migration") try awaitPromise(DataService.shared.performMigrationIfNeeded()) } private func setupSession() throws { - logger.logInfo("Session") + logger.logInfo("Setup Session") try awaitPromise(renewSessionIfValid()) } @@ -106,7 +104,7 @@ final class AppStartup { private func makeUserIdForSetup(session: SessionType) -> UserId? { guard let currentUser = DataService.shared.currentUser else { - logger.logInfo("Can't create user id for setup") + Logger.logInfo("Can't create user id for setup") return nil } @@ -115,17 +113,17 @@ final class AppStartup { switch session { case let .google(email, name, _): guard currentUser.email != email else { - logger.logInfo("UserId = current user id") + Logger.logInfo("UserId = current user id") return userId } - logger.logInfo("UserId = google user id") + Logger.logInfo("UserId = google user id") userId = UserId(email: email, name: name) case let .session(userObject): guard userObject.email != currentUser.email else { - logger.logInfo("UserId = current user id") + Logger.logInfo("UserId = current user id") return userId } - logger.logInfo("UserId = session user id") + Logger.logInfo("UserId = session user id") userId = UserId(email: userObject.email, name: userObject.name) } From 5d39d07b97ccd3f4693e698f279ba65a3b3d8944 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Sun, 27 Jun 2021 17:42:12 +0300 Subject: [PATCH 09/15] Add logs to debug issue --- .../DataManager/UserAccountService.swift | 15 +++++++++++++-- .../Services/Key Services/KeyDataStorage.swift | 5 +++++ FlowCrypt/Models/Realm Models/KeyInfo.swift | 7 ++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/FlowCrypt/Functionality/DataManager/UserAccountService.swift b/FlowCrypt/Functionality/DataManager/UserAccountService.swift index 05cc1bbd0..c51f2b71b 100644 --- a/FlowCrypt/Functionality/DataManager/UserAccountService.swift +++ b/FlowCrypt/Functionality/DataManager/UserAccountService.swift @@ -97,9 +97,16 @@ extension UserAccountService: UserAccountServiceType { } func cleanupSessions() { + logger.logInfo("Clean up sessions") + encryptedStorage.getAllUsers() - .filter { !encryptedStorage.doesAnyKeyExist(for: $0.email) } - .map(\.email) + .filter { + !encryptedStorage.doesAnyKeyExist(for: $0.email) + } + .map { + logger.logInfo("User session to clean up \($0.email)") + return $0.email + } .forEach(logOut) let users = encryptedStorage.getAllUsers() @@ -111,6 +118,8 @@ extension UserAccountService: UserAccountServiceType { @discardableResult private func switchActiveSession(for userObject: UserObject) -> SessionType? { + logger.logInfo("Try to switch session for \(userObject.email)") + let sessionType: SessionType switch userObject.authType { case .oAuthGmail(let token): @@ -136,6 +145,8 @@ extension UserAccountService: UserAccountServiceType { } private func logOut(user email: String) { + logger.logInfo("Log out user with \(email)") + switch dataService.currentAuthType { case .oAuthGmail: googleService.signOut(user: email) diff --git a/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift b/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift index aca0ef9b7..fe659a8c1 100644 --- a/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift +++ b/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift @@ -9,6 +9,8 @@ import Foundation final class KeyDataStorage { + private lazy var logger = Logger.nested(Self.self) + private let encryptedStorage: EncryptedStorageType init( @@ -32,6 +34,9 @@ extension KeyDataStorage: KeyStorageType { } func addKeys(keyDetails: [KeyDetails], source: KeySource) { + keyDetails.forEach { + logger.logInfo("Add keys for \($0.users)") + } encryptedStorage.addKeys(keyDetails: keyDetails, source: source) } } diff --git a/FlowCrypt/Models/Realm Models/KeyInfo.swift b/FlowCrypt/Models/Realm Models/KeyInfo.swift index 08beb9449..a27063795 100644 --- a/FlowCrypt/Models/Realm Models/KeyInfo.swift +++ b/FlowCrypt/Models/Realm Models/KeyInfo.swift @@ -36,7 +36,12 @@ final class KeyInfo: Object { self.`public` = keyDetails.public self.longid = keyDetails.longid self.source = source.rawValue - self.account = keyDetails.users.first ?? "" + if let account = keyDetails.users.first { + self.account = account + } else { + Logger.logError("Key Details users is empty for \(keyDetails.longid)") + self.account = "" + } } override class func primaryKey() -> String? { From 98b2ca5ae1275d1fb08eae9ce29374cb22e3c7fc Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Mon, 5 Jul 2021 11:30:42 +0300 Subject: [PATCH 10/15] Add debug logs --- FlowCrypt/Core/Models/KeyDetails.swift | 6 +++++- .../DataManager/Encrypted Storage/EncryptedStorage.swift | 6 +++++- .../Functionality/DataManager/UserAccountService.swift | 6 +++--- .../Services/Key Services/KeyDataStorage.swift | 5 ++++- test-ci-secrets.json | 1 + 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/FlowCrypt/Core/Models/KeyDetails.swift b/FlowCrypt/Core/Models/KeyDetails.swift index 1ad840b7e..dc528e8d1 100644 --- a/FlowCrypt/Core/Models/KeyDetails.swift +++ b/FlowCrypt/Core/Models/KeyDetails.swift @@ -9,7 +9,7 @@ import FlowCryptCommon import Foundation -struct KeyDetails: Decodable { +struct KeyDetails: Decodable, CustomStringConvertible { let `public`: String let `private`: String? // ony if this is prv let isFullyDecrypted: Bool? // only if this is prv @@ -22,6 +22,10 @@ struct KeyDetails: Decodable { var longid: String { ids.first?.longid ?? "" } + + var description: String { + "public = \(`public`) ### ids = \(ids) ### users = \(users) ### algo = \(algo.debugDescription)" + } } extension KeyDetails: Hashable { diff --git a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift index 132d8e4cd..f6c63000c 100644 --- a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift +++ b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift @@ -182,7 +182,11 @@ extension EncryptedStorage { } func doesAnyKeyExist(for email: String) -> Bool { - keysInfo() + let keys = keysInfo() + debugPrint("Tom: doesAnyKeyExist - email \(email)") + debugPrint("Tom: doesAnyKeyExist - keys \(keys)") + + return keysInfo() .map(\.account) .map { $0.contains(email) } .contains(true) diff --git a/FlowCrypt/Functionality/DataManager/UserAccountService.swift b/FlowCrypt/Functionality/DataManager/UserAccountService.swift index c51f2b71b..ddf5ade79 100644 --- a/FlowCrypt/Functionality/DataManager/UserAccountService.swift +++ b/FlowCrypt/Functionality/DataManager/UserAccountService.swift @@ -98,7 +98,7 @@ extension UserAccountService: UserAccountServiceType { func cleanupSessions() { logger.logInfo("Clean up sessions") - + encryptedStorage.getAllUsers() .filter { !encryptedStorage.doesAnyKeyExist(for: $0.email) @@ -119,7 +119,7 @@ extension UserAccountService: UserAccountServiceType { @discardableResult private func switchActiveSession(for userObject: UserObject) -> SessionType? { logger.logInfo("Try to switch session for \(userObject.email)") - + let sessionType: SessionType switch userObject.authType { case .oAuthGmail(let token): @@ -146,7 +146,7 @@ extension UserAccountService: UserAccountServiceType { private func logOut(user email: String) { logger.logInfo("Log out user with \(email)") - + switch dataService.currentAuthType { case .oAuthGmail: googleService.signOut(user: email) diff --git a/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift b/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift index fe659a8c1..15972ea9d 100644 --- a/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift +++ b/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift @@ -10,7 +10,7 @@ import Foundation final class KeyDataStorage { private lazy var logger = Logger.nested(Self.self) - + private let encryptedStorage: EncryptedStorageType init( @@ -34,7 +34,10 @@ extension KeyDataStorage: KeyStorageType { } func addKeys(keyDetails: [KeyDetails], source: KeySource) { + debugPrint("Tom: keyDetails \(keyDetails)") + keyDetails.forEach { + debugPrint("Tom: keyDetails - Add keys for \($0.users)") logger.logInfo("Add keys for \($0.users)") } encryptedStorage.addKeys(keyDetails: keyDetails, source: source) diff --git a/test-ci-secrets.json b/test-ci-secrets.json index e69de29bb..8b1378917 100644 --- a/test-ci-secrets.json +++ b/test-ci-secrets.json @@ -0,0 +1 @@ + From 58724ab0f72a70c506851a00100651353948be51 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Mon, 5 Jul 2021 20:42:11 +0300 Subject: [PATCH 11/15] Add UserObject to KeyInfo object --- .../Setup/SetupBackupsViewController.swift | 2 +- .../SetupGenerateKeyViewController.swift | 3 +- ...anuallyEnterPassPhraseViewController.swift | 4 +-- .../Encrypted Storage/EncryptedStorage.swift | 33 ++++++++++++------- .../EncryptedStorageProtocols.swift | 4 +-- .../Key Services/KeyDataStorage.swift | 14 +++----- .../Services/Key Services/KeyService.swift | 2 +- FlowCrypt/Models/Realm Models/KeyInfo.swift | 21 +++++++----- 8 files changed, 45 insertions(+), 38 deletions(-) diff --git a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift index 245704125..ba2dcad00 100644 --- a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift @@ -152,7 +152,7 @@ extension SetupBackupsViewController { } // save keys - keyStorage.addKeys(keyDetails: Array(matchingKeyBackups), source: .backup) + keyStorage.addKeys(keyDetails: Array(matchingKeyBackups), source: .backup, for: user.email) moveToMainFlow() } diff --git a/FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift index a0f9f70fb..40f28b9c0 100644 --- a/FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift @@ -152,7 +152,7 @@ extension SetupGenerateKeyViewController { let passPhrase = PassPhrase(value: passPhrase, longid: encryptedPrv.key.longid) - self.keyStorage.addKeys(keyDetails: [encryptedPrv.key], source: .generated) + self.keyStorage.addKeys(keyDetails: [encryptedPrv.key], source: .generated, for: self.user.email) self.passPhraseService.savePassPhrase(with: passPhrase, inStorage: self.shouldSaveLocally) let updateKey = self.attester.updateKey( @@ -256,6 +256,7 @@ extension SetupGenerateKeyViewController { } private func handleButtonAction() { + view.endEditing(true) guard let passPhrase = passPhrase, passPhrase.isNotEmpty else { showAlert(message: "setup_wrong_pass_phrase_retry".localized) return diff --git a/FlowCrypt/Controllers/Setup/SetupManuallyEnterPassPhraseViewController.swift b/FlowCrypt/Controllers/Setup/SetupManuallyEnterPassPhraseViewController.swift index 38caabeb3..cc78459be 100644 --- a/FlowCrypt/Controllers/Setup/SetupManuallyEnterPassPhraseViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupManuallyEnterPassPhraseViewController.swift @@ -241,8 +241,8 @@ extension SetupManuallyEnterPassPhraseViewController { let keysToUpdate = Array(Set(existedKeys).intersection(fetchedKeys)) let newKeysToAdd = Array(Set(fetchedKeys).subtracting(existedKeys)) - keysStorage.addKeys(keyDetails: newKeysToAdd, source: .imported) - keysStorage.updateKeys(keyDetails: keysToUpdate, source: .imported) + keysStorage.addKeys(keyDetails: newKeysToAdd, source: .imported, for: email) + keysStorage.updateKeys(keyDetails: keysToUpdate, source: .imported, for: email) keysToUpdate .map { diff --git a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift index f6c63000c..37bf294d4 100644 --- a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift +++ b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift @@ -53,6 +53,7 @@ final class EncryptedStorage: EncryptedStorageType { } private lazy var migrationLogger = Logger.nested(in: Self.self, with: .migration) + private lazy var logger = Logger.nested(Self.self) private let currentSchema: EncryptedStorageSchema = .initial private let supportedSchemas = EncryptedStorageSchema.allCases @@ -107,7 +108,7 @@ extension EncryptedStorage: LogOutHandler { let userToDelete = users .filter { $0.email == email } let keys = storage.objects(KeyInfo.self) - .filter { $0.account.contains(email) } + .filter { $0.account == email } let passPhrases = storage.objects(PassPhraseObject.self) .filter { keys.map(\.longid).contains($0.longid) } let sessions = storage.objects(SessionObject.self) @@ -154,18 +155,26 @@ extension EncryptedStorage { // MARK: - Keys extension EncryptedStorage { - func addKeys(keyDetails: [KeyDetails], source: KeySource) { + func addKeys(keyDetails: [KeyDetails], source: KeySource, for email: String) { + guard let user = storage.objects(UserObject.self).first(where: { $0.email == email }) else { + logger.logError("Can't find user with given email to add keys. User should be already saved") + return + } try! storage.write { for key in keyDetails { - storage.add(try! KeyInfo(key, source: source)) + storage.add(try! KeyInfo(key, source: source, user: user)) } } } - func updateKeys(keyDetails: [KeyDetails], source: KeySource) { + func updateKeys(keyDetails: [KeyDetails], source: KeySource, for email: String) { + guard let user = getUserObject(for: email) else { + logger.logError("Can't find user with given email to update keys. User should be already saved") + return + } try! storage.write { for key in keyDetails { - storage.add(try! KeyInfo(key, source: source), update: .all) + storage.add(try! KeyInfo(key, source: source, user: user), update: .all) } } } @@ -182,15 +191,15 @@ extension EncryptedStorage { } func doesAnyKeyExist(for email: String) -> Bool { - let keys = keysInfo() - debugPrint("Tom: doesAnyKeyExist - email \(email)") - debugPrint("Tom: doesAnyKeyExist - keys \(keys)") - - return keysInfo() - .map(\.account) - .map { $0.contains(email) } + keysInfo() + .compactMap(\.user) + .map { $0.email.contains(email) } .contains(true) } + + private func getUserObject(for email: String) -> UserObject? { + storage.objects(UserObject.self).first(where: { $0.email == email }) + } } // MARK: - PassPhrase diff --git a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorageProtocols.swift b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorageProtocols.swift index 7c717c823..492d775e0 100644 --- a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorageProtocols.swift +++ b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorageProtocols.swift @@ -9,8 +9,8 @@ import Foundation protocol KeyStorageType { - func addKeys(keyDetails: [KeyDetails], source: KeySource) - func updateKeys(keyDetails: [KeyDetails], source: KeySource) + func addKeys(keyDetails: [KeyDetails], source: KeySource, for email: String) + func updateKeys(keyDetails: [KeyDetails], source: KeySource, for email: String) func publicKey() -> String? func keysInfo() -> [KeyInfo] } diff --git a/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift b/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift index 15972ea9d..60fbec915 100644 --- a/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift +++ b/FlowCrypt/Functionality/Services/Key Services/KeyDataStorage.swift @@ -21,8 +21,8 @@ final class KeyDataStorage { } extension KeyDataStorage: KeyStorageType { - func updateKeys(keyDetails: [KeyDetails], source: KeySource) { - encryptedStorage.updateKeys(keyDetails: keyDetails, source: source) + func updateKeys(keyDetails: [KeyDetails], source: KeySource, for email: String) { + encryptedStorage.updateKeys(keyDetails: keyDetails, source: source, for: email) } func publicKey() -> String? { @@ -33,13 +33,7 @@ extension KeyDataStorage: KeyStorageType { encryptedStorage.keysInfo() } - func addKeys(keyDetails: [KeyDetails], source: KeySource) { - debugPrint("Tom: keyDetails \(keyDetails)") - - keyDetails.forEach { - debugPrint("Tom: keyDetails - Add keys for \($0.users)") - logger.logInfo("Add keys for \($0.users)") - } - encryptedStorage.addKeys(keyDetails: keyDetails, source: source) + func addKeys(keyDetails: [KeyDetails], source: KeySource, for email: String) { + encryptedStorage.addKeys(keyDetails: keyDetails, source: source, for: email) } } diff --git a/FlowCrypt/Functionality/Services/Key Services/KeyService.swift b/FlowCrypt/Functionality/Services/Key Services/KeyService.swift index 58b4d9572..727af1a0f 100644 --- a/FlowCrypt/Functionality/Services/Key Services/KeyService.swift +++ b/FlowCrypt/Functionality/Services/Key Services/KeyService.swift @@ -58,7 +58,7 @@ final class KeyService: KeyServiceType { } let keysInfo = storage.keysInfo() - .filter { $0.account.contains(email) } + .filter { $0.account == email } let storedPassPhrases = passPhraseService.getPassPhrases() diff --git a/FlowCrypt/Models/Realm Models/KeyInfo.swift b/FlowCrypt/Models/Realm Models/KeyInfo.swift index a27063795..6c2d71ba6 100644 --- a/FlowCrypt/Models/Realm Models/KeyInfo.swift +++ b/FlowCrypt/Models/Realm Models/KeyInfo.swift @@ -20,10 +20,11 @@ final class KeyInfo: Object { @objc dynamic var `public`: String = "" @objc dynamic var longid: String = "" @objc dynamic var source: String = "" - @objc dynamic var account: String = "" + @objc dynamic var user: UserObject! - convenience init(_ keyDetails: KeyDetails, source: KeySource) throws { + convenience init(_ keyDetails: KeyDetails, source: KeySource, user: UserObject) throws { self.init() + guard let privateKey = keyDetails.private else { assertionFailure("storing pubkey as private") // crash tests throw KeyInfoError.missedPrivateKey("storing pubkey as private") @@ -36,12 +37,7 @@ final class KeyInfo: Object { self.`public` = keyDetails.public self.longid = keyDetails.longid self.source = source.rawValue - if let account = keyDetails.users.first { - self.account = account - } else { - Logger.logError("Key Details users is empty for \(keyDetails.longid)") - self.account = "" - } + self.user = user } override class func primaryKey() -> String? { @@ -49,6 +45,13 @@ final class KeyInfo: Object { } override var description: String { - "account = \(account) ####### longid = \(longid)" + "account = \(user?.email ?? "N/A") ####### longid = \(longid)" + } +} + +extension KeyInfo { + /// associated user email + var account: String { + user.email } } From 08c9b66f285838b9e9a274714abc83f12f438787 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Mon, 5 Jul 2021 20:43:22 +0300 Subject: [PATCH 12/15] Remove buildForTesting in FlowCrypt.scheme --- FlowCrypt.xcodeproj/xcshareddata/xcschemes/FlowCrypt.xcscheme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlowCrypt.xcodeproj/xcshareddata/xcschemes/FlowCrypt.xcscheme b/FlowCrypt.xcodeproj/xcshareddata/xcschemes/FlowCrypt.xcscheme index 4e86d244d..d92b235e8 100644 --- a/FlowCrypt.xcodeproj/xcshareddata/xcschemes/FlowCrypt.xcscheme +++ b/FlowCrypt.xcodeproj/xcshareddata/xcschemes/FlowCrypt.xcscheme @@ -7,7 +7,7 @@ buildImplicitDependencies = "YES"> Date: Mon, 5 Jul 2021 20:46:16 +0300 Subject: [PATCH 13/15] Update doesAnyKeyExist --- .../DataManager/Encrypted Storage/EncryptedStorage.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift index 37bf294d4..469d0edfc 100644 --- a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift +++ b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift @@ -192,8 +192,8 @@ extension EncryptedStorage { func doesAnyKeyExist(for email: String) -> Bool { keysInfo() - .compactMap(\.user) - .map { $0.email.contains(email) } + .map(\.account) + .map { $0 == email } .contains(true) } From 2976d6a42917bf499733d63c22337e04550ef58b Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Thu, 8 Jul 2021 12:19:00 +0300 Subject: [PATCH 14/15] Rearange order of deleting objects during logout --- .../DataManager/Encrypted Storage/EncryptedStorage.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift index 469d0edfc..4e48dce09 100644 --- a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift +++ b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift @@ -115,10 +115,10 @@ extension EncryptedStorage: LogOutHandler { .filter { $0.email == email } try storage.write { - storage.delete(userToDelete) storage.delete(keys) storage.delete(sessions) storage.delete(passPhrases) + storage.delete(userToDelete) } } } From c4f82084279b538adfa78d1aca585e3f60ad9844 Mon Sep 17 00:00:00 2001 From: Anton Kharchevskyi Date: Thu, 8 Jul 2021 12:28:49 +0300 Subject: [PATCH 15/15] Fix mock for KeyInfo --- .../PassPhraseStorageTests/PassPhraseStorageTests.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/FlowCryptAppTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift b/FlowCryptAppTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift index 361c7a186..7628706bc 100644 --- a/FlowCryptAppTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift +++ b/FlowCryptAppTests/Functionallity/Services/PassPhraseStorageTests/PassPhraseStorageTests.swift @@ -182,9 +182,14 @@ extension KeyInfo { users: [], algo: nil ), - source: .backup + source: .backup, + user: UserObject( + name: "name", + email: "email@gmail.com", + imap: nil, + smtp: nil + ) ) - key.account = account key.longid = longid return key }