Skip to content

Commit

Permalink
issue #1111 only initialize Realm and KeyChainService once (#1117)
Browse files Browse the repository at this point in the history
* issue 1111 only initialize Realm and KeyChainService once

* wip

* wip

* wip

* [skip ci] fixed controllers

* [skip ci] more fixes

* [skip ci] a few more

* [skip ci] fix services

* mail provider fixes [skip ci]

* a few more fixes [skip ci]

* a few more fixes [skip ci]

* it builds

* add to test scope

* [skip ci] remove AppReset, fix some test usages

* Project file fixed

* removed unwanted files from test target

* intermediate

* fix

* PR fixes

* PR fixes II

* less verbose backup service init

* cleanup

* controller cleanup

* fix

* cleanup

* issue #1111 fix unit tests running

* issue #1131 use in-memory Realm for tests

* issue #1111 fix tests

Co-authored-by: Ivan <[email protected]>
Co-authored-by: Roma Sosnovsky <[email protected]>
  • Loading branch information
3 people authored Dec 1, 2021
1 parent 5580caf commit 4162138
Show file tree
Hide file tree
Showing 88 changed files with 967 additions and 815 deletions.
60 changes: 39 additions & 21 deletions FlowCrypt.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

133 changes: 133 additions & 0 deletions FlowCrypt/App/AppContext.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//
// AppContext.swift
// FlowCrypt
//
// Created by Tom on 30.11.2021
// Copyright © 2017-present FlowCrypt a. s. All rights reserved.
//

import Foundation
import UIKit

class AppContext {

let globalRouter: GlobalRouterType
let encryptedStorage: EncryptedStorageType
let session: SessionType?
// todo - session service should have maybe `.currentSession` on it, then we don't have to have `session` above?
let userAccountService: SessionServiceType
let dataService: DataServiceType
let keyStorage: KeyStorageType
let keyService: KeyServiceType
let passPhraseService: PassPhraseServiceType
let clientConfigurationService: ClientConfigurationServiceType

private init(
encryptedStorage: EncryptedStorageType,
session: SessionType?,
userAccountService: SessionServiceType,
dataService: DataServiceType,
keyStorage: KeyStorageType,
keyService: KeyServiceType,
passPhraseService: PassPhraseServiceType,
clientConfigurationService: ClientConfigurationServiceType,
globalRouter: GlobalRouterType
) {
self.encryptedStorage = encryptedStorage
self.session = session
self.userAccountService = userAccountService
self.dataService = dataService
self.keyStorage = keyStorage // todo - keyStorage and keyService should be the same
self.keyService = keyService
self.passPhraseService = passPhraseService
self.clientConfigurationService = clientConfigurationService
self.globalRouter = globalRouter
}

@MainActor
static func setUpAppContext(globalRouter: GlobalRouterType) throws -> AppContext {
let keyChainService = KeyChainService()
let encryptedStorage = EncryptedStorage(
storageEncryptionKey: try keyChainService.getStorageEncryptionKey()
)
let dataService = DataService(encryptedStorage: encryptedStorage)
let passPhraseService = PassPhraseService(encryptedStorage: encryptedStorage)
let keyStorage = KeyDataStorage(encryptedStorage: encryptedStorage)
let keyService = KeyService(
storage: keyStorage,
passPhraseService: passPhraseService,
currentUserEmail: { dataService.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
userAccountService: SessionService(
encryptedStorage: encryptedStorage,
dataService: dataService,
googleService: GoogleUserService(
currentUserEmail: dataService.currentUser?.email,
appDelegateGoogleSessionContainer: UIApplication.shared.delegate as? AppDelegate
)
),
dataService: dataService,
keyStorage: keyStorage,
keyService: keyService,
passPhraseService: passPhraseService,
clientConfigurationService: clientConfigurationService,
globalRouter: globalRouter
)
}

func withSession(_ session: SessionType?) -> AppContext {
return AppContext(
encryptedStorage: self.encryptedStorage,
session: session,
userAccountService: self.userAccountService,
dataService: self.dataService,
keyStorage: self.keyStorage,
keyService: self.keyService,
passPhraseService: self.passPhraseService,
clientConfigurationService: self.clientConfigurationService,
globalRouter: globalRouter
)
}

func getRequiredMailProvider() -> MailProvider {
guard let mailProvider = getOptionalMailProvider() else {
// todo - should throw instead
fatalError("wrongly using mail provider when not logged in")
}
return mailProvider
}

func getOptionalMailProvider() -> MailProvider? {
guard let currentUser = self.dataService.currentUser, let currentAuthType = self.dataService.currentAuthType else {
return nil
}
return MailProvider(
currentAuthType: currentAuthType,
currentUser: currentUser
)
}

func getBackupService() -> BackupService {
let mailProvider = self.getRequiredMailProvider()
return BackupService(
backupProvider: mailProvider.backupProvider,
messageSender: mailProvider.messageSender
)
}

func getFoldersService() -> FoldersService {
return FoldersService(
encryptedStorage: self.encryptedStorage,
remoteFoldersProvider: self.getRequiredMailProvider().remoteFoldersProvider
)
}

}
32 changes: 0 additions & 32 deletions FlowCrypt/App/AppReset.swift

This file was deleted.

13 changes: 0 additions & 13 deletions FlowCrypt/App/Configuration.swift

This file was deleted.

19 changes: 6 additions & 13 deletions FlowCrypt/App/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,9 @@
import Foundation
import UIKit

autoreleasepool {
if ProcessInfo().arguments.contains(AppReset.reset.rawValue) {
AppReset.resetKeychain()
AppReset.resetUserDefaults()
}

UIApplicationMain(
CommandLine.argc,
CommandLine.unsafeArgv,
nil,
NSStringFromClass(AppDelegate.self)
)
}
UIApplicationMain(
CommandLine.argc,
CommandLine.unsafeArgv,
nil,
NSStringFromClass(AppDelegate.self)
)
8 changes: 4 additions & 4 deletions FlowCrypt/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

import AppAuth
import UIKit
import GTMAppAuth

class AppDelegate: UIResponder, UIApplicationDelegate {
class AppDelegate: UIResponder, UIApplicationDelegate, AppDelegateGoogleSesssionContainer {
var blurViewController: BlurViewController?
var googleAuthSession: OIDExternalUserAgentSession?
let window: UIWindow = UIWindow(frame: UIScreen.main.bounds)

func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let isRunningTests = NSClassFromString("XCTestCase") != nil
if isRunningTests {
func application(_ application: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if application.isRunningTests {
return true
}
GlobalRouter().proceed()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ final class InvalidStorageViewController: TableNodeViewController {
}

private let error: Error
private let encryptedStorage: EncryptedStorageType
private let encryptedStorage: EncryptedStorageType? // nil if failed to initialize
private let router: GlobalRouterType

init(error: Error, encryptedStorage: EncryptedStorageType, router: GlobalRouterType) {
init(error: Error, encryptedStorage: EncryptedStorageType?, router: GlobalRouterType) {
self.error = error
self.encryptedStorage = encryptedStorage
self.router = router
Expand All @@ -44,6 +44,10 @@ final class InvalidStorageViewController: TableNodeViewController {
}

@objc private func handleTap() {
guard let encryptedStorage = encryptedStorage else {
showAlert(message: "invalid_storage_failed_to_initialize".localized)
return
}
do {
try encryptedStorage.reset()
router.proceed()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import FlowCryptUI
import UIKit

class CheckMailAuthViewController: TableNodeViewController {
private let globalRouter: GlobalRouterType
private let appContext: AppContext

init(globalRouter: GlobalRouterType = GlobalRouter()) {
self.globalRouter = globalRouter
init(appContext: AppContext) {
self.appContext = appContext
super.init(node: TableNode())
}

Expand Down Expand Up @@ -85,7 +85,7 @@ extension CheckMailAuthViewController {
case 2:
return ButtonCellNode(input: .signInAgain) { [weak self] in
guard let self = self else { return }
self.globalRouter.signIn(with: .gmailLogin(self))
self.appContext.globalRouter.signIn(appContext: self.appContext, route: .gmailLogin(self))
}
default:
return ASCellNode()
Expand Down
53 changes: 35 additions & 18 deletions FlowCrypt/Controllers/Compose/ComposeViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,18 @@ final class ComposeViewController: TableNodeViewController {
case subject, subjectDivider, text
}

private let appContext: AppContext
private let composeMessageService: ComposeMessageService
private let notificationCenter: NotificationCenter
private let decorator: ComposeViewDecorator
private let contactsService: ContactsServiceType
private let cloudContactProvider: CloudContactsProvider
private let filesManager: FilesManagerType
private let photosManager: PhotosManagerType
private let keyService: KeyServiceType
private let keyMethods: KeyMethodsType
private let service: ServiceActor
private let passPhraseService: PassPhraseService
private let router: GlobalRouterType
private let clientConfiguration: ClientConfiguration

private let search = PassthroughSubject<String, Never>()
private let userDefaults: UserDefaults
Expand All @@ -68,42 +68,59 @@ final class ComposeViewController: TableNodeViewController {
private var composedLatestDraft: ComposedDraft?

init(
email: String,
appContext: AppContext,
notificationCenter: NotificationCenter = .default,
decorator: ComposeViewDecorator = ComposeViewDecorator(),
input: ComposeMessageInput = .empty,
cloudContactProvider: CloudContactsProvider = UserContactsProvider(),
cloudContactProvider: CloudContactsProvider? = nil,
userDefaults: UserDefaults = .standard,
contactsService: ContactsServiceType = ContactsService(),
composeMessageService: ComposeMessageService = ComposeMessageService(),
contactsService: ContactsServiceType? = nil,
composeMessageService: ComposeMessageService? = nil,
filesManager: FilesManagerType = FilesManager(),
photosManager: PhotosManagerType = PhotosManager(),
keyService: KeyServiceType = KeyService(),
passPhraseService: PassPhraseService = PassPhraseService(),
keyMethods: KeyMethodsType = KeyMethods(),
router: GlobalRouterType = GlobalRouter()
) {
self.appContext = appContext
guard let email = appContext.dataService.email else {
fatalError("missing current user email") // todo - need a more elegant solution
}
self.email = email
self.notificationCenter = notificationCenter
self.input = input
self.decorator = decorator
self.userDefaults = userDefaults
self.contactsService = contactsService
let clientConfiguration = appContext.clientConfigurationService.getSaved(for: email)
self.contactsService = contactsService ?? ContactsService(
localContactsProvider: LocalContactsProvider(
encryptedStorage: appContext.encryptedStorage
),
clientConfiguration: clientConfiguration
)
let cloudContactProvider = cloudContactProvider ?? UserContactsProvider(
userService: GoogleUserService(
currentUserEmail: email,
appDelegateGoogleSessionContainer: UIApplication.shared.delegate as? AppDelegate
)
)
self.cloudContactProvider = cloudContactProvider
self.composeMessageService = composeMessageService
self.composeMessageService = composeMessageService ?? ComposeMessageService(
clientConfiguration: clientConfiguration,
encryptedStorage: appContext.encryptedStorage,
messageGateway: appContext.getRequiredMailProvider().messageSender
)
self.filesManager = filesManager
self.photosManager = photosManager
self.keyService = keyService
self.keyMethods = keyMethods
self.service = ServiceActor(
composeMessageService: composeMessageService,
contactsService: contactsService,
composeMessageService: self.composeMessageService,
contactsService: self.contactsService,
cloudContactProvider: cloudContactProvider
)
self.passPhraseService = passPhraseService
self.router = router
self.contextToSend.subject = input.subject
self.contextToSend.attachments = input.attachments
self.clientConfiguration = clientConfiguration
super.init(node: TableNode())
}

Expand Down Expand Up @@ -354,7 +371,7 @@ extension ComposeViewController {

extension ComposeViewController {
private func prepareSigningKey() async throws -> PrvKeyInfo {
guard let signingKey = try await keyService.getSigningKey() else {
guard let signingKey = try await appContext.keyService.getSigningKey() else {
throw AppErr.general("None of your private keys have your user id \"\(email)\". Please import the appropriate key.")
}

Expand Down Expand Up @@ -396,15 +413,15 @@ extension ComposeViewController {
private func handlePassPhraseEntry(_ passPhrase: String, for signingKey: PrvKeyInfo) async throws -> Bool {
// since pass phrase was entered (an inconvenient thing for user to do),
// let's find all keys that match and save the pass phrase for all
let allKeys = try await self.keyService.getPrvKeyInfo()
let allKeys = try await appContext.keyService.getPrvKeyInfo()
guard allKeys.isNotEmpty else {
// tom - todo - nonsensical error type choice https://github.com/FlowCrypt/flowcrypt-ios/issues/859
// I copied it from another usage, but has to be changed
throw KeyServiceError.retrieve
}
let matchingKeys = try await self.keyMethods.filterByPassPhraseMatch(keys: allKeys, passPhrase: passPhrase)
// save passphrase for all matching keys
self.passPhraseService.savePassPhrasesInMemory(passPhrase, for: matchingKeys)
appContext.passPhraseService.savePassPhrasesInMemory(passPhrase, for: matchingKeys)
// now figure out if the pass phrase also matched the signing prv itself
let matched = matchingKeys.first(where: { $0.fingerprints.first == signingKey.fingerprints.first })
return matched != nil// true if the pass phrase matched signing key
Expand Down Expand Up @@ -1110,7 +1127,7 @@ extension ComposeViewController {

Task {
do {
try await router.askForContactsPermission(for: .gmailLogin(self))
try await router.askForContactsPermission(for: .gmailLogin(self), appContext: appContext)
node.reloadSections([2], with: .automatic)
} catch {
handleContactsPermissionError(error)
Expand Down
Loading

0 comments on commit 4162138

Please sign in to comment.