From 85b4d2938b4782f2408802919880dca19a724416 Mon Sep 17 00:00:00 2001 From: ykyivskyi-gd Date: Sat, 3 Jul 2021 18:41:24 +0300 Subject: [PATCH 1/3] Skipping backups searching on Setup and Settings if OrgRules has canBackupKeys false; Handling unknown ClientConfigurationFlag; Added tests for unknown ClientConfigurationFlag; Waiting for OrgRules to be fetched on Startup; --- FlowCrypt.xcodeproj/project.pbxproj | 4 +++ .../SettingsViewController.swift | 32 +++++++++++++++---- .../Setup/SetupInitialViewController.swift | 11 ++++++- .../Functionality/Services/AppStartup.swift | 8 ++--- .../OrganisationalRulesService.swift | 31 +++++++++++++----- FlowCrypt/Models/ClientConfiguration.swift | 7 ++++ .../ClientConfigurationTests.swift | 19 +++++++++++ ...client_configuraion_with_unknown_flag.json | 8 +++++ 8 files changed, 101 insertions(+), 19 deletions(-) create mode 100644 FlowCryptAppTests/Models Parsing/client_configuraion_with_unknown_flag.json diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 786d34444..bca5e0b00 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 21489B7C267CBA0E00BDE4AC /* ClientConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21489B7B267CBA0E00BDE4AC /* ClientConfiguration.swift */; }; 21489B80267CC39E00BDE4AC /* OrganisationalRulesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21489B7F267CC39E00BDE4AC /* OrganisationalRulesService.swift */; }; 21489B83267CC99C00BDE4AC /* OrganisationalRulesServiceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21489B82267CC99C00BDE4AC /* OrganisationalRulesServiceError.swift */; }; + 215002A32690B1DD00980DDD /* client_configuraion_with_unknown_flag.json in Resources */ = {isa = PBXBuildFile; fileRef = 215002A22690B1DD00980DDD /* client_configuraion_with_unknown_flag.json */; }; 215897E8267A553300423694 /* FilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 215897E7267A553200423694 /* FilesManager.swift */; }; 2196A2202684B9BE001B9E00 /* URLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2196A21F2684B9BE001B9E00 /* URLExtension.swift */; }; 21C7DEFC26669A3700C44800 /* CalendarExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C7DEFB26669A3700C44800 /* CalendarExtension.swift */; }; @@ -368,6 +369,7 @@ 21489B7B267CBA0E00BDE4AC /* ClientConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientConfiguration.swift; sourceTree = ""; }; 21489B7F267CC39E00BDE4AC /* OrganisationalRulesService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrganisationalRulesService.swift; sourceTree = ""; }; 21489B82267CC99C00BDE4AC /* OrganisationalRulesServiceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrganisationalRulesServiceError.swift; sourceTree = ""; }; + 215002A22690B1DD00980DDD /* client_configuraion_with_unknown_flag.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = client_configuraion_with_unknown_flag.json; sourceTree = ""; }; 215897E7267A553200423694 /* FilesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesManager.swift; sourceTree = ""; }; 2196A21F2684B9BE001B9E00 /* URLExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLExtension.swift; sourceTree = ""; }; 21C7DEFB26669A3700C44800 /* CalendarExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarExtension.swift; sourceTree = ""; }; @@ -814,6 +816,7 @@ 21EA3B2E26565B7400691848 /* client_configuraion.json */, 21EA3B3C26565B9800691848 /* client_configuraion_partly_empty.json */, 21EA3B3526565B8100691848 /* client_configuraion_empty.json */, + 215002A22690B1DD00980DDD /* client_configuraion_with_unknown_flag.json */, ); path = "Models Parsing"; sourceTree = ""; @@ -2041,6 +2044,7 @@ 9F976569267E18F30058419D /* client_configuraion.json in Resources */, 9F976576267E18F90058419D /* client_configuraion_partly_empty.json in Resources */, 9F97657D267E18FE0058419D /* client_configuraion_empty.json in Resources */, + 215002A32690B1DD00980DDD /* client_configuraion_with_unknown_flag.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/FlowCrypt/Controllers/Settings/Settings List/SettingsViewController.swift b/FlowCrypt/Controllers/Settings/Settings List/SettingsViewController.swift index 59d8ee368..cf3aa1cd9 100644 --- a/FlowCrypt/Controllers/Settings/Settings List/SettingsViewController.swift +++ b/FlowCrypt/Controllers/Settings/Settings List/SettingsViewController.swift @@ -31,17 +31,35 @@ final class SettingsViewController: TableNodeViewController { case .experimental: return "settings_screen_experimental".localized } } + + static func allCases(with rules: OrganisationalRules?) -> [Settings] { + guard let rules = rules else { + return allCases + } + let cases: [Settings] + if !rules.canBackupKeys { + cases = [.privacy, .contacts, .keys, .atteseter, .notifications, .legal, .experimental] + } else { + cases = allCases + } + return cases + } } private let decorator: SettingsViewDecoratorType private let currentUser: User? + private let organisationalRules: OrganisationalRules? + private let rows: [Settings] init( decorator: SettingsViewDecoratorType = SettingsViewDecorator(), - currentUser: User? = DataService.shared.currentUser + currentUser: User? = DataService.shared.currentUser, + organisationalRulesService: OrganisationalRulesServiceType = OrganisationalRulesService() ) { self.decorator = decorator self.currentUser = currentUser + self.organisationalRules = organisationalRulesService.getSavedOrganisationalRulesForCurrentUser() + self.rows = Settings.allCases(with: self.organisationalRules) super.init(node: TableNode()) } @@ -71,13 +89,13 @@ final class SettingsViewController: TableNodeViewController { extension SettingsViewController: ASTableDelegate, ASTableDataSource { func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { - Settings.allCases.count + rows.count } func tableNode(_: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { { [weak self] in - guard let self = self, let setting = Settings(rawValue: indexPath.row) else { return ASCellNode() } - + guard let self = self else { return ASCellNode() } + let setting = self.rows[indexPath.row] return SettingsCellNode( title: self.decorator.attributedSetting(setting.title), insets: self.decorator.insets @@ -86,7 +104,7 @@ extension SettingsViewController: ASTableDelegate, ASTableDataSource { } func tableNode(_: ASTableNode, didSelectRowAt indexPath: IndexPath) { - guard let setting = Settings(rawValue: indexPath.row) else { return assertionFailure() } + let setting = rows[indexPath.row] proceed(to: setting) } } @@ -105,7 +123,9 @@ extension SettingsViewController { case .contacts: viewController = ContactsListViewController() case .backups: - guard let currentUser = currentUser else { + guard let currentUser = currentUser, + let organisationalRules = self.organisationalRules, + !organisationalRules.canBackupKeys else { viewController = nil return } diff --git a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift index fac5b7e4a..8bea80412 100644 --- a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift @@ -53,6 +53,7 @@ final class SetupInitialViewController: TableNodeViewController { private let user: UserId private let router: GlobalRouterType private let decorator: SetupViewDecorator + private let organisationalRules: OrganisationalRules? private lazy var logger = Logger.nested(in: Self.self, with: .setup) @@ -60,12 +61,14 @@ final class SetupInitialViewController: TableNodeViewController { user: UserId, backupService: BackupServiceType = BackupService(), router: GlobalRouterType = GlobalRouter(), - decorator: SetupViewDecorator = SetupViewDecorator() + decorator: SetupViewDecorator = SetupViewDecorator(), + organisationalRulesService: OrganisationalRulesServiceType = OrganisationalRulesService() ) { self.user = user self.backupService = backupService self.router = router self.decorator = decorator + self.organisationalRules = organisationalRulesService.getSavedOrganisationalRulesForCurrentUser() super.init(node: TableNode()) } @@ -103,6 +106,12 @@ extension SetupInitialViewController { } private func searchBackups() { + if organisationalRules?.canBackupKeys == false { + logger.logInfo("Skipping backups searching because canBackupKeys == false") + proceedToSetupWith(keys: []) + return + } + logger.logInfo("Searching for backups in inbox") backupService.fetchBackupsFromInbox(for: user) diff --git a/FlowCrypt/Functionality/Services/AppStartup.swift b/FlowCrypt/Functionality/Services/AppStartup.swift index 458d0e22c..3a82ec8c1 100644 --- a/FlowCrypt/Functionality/Services/AppStartup.swift +++ b/FlowCrypt/Functionality/Services/AppStartup.swift @@ -22,8 +22,7 @@ struct AppStartup { try awaitPromise(self.setupCore()) try self.setupMigrationIfNeeded() try self.setupSession() - // Fetching of org rules is being called async in purpose we don't need to wait until it's fetched - self.getUserOrgRulesIfNeeded() + try self.getUserOrgRulesIfNeeded() }.then(on: .main) { self.chooseView(for: window, session: session) }.catch(on: .main) { error in @@ -87,9 +86,10 @@ struct AppStartup { } } - private func getUserOrgRulesIfNeeded() { + private func getUserOrgRulesIfNeeded() throws { if DataService.shared.isLoggedIn { - _ = OrganisationalRulesService().fetchOrganisationalRulesForCurrentUser() + let service = OrganisationalRulesService() + _ = try awaitPromise(service.fetchOrganisationalRulesForCurrentUser()) } } diff --git a/FlowCrypt/Functionality/Services/Organisational Rules Service/OrganisationalRulesService.swift b/FlowCrypt/Functionality/Services/Organisational Rules Service/OrganisationalRulesService.swift index 20c0956e8..210610d5b 100644 --- a/FlowCrypt/Functionality/Services/Organisational Rules Service/OrganisationalRulesService.swift +++ b/FlowCrypt/Functionality/Services/Organisational Rules Service/OrganisationalRulesService.swift @@ -10,8 +10,10 @@ import Foundation import Promises protocol OrganisationalRulesServiceType { - func fetchOrganisationalRulesForCurrentUser() -> Promise - func fetchOrganisationalRules(for email: String) -> Promise + func fetchOrganisationalRulesForCurrentUser() -> Promise + func fetchOrganisationalRules(for email: String) -> Promise + + func getSavedOrganisationalRulesForCurrentUser() -> OrganisationalRules? } final class OrganisationalRulesService { @@ -31,30 +33,30 @@ final class OrganisationalRulesService { // MARK: - OrganisationalRulesServiceType extension OrganisationalRulesService: OrganisationalRulesServiceType { - func fetchOrganisationalRulesForCurrentUser() -> Promise { + func fetchOrganisationalRulesForCurrentUser() -> Promise { guard let currentUser = DataService.shared.currentUser else { - return Promise { _, reject in + return Promise { _, reject in reject(OrganisationalRulesServiceError.noCurrentUser) } } return fetchOrganisationalRules(for: currentUser.email) } - func fetchOrganisationalRules(for email: String) -> Promise { - Promise { [weak self] resolve, reject in + func fetchOrganisationalRules(for email: String) -> Promise { + Promise { [weak self] resolve, _ in guard let self = self else { throw AppErr.nilSelf } guard let clientConfigurationResponse = try awaitPromise( self.enterpriseServerApi.getClientConfiguration(for: email) ) else { - reject(OrganisationalRulesServiceError.parse) + resolve(nil) return } guard let organisationalRules = OrganisationalRules( clientConfiguration: clientConfigurationResponse, email: email ) else { - reject(OrganisationalRulesServiceError.emailFormat) + resolve(nil) return } @@ -63,4 +65,17 @@ extension OrganisationalRulesService: OrganisationalRulesServiceType { resolve(organisationalRules) } } + + func getSavedOrganisationalRulesForCurrentUser() -> OrganisationalRules? { + guard let currentUser = DataService.shared.currentUser, + let configuration = self.clientConfigurationProvider.fetch() + else { + return nil + } + + return OrganisationalRules( + clientConfiguration: configuration, + email: currentUser.email + ) + } } diff --git a/FlowCrypt/Models/ClientConfiguration.swift b/FlowCrypt/Models/ClientConfiguration.swift index 46b6e49c5..6407170f4 100644 --- a/FlowCrypt/Models/ClientConfiguration.swift +++ b/FlowCrypt/Models/ClientConfiguration.swift @@ -20,6 +20,13 @@ enum ClientConfigurationFlag: String, Codable { case useLegacyAttesterSubmit = "USE_LEGACY_ATTESTER_SUBMIT" case defaultRememberPassphrase = "DEFAULT_REMEMBER_PASS_PHRASE" case hideArmorMeta = "HIDE_ARMOR_META" + case forbidStoringPassphrase = "FORBID_STORING_PASS_PHRASE" + + case unknown + + init(from decoder: Decoder) throws { + self = try ClientConfigurationFlag(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown + } } struct ClientConfiguration: Codable, Equatable { diff --git a/FlowCryptAppTests/Models Parsing/ClientConfigurationTests.swift b/FlowCryptAppTests/Models Parsing/ClientConfigurationTests.swift index 986d4066f..aa8da13b0 100644 --- a/FlowCryptAppTests/Models Parsing/ClientConfigurationTests.swift +++ b/FlowCryptAppTests/Models Parsing/ClientConfigurationTests.swift @@ -72,4 +72,23 @@ class ClientConfigurationTests: XCTestCase { XCTAssert(model?.keyManagerUrl == nil) XCTAssert(model?.disallowAttesterSearchForDomains == nil) } + + func test_client_configuraion_with_unknown_flag_json_parse() { + let urlPath = URL(fileURLWithPath: Bundle(for: type(of: self)).path(forResource: "client_configuraion_with_unknown_flag", ofType: "json")!) + let data = try! Data(contentsOf: urlPath, options: .dataReadingMapped) + + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + + let model = try? decoder.decode(ClientConfiguration.self, from: data) + + XCTAssert(model?.flags != nil) + XCTAssert(model?.customKeyserverUrl != nil) + XCTAssert(model?.keyManagerUrl != nil) + XCTAssert(model?.disallowAttesterSearchForDomains != nil) + XCTAssert(model?.enforceKeygenAlgo != nil) + XCTAssert(model?.enforceKeygenExpireMonths != nil) + + XCTAssert(model?.flags?.contains(.unknown) == true) + } } diff --git a/FlowCryptAppTests/Models Parsing/client_configuraion_with_unknown_flag.json b/FlowCryptAppTests/Models Parsing/client_configuraion_with_unknown_flag.json new file mode 100644 index 000000000..785fb2caa --- /dev/null +++ b/FlowCryptAppTests/Models Parsing/client_configuraion_with_unknown_flag.json @@ -0,0 +1,8 @@ +{ + "flags": ["NO_PRV_BACKUP", "NO_KEY_MANAGER_PUB_LOOKUP", "ANY_RANDOM_NEW_FLAG_THAT_IS_NOT_PRESENT_IN_FLAGS_ENUM"], + "custom_keyserver_url": "https://hello", + "key_manager_url": "https://there", + "disallow_attester_search_for_domains": [], + "enforce_keygen_algo": "curve25519", + "enforce_keygen_expire_months": 12 +} From 474ca42e9d0b8ab194c25a5ad128feafaa90e0f3 Mon Sep 17 00:00:00 2001 From: ykyivskyi-gd Date: Thu, 8 Jul 2021 22:53:02 +0300 Subject: [PATCH 2/3] Fixed PR comments; --- .../SettingsViewController.swift | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/FlowCrypt/Controllers/Settings/Settings List/SettingsViewController.swift b/FlowCrypt/Controllers/Settings/Settings List/SettingsViewController.swift index cf3aa1cd9..7b6e82abc 100644 --- a/FlowCrypt/Controllers/Settings/Settings List/SettingsViewController.swift +++ b/FlowCrypt/Controllers/Settings/Settings List/SettingsViewController.swift @@ -16,8 +16,8 @@ import FlowCryptUI * - Tap on each row will navigate user to appropriate settings controller */ final class SettingsViewController: TableNodeViewController { - private enum Settings: Int, CaseIterable { - case backups, privacy, contacts, keys, atteseter, notifications, legal, experimental + private enum SettingsMenuItem: Int, CaseIterable { + case backups, privacy, contacts, keys, attester, notifications, legal, experimental var title: String { switch self { @@ -25,23 +25,23 @@ final class SettingsViewController: TableNodeViewController { case .privacy: return "settings_screen_security".localized case .contacts: return "settings_screen_contacts".localized case .keys: return "settings_screen_keys".localized - case .atteseter: return "settings_screen_attester".localized + case .attester: return "settings_screen_attester".localized case .notifications: return "settings_screen_notifications".localized case .legal: return "settings_screen_legal".localized case .experimental: return "settings_screen_experimental".localized } } - static func allCases(with rules: OrganisationalRules?) -> [Settings] { + static func allCases(with rules: OrganisationalRules?) -> [SettingsMenuItem] { guard let rules = rules else { return allCases } - let cases: [Settings] + var cases = SettingsMenuItem.allCases + if !rules.canBackupKeys { - cases = [.privacy, .contacts, .keys, .atteseter, .notifications, .legal, .experimental] - } else { - cases = allCases + cases.removeAll(where: { $0 == .backups }) } + return cases } } @@ -49,7 +49,7 @@ final class SettingsViewController: TableNodeViewController { private let decorator: SettingsViewDecoratorType private let currentUser: User? private let organisationalRules: OrganisationalRules? - private let rows: [Settings] + private let rows: [SettingsMenuItem] init( decorator: SettingsViewDecoratorType = SettingsViewDecorator(), @@ -59,7 +59,7 @@ final class SettingsViewController: TableNodeViewController { self.decorator = decorator self.currentUser = currentUser self.organisationalRules = organisationalRulesService.getSavedOrganisationalRulesForCurrentUser() - self.rows = Settings.allCases(with: self.organisationalRules) + self.rows = SettingsMenuItem.allCases(with: self.organisationalRules) super.init(node: TableNode()) } @@ -94,6 +94,7 @@ extension SettingsViewController: ASTableDelegate, ASTableDataSource { func tableNode(_: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { { [weak self] in + guard let self = self else { return ASCellNode() } let setting = self.rows[indexPath.row] return SettingsCellNode( @@ -112,7 +113,7 @@ extension SettingsViewController: ASTableDelegate, ASTableDataSource { // MARK: - Actions extension SettingsViewController { - private func proceed(to setting: Settings) { + private func proceed(to setting: SettingsMenuItem) { let viewController: UIViewController? switch setting { From 58ea37327342754da1e85f7f369a726a91436e7b Mon Sep 17 00:00:00 2001 From: ykyivskyi-gd Date: Thu, 8 Jul 2021 23:50:02 +0300 Subject: [PATCH 3/3] Changed error handling for EnterpriseServerApiType; Made OrganisationalRules not optional; Fixed PR comments; --- .../SettingsViewController.swift | 10 +--- .../Setup/SetupInitialViewController.swift | 4 +- .../Encrypted Storage/EncryptedStorage.swift | 3 + .../Functionality/Services/AppStartup.swift | 2 +- .../Services/EnterpriseServerApi.swift | 39 +++++++++---- .../OrganisationalRulesService.swift | 55 +++++++++---------- FlowCrypt/Models/ClientConfiguration.swift | 14 +++++ FlowCrypt/Models/OrganisationalRule.swift | 13 +---- .../Resources/en.lproj/Localizable.strings | 4 ++ 9 files changed, 82 insertions(+), 62 deletions(-) diff --git a/FlowCrypt/Controllers/Settings/Settings List/SettingsViewController.swift b/FlowCrypt/Controllers/Settings/Settings List/SettingsViewController.swift index 7b6e82abc..59d226b14 100644 --- a/FlowCrypt/Controllers/Settings/Settings List/SettingsViewController.swift +++ b/FlowCrypt/Controllers/Settings/Settings List/SettingsViewController.swift @@ -32,10 +32,7 @@ final class SettingsViewController: TableNodeViewController { } } - static func allCases(with rules: OrganisationalRules?) -> [SettingsMenuItem] { - guard let rules = rules else { - return allCases - } + static func filtered(with rules: OrganisationalRules) -> [SettingsMenuItem] { var cases = SettingsMenuItem.allCases if !rules.canBackupKeys { @@ -48,7 +45,7 @@ final class SettingsViewController: TableNodeViewController { private let decorator: SettingsViewDecoratorType private let currentUser: User? - private let organisationalRules: OrganisationalRules? + private let organisationalRules: OrganisationalRules private let rows: [SettingsMenuItem] init( @@ -59,7 +56,7 @@ final class SettingsViewController: TableNodeViewController { self.decorator = decorator self.currentUser = currentUser self.organisationalRules = organisationalRulesService.getSavedOrganisationalRulesForCurrentUser() - self.rows = SettingsMenuItem.allCases(with: self.organisationalRules) + self.rows = SettingsMenuItem.filtered(with: self.organisationalRules) super.init(node: TableNode()) } @@ -125,7 +122,6 @@ extension SettingsViewController { viewController = ContactsListViewController() case .backups: guard let currentUser = currentUser, - let organisationalRules = self.organisationalRules, !organisationalRules.canBackupKeys else { viewController = nil return diff --git a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift index 8bea80412..29b81d6b5 100644 --- a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift @@ -53,7 +53,7 @@ final class SetupInitialViewController: TableNodeViewController { private let user: UserId private let router: GlobalRouterType private let decorator: SetupViewDecorator - private let organisationalRules: OrganisationalRules? + private let organisationalRules: OrganisationalRules private lazy var logger = Logger.nested(in: Self.self, with: .setup) @@ -106,7 +106,7 @@ extension SetupInitialViewController { } private func searchBackups() { - if organisationalRules?.canBackupKeys == false { + if !organisationalRules.canBackupKeys { logger.logInfo("Skipping backups searching because canBackupKeys == false") proceedToSetupWith(keys: []) return diff --git a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift index 4e48dce09..c22e1b583 100644 --- a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift +++ b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift @@ -113,12 +113,15 @@ extension EncryptedStorage: LogOutHandler { .filter { keys.map(\.longid).contains($0.longid) } let sessions = storage.objects(SessionObject.self) .filter { $0.email == email } + let clientConfigurations = storage.objects(ClientConfigurationObject.self) + .filter { $0.userEmail == email } try storage.write { storage.delete(keys) storage.delete(sessions) storage.delete(passPhrases) storage.delete(userToDelete) + storage.delete(clientConfigurations) } } } diff --git a/FlowCrypt/Functionality/Services/AppStartup.swift b/FlowCrypt/Functionality/Services/AppStartup.swift index c35b8ecf1..b54532e5b 100644 --- a/FlowCrypt/Functionality/Services/AppStartup.swift +++ b/FlowCrypt/Functionality/Services/AppStartup.swift @@ -131,7 +131,7 @@ struct AppStartup { } private func showErrorAlert(with error: Error, on window: UIWindow, session: SessionType?) { - let alert = UIAlertController(title: "Startup Error", message: "\(error)", preferredStyle: .alert) + let alert = UIAlertController(title: "Startup Error", message: "\(error.localizedDescription)", preferredStyle: .alert) let retry = UIAlertAction(title: "Retry", style: .default) { _ in self.initializeApp(window: window, session: session) } diff --git a/FlowCrypt/Functionality/Services/EnterpriseServerApi.swift b/FlowCrypt/Functionality/Services/EnterpriseServerApi.swift index d8df4c477..e802ece1b 100644 --- a/FlowCrypt/Functionality/Services/EnterpriseServerApi.swift +++ b/FlowCrypt/Functionality/Services/EnterpriseServerApi.swift @@ -12,8 +12,21 @@ protocol EnterpriseServerApiType { func getActiveFesUrl(for email: String) -> Promise func getActiveFesUrlForCurrentUser() -> Promise - func getClientConfiguration(for email: String) -> Promise - func getClientConfigurationForCurrentUser() -> Promise + func getClientConfiguration(for email: String) -> Promise + func getClientConfigurationForCurrentUser() -> Promise +} + +enum EnterpriseServerApiError: Error { + case parse + case emailFormat +} +extension EnterpriseServerApiError: LocalizedError { + var errorDescription: String? { + switch self { + case .parse: return "organisational_rules_parse_error_description".localized + case .emailFormat: return "organisational_rules_email_format_error_description".localized + } + } } class EnterpriseServerApi: EnterpriseServerApiType { @@ -71,11 +84,14 @@ class EnterpriseServerApi: EnterpriseServerApiType { .recoverFromTimeOut(result: nil) } - func getClientConfiguration(for email: String) -> Promise { - Promise { resolve, _ in - guard let userDomain = email.recipientDomain, - !Configuration.publicEmailProviderDomains.contains(userDomain) else { - resolve(nil) + func getClientConfiguration(for email: String) -> Promise { + Promise { resolve, reject in + guard let userDomain = email.recipientDomain else { + reject(EnterpriseServerApiError.emailFormat) + return + } + if Configuration.publicEmailProviderDomains.contains(userDomain) { + resolve(.empty) return } let request = URLRequest.urlRequest( @@ -93,17 +109,18 @@ class EnterpriseServerApi: EnterpriseServerApiType { from: safeReponse.data ))?.clientConfiguration else { - resolve(nil) + reject(EnterpriseServerApiError.parse) return } resolve(clientConfiguration) } } - func getClientConfigurationForCurrentUser() -> Promise { + func getClientConfigurationForCurrentUser() -> Promise { guard let email = DataService.shared.currentUser?.email else { - return Promise { resolve, _ in - resolve(nil) + return Promise { _, reject in + assertionFailure("User has to be set while getting client configuration") + reject(AppErr.user("currentUser == nil")) } } return getClientConfiguration(for: email) diff --git a/FlowCrypt/Functionality/Services/Organisational Rules Service/OrganisationalRulesService.swift b/FlowCrypt/Functionality/Services/Organisational Rules Service/OrganisationalRulesService.swift index 210610d5b..3f1d15ee6 100644 --- a/FlowCrypt/Functionality/Services/Organisational Rules Service/OrganisationalRulesService.swift +++ b/FlowCrypt/Functionality/Services/Organisational Rules Service/OrganisationalRulesService.swift @@ -10,10 +10,10 @@ import Foundation import Promises protocol OrganisationalRulesServiceType { - func fetchOrganisationalRulesForCurrentUser() -> Promise - func fetchOrganisationalRules(for email: String) -> Promise + func fetchOrganisationalRulesForCurrentUser() -> Promise + func fetchOrganisationalRules(for email: String) -> Promise - func getSavedOrganisationalRulesForCurrentUser() -> OrganisationalRules? + func getSavedOrganisationalRulesForCurrentUser() -> OrganisationalRules } final class OrganisationalRulesService { @@ -33,49 +33,46 @@ final class OrganisationalRulesService { // MARK: - OrganisationalRulesServiceType extension OrganisationalRulesService: OrganisationalRulesServiceType { - func fetchOrganisationalRulesForCurrentUser() -> Promise { + func fetchOrganisationalRulesForCurrentUser() -> Promise { guard let currentUser = DataService.shared.currentUser else { - return Promise { _, reject in + return Promise { _, reject in reject(OrganisationalRulesServiceError.noCurrentUser) } } return fetchOrganisationalRules(for: currentUser.email) } - func fetchOrganisationalRules(for email: String) -> Promise { - Promise { [weak self] resolve, _ in + func fetchOrganisationalRules(for email: String) -> Promise { + Promise { [weak self] resolve, _ in guard let self = self else { throw AppErr.nilSelf } - guard let clientConfigurationResponse = try awaitPromise( - self.enterpriseServerApi.getClientConfiguration(for: email) - ) else { - resolve(nil) - return - } - guard let organisationalRules = OrganisationalRules( - clientConfiguration: clientConfigurationResponse, - email: email - ) else { - resolve(nil) - return - } + let clientConfigurationResponse = try awaitPromise( + self.enterpriseServerApi.getClientConfiguration(for: email) + ) + + let organisationalRules = OrganisationalRules( + clientConfiguration: clientConfigurationResponse + ) self.clientConfigurationProvider.save(clientConfiguration: clientConfigurationResponse) resolve(organisationalRules) } + .recover { [weak self] error -> OrganisationalRules in + guard let self = self else { throw AppErr.nilSelf } + guard let clientConfig = self.clientConfigurationProvider.fetch() else { + throw error + } + return OrganisationalRules(clientConfiguration: clientConfig) + } } - func getSavedOrganisationalRulesForCurrentUser() -> OrganisationalRules? { - guard let currentUser = DataService.shared.currentUser, - let configuration = self.clientConfigurationProvider.fetch() - else { - return nil + func getSavedOrganisationalRulesForCurrentUser() -> OrganisationalRules { + guard let configuration = self.clientConfigurationProvider.fetch() else { + assertionFailure("There should not be a user without OrganisationalRules") + return OrganisationalRules(clientConfiguration: .empty) } - return OrganisationalRules( - clientConfiguration: configuration, - email: currentUser.email - ) + return OrganisationalRules(clientConfiguration: configuration) } } diff --git a/FlowCrypt/Models/ClientConfiguration.swift b/FlowCrypt/Models/ClientConfiguration.swift index 6407170f4..83bacf2a6 100644 --- a/FlowCrypt/Models/ClientConfiguration.swift +++ b/FlowCrypt/Models/ClientConfiguration.swift @@ -39,6 +39,20 @@ struct ClientConfiguration: Codable, Equatable { let enforceKeygenExpireMonths: Int? } +// MARK: - Empty model +extension ClientConfiguration { + static var empty: ClientConfiguration { + return ClientConfiguration( + flags: [], + customKeyserverUrl: nil, + keyManagerUrl: nil, + disallowAttesterSearchForDomains: nil, + enforceKeygenAlgo: nil, + enforceKeygenExpireMonths: nil + ) + } +} + // MARK: - Map from realm model extension ClientConfiguration { init?(_ object: ClientConfigurationObject?) { diff --git a/FlowCrypt/Models/OrganisationalRule.swift b/FlowCrypt/Models/OrganisationalRule.swift index 57b029675..fa8747da1 100644 --- a/FlowCrypt/Models/OrganisationalRule.swift +++ b/FlowCrypt/Models/OrganisationalRule.swift @@ -13,19 +13,8 @@ import Foundation class OrganisationalRules { private let clientConfiguration: ClientConfiguration - let domain: String - init(clientConfiguration: ClientConfiguration, domain: String) { - self.clientConfiguration = clientConfiguration - self.domain = domain - } - - init?(clientConfiguration: ClientConfiguration, email: String) { - guard let recipientDomain = email.recipientDomain else { - return nil - } - - self.domain = recipientDomain + init(clientConfiguration: ClientConfiguration) { self.clientConfiguration = clientConfiguration } diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index b3fbb4010..234652841 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -200,3 +200,7 @@ // Backup select keys screen "backup_select_key_screen_title" = "Select keys"; "backup_select_key_screen_no_selection" = "Please select keys to backup"; + +// Organisational rules error +"organisational_rules_parse_error_description" = "Couldn't parse data while getting organisational rules"; +"organisational_rules_email_format_error_description" = "Wrong user email format";