From edc236226e08328f4b80245701ad52c2c8ad864d Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Mon, 28 Mar 2022 22:37:26 +0300 Subject: [PATCH 1/8] #1348 add email to EnterpriseServerApi --- .../xcshareddata/swiftpm/Package.resolved | 222 +++++++++--------- FlowCrypt/App/AppContext.swift | 32 +-- .../Compose/ComposeViewController.swift | 6 +- .../EnterpriseServerApi.swift | 20 +- .../Functionality/Services/AppStartup.swift | 26 +- .../ClientConfigurationService.swift | 6 +- .../ComposeMessageService.swift | 6 +- .../Functionality/Services/GlobalRouter.swift | 4 +- .../Resources/en.lproj/Localizable.strings | 3 +- Gemfile.lock | 30 +-- Podfile.lock | 6 +- 11 files changed, 182 insertions(+), 179 deletions(-) diff --git a/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved b/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9b379cb07..dbe188328 100644 --- a/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,115 +1,113 @@ { - "object": { - "pins": [ - { - "package": "AppAuth", - "repositoryURL": "https://github.com/openid/AppAuth-iOS.git", - "state": { - "branch": null, - "revision": "01131d68346c8ae552961c768d583c715fbe1410", - "version": "1.4.0" - } - }, - { - "package": "BigInt", - "repositoryURL": "https://github.com/attaswift/BigInt", - "state": { - "branch": null, - "revision": "0ed110f7555c34ff468e72e1686e59721f2b0da6", - "version": "5.3.0" - } - }, - { - "package": "GoogleAPIClientForREST", - "repositoryURL": "https://github.com/google/google-api-objectivec-client-for-rest", - "state": { - "branch": null, - "revision": "22e0bb02729d60db396e8b90d8189313cd86ba53", - "version": "1.6.0" - } - }, - { - "package": "GoogleSignIn", - "repositoryURL": "https://github.com/google/GoogleSignIn-iOS", - "state": { - "branch": null, - "revision": "60ca2bfd218ccb194a746a79b41d9d50eb7e3af0", - "version": "6.1.0" - } - }, - { - "package": "GTMSessionFetcher", - "repositoryURL": "https://github.com/google/gtm-session-fetcher.git", - "state": { - "branch": null, - "revision": "bc6a19702ac76ac4e488b68148710eb815f9bc56", - "version": "1.7.0" - } - }, - { - "package": "GTMAppAuth", - "repositoryURL": "https://github.com/google/GTMAppAuth.git", - "state": { - "branch": null, - "revision": "40f4103fb52109032c05599a0c39ad43edbdf80a", - "version": "1.2.2" - } - }, - { - "package": "IDZSwiftCommonCrypto", - "repositoryURL": "https://github.com/iosdevzone/IDZSwiftCommonCrypto", - "state": { - "branch": null, - "revision": "d824371e670bd57eb456bbc41139b4997f7207b8", - "version": "0.13.1" - } - }, - { - "package": "MailCore", - "repositoryURL": "https://github.com/FlowCrypt/mailcore2", - "state": { - "branch": null, - "revision": "512e3074f7b80a4425be806e3ce968f1e84b995f", - "version": "0.7.5" - } - }, - { - "package": "MBProgressHUD", - "repositoryURL": "https://github.com/jdg/MBProgressHUD", - "state": { - "branch": null, - "revision": "bca42b801100b2b3a4eda0ba8dd33d858c780b0d", - "version": "1.2.0" - } - }, - { - "package": "Realm", - "repositoryURL": "https://github.com/realm/realm-cocoa", - "state": { - "branch": null, - "revision": "9dff9f2862240d521ad6ad599541269177ddb993", - "version": "10.22.0" - } - }, - { - "package": "RealmDatabase", - "repositoryURL": "https://github.com/realm/realm-core", - "state": { - "branch": null, - "revision": "6b81f1a7a2d421f9e0b9e7f04e76bcf736a54409", - "version": "11.9.0" - } - }, - { - "package": "Toast", - "repositoryURL": "https://github.com/scalessec/Toast-Swift", - "state": { - "branch": null, - "revision": "0c9493eeacb102cc614da385cfaaf475379f4ab4", - "version": "5.0.1" - } + "pins" : [ + { + "identity" : "appauth-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/openid/AppAuth-iOS.git", + "state" : { + "revision" : "33660c271c961f8ce1084cc13f2ea8195e864f7d", + "version" : "1.5.0" } - ] - }, - "version": 1 + }, + { + "identity" : "bigint", + "kind" : "remoteSourceControl", + "location" : "https://github.com/attaswift/BigInt", + "state" : { + "revision" : "0ed110f7555c34ff468e72e1686e59721f2b0da6", + "version" : "5.3.0" + } + }, + { + "identity" : "google-api-objectivec-client-for-rest", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/google-api-objectivec-client-for-rest", + "state" : { + "revision" : "3228334d0584cb9174796fecbe628a723be70452", + "version" : "1.7.0" + } + }, + { + "identity" : "googlesignin-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleSignIn-iOS", + "state" : { + "revision" : "60ca2bfd218ccb194a746a79b41d9d50eb7e3af0", + "version" : "6.1.0" + } + }, + { + "identity" : "gtm-session-fetcher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/gtm-session-fetcher.git", + "state" : { + "revision" : "eca9404a18f53727e4698211aaf2615eb93b962a", + "version" : "1.7.1" + } + }, + { + "identity" : "gtmappauth", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GTMAppAuth.git", + "state" : { + "revision" : "e803d09da0147fbf1bbb30e126c47ff43254e057", + "version" : "1.2.3" + } + }, + { + "identity" : "idzswiftcommoncrypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/iosdevzone/IDZSwiftCommonCrypto", + "state" : { + "revision" : "d824371e670bd57eb456bbc41139b4997f7207b8", + "version" : "0.13.1" + } + }, + { + "identity" : "mailcore2", + "kind" : "remoteSourceControl", + "location" : "https://github.com/FlowCrypt/mailcore2", + "state" : { + "revision" : "512e3074f7b80a4425be806e3ce968f1e84b995f", + "version" : "0.7.5" + } + }, + { + "identity" : "mbprogresshud", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jdg/MBProgressHUD", + "state" : { + "revision" : "bca42b801100b2b3a4eda0ba8dd33d858c780b0d", + "version" : "1.2.0" + } + }, + { + "identity" : "realm-cocoa", + "kind" : "remoteSourceControl", + "location" : "https://github.com/realm/realm-cocoa", + "state" : { + "revision" : "628cf20632d65d3a3e90ae8aaf52bce596d7ad8f", + "version" : "10.24.2" + } + }, + { + "identity" : "realm-core", + "kind" : "remoteSourceControl", + "location" : "https://github.com/realm/realm-core", + "state" : { + "revision" : "dcd3788f75bafb0cc397d145e1651aab3ad4bf0e", + "version" : "11.12.0" + } + }, + { + "identity" : "toast-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/scalessec/Toast-Swift", + "state" : { + "revision" : "0c9493eeacb102cc614da385cfaaf475379f4ab4", + "version" : "5.0.1" + } + } + ], + "version" : 2 } diff --git a/FlowCrypt/App/AppContext.swift b/FlowCrypt/App/AppContext.swift index 33ef97756..feab763a4 100644 --- a/FlowCrypt/App/AppContext.swift +++ b/FlowCrypt/App/AppContext.swift @@ -18,7 +18,6 @@ class AppContext { let userAccountService: SessionServiceType let keyService: KeyServiceType let passPhraseService: PassPhraseServiceType - let clientConfigurationService: ClientConfigurationServiceType init( encryptedStorage: EncryptedStorageType, @@ -26,7 +25,6 @@ class AppContext { userAccountService: SessionServiceType, keyService: KeyServiceType, passPhraseService: PassPhraseServiceType, - clientConfigurationService: ClientConfigurationServiceType, globalRouter: GlobalRouterType ) { self.encryptedStorage = encryptedStorage @@ -34,12 +32,11 @@ class AppContext { self.userAccountService = userAccountService self.keyService = keyService self.passPhraseService = passPhraseService - self.clientConfigurationService = clientConfigurationService self.globalRouter = globalRouter } @MainActor - static func setUpAppContext(globalRouter: GlobalRouterType) throws -> AppContext { + static func setup(globalRouter: GlobalRouterType) throws -> AppContext { let keyChainService = KeyChainService() let encryptedStorage = EncryptedStorage( storageEncryptionKey: try keyChainService.getStorageEncryptionKey() @@ -50,11 +47,6 @@ class AppContext { passPhraseService: passPhraseService, currentUserEmail: { try? encryptedStorage.activeUser?.email } ) - let clientConfigurationService = ClientConfigurationService( - local: LocalClientConfiguration( - encryptedStorage: encryptedStorage - ) - ) return AppContext( encryptedStorage: encryptedStorage, session: nil, // will be set later. But would be nice to already set here, if available @@ -67,19 +59,17 @@ class AppContext { ), keyService: keyService, passPhraseService: passPhraseService, - clientConfigurationService: clientConfigurationService, globalRouter: globalRouter ) } - func withSession(session: SessionType?, authType: AuthType, user: User) -> AppContextWithUser { + func with(session: SessionType?, authType: AuthType, user: User) -> AppContextWithUser { return AppContextWithUser( encryptedStorage: encryptedStorage, session: session, userAccountService: userAccountService, keyService: keyService, passPhraseService: passPhraseService, - clientConfigurationService: clientConfigurationService, globalRouter: globalRouter, authType: authType, user: user @@ -130,7 +120,13 @@ class AppContext { class AppContextWithUser: AppContext { let authType: AuthType let user: User - let userId: UserId + + let enterpriseServer: EnterpriseServerApiType + let clientConfigurationService: ClientConfigurationServiceType + + var userId: UserId { + UserId(email: user.email, name: user.name) + } init( encryptedStorage: EncryptedStorageType, @@ -138,14 +134,19 @@ class AppContextWithUser: AppContext { userAccountService: SessionServiceType, keyService: KeyServiceType, passPhraseService: PassPhraseServiceType, - clientConfigurationService: ClientConfigurationServiceType, globalRouter: GlobalRouterType, authType: AuthType, user: User ) { self.authType = authType self.user = user - self.userId = UserId(email: user.email, name: user.name) + self.enterpriseServer = EnterpriseServerApi(email: user.email) + self.clientConfigurationService = ClientConfigurationService( + server: enterpriseServer, + local: LocalClientConfiguration( + encryptedStorage: encryptedStorage + ) + ) super.init( encryptedStorage: encryptedStorage, @@ -153,7 +154,6 @@ class AppContextWithUser: AppContext { userAccountService: userAccountService, keyService: keyService, passPhraseService: passPhraseService, - clientConfigurationService: clientConfigurationService, globalRouter: globalRouter ) } diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index 177f9dd98..e5103aa01 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -125,12 +125,16 @@ final class ComposeViewController: TableNodeViewController { encryptedStorage: appContext.encryptedStorage, messageGateway: appContext.getRequiredMailProvider().messageSender, passPhraseService: appContext.passPhraseService, + enterpriseServer: appContext.enterpriseServer, sender: appContext.user.email ) self.filesManager = filesManager self.photosManager = photosManager self.keyMethods = keyMethods - self.pubLookup = PubLookup(clientConfiguration: clientConfiguration, localContactsProvider: self.localContactsProvider) + self.pubLookup = PubLookup( + clientConfiguration: clientConfiguration, + localContactsProvider: self.localContactsProvider + ) self.router = appContext.globalRouter self.contextToSend.subject = input.subject self.contextToSend.attachments = input.attachments diff --git a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift index 62b45c277..e8c780d08 100644 --- a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift +++ b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift @@ -9,9 +9,9 @@ import FlowCryptCommon protocol EnterpriseServerApiType { - func getActiveFesUrl(for email: String) async throws -> String? - func getClientConfiguration(for email: String) async throws -> RawClientConfiguration - func getReplyToken(for email: String) async throws -> String + func getActiveFesUrl() async throws -> String? + func getClientConfiguration() async throws -> RawClientConfiguration + func getReplyToken() async throws -> String func upload(message: Data, details: MessageUploadDetails, progressHandler: ((Float) -> Void)?) async throws -> String } @@ -57,7 +57,13 @@ class EnterpriseServerApi: NSObject, EnterpriseServerApiType { private var messageUploadProgressHandler: ((Float) -> Void)? - func getActiveFesUrl(for email: String) async throws -> String? { + private let email: String + + init(email: String) { + self.email = email + } + + func getActiveFesUrl() async throws -> String? { do { guard let userDomain = email.emailParts?.domain, !EnterpriseServerApi.publicEmailProviderDomains.contains(userDomain) else { @@ -93,7 +99,7 @@ class EnterpriseServerApi: NSObject, EnterpriseServerApiType { } } - func getClientConfiguration(for email: String) async throws -> RawClientConfiguration { + func getClientConfiguration() async throws -> RawClientConfiguration { guard let userDomain = email.emailParts?.domain else { throw EnterpriseServerApiError.emailFormat } @@ -114,7 +120,7 @@ class EnterpriseServerApi: NSObject, EnterpriseServerApiType { } } - func getReplyToken(for email: String) async throws -> String { + func getReplyToken() async throws -> String { let response: MessageReplyTokenResponse = try await performRequest( email: email, url: "/api/v1/message/new-reply-token" @@ -211,7 +217,7 @@ class EnterpriseServerApi: NSObject, EnterpriseServerApiType { withAuthorization: Bool = true, delegate: URLSessionTaskDelegate? = nil ) async throws -> T { - guard let fesUrl = try await getActiveFesUrl(for: email) else { + guard let fesUrl = try await getActiveFesUrl() else { throw EnterpriseServerApiError.noActiveFesUrl } diff --git a/FlowCrypt/Functionality/Services/AppStartup.swift b/FlowCrypt/Functionality/Services/AppStartup.swift index 7bb1beea8..9e4403dc8 100644 --- a/FlowCrypt/Functionality/Services/AppStartup.swift +++ b/FlowCrypt/Functionality/Services/AppStartup.swift @@ -33,8 +33,7 @@ struct AppStartup { do { await setupCore() try await setupSession() - try await getUserOrgRulesIfNeeded() - try chooseView(for: window) + try await chooseView(for: window) } catch { showErrorAlert(of: error, on: window) } @@ -60,10 +59,10 @@ struct AppStartup { } @MainActor - private func chooseView(for window: UIWindow) throws { + private func chooseView(for window: UIWindow) async throws { switch try entryPointForUser() { case .mainFlow: - startWithUserContext(appContext: appContext, window: window) { context in + try await startWithUserContext(appContext: appContext, window: window) { context in let controller = InboxViewContainerController(appContext: context) window.rootViewController = SideMenuNavigationController( appContext: context, @@ -75,7 +74,7 @@ struct AppStartup { rootViewController: SignInViewController(appContext: appContext) ) case .setupFlow: - startWithUserContext(appContext: appContext, window: window) { context in + try await startWithUserContext(appContext: appContext, window: window) { context in do { let controller = try SetupInitialViewController(appContext: context) window.rootViewController = MainNavigationController(rootViewController: controller) @@ -104,13 +103,6 @@ struct AppStartup { } } - private func getUserOrgRulesIfNeeded() async throws { - guard let currentUser = try appContext.encryptedStorage.activeUser else { - return - } - _ = try await appContext.clientConfigurationService.fetch(for: currentUser) - } - private func makeUserIdForSetup(session: SessionType) throws -> UserId? { guard let activeUser = try appContext.encryptedStorage.activeUser else { Logger.logInfo("Can't create user id for setup") @@ -168,14 +160,16 @@ struct AppStartup { } @MainActor - private func startWithUserContext(appContext: AppContext, window: UIWindow, callback: (AppContextWithUser) -> Void) { + private func startWithUserContext(appContext: AppContext, window: UIWindow, callback: (AppContextWithUser) -> Void) async throws { let session = appContext.session guard let user = try? appContext.encryptedStorage.activeUser, let authType = user.authType else { - let message = "Wrong application state. User not found for session \(session?.description ?? "nil")" + let sessionName = appContext.session?.description ?? "nil" + let message = "error_wrong_app_state".localizeWithArguments(sessionName) + logger.logError(message) if window.rootViewController == nil { @@ -191,6 +185,8 @@ struct AppStartup { return } - callback(appContext.withSession(session: session, authType: authType, user: user)) + let contextWithUser = appContext.with(session: session, authType: authType, user: user) + _ = try await contextWithUser.clientConfigurationService.fetch(for: contextWithUser.user) + callback(contextWithUser) } } diff --git a/FlowCrypt/Functionality/Services/Client Configuration Service/ClientConfigurationService.swift b/FlowCrypt/Functionality/Services/Client Configuration Service/ClientConfigurationService.swift index 05f6e2a8a..edffd8eda 100644 --- a/FlowCrypt/Functionality/Services/Client Configuration Service/ClientConfigurationService.swift +++ b/FlowCrypt/Functionality/Services/Client Configuration Service/ClientConfigurationService.swift @@ -20,7 +20,7 @@ final class ClientConfigurationService { private let local: LocalClientConfigurationType init( - server: EnterpriseServerApiType = EnterpriseServerApi(), + server: EnterpriseServerApiType, local: LocalClientConfigurationType ) { self.server = server @@ -33,8 +33,8 @@ extension ClientConfigurationService: ClientConfigurationServiceType { func fetch(for user: User) async throws -> ClientConfiguration { do { - let raw = try await server.getClientConfiguration(for: user.email) - let fesUrl = try await server.getActiveFesUrl(for: user.email) + let raw = try await server.getClientConfiguration() + let fesUrl = try await server.getActiveFesUrl() try local.save(for: user, raw: raw, fesUrl: fesUrl) return ClientConfiguration(raw: raw) } catch { diff --git a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift index 934008a8c..81f8bda27 100644 --- a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift +++ b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift @@ -46,9 +46,9 @@ final class ComposeMessageService { passPhraseService: PassPhraseServiceType, draftGateway: DraftGateway? = nil, localContactsProvider: LocalContactsProviderType? = nil, + enterpriseServer: EnterpriseServerApiType, sender: String, - core: CoreComposeMessageType & KeyParser = Core.shared, - enterpriseServer: EnterpriseServerApiType = EnterpriseServerApi() + core: CoreComposeMessageType & KeyParser = Core.shared ) { self.messageGateway = messageGateway self.passPhraseService = passPhraseService @@ -291,7 +291,7 @@ extension ComposeMessageService { } private func prepareAndUploadPwdEncryptedMsg(message: SendableMsg) async throws -> String { - let replyToken = try await enterpriseServer.getReplyToken(for: message.from) + let replyToken = try await enterpriseServer.getReplyToken() let bodyWithReplyToken = try getPwdMsgBodyWithReplyToken( message: message, diff --git a/FlowCrypt/Functionality/Services/GlobalRouter.swift b/FlowCrypt/Functionality/Services/GlobalRouter.swift index c0bd2a370..817757e87 100644 --- a/FlowCrypt/Functionality/Services/GlobalRouter.swift +++ b/FlowCrypt/Functionality/Services/GlobalRouter.swift @@ -45,7 +45,7 @@ extension GlobalRouter: GlobalRouterType { /// proceed to flow (signing/setup/app) depends on user status (isLoggedIn/isSetupFinished) func proceed() { do { - let appContext = try AppContext.setUpAppContext(globalRouter: self) + let appContext = try AppContext.setup(globalRouter: self) do { try appContext.encryptedStorage.validate() proceed(with: appContext) @@ -171,7 +171,7 @@ extension GlobalRouter: GlobalRouterType { return } - let appContextWithUser = appContext.withSession(session: session, authType: authType, user: user) + let appContextWithUser = appContext.with(session: session, authType: authType, user: user) AppStartup(appContext: appContextWithUser).initializeApp(window: keyWindow) } diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index 2a0e3eb81..62fd08940 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -78,8 +78,7 @@ "error_internal_parse_block" = "Internal error: could not parse MsgBlock. Please report this error to us."; "error_decrypt" = "Could not decrypt:"; "error_startup" = "Startup Error"; - - +"error_wrong_app_state" = "Wrong application state. User not found for session %@"; // KeyServiceError "keyServiceError_retrieve_error" = "Could not retrieve keys from DataService. Please restart the app and try again."; diff --git a/Gemfile.lock b/Gemfile.lock index 611c040a0..1381e3e52 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,7 +3,7 @@ GEM specs: CFPropertyList (3.0.5) rexml - activesupport (6.1.4.6) + activesupport (6.1.5) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -17,8 +17,8 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.559.0) - aws-sdk-core (3.127.0) + aws-partitions (1.570.0) + aws-sdk-core (3.130.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) @@ -34,10 +34,10 @@ GEM aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) claide (1.0.3) - cocoapods (1.11.2) + cocoapods (1.11.3) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.11.2) + cocoapods-core (= 1.11.3) cocoapods-deintegrate (>= 1.0.3, < 2.0) cocoapods-downloader (>= 1.4.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) @@ -52,7 +52,7 @@ GEM nap (~> 1.0) ruby-macho (>= 1.0, < 3.0) xcodeproj (>= 1.21.0, < 2.0) - cocoapods-core (1.11.2) + cocoapods-core (1.11.3) activesupport (>= 5.0, < 7) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -63,7 +63,7 @@ GEM public_suffix (~> 4.0) typhoeus (~> 1.0) cocoapods-deintegrate (1.0.5) - cocoapods-downloader (1.5.1) + cocoapods-downloader (1.6.1) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.1) @@ -75,7 +75,7 @@ GEM colored2 (3.1.2) commander (4.6.0) highline (~> 2.0.0) - concurrent-ruby (1.1.9) + concurrent-ruby (1.1.10) declarative (0.0.20) digest-crc (0.6.4) rake (>= 12.0.0, < 14.0.0) @@ -86,7 +86,7 @@ GEM escape (0.0.4) ethon (0.15.0) ffi (>= 1.15.0) - excon (0.91.0) + excon (0.92.1) faraday (1.10.0) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -116,7 +116,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.2.6) - fastlane (2.204.3) + fastlane (2.205.1) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -160,7 +160,7 @@ GEM fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.16.0) + google-apis-androidpublisher_v3 (0.17.0) google-apis-core (>= 0.4, < 2.a) google-apis-core (0.4.2) addressable (~> 2.5, >= 2.5.1) @@ -180,8 +180,8 @@ GEM google-cloud-core (1.6.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) - google-cloud-env (1.5.0) - faraday (>= 0.17.3, < 2.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) google-cloud-errors (1.2.0) google-cloud-storage (1.36.1) addressable (~> 2.8) @@ -205,7 +205,7 @@ GEM httpclient (2.8.3) i18n (1.10.0) concurrent-ruby (~> 1.0) - jmespath (1.6.0) + jmespath (1.6.1) json (2.6.1) jwt (2.3.0) memoist (0.16.2) @@ -266,7 +266,7 @@ GEM uber (0.1.0) unf (0.1.4) unf_ext - unf_ext (0.0.8) + unf_ext (0.0.8.1) unicode-display_width (1.8.0) webrick (1.7.0) word_wrap (1.0.0) diff --git a/Podfile.lock b/Podfile.lock index 039a2bd8e..4300d731a 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -15,7 +15,7 @@ PODS: - PINRemoteImage/PINCache (3.0.3): - PINCache (~> 3.0.3) - PINRemoteImage/Core - - SwiftLint (0.46.3) + - SwiftLint (0.47.0) - SwiftyRSA (1.7.0): - SwiftyRSA/ObjC (= 1.7.0) - SwiftyRSA/ObjC (1.7.0) @@ -61,10 +61,10 @@ SPEC CHECKSUMS: PINCache: 7a8fc1a691173d21dbddbf86cd515de6efa55086 PINOperation: 00c935935f1e8cf0d1e2d6b542e75b88fc3e5e20 PINRemoteImage: f1295b29f8c5e640e25335a1b2bd9d805171bd01 - SwiftLint: ae76d82f056734f853c8cd0371503252c606c482 + SwiftLint: d41cc46a2ae58ac6d9f26954bc89f1d72e71fdef SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6 Texture: 2e8ab2519452515f7f5a520f5a8f7e0a413abfa3 PODFILE CHECKSUM: abafa6480e0cf87ece2c94e283e4fe84fd790f94 -COCOAPODS: 1.11.2 +COCOAPODS: 1.11.3 From 1d1a796da50223723b090bb9b96e4bf3d8d12ca5 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Tue, 29 Mar 2022 14:33:34 +0300 Subject: [PATCH 2/8] fix tests --- BuildTools/Package.resolved | 4 +-- BuildTools/Package.swift | 2 +- FlowCrypt.xcodeproj/project.pbxproj | 6 +++- FlowCrypt/App/AppContext.swift | 8 ++--- .../SetupGenerateKeyViewController.swift | 2 +- .../EnterpriseServerApi.swift | 14 +++++--- .../Functionality/Services/AppStartup.swift | 2 +- .../ClientConfigurationService.swift | 3 +- .../Functionality/Services/GlobalRouter.swift | 6 ++-- .../Realm Models/KeypairRealmObject.swift | 2 +- .../Core/Models/KeyInfoTests.swift | 30 +++++++--------- .../Extensions/XCTestCaseExtension.swift | 36 +++++++++++++++++++ .../EnterpriseServerApiTests.swift | 24 ++++--------- .../Mocks/EnterpriseServerApiMock.swift | 9 +++-- .../Services/ComposeMessageServiceTests.swift | 10 +++--- 15 files changed, 97 insertions(+), 61 deletions(-) create mode 100644 FlowCryptAppTests/Extensions/XCTestCaseExtension.swift diff --git a/BuildTools/Package.resolved b/BuildTools/Package.resolved index e93a7ca4b..2096287e1 100644 --- a/BuildTools/Package.resolved +++ b/BuildTools/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/nicklockwood/SwiftFormat", "state": { "branch": null, - "revision": "f14f4f717e7e1d275acd7557d64c94cfef5723e6", - "version": "0.49.4" + "revision": "a07e7dca002072f1761bae8d5c53fa64462b2c2a", + "version": "0.49.5" } } ] diff --git a/BuildTools/Package.swift b/BuildTools/Package.swift index 567f2cfbd..dd68d1308 100644 --- a/BuildTools/Package.swift +++ b/BuildTools/Package.swift @@ -6,7 +6,7 @@ let package = Package( name: "BuildTools", platforms: [.macOS(.v10_11)], dependencies: [ - .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.49.4"), + .package(url: "https://github.com/nicklockwood/SwiftFormat", .exact("0.49.5")), ], targets: [.target(name: "BuildTools", path: "")] ) diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 0eba7ab2b..ecb03242c 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -93,6 +93,7 @@ 51B0C774272AB61000124663 /* StringTestExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B0C773272AB61000124663 /* StringTestExtension.swift */; }; 51B4AE51271444580001F33B /* PubKeyRealmObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B4AE50271444580001F33B /* PubKeyRealmObject.swift */; }; 51B4AE5327144E590001F33B /* PubKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B4AE5227144E590001F33B /* PubKey.swift */; }; + 51B7421B27F318D300E702C8 /* XCTestCaseExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B7421A27F318D300E702C8 /* XCTestCaseExtension.swift */; }; 51B9EE6F27567B520080B2D5 /* MessageRecipientsNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B9EE6E27567B520080B2D5 /* MessageRecipientsNode.swift */; }; 51C0C1EF271982A1000C9738 /* MailCore in Frameworks */ = {isa = PBXBuildFile; productRef = 51C0C1EE271982A1000C9738 /* MailCore */; }; 51DA5BD62721AB07001C4359 /* PubKeyState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DA5BD52721AB07001C4359 /* PubKeyState.swift */; }; @@ -536,6 +537,7 @@ 51B0C773272AB61000124663 /* StringTestExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringTestExtension.swift; sourceTree = ""; }; 51B4AE50271444580001F33B /* PubKeyRealmObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubKeyRealmObject.swift; sourceTree = ""; }; 51B4AE5227144E590001F33B /* PubKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubKey.swift; sourceTree = ""; }; + 51B7421A27F318D300E702C8 /* XCTestCaseExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestCaseExtension.swift; sourceTree = ""; }; 51B9EE6E27567B520080B2D5 /* MessageRecipientsNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRecipientsNode.swift; sourceTree = ""; }; 51DA5BD52721AB07001C4359 /* PubKeyState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubKeyState.swift; sourceTree = ""; }; 51DA5BD92722C82E001C4359 /* RecipientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipientTests.swift; sourceTree = ""; }; @@ -1080,6 +1082,7 @@ isa = PBXGroup; children = ( 51B0C773272AB61000124663 /* StringTestExtension.swift */, + 51B7421A27F318D300E702C8 /* XCTestCaseExtension.swift */, ); path = Extensions; sourceTree = ""; @@ -2531,7 +2534,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "cd BuildTools\nSDKROOT=(xcrun --sdk macosx --show-sdk-path)\n#swift package update \n#Uncomment this line temporarily to update the version used to the latest matching your BuildTools/Package.swift file\nswift run -c release swiftformat \"$SRCROOT\" --exclude appium,fastlane,Pods,vendor\n"; + shellScript = "cd BuildTools\nSDKROOT=(xcrun --sdk macosx --show-sdk-path)\n# swift package update\n#Uncomment this line temporarily to update the version used to the latest matching your BuildTools/Package.swift file\nswift run -c release swiftformat \"$SRCROOT\" --exclude appium,fastlane,Pods,vendor\n"; }; E531C3B50A9C90454C72F878 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; @@ -2591,6 +2594,7 @@ A36108E9273C7A2E00A90E34 /* MockError.swift in Sources */, 2C03CC16275BB53400887EEB /* EnterpriseServerApiTests.swift in Sources */, 9F7E8EC6269877E70021C07F /* KeyInfoTests.swift in Sources */, + 51B7421B27F318D300E702C8 /* XCTestCaseExtension.swift in Sources */, 9F5F504326FA6C7500294FA2 /* EnterpriseServerApiMock.swift in Sources */, 51DA5BDA2722C82E001C4359 /* RecipientTests.swift in Sources */, 9FC41171268118A7004C0A69 /* PassPhraseStorageTests.swift in Sources */, diff --git a/FlowCrypt/App/AppContext.swift b/FlowCrypt/App/AppContext.swift index 6129238d2..807d1951d 100644 --- a/FlowCrypt/App/AppContext.swift +++ b/FlowCrypt/App/AppContext.swift @@ -62,8 +62,8 @@ class AppContext { ) } - func with(session: SessionType?, authType: AuthType, user: User) -> AppContextWithUser { - return AppContextWithUser( + func with(session: SessionType?, authType: AuthType, user: User) async throws -> AppContextWithUser { + return try await AppContextWithUser( encryptedStorage: encryptedStorage, session: session, userAccountService: userAccountService, @@ -136,10 +136,10 @@ class AppContextWithUser: AppContext { globalRouter: GlobalRouterType, authType: AuthType, user: User - ) { + ) async throws { self.authType = authType self.user = user - self.enterpriseServer = EnterpriseServerApi(email: user.email) + self.enterpriseServer = try await EnterpriseServerApi(email: user.email) self.clientConfigurationService = ClientConfigurationService( server: enterpriseServer, local: LocalClientConfiguration( diff --git a/FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift index 0241afab1..7c943e978 100644 --- a/FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift @@ -69,7 +69,7 @@ final class SetupGenerateKeyViewController: SetupCreatePassphraseAbstractViewCon try await submitKeyToAttester(user: appContext.user, publicKey: encryptedPrv.key.public) try await appContext.getBackupService().backupToInbox(keys: [encryptedPrv.key], for: appContext.userId) - try await putKeypairsInEncryptedStorage(encryptedPrv: encryptedPrv, storageMethod: storageMethod, passPhrase: passPhrase) + try putKeypairsInEncryptedStorage(encryptedPrv: encryptedPrv, storageMethod: storageMethod, passPhrase: passPhrase) if storageMethod == .memory { let passPhrase = PassPhrase( diff --git a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift index e8c780d08..17685f710 100644 --- a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift +++ b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift @@ -9,7 +9,8 @@ import FlowCryptCommon protocol EnterpriseServerApiType { - func getActiveFesUrl() async throws -> String? + var fesUrl: String? { get } + func getClientConfiguration() async throws -> RawClientConfiguration func getReplyToken() async throws -> String func upload(message: Data, details: MessageUploadDetails, progressHandler: ((Float) -> Void)?) async throws -> String @@ -58,12 +59,17 @@ class EnterpriseServerApi: NSObject, EnterpriseServerApiType { private var messageUploadProgressHandler: ((Float) -> Void)? private let email: String + private(set) var fesUrl: String? - init(email: String) { + init(email: String) async throws { self.email = email + + super.init() + + self.fesUrl = try await getActiveFesUrl() } - func getActiveFesUrl() async throws -> String? { + private func getActiveFesUrl() async throws -> String? { do { guard let userDomain = email.emailParts?.domain, !EnterpriseServerApi.publicEmailProviderDomains.contains(userDomain) else { @@ -217,7 +223,7 @@ class EnterpriseServerApi: NSObject, EnterpriseServerApiType { withAuthorization: Bool = true, delegate: URLSessionTaskDelegate? = nil ) async throws -> T { - guard let fesUrl = try await getActiveFesUrl() else { + guard let fesUrl = fesUrl else { throw EnterpriseServerApiError.noActiveFesUrl } diff --git a/FlowCrypt/Functionality/Services/AppStartup.swift b/FlowCrypt/Functionality/Services/AppStartup.swift index 9e4403dc8..47cb5a938 100644 --- a/FlowCrypt/Functionality/Services/AppStartup.swift +++ b/FlowCrypt/Functionality/Services/AppStartup.swift @@ -185,7 +185,7 @@ struct AppStartup { return } - let contextWithUser = appContext.with(session: session, authType: authType, user: user) + let contextWithUser = try await appContext.with(session: session, authType: authType, user: user) _ = try await contextWithUser.clientConfigurationService.fetch(for: contextWithUser.user) callback(contextWithUser) } diff --git a/FlowCrypt/Functionality/Services/Client Configuration Service/ClientConfigurationService.swift b/FlowCrypt/Functionality/Services/Client Configuration Service/ClientConfigurationService.swift index edffd8eda..eef94d89b 100644 --- a/FlowCrypt/Functionality/Services/Client Configuration Service/ClientConfigurationService.swift +++ b/FlowCrypt/Functionality/Services/Client Configuration Service/ClientConfigurationService.swift @@ -34,8 +34,7 @@ extension ClientConfigurationService: ClientConfigurationServiceType { func fetch(for user: User) async throws -> ClientConfiguration { do { let raw = try await server.getClientConfiguration() - let fesUrl = try await server.getActiveFesUrl() - try local.save(for: user, raw: raw, fesUrl: fesUrl) + try local.save(for: user, raw: raw, fesUrl: server.fesUrl) return ClientConfiguration(raw: raw) } catch { guard let raw = try local.load(for: user.email) else { diff --git a/FlowCrypt/Functionality/Services/GlobalRouter.swift b/FlowCrypt/Functionality/Services/GlobalRouter.swift index 817757e87..fb2a0cd07 100644 --- a/FlowCrypt/Functionality/Services/GlobalRouter.swift +++ b/FlowCrypt/Functionality/Services/GlobalRouter.swift @@ -171,8 +171,10 @@ extension GlobalRouter: GlobalRouterType { return } - let appContextWithUser = appContext.with(session: session, authType: authType, user: user) - AppStartup(appContext: appContextWithUser).initializeApp(window: keyWindow) + Task { + let appContextWithUser = try await appContext.with(session: session, authType: authType, user: user) + AppStartup(appContext: appContextWithUser).initializeApp(window: keyWindow) + } } @MainActor diff --git a/FlowCrypt/Models/Realm Models/KeypairRealmObject.swift b/FlowCrypt/Models/Realm Models/KeypairRealmObject.swift index 1956a83bd..fb600066a 100644 --- a/FlowCrypt/Models/Realm Models/KeypairRealmObject.swift +++ b/FlowCrypt/Models/Realm Models/KeypairRealmObject.swift @@ -13,7 +13,7 @@ enum KeySource: String { case ekm } -enum KeyInfoError: Error { +enum KeyInfoError: Error, Equatable { case missingPrivateKey(String) case notEncrypted(String) case missingKeyIds diff --git a/FlowCryptAppTests/Core/Models/KeyInfoTests.swift b/FlowCryptAppTests/Core/Models/KeyInfoTests.swift index f2537bd75..ec4e2312c 100644 --- a/FlowCryptAppTests/Core/Models/KeyInfoTests.swift +++ b/FlowCryptAppTests/Core/Models/KeyInfoTests.swift @@ -30,12 +30,10 @@ class KeyInfoTests: XCTestCase { revoked: false ) - var thrownError: Error? - XCTAssertThrowsError(try KeypairRealmObject(keyDetail, passphrase: nil, source: .backup, user: user)) { error in - thrownError = error - } - - XCTAssertTrue(thrownError is KeyInfoError) + assert( + try KeypairRealmObject(keyDetail, passphrase: nil, source: .backup, user: user), + throws: KeyInfoError.missingPrivateKey("storing pubkey as private") + ) } func testKeyInfoInitNotFullyEcryptedThrowsError() { @@ -55,12 +53,10 @@ class KeyInfoTests: XCTestCase { revoked: false ) - var thrownError: Error? - XCTAssertThrowsError(try KeypairRealmObject(keyDetail, passphrase: nil, source: .backup, user: user)) { error in - thrownError = error - } - - XCTAssertTrue(thrownError is KeyInfoError) + assert( + try KeypairRealmObject(keyDetail, passphrase: nil, source: .backup, user: user), + throws: KeyInfoError.notEncrypted("Will not store Private Key that is not fully encrypted") + ) } func testKeyInfoWithEmptyKeyIdsThrowsError() { @@ -78,12 +74,10 @@ class KeyInfoTests: XCTestCase { revoked: false ) - var thrownError: Error? - XCTAssertThrowsError(try KeypairRealmObject(keyDetail, passphrase: nil, source: .backup, user: user)) { error in - thrownError = error - } - - XCTAssertTrue(thrownError is KeyInfoError) + assert( + try KeypairRealmObject(keyDetail, passphrase: nil, source: .backup, user: user), + throws: KeyInfoError.missingKeyIds + ) } func testKeyInfoInit() throws { diff --git a/FlowCryptAppTests/Extensions/XCTestCaseExtension.swift b/FlowCryptAppTests/Extensions/XCTestCaseExtension.swift new file mode 100644 index 000000000..211b442a8 --- /dev/null +++ b/FlowCryptAppTests/Extensions/XCTestCaseExtension.swift @@ -0,0 +1,36 @@ +// +// XCTestCaseExtension.swift +// FlowCryptAppTests +// +// Created by Roma Sosnovsky on 29/03/22 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import XCTest + +extension XCTestCase { + func assert( + _ expression: @autoclosure () throws -> T, + throws error: E, + in file: StaticString = #file, + line: UInt = #line + ) { + var thrownError: Error? + + XCTAssertThrowsError(try expression(), + file: file, line: line) { + thrownError = $0 + } + + XCTAssertTrue( + thrownError is E, + "Unexpected error type: \(type(of: thrownError))", + file: file, line: line + ) + + XCTAssertEqual( + thrownError as? E, error, + file: file, line: line + ) + } +} diff --git a/FlowCryptAppTests/Functionality/Services/Account Server Services/EnterpriseServerApiTests.swift b/FlowCryptAppTests/Functionality/Services/Account Server Services/EnterpriseServerApiTests.swift index 053d2fd42..51f899e80 100644 --- a/FlowCryptAppTests/Functionality/Services/Account Server Services/EnterpriseServerApiTests.swift +++ b/FlowCryptAppTests/Functionality/Services/Account Server Services/EnterpriseServerApiTests.swift @@ -11,25 +11,15 @@ import XCTest final class EnterpriseServerApiTests: XCTestCase { - func testGetActiveFesUrlWithUnknownDomain() async throws { - // arrange - let service = EnterpriseServerApi() - - // act - let result = try? await service.getActiveFesUrl(for: "user@nonexistentdomain.test") - - // assert - XCTAssertNil(result) + func testGetClientConfigurationWithUnknownDomain() async throws { + let service = try await EnterpriseServerApi(email: "user@nonexistentdomain.test") + let configuration = try await service.getClientConfiguration() + XCTAssertEqual(configuration, .empty) } func testGetClientConfigurationWithoutActiveFesUrl() async throws { - // arrange - let service = EnterpriseServerApi() - - // act - let result = try await service.getClientConfiguration(for: "user@gmail.com") - - // assert - XCTAssertEqual(result, .empty) + let service = try await EnterpriseServerApi(email: "user@gmail.com") + let configuration = try await service.getClientConfiguration() + XCTAssertEqual(configuration, .empty) } } diff --git a/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift b/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift index 47c935568..557d7bcd1 100644 --- a/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift +++ b/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift @@ -10,6 +10,9 @@ import Foundation final class EnterpriseServerApiMock: EnterpriseServerApiType { + var email = "example@flowcrypt.test" + var fesUrl: String? + var getActiveFesUrlInvoked = false var getActiveFesUrlInvokedCount = 0 var getActiveFesUrlCall: (String) -> String? = { _ in @@ -18,7 +21,7 @@ final class EnterpriseServerApiMock: EnterpriseServerApiType { func getActiveFesUrl(for email: String) async throws -> String? { getActiveFesUrlInvoked = true getActiveFesUrlInvokedCount += 1 - return try getActiveFesUrlCall(email) + return getActiveFesUrlCall(email) } var getActiveFesUrlForCurrentUserInvoked = false @@ -37,7 +40,7 @@ final class EnterpriseServerApiMock: EnterpriseServerApiType { var getClientConfigurationCall: (String) throws -> RawClientConfiguration = { _ in throw OrganisationalRulesServiceError.getClientConfigurationCall } - func getClientConfiguration(for email: String) async throws -> RawClientConfiguration { + func getClientConfiguration() async throws -> RawClientConfiguration { getClientConfigurationInvoked = true getClientConfigurationCount += 1 return try getClientConfigurationCall(email) @@ -54,7 +57,7 @@ final class EnterpriseServerApiMock: EnterpriseServerApiType { return try getClientConfigurationForCurrentUserCall() } - func getReplyToken(for email: String) async throws -> String { + func getReplyToken() async throws -> String { return "" } diff --git a/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift b/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift index 4180fc9e8..aa548f05c 100644 --- a/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift +++ b/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift @@ -34,6 +34,7 @@ class ComposeMessageServiceTests: XCTestCase { var core = CoreComposeMessageMock() var encryptedStorage = EncryptedStorageMock() var localContactsProvider = LocalContactsProviderMock() + var enterpriseServerMock = EnterpriseServerApiMock() override func setUp() { super.setUp() @@ -46,6 +47,7 @@ class ComposeMessageServiceTests: XCTestCase { passPhraseService: PassPhraseServiceMock(), draftGateway: DraftGatewayMock(), localContactsProvider: localContactsProvider, + enterpriseServer: enterpriseServerMock, sender: "some@gmail.com", core: core ) @@ -207,7 +209,7 @@ class ComposeMessageServiceTests: XCTestCase { func testValidateMessageInputWithAllEmptyRecipientPubKeys() async { encryptedStorage.getKeypairsResult = [keypair] - for recipient in recipients { + for _ in recipients { localContactsProvider.retrievePubKeysResult = { _ in [] } @@ -234,7 +236,7 @@ class ComposeMessageServiceTests: XCTestCase { return CoreRes.ParseKeys(format: .armored, keyDetails: [keyDetails]) } encryptedStorage.getKeypairsResult = [keypair] - for recipient in recipients { + for _ in recipients { localContactsProvider.retrievePubKeysResult = { _ in ["pubKey"] } @@ -261,7 +263,7 @@ class ComposeMessageServiceTests: XCTestCase { return CoreRes.ParseKeys(format: .armored, keyDetails: [keyDetails]) } encryptedStorage.getKeypairsResult = [keypair] - for recipient in recipients { + for _ in recipients { localContactsProvider.retrievePubKeysResult = { _ in ["pubKey"] } @@ -300,7 +302,7 @@ class ComposeMessageServiceTests: XCTestCase { return CoreRes.ParseKeys(format: .armored, keyDetails: allKeyDetails) } encryptedStorage.getKeypairsResult = [keypair] - for recipient in recipients { + for _ in recipients { localContactsProvider.retrievePubKeysResult = { _ in ["revoked", "expired", "valid"] } From ac60339f2689a979e6d1fcf9d907f17369ea510c Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Tue, 29 Mar 2022 15:04:45 +0300 Subject: [PATCH 3/8] AppContext init update --- FlowCrypt/App/AppContext.swift | 2 ++ FlowCrypt/Functionality/Services/AppStartup.swift | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/FlowCrypt/App/AppContext.swift b/FlowCrypt/App/AppContext.swift index 807d1951d..cd8605e33 100644 --- a/FlowCrypt/App/AppContext.swift +++ b/FlowCrypt/App/AppContext.swift @@ -147,6 +147,8 @@ class AppContextWithUser: AppContext { ) ) + _ = try await self.clientConfigurationService.fetch(for: user) + super.init( encryptedStorage: encryptedStorage, session: session, diff --git a/FlowCrypt/Functionality/Services/AppStartup.swift b/FlowCrypt/Functionality/Services/AppStartup.swift index 47cb5a938..e6224feb0 100644 --- a/FlowCrypt/Functionality/Services/AppStartup.swift +++ b/FlowCrypt/Functionality/Services/AppStartup.swift @@ -186,7 +186,6 @@ struct AppStartup { } let contextWithUser = try await appContext.with(session: session, authType: authType, user: user) - _ = try await contextWithUser.clientConfigurationService.fetch(for: contextWithUser.user) callback(contextWithUser) } } From 46f9913eff5dae6fddf1c596fb5e7764498ce0e1 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Tue, 29 Mar 2022 16:17:18 +0300 Subject: [PATCH 4/8] revert Package.resolved --- .../xcshareddata/swiftpm/Package.resolved | 224 +++++++++--------- 1 file changed, 113 insertions(+), 111 deletions(-) diff --git a/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved b/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved index dbe188328..d0d3880a9 100644 --- a/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,113 +1,115 @@ { - "pins" : [ - { - "identity" : "appauth-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/openid/AppAuth-iOS.git", - "state" : { - "revision" : "33660c271c961f8ce1084cc13f2ea8195e864f7d", - "version" : "1.5.0" + "object": { + "pins": [ + { + "package": "AppAuth", + "repositoryURL": "https://github.com/openid/AppAuth-iOS.git", + "state": { + "branch": null, + "revision": "01131d68346c8ae552961c768d583c715fbe1410", + "version": "1.4.0" + } + }, + { + "package": "BigInt", + "repositoryURL": "https://github.com/attaswift/BigInt", + "state": { + "branch": null, + "revision": "0ed110f7555c34ff468e72e1686e59721f2b0da6", + "version": "5.3.0" + } + }, + { + "package": "GoogleAPIClientForREST", + "repositoryURL": "https://github.com/google/google-api-objectivec-client-for-rest", + "state": { + "branch": null, + "revision": "22e0bb02729d60db396e8b90d8189313cd86ba53", + "version": "1.6.0" + } + }, + { + "package": "GoogleSignIn", + "repositoryURL": "https://github.com/google/GoogleSignIn-iOS", + "state": { + "branch": null, + "revision": "60ca2bfd218ccb194a746a79b41d9d50eb7e3af0", + "version": "6.1.0" + } + }, + { + "package": "GTMSessionFetcher", + "repositoryURL": "https://github.com/google/gtm-session-fetcher.git", + "state": { + "branch": null, + "revision": "bc6a19702ac76ac4e488b68148710eb815f9bc56", + "version": "1.7.0" + } + }, + { + "package": "GTMAppAuth", + "repositoryURL": "https://github.com/google/GTMAppAuth.git", + "state": { + "branch": null, + "revision": "40f4103fb52109032c05599a0c39ad43edbdf80a", + "version": "1.2.2" + } + }, + { + "package": "IDZSwiftCommonCrypto", + "repositoryURL": "https://github.com/iosdevzone/IDZSwiftCommonCrypto", + "state": { + "branch": null, + "revision": "d824371e670bd57eb456bbc41139b4997f7207b8", + "version": "0.13.1" + } + }, + { + "package": "MailCore", + "repositoryURL": "https://github.com/FlowCrypt/mailcore2", + "state": { + "branch": null, + "revision": "512e3074f7b80a4425be806e3ce968f1e84b995f", + "version": "0.7.5" + } + }, + { + "package": "MBProgressHUD", + "repositoryURL": "https://github.com/jdg/MBProgressHUD", + "state": { + "branch": null, + "revision": "bca42b801100b2b3a4eda0ba8dd33d858c780b0d", + "version": "1.2.0" + } + }, + { + "package": "Realm", + "repositoryURL": "https://github.com/realm/realm-cocoa", + "state": { + "branch": null, + "revision": "9dff9f2862240d521ad6ad599541269177ddb993", + "version": "10.22.0" + } + }, + { + "package": "RealmDatabase", + "repositoryURL": "https://github.com/realm/realm-core", + "state": { + "branch": null, + "revision": "6b81f1a7a2d421f9e0b9e7f04e76bcf736a54409", + "version": "11.9.0" + } + }, + { + "package": "Toast", + "repositoryURL": "https://github.com/scalessec/Toast-Swift", + "state": { + "branch": null, + "revision": "0c9493eeacb102cc614da385cfaaf475379f4ab4", + "version": "5.0.1" + } } - }, - { - "identity" : "bigint", - "kind" : "remoteSourceControl", - "location" : "https://github.com/attaswift/BigInt", - "state" : { - "revision" : "0ed110f7555c34ff468e72e1686e59721f2b0da6", - "version" : "5.3.0" - } - }, - { - "identity" : "google-api-objectivec-client-for-rest", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/google-api-objectivec-client-for-rest", - "state" : { - "revision" : "3228334d0584cb9174796fecbe628a723be70452", - "version" : "1.7.0" - } - }, - { - "identity" : "googlesignin-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/GoogleSignIn-iOS", - "state" : { - "revision" : "60ca2bfd218ccb194a746a79b41d9d50eb7e3af0", - "version" : "6.1.0" - } - }, - { - "identity" : "gtm-session-fetcher", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/gtm-session-fetcher.git", - "state" : { - "revision" : "eca9404a18f53727e4698211aaf2615eb93b962a", - "version" : "1.7.1" - } - }, - { - "identity" : "gtmappauth", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/GTMAppAuth.git", - "state" : { - "revision" : "e803d09da0147fbf1bbb30e126c47ff43254e057", - "version" : "1.2.3" - } - }, - { - "identity" : "idzswiftcommoncrypto", - "kind" : "remoteSourceControl", - "location" : "https://github.com/iosdevzone/IDZSwiftCommonCrypto", - "state" : { - "revision" : "d824371e670bd57eb456bbc41139b4997f7207b8", - "version" : "0.13.1" - } - }, - { - "identity" : "mailcore2", - "kind" : "remoteSourceControl", - "location" : "https://github.com/FlowCrypt/mailcore2", - "state" : { - "revision" : "512e3074f7b80a4425be806e3ce968f1e84b995f", - "version" : "0.7.5" - } - }, - { - "identity" : "mbprogresshud", - "kind" : "remoteSourceControl", - "location" : "https://github.com/jdg/MBProgressHUD", - "state" : { - "revision" : "bca42b801100b2b3a4eda0ba8dd33d858c780b0d", - "version" : "1.2.0" - } - }, - { - "identity" : "realm-cocoa", - "kind" : "remoteSourceControl", - "location" : "https://github.com/realm/realm-cocoa", - "state" : { - "revision" : "628cf20632d65d3a3e90ae8aaf52bce596d7ad8f", - "version" : "10.24.2" - } - }, - { - "identity" : "realm-core", - "kind" : "remoteSourceControl", - "location" : "https://github.com/realm/realm-core", - "state" : { - "revision" : "dcd3788f75bafb0cc397d145e1651aab3ad4bf0e", - "version" : "11.12.0" - } - }, - { - "identity" : "toast-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/scalessec/Toast-Swift", - "state" : { - "revision" : "0c9493eeacb102cc614da385cfaaf475379f4ab4", - "version" : "5.0.1" - } - } - ], - "version" : 2 -} + ] + }, + "version": 1 +} \ No newline at end of file From 648bdff0121082a7d70a8357f6a1fdad69c866ef Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Sat, 2 Apr 2022 23:00:28 +0300 Subject: [PATCH 5/8] pr updates --- FlowCrypt.xcodeproj/project.pbxproj | 4 + FlowCrypt/App/AppContext.swift | 6 +- .../EnterpriseServerApi.swift | 99 +--------------- .../EnterpriseServerApiHelper.swift | 107 ++++++++++++++++++ .../Services/GoogleUserService.swift | 3 +- .../Services/ComposeMessageServiceTests.swift | 28 ++--- 6 files changed, 128 insertions(+), 119 deletions(-) create mode 100644 FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApiHelper.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index ecb03242c..607d9197c 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -74,6 +74,7 @@ 5133B6702716320F00C95463 /* ContactKeyDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133B66F2716320F00C95463 /* ContactKeyDetailViewController.swift */; }; 5133B6722716321F00C95463 /* ContactKeyDetailDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133B6712716321F00C95463 /* ContactKeyDetailDecorator.swift */; }; 5133B6742716E5EA00C95463 /* LabelCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133B6732716E5EA00C95463 /* LabelCellNode.swift */; }; + 5137CB1427F8E0A900AEF895 /* EnterpriseServerApiHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5137CB1327F8E0A900AEF895 /* EnterpriseServerApiHelper.swift */; }; 514C34DB276CE19C00FCAB79 /* ComposeMessageContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514C34DA276CE19C00FCAB79 /* ComposeMessageContext.swift */; }; 514C34DD276CE1C000FCAB79 /* ComposeMessageRecipient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514C34DC276CE1C000FCAB79 /* ComposeMessageRecipient.swift */; }; 514C34DF276CE20700FCAB79 /* ComposeMessageService+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514C34DE276CE20700FCAB79 /* ComposeMessageService+State.swift */; }; @@ -517,6 +518,7 @@ 5133B66F2716320F00C95463 /* ContactKeyDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactKeyDetailViewController.swift; sourceTree = ""; }; 5133B6712716321F00C95463 /* ContactKeyDetailDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactKeyDetailDecorator.swift; sourceTree = ""; }; 5133B6732716E5EA00C95463 /* LabelCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelCellNode.swift; sourceTree = ""; }; + 5137CB1327F8E0A900AEF895 /* EnterpriseServerApiHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterpriseServerApiHelper.swift; sourceTree = ""; }; 514C34DA276CE19C00FCAB79 /* ComposeMessageContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeMessageContext.swift; sourceTree = ""; }; 514C34DC276CE1C000FCAB79 /* ComposeMessageRecipient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeMessageRecipient.swift; sourceTree = ""; }; 514C34DE276CE20700FCAB79 /* ComposeMessageService+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeMessageService+State.swift"; sourceTree = ""; }; @@ -1687,6 +1689,7 @@ children = ( 32DCAC9C0512037018F434A1 /* BackendApi.swift */, 21C7DF08266C0D8F00C44800 /* EnterpriseServerApi.swift */, + 5137CB1327F8E0A900AEF895 /* EnterpriseServerApiHelper.swift */, 517C2E312779F91300FECF32 /* Models */, ); path = "Account Server Services"; @@ -2809,6 +2812,7 @@ 9F2AC5B1267BDED100F6149B /* GmailSearchExpressionGenerator.swift in Sources */, 040FDF1227EDFC5C00CB936A /* IdTokenUtils.swift in Sources */, 9F953E09238310D500AEB98B /* KeyMethods.swift in Sources */, + 5137CB1427F8E0A900AEF895 /* EnterpriseServerApiHelper.swift in Sources */, 9F92EE72236F165E009BE0D7 /* EncryptedStorage.swift in Sources */, 32DCA00224982EDA88D69C6E /* AppErr.swift in Sources */, 9F6EE17B2598F9FA0059BA51 /* Gmail+Backup.swift in Sources */, diff --git a/FlowCrypt/App/AppContext.swift b/FlowCrypt/App/AppContext.swift index cd8605e33..b5a93817b 100644 --- a/FlowCrypt/App/AppContext.swift +++ b/FlowCrypt/App/AppContext.swift @@ -119,14 +119,11 @@ class AppContext { class AppContextWithUser: AppContext { let authType: AuthType let user: User + let userId: UserId let enterpriseServer: EnterpriseServerApiType let clientConfigurationService: ClientConfigurationServiceType - var userId: UserId { - UserId(email: user.email, name: user.name) - } - init( encryptedStorage: EncryptedStorageType, session: SessionType?, @@ -139,6 +136,7 @@ class AppContextWithUser: AppContext { ) async throws { self.authType = authType self.user = user + self.userId = UserId(email: user.email, name: user.name) self.enterpriseServer = try await EnterpriseServerApi(email: user.email) self.clientConfigurationService = ClientConfigurationService( server: enterpriseServer, diff --git a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift index 17685f710..305a2f07b 100644 --- a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift +++ b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift @@ -23,11 +23,6 @@ class EnterpriseServerApi: NSObject, EnterpriseServerApiType { static let publicEmailProviderDomains = ["gmail.com", "googlemail.com", "outlook.com"] private enum Constants { - /// -1001 - request timed out, -1003 - сannot resolve host, -1004 - can't connect to hosts - /// -1005 - network connection lost, -1006 - dns lookup failed, -1007 - too many redirects - /// -1008 - resource unavailable - static let getToleratedNSErrorCodes = [-1001, -1003, -1004, -1005, -1006, -1007, -1008] - static let getActiveFesTimeout: TimeInterval = 4 static let apiName = "EnterpriseServerApi" } @@ -49,60 +44,18 @@ class EnterpriseServerApi: NSObject, EnterpriseServerApiType { return decoder }() - private func constructUrlBase(emailDomain: String) -> String { - guard !Bundle.isDebugBundleWithArgument("--mock-fes-api") else { - return "http://127.0.0.1:8001/fes" // mock - } - return "https://fes.\(emailDomain)" // live - } - private var messageUploadProgressHandler: ((Float) -> Void)? private let email: String - private(set) var fesUrl: String? + let fesUrl: String? init(email: String) async throws { self.email = email - super.init() - - self.fesUrl = try await getActiveFesUrl() - } - - private func getActiveFesUrl() async throws -> String? { - do { - guard let userDomain = email.emailParts?.domain, - !EnterpriseServerApi.publicEmailProviderDomains.contains(userDomain) else { - return nil - } - let urlBase = constructUrlBase(emailDomain: userDomain) - let request = ApiCall.Request( - apiName: Constants.apiName, - url: "\(urlBase)/api/", - timeout: Constants.getActiveFesTimeout, - tolerateStatus: [404] // 404 tells the app that FES is disabled - ) - let response = try await ApiCall.call(request) - - if response.status == 404 { - return nil // FES is explicitly disabled - } - - guard isExpectedFesServiceResponse(responseData: response.data) else { - if Bundle.isEnterprise() { // on enterprise build, FES is expected to be running - throw AppErr.general("Unpexpected response from FlowCrypt Enterprise Server") - } - return nil // on consumer installations, we only use FES if it returns as expected - } + let helper = EnterpriseServerApiHelper() + self.fesUrl = try await helper.getActiveFesUrl(for: email) - return urlBase - } catch { - if await shouldTolerateWhenCallingOpportunistically(error) { - return nil - } else { - throw error - } - } + super.init() } func getClientConfiguration() async throws -> RawClientConfiguration { @@ -170,50 +123,6 @@ class EnterpriseServerApi: NSObject, EnterpriseServerApiType { return response.url } - private func isExpectedFesServiceResponse(responseData: Data) -> Bool { - // "try?" because unsure what server is running there, want to test without failing - guard let responseDictionary = try? responseData.toDict() else { return false } - guard let service = responseDictionary["service"] as? String else { return false } - return service == "enterprise-server" - } - - private func shouldTolerateWhenCallingOpportunistically(_ error: Error) async -> Bool { - if Bundle.isEnterprise() { - return false // FlowCrypt Enterprise Server (FES) required on enterprise bundle - } - // on consumer release, FES is called opportunistically - if it's there, it will be used - // guards first - don't tolerate unknown / other errors. Only interested in network errors. - guard let apiError = error as? ApiError else { return false } - guard let nsError = apiError.internalError as NSError? else { return false } - guard Constants.getToleratedNSErrorCodes.contains(nsError.code) else { return false } - // when calling FES, we got some sort of network error. Could be FES down or internet down. - if await doesTheInternetWork() { - // we got network error from FES, but internet works. We are on consumer release. - // we can assume that there is no FES running - return true // tolerate the error - } else { - // we got network error from FES because internet actually doesn't work - // throw original error so user can retry - return false // do not tolertate the error - } - } - - private func doesTheInternetWork() async -> Bool { - // this API is mentioned here: - // https://www.chromium.org/chromium-os/chromiumos-design-docs/network-portal-detection - let request = ApiCall.Request( - apiName: "ConnectionTest", - url: "https://client3.google.com/generate_204", - timeout: Constants.getActiveFesTimeout - ) - do { - let response = try await ApiCall.call(request) - return response.status == 204 - } catch { - return false - } - } - private func performRequest( email: String, url: String, diff --git a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApiHelper.swift b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApiHelper.swift new file mode 100644 index 000000000..3d959bf40 --- /dev/null +++ b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApiHelper.swift @@ -0,0 +1,107 @@ +// +// EnterpriseServerApiHelper.swift +// FlowCrypt +// +// Created by Roma Sosnovsky on 02/04/22 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import Foundation + +struct EnterpriseServerApiHelper { + private enum Constants { + /// -1001 - request timed out, -1003 - сannot resolve host, -1004 - can't connect to hosts + /// -1005 - network connection lost, -1006 - dns lookup failed, -1007 - too many redirects + /// -1008 - resource unavailable + static let getToleratedNSErrorCodes = [-1001, -1003, -1004, -1005, -1006, -1007, -1008] + static let getActiveFesTimeout: TimeInterval = 4 + static let apiName = "EnterpriseServerApi" + } + + private func constructUrlBase(emailDomain: String) -> String { + guard !Bundle.isDebugBundleWithArgument("--mock-fes-api") else { + return "http://127.0.0.1:8001/fes" // mock + } + return "https://fes.\(emailDomain)" // live + } + + func getActiveFesUrl(for email: String) async throws -> String? { + do { + guard let userDomain = email.emailParts?.domain, + !EnterpriseServerApi.publicEmailProviderDomains.contains(userDomain) else { + return nil + } + let urlBase = constructUrlBase(emailDomain: userDomain) + let request = ApiCall.Request( + apiName: Constants.apiName, + url: "\(urlBase)/api/", + timeout: Constants.getActiveFesTimeout, + tolerateStatus: [404] // 404 tells the app that FES is disabled + ) + let response = try await ApiCall.call(request) + + if response.status == 404 { + return nil // FES is explicitly disabled + } + + guard isExpectedFesServiceResponse(responseData: response.data) else { + if Bundle.isEnterprise() { // on enterprise build, FES is expected to be running + throw AppErr.general("Unpexpected response from FlowCrypt Enterprise Server") + } + return nil // on consumer installations, we only use FES if it returns as expected + } + + return urlBase + } catch { + if await shouldTolerateWhenCallingOpportunistically(error) { + return nil + } else { + throw error + } + } + } + + private func isExpectedFesServiceResponse(responseData: Data) -> Bool { + // "try?" because unsure what server is running there, want to test without failing + guard let responseDictionary = try? responseData.toDict() else { return false } + guard let service = responseDictionary["service"] as? String else { return false } + return service == "enterprise-server" + } + + private func shouldTolerateWhenCallingOpportunistically(_ error: Error) async -> Bool { + if Bundle.isEnterprise() { + return false // FlowCrypt Enterprise Server (FES) required on enterprise bundle + } + // on consumer release, FES is called opportunistically - if it's there, it will be used + // guards first - don't tolerate unknown / other errors. Only interested in network errors. + guard let apiError = error as? ApiError else { return false } + guard let nsError = apiError.internalError as NSError? else { return false } + guard Constants.getToleratedNSErrorCodes.contains(nsError.code) else { return false } + // when calling FES, we got some sort of network error. Could be FES down or internet down. + if await doesTheInternetWork() { + // we got network error from FES, but internet works. We are on consumer release. + // we can assume that there is no FES running + return true // tolerate the error + } else { + // we got network error from FES because internet actually doesn't work + // throw original error so user can retry + return false // do not tolertate the error + } + } + + private func doesTheInternetWork() async -> Bool { + // this API is mentioned here: + // https://www.chromium.org/chromium-os/chromiumos-design-docs/network-portal-detection + let request = ApiCall.Request( + apiName: "ConnectionTest", + url: "https://client3.google.com/generate_204", + timeout: Constants.getActiveFesTimeout + ) + do { + let response = try await ApiCall.call(request) + return response.status == 204 + } catch { + return false + } + } +} diff --git a/FlowCrypt/Functionality/Services/GoogleUserService.swift b/FlowCrypt/Functionality/Services/GoogleUserService.swift index 4f742a5a9..c1e6876bd 100644 --- a/FlowCrypt/Functionality/Services/GoogleUserService.swift +++ b/FlowCrypt/Functionality/Services/GoogleUserService.swift @@ -122,7 +122,8 @@ extension GoogleUserService: UserServiceType { // GTMAppAuth should renew session via OIDAuthStateChangeDelegate } - @MainActor func signIn(in viewController: UIViewController, scopes: [GoogleScope], userEmail: String? = nil) async throws -> SessionType { + @MainActor + func signIn(in viewController: UIViewController, scopes: [GoogleScope], userEmail: String? = nil) async throws -> SessionType { return try await withCheckedThrowingContinuation { continuation in let request = self.makeAuthorizationRequest(scopes: scopes, userEmail: userEmail) let googleDelegateSess = OIDAuthState.authState( diff --git a/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift b/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift index aa548f05c..b9c88cf25 100644 --- a/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift +++ b/FlowCryptAppTests/Functionality/Services/ComposeMessageServiceTests.swift @@ -209,11 +209,8 @@ class ComposeMessageServiceTests: XCTestCase { func testValidateMessageInputWithAllEmptyRecipientPubKeys() async { encryptedStorage.getKeypairsResult = [keypair] - for _ in recipients { - localContactsProvider.retrievePubKeysResult = { _ in - [] - } - } + localContactsProvider.retrievePubKeysResult = { _ in [] } + do { _ = try await sut.validateAndProduceSendableMsg( input: ComposeMessageInput(type: .idle), @@ -236,11 +233,8 @@ class ComposeMessageServiceTests: XCTestCase { return CoreRes.ParseKeys(format: .armored, keyDetails: [keyDetails]) } encryptedStorage.getKeypairsResult = [keypair] - for _ in recipients { - localContactsProvider.retrievePubKeysResult = { _ in - ["pubKey"] - } - } + localContactsProvider.retrievePubKeysResult = { _ in ["pubKey"] } + do { _ = try await sut.validateAndProduceSendableMsg( input: ComposeMessageInput(type: .idle), @@ -263,11 +257,8 @@ class ComposeMessageServiceTests: XCTestCase { return CoreRes.ParseKeys(format: .armored, keyDetails: [keyDetails]) } encryptedStorage.getKeypairsResult = [keypair] - for _ in recipients { - localContactsProvider.retrievePubKeysResult = { _ in - ["pubKey"] - } - } + localContactsProvider.retrievePubKeysResult = { _ in ["pubKey"] } + do { _ = try await sut.validateAndProduceSendableMsg( input: ComposeMessageInput(type: .idle), @@ -302,11 +293,10 @@ class ComposeMessageServiceTests: XCTestCase { return CoreRes.ParseKeys(format: .armored, keyDetails: allKeyDetails) } encryptedStorage.getKeypairsResult = [keypair] - for _ in recipients { - localContactsProvider.retrievePubKeysResult = { _ in - ["revoked", "expired", "valid"] - } + localContactsProvider.retrievePubKeysResult = { _ in + ["revoked", "expired", "valid"] } + let message = "some message" let subject = "Some subject" let email = "some@gmail.com" From 7249f7c70b171f40ba89a08f51bffc8fa6ba9fc7 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Mon, 4 Apr 2022 13:59:09 +0300 Subject: [PATCH 6/8] fix startup error handling --- .../Setup/SetupBackupsViewController.swift | 10 ++++--- ...eatePassphraseAbstractViewController.swift | 10 ++++--- .../Setup/SetupInitialViewController.swift | 10 ++++--- .../SideMenu/Menu/MyMenuViewController.swift | 20 ++++++++------ .../Functionality/Services/AppStartup.swift | 12 +++++---- .../Functionality/Services/GlobalRouter.swift | 26 +++++++++---------- appium/package.json | 2 +- 7 files changed, 50 insertions(+), 40 deletions(-) diff --git a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift index 8a01a766c..2ef5f2dd3 100644 --- a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift @@ -173,10 +173,12 @@ extension SetupBackupsViewController { } func handleBackButtonTap() { - do { - try appContext.globalRouter.signOut(appContext: appContext) - } catch { - showAlert(message: error.localizedDescription) + Task { + do { + try await appContext.globalRouter.signOut(appContext: appContext) + } catch { + showAlert(message: error.localizedDescription) + } } } diff --git a/FlowCrypt/Controllers/Setup/SetupCreatePassphraseAbstractViewController.swift b/FlowCrypt/Controllers/Setup/SetupCreatePassphraseAbstractViewController.swift index 243e051a7..498f857a7 100644 --- a/FlowCrypt/Controllers/Setup/SetupCreatePassphraseAbstractViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupCreatePassphraseAbstractViewController.swift @@ -85,10 +85,12 @@ class SetupCreatePassphraseAbstractViewController: TableNodeViewController, Pass } func handleBackButtonTap() { - do { - try appContext.globalRouter.signOut(appContext: self.appContext) - } catch { - showAlert(message: error.localizedDescription) + Task { + do { + try await appContext.globalRouter.signOut(appContext: self.appContext) + } catch { + showAlert(message: error.localizedDescription) + } } } } diff --git a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift index 755ee2caa..e155f8664 100644 --- a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift @@ -127,10 +127,12 @@ extension SetupInitialViewController { } private func signOut() { - do { - try appContext.globalRouter.signOut(appContext: appContext) - } catch { - showAlert(message: error.localizedDescription) + Task { + do { + try await appContext.globalRouter.signOut(appContext: appContext) + } catch { + showAlert(message: error.localizedDescription) + } } } diff --git a/FlowCrypt/Controllers/SideMenu/Menu/MyMenuViewController.swift b/FlowCrypt/Controllers/SideMenu/Menu/MyMenuViewController.swift index 141accf95..ef5b9556d 100644 --- a/FlowCrypt/Controllers/SideMenu/Menu/MyMenuViewController.swift +++ b/FlowCrypt/Controllers/SideMenu/Menu/MyMenuViewController.swift @@ -227,10 +227,12 @@ extension MyMenuViewController { return } - do { - try appContext.globalRouter.switchActive(user: account, appContext: appContext) - } catch { - showAlert(message: error.localizedDescription) + Task { + do { + try await appContext.globalRouter.switchActive(user: account, appContext: appContext) + } catch { + showAlert(message: error.localizedDescription) + } } } } @@ -309,10 +311,12 @@ extension MyMenuViewController { showAlert(message: error.localizedDescription) } case .logOut: - do { - try appContext.globalRouter.signOut(appContext: appContext) - } catch { - showAlert(message: error.localizedDescription) + Task { + do { + try await appContext.globalRouter.signOut(appContext: appContext) + } catch { + showAlert(message: error.localizedDescription) + } } } } diff --git a/FlowCrypt/Functionality/Services/AppStartup.swift b/FlowCrypt/Functionality/Services/AppStartup.swift index e6224feb0..27942059a 100644 --- a/FlowCrypt/Functionality/Services/AppStartup.swift +++ b/FlowCrypt/Functionality/Services/AppStartup.swift @@ -135,7 +135,7 @@ struct AppStartup { private func showErrorAlert(of error: Error, on window: UIWindow) { let alert = UIAlertController( title: "error_startup".localized, - message: "\(error.localizedDescription)", + message: error.errorMessage, preferredStyle: .alert ) let retry = UIAlertAction( @@ -148,10 +148,12 @@ struct AppStartup { title: "log_out".localized, style: .default ) { _ in - do { - try appContext.globalRouter.signOut(appContext: appContext) - } catch let logoutError { - Logger.logError("Logout failed due to \(logoutError.localizedDescription)") + Task { + do { + try await appContext.globalRouter.signOut(appContext: appContext) + } catch let logoutError { + Logger.logError("Logout failed due to \(logoutError.localizedDescription)") + } } } alert.addAction(retry) diff --git a/FlowCrypt/Functionality/Services/GlobalRouter.swift b/FlowCrypt/Functionality/Services/GlobalRouter.swift index fb2a0cd07..ac5167ac4 100644 --- a/FlowCrypt/Functionality/Services/GlobalRouter.swift +++ b/FlowCrypt/Functionality/Services/GlobalRouter.swift @@ -14,8 +14,8 @@ protocol GlobalRouterType { func proceed() func signIn(appContext: AppContext, route: GlobalRoutingType) async func askForContactsPermission(for route: GlobalRoutingType, appContext: AppContextWithUser) async throws - func switchActive(user: User, appContext: AppContext) throws - func signOut(appContext: AppContext) throws + func switchActive(user: User, appContext: AppContext) async throws + func signOut(appContext: AppContext) async throws } enum GlobalRoutingType { @@ -74,10 +74,10 @@ extension GlobalRouter: GlobalRouterType { ) try appContext.userAccountService.startSessionFor(session: session) viewController.hideSpinner() - try proceed(with: appContext, session: session) + try await proceed(with: appContext, session: session) case .other(let session): try appContext.userAccountService.startSessionFor(session: session) - try proceed(with: appContext, session: session) + try await proceed(with: appContext, session: session) } } catch { if case .gmailLogin(let viewController) = route { @@ -88,10 +88,10 @@ extension GlobalRouter: GlobalRouterType { } } - func signOut(appContext: AppContext) throws { + func signOut(appContext: AppContext) async throws { if let session = try appContext.userAccountService.startActiveSessionForNextUser() { logger.logInfo("Start session for another email user \(session)") - try proceed(with: appContext, session: session) + try await proceed(with: appContext, session: session) } else { logger.logInfo("Sign out") try appContext.userAccountService.cleanup() @@ -125,13 +125,13 @@ extension GlobalRouter: GlobalRouterType { } } - func switchActive(user: User, appContext: AppContext) throws { + func switchActive(user: User, appContext: AppContext) async throws { logger.logInfo("Switching active user \(user)") guard let session = try appContext.userAccountService.switchActiveSessionFor(user: user) else { logger.logWarning("Can't switch active user with \(user.email)") return } - try proceed(with: appContext, session: session) + try await proceed(with: appContext, session: session) } @MainActor @@ -153,7 +153,7 @@ extension GlobalRouter: GlobalRouterType { } @MainActor - private func proceed(with appContext: AppContext, session: SessionType) throws { + private func proceed(with appContext: AppContext, session: SessionType) async throws { logger.logInfo("proceed for session: \(session.description)") guard let user = try appContext.encryptedStorage.activeUser, @@ -171,10 +171,8 @@ extension GlobalRouter: GlobalRouterType { return } - Task { - let appContextWithUser = try await appContext.with(session: session, authType: authType, user: user) - AppStartup(appContext: appContextWithUser).initializeApp(window: keyWindow) - } + let appContextWithUser = try await appContext.with(session: session, authType: authType, user: user) + AppStartup(appContext: appContextWithUser).initializeApp(window: keyWindow) } @MainActor @@ -197,7 +195,7 @@ extension GlobalRouter: GlobalRouterType { keyWindow.rootViewController?.showAlert( title: "error".localized, - message: error.localizedDescription, + message: error.errorMessage, onOk: { [weak self] in self?.proceed() } ) } diff --git a/appium/package.json b/appium/package.json index b7f8b0825..e29fbcd78 100644 --- a/appium/package.json +++ b/appium/package.json @@ -32,7 +32,7 @@ "@wdio/junit-reporter": "^7.16.15", "@wdio/local-runner": "^7.16.15", "@wdio/spec-reporter": "^7.16.14", - "appium": "1.22.2", + "appium": "1.22.3", "babel-eslint": "^10.1.0", "dotenv": "^16.0.0", "eslint": "^8.9.0", From e5936d9391310a9953bd07b7a1acf000502adca5 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Mon, 4 Apr 2022 16:11:29 +0300 Subject: [PATCH 7/8] ui tests fix --- FlowCrypt/Functionality/Services/AppStartup.swift | 2 +- appium/tests/screenobjects/base.screen.ts | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/FlowCrypt/Functionality/Services/AppStartup.swift b/FlowCrypt/Functionality/Services/AppStartup.swift index 27942059a..b68bc9c8b 100644 --- a/FlowCrypt/Functionality/Services/AppStartup.swift +++ b/FlowCrypt/Functionality/Services/AppStartup.swift @@ -179,7 +179,7 @@ struct AppStartup { } window.rootViewController?.showAlert( - title: "error".localized, + title: "error_startup".localized, message: message, onOk: { fatalError() } ) diff --git a/appium/tests/screenobjects/base.screen.ts b/appium/tests/screenobjects/base.screen.ts index ac887d101..966283314 100644 --- a/appium/tests/screenobjects/base.screen.ts +++ b/appium/tests/screenobjects/base.screen.ts @@ -3,10 +3,7 @@ import ElementHelper from "../helpers/ElementHelper"; const SELECTORS = { - ERROR_HEADER: '-ios class chain:**/XCUIElementTypeStaticText[`label == "Error"`]', OK_BUTTON: '~Ok', - ERROR_FES_HEADER: '-ios class chain:**/XCUIElementTypeStaticText[`label == "Startup Error"`]', - RETRY_BUTTON: '~Retry', CURRENT_MODAL: '-ios predicate string:type == "XCUIElementTypeAlert"' }; @@ -33,7 +30,7 @@ export default class BaseScreen { } static checkModalMessage = async (message: string) => { - await expect(await this.currentModal).toBeDisplayed(); + await ElementHelper.waitElementVisible(await this.currentModal); const alertText = await driver.getAlertText(); expect(alertText).toEqual(message); } From 8b274900e7e4b52e0f6cd61839fd9fd443f7e5c1 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Mon, 4 Apr 2022 17:56:36 +0300 Subject: [PATCH 8/8] update ui test --- FlowCrypt/Functionality/Services/AppStartup.swift | 4 ++-- FlowCrypt/Functionality/Services/GlobalRouter.swift | 4 ++-- FlowCrypt/Resources/en.lproj/Localizable.strings | 1 + .../specs/mock/setup/SetupShowsMeaningfulErrorFromFES.spec.ts | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/FlowCrypt/Functionality/Services/AppStartup.swift b/FlowCrypt/Functionality/Services/AppStartup.swift index b68bc9c8b..dbab88c34 100644 --- a/FlowCrypt/Functionality/Services/AppStartup.swift +++ b/FlowCrypt/Functionality/Services/AppStartup.swift @@ -79,7 +79,7 @@ struct AppStartup { let controller = try SetupInitialViewController(appContext: context) window.rootViewController = MainNavigationController(rootViewController: controller) } catch { - window.rootViewController?.showAlert(message: error.localizedDescription) + window.rootViewController?.showAlert(message: error.errorMessage) } } } @@ -179,7 +179,7 @@ struct AppStartup { } window.rootViewController?.showAlert( - title: "error_startup".localized, + title: "error".localized, message: message, onOk: { fatalError() } ) diff --git a/FlowCrypt/Functionality/Services/GlobalRouter.swift b/FlowCrypt/Functionality/Services/GlobalRouter.swift index ac5167ac4..587f7e44b 100644 --- a/FlowCrypt/Functionality/Services/GlobalRouter.swift +++ b/FlowCrypt/Functionality/Services/GlobalRouter.swift @@ -83,7 +83,7 @@ extension GlobalRouter: GlobalRouterType { if case .gmailLogin(let viewController) = route { viewController.hideSpinner() } - logger.logError("Failed to sign in due to \(error.localizedDescription)") + logger.logError("Failed to sign in due to \(error.errorMessage)") handleSignInError(error: error, appContext: appContext) } } @@ -194,7 +194,7 @@ extension GlobalRouter: GlobalRouterType { } keyWindow.rootViewController?.showAlert( - title: "error".localized, + title: "error_login".localized, message: error.errorMessage, onOk: { [weak self] in self?.proceed() } ) diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index 62fd08940..e92fcd1fe 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -78,6 +78,7 @@ "error_internal_parse_block" = "Internal error: could not parse MsgBlock. Please report this error to us."; "error_decrypt" = "Could not decrypt:"; "error_startup" = "Startup Error"; +"error_login" = "Login Error"; "error_wrong_app_state" = "Wrong application state. User not found for session %@"; // KeyServiceError diff --git a/appium/tests/specs/mock/setup/SetupShowsMeaningfulErrorFromFES.spec.ts b/appium/tests/specs/mock/setup/SetupShowsMeaningfulErrorFromFES.spec.ts index f8fec687b..393de9d40 100644 --- a/appium/tests/specs/mock/setup/SetupShowsMeaningfulErrorFromFES.spec.ts +++ b/appium/tests/specs/mock/setup/SetupShowsMeaningfulErrorFromFES.spec.ts @@ -17,7 +17,7 @@ describe('SETUP: ', () => { }; await mockApi.withMockedApis(async () => { await SplashScreen.login(); - await BaseScreen.checkModalMessage('Startup Error\n' + + await BaseScreen.checkModalMessage('Login Error\n' + 'EnterpriseServerApi 400 message:some client err get http://127.0.0.1:8001/fes/api/'); }); });