diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 8a032bd68..89a7abfc6 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -11,14 +11,18 @@ 04B472951ECE29F600B8266F /* MyMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B472921ECE29F600B8266F /* MyMenuViewController.swift */; }; 04B472961ECE29F600B8266F /* SideMenuNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B472931ECE29F600B8266F /* SideMenuNavigationController.swift */; }; 211392A5266511E6009202EC /* PubLookup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 211392A4266511E6009202EC /* PubLookup.swift */; }; + 2133D51626A0571F00CC686F /* ClientConfigurationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2133D51526A0571F00CC686F /* ClientConfigurationService.swift */; }; + 2133D51826A1E45400CC686F /* DecryptedPrivateKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2133D51726A1E45300CC686F /* DecryptedPrivateKey.swift */; }; 21489B78267CB42400BDE4AC /* ClientConfigurationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21489B77267CB42400BDE4AC /* ClientConfigurationProvider.swift */; }; 21489B7A267CB4DF00BDE4AC /* ClientConfigurationObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21489B79267CB4DF00BDE4AC /* ClientConfigurationObject.swift */; }; 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 */; }; + 214A023A26A3029700C24066 /* EmailKeyManagerApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 214A023926A3029700C24066 /* EmailKeyManagerApi.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 */; }; + 21B15C9426AA00A400D8522B /* ClientConfigurationServiceResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21B15C9326AA00A400D8522B /* ClientConfigurationServiceResults.swift */; }; 21C7DEFC26669A3700C44800 /* CalendarExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C7DEFB26669A3700C44800 /* CalendarExtension.swift */; }; 21C7DEFE26669CE100C44800 /* DateFormattingExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F56BD3723438C7000A7371A /* DateFormattingExtensions.swift */; }; 21C7DF0526697DA500C44800 /* PromiseKitExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C7DF0426697DA500C44800 /* PromiseKitExtension.swift */; }; @@ -367,6 +371,8 @@ 113F04B20ECC35FC59A81A6C /* Pods-FlowCryptTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlowCryptTests.release.xcconfig"; path = "Target Support Files/Pods-FlowCryptTests/Pods-FlowCryptTests.release.xcconfig"; sourceTree = ""; }; 11C1375F41411882DC4C9431 /* Pods-FlowCryptUIApplication.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlowCryptUIApplication.release.xcconfig"; path = "Target Support Files/Pods-FlowCryptUIApplication/Pods-FlowCryptUIApplication.release.xcconfig"; sourceTree = ""; }; 211392A4266511E6009202EC /* PubLookup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubLookup.swift; sourceTree = ""; }; + 2133D51526A0571F00CC686F /* ClientConfigurationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientConfigurationService.swift; sourceTree = ""; }; + 2133D51726A1E45300CC686F /* DecryptedPrivateKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecryptedPrivateKey.swift; sourceTree = ""; }; 21489B6A267B7BD800BDE4AC /* FilesManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesManagerTests.swift; sourceTree = ""; }; 21489B6D267B7D5000BDE4AC /* FileMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileMock.swift; sourceTree = ""; }; 21489B77267CB42400BDE4AC /* ClientConfigurationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientConfigurationProvider.swift; sourceTree = ""; }; @@ -374,9 +380,11 @@ 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 = ""; }; + 214A023926A3029700C24066 /* EmailKeyManagerApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailKeyManagerApi.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 = ""; }; + 21B15C9326AA00A400D8522B /* ClientConfigurationServiceResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientConfigurationServiceResults.swift; sourceTree = ""; }; 21C7DEFB26669A3700C44800 /* CalendarExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarExtension.swift; sourceTree = ""; }; 21C7DF0426697DA500C44800 /* PromiseKitExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromiseKitExtension.swift; sourceTree = ""; }; 21C7DF08266C0D8F00C44800 /* EnterpriseServerApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterpriseServerApi.swift; sourceTree = ""; }; @@ -795,9 +803,11 @@ 21489B81267CC3BC00BDE4AC /* Organisational Rules Service */ = { isa = PBXGroup; children = ( + 2133D51526A0571F00CC686F /* ClientConfigurationService.swift */, 21489B77267CB42400BDE4AC /* ClientConfigurationProvider.swift */, 21489B7F267CC39E00BDE4AC /* OrganisationalRulesService.swift */, 21489B82267CC99C00BDE4AC /* OrganisationalRulesServiceError.swift */, + 21B15C9326AA00A400D8522B /* ClientConfigurationServiceResults.swift */, ); path = "Organisational Rules Service"; sourceTree = ""; @@ -997,6 +1007,7 @@ 32DCAC9C0512037018F434A1 /* BackendApi.swift */, 21EFF61E265A5C6700AB0B71 /* WKDURLsApi.swift */, 21C7DF08266C0D8F00C44800 /* EnterpriseServerApi.swift */, + 214A023926A3029700C24066 /* EmailKeyManagerApi.swift */, D274724024F97C5C006BA6EF /* CacheService.swift */, C132B9CA1EC2DE6400763715 /* GeneralConstants.swift */, 9FB22CFD25715DDF0026EE64 /* Key Services */, @@ -1584,6 +1595,7 @@ D212D35C24C1AACF00035991 /* PrvKeyInfo.swift */, D2E26F6B24F25B1F00612AF1 /* KeyAlgo.swift */, 9FC7EAB2266A404D00F3BF5D /* PassPhraseObject.swift */, + 2133D51726A1E45300CC686F /* DecryptedPrivateKey.swift */, ); path = Models; sourceTree = ""; @@ -2485,9 +2497,12 @@ D2F6D147243506DA00DB4065 /* MailSettingsCredentials.swift in Sources */, 9F9361A52573CE260009912F /* MessageProvider.swift in Sources */, 32DCA2BB910FA1B19BA8B328 /* Imap.swift in Sources */, + 2133D51626A0571F00CC686F /* ClientConfigurationService.swift in Sources */, D274724424FD1932006BA6EF /* FolderObject.swift in Sources */, + 21B15C9426AA00A400D8522B /* ClientConfigurationServiceResults.swift in Sources */, 9FE1B3802563F85400D6D086 /* MessagesListProvider.swift in Sources */, 32DCA1B95DDC04D671F662F8 /* URLSessionExtension.swift in Sources */, + 214A023A26A3029700C24066 /* EmailKeyManagerApi.swift in Sources */, D2E26F7424F2705B00612AF1 /* ContactDetailDecorator.swift in Sources */, 21C7DF0526697DA500C44800 /* PromiseKitExtension.swift in Sources */, 9F7E8F19269C538E0021C07F /* NavigationChildController.swift in Sources */, @@ -2551,6 +2566,7 @@ 9F9362192573D10E0009912F /* Imap+Message.swift in Sources */, 32DCA9C61ABB3234649B374E /* CoreHost.swift in Sources */, 9F41FA28253B75F4003B970D /* BackupSelectKeyViewController.swift in Sources */, + 2133D51826A1E45400CC686F /* DecryptedPrivateKey.swift in Sources */, 21489B83267CC99C00BDE4AC /* OrganisationalRulesServiceError.swift in Sources */, D2F6D1402435008500DB4065 /* SessionCredentialsProvider.swift in Sources */, 9F9ABC8723AC1EAA00D560E3 /* MessageContext.swift in Sources */, diff --git a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift index 29b81d6b5..d8ff44735 100644 --- a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift @@ -23,19 +23,19 @@ final class SetupInitialViewController: TableNodeViewController { } private enum State { - case idle, searching, noKeyBackups, error(Error) + case idle, decidingIfEKMshouldBeUsed, fetchingKeysFromEKM, searchingKeyBackupsInInbox, noKeyBackupsInInbox, error(Error) var numberOfRows: Int { switch self { // title - case .idle: + case .idle, .decidingIfEKMshouldBeUsed, .fetchingKeysFromEKM: return 1 // title, loading - case .searching: + case .searchingKeyBackupsInInbox: return 2 case .error: return 3 - case .noKeyBackups: + case .noKeyBackupsInInbox: return Parts.allCases.count } } @@ -54,6 +54,8 @@ final class SetupInitialViewController: TableNodeViewController { private let router: GlobalRouterType private let decorator: SetupViewDecorator private let organisationalRules: OrganisationalRules + private let emailKeyManagerApi: EmailKeyManagerApiType + private let clientConfigurationService: ClientConfigurationServiceType private lazy var logger = Logger.nested(in: Self.self, with: .setup) @@ -62,13 +64,17 @@ final class SetupInitialViewController: TableNodeViewController { backupService: BackupServiceType = BackupService(), router: GlobalRouterType = GlobalRouter(), decorator: SetupViewDecorator = SetupViewDecorator(), - organisationalRulesService: OrganisationalRulesServiceType = OrganisationalRulesService() + organisationalRulesService: OrganisationalRulesServiceType = OrganisationalRulesService(), + emailKeyManagerApi: EmailKeyManagerApiType = EmailKeyManagerApi(), + clientConfigurationService: ClientConfigurationServiceType = ClientConfigurationService() ) { self.user = user self.backupService = backupService self.router = router self.decorator = decorator self.organisationalRules = organisationalRulesService.getSavedOrganisationalRulesForCurrentUser() + self.emailKeyManagerApi = emailKeyManagerApi + self.clientConfigurationService = clientConfigurationService super.init(node: TableNode()) } @@ -86,7 +92,7 @@ final class SetupInitialViewController: TableNodeViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) setNeedsStatusBarAppearanceUpdate() - state = .searching + state = .decidingIfEKMshouldBeUsed } } @@ -97,15 +103,19 @@ extension SetupInitialViewController { logger.logInfo("Changed to new state \(state)") switch state { - case .searching: - searchBackups() - default: + case .searchingKeyBackupsInInbox: + searchKeyBackupsInInbox() + case .decidingIfEKMshouldBeUsed: + decideIfEKMshouldBeUsed() + case .fetchingKeysFromEKM: + fetchKeysFromEKM() + case .error, .idle, .noKeyBackupsInInbox: break } node.reloadData() } - private func searchBackups() { + private func searchKeyBackupsInInbox() { if !organisationalRules.canBackupKeys { logger.logInfo("Skipping backups searching because canBackupKeys == false") proceedToSetupWith(keys: []) @@ -131,6 +141,42 @@ extension SetupInitialViewController { handleCommon(error: error) state = .error(error) } + + private func decideIfEKMshouldBeUsed() { + switch clientConfigurationService.checkShouldUseEKM() { + case .usesEKM: + state = .fetchingKeysFromEKM + case .doesNotUseEKM: + state = .searchingKeyBackupsInInbox + case .inconsistentClientConfiguration(let message): + showAlert(message: message) { [weak self] in + self?.router.signOut() + } + } + } + + private func fetchKeysFromEKM() { + emailKeyManagerApi.getPrivateKeys() + .then { [weak self] result in + guard let urlString = self?.emailKeyManagerApi.getPrivateKeysUrlString() else { + fatalError("Private keys URL can not be nil at this point") + } + self?.showToast( + "organisational_rules_ekm_private_keys_message".localizeWithArguments(result.privateKeys.count, urlString) + ) + // todo - this is temporary, until we finish EKM integration + // instead we should use the keys from EKM for setup + self?.state = .searchingKeyBackupsInInbox + } + .catch { [weak self] error in + if case .noPrivateKeysUrlString = error as? EmailKeyManagerApiError { + return + } + self?.showAlert(message: error.localizedDescription, onOk: { + self?.state = .decidingIfEKMshouldBeUsed + }) + } + } } // MARK: - ASTableDelegate, ASTableDataSource @@ -144,13 +190,13 @@ extension SetupInitialViewController: ASTableDelegate, ASTableDataSource { guard let self = self else { return ASCellNode() } switch self.state { - case .idle: + case .idle, .decidingIfEKMshouldBeUsed, .fetchingKeysFromEKM: return ASCellNode() - case .searching: + case .searchingKeyBackupsInInbox: return self.searchStateNode(for: indexPath) case .error(let error): return self.errorStateNode(for: indexPath, error: error) - case .noKeyBackups: + case .noKeyBackupsInInbox: return self.noKeysStateNode(for: indexPath) } } @@ -245,7 +291,7 @@ extension SetupInitialViewController { ) case 2: return ButtonCellNode(input: .retry) { [weak self] in - self?.state = .searching + self?.state = .searchingKeyBackupsInInbox } default: return ASCellNode() @@ -270,7 +316,7 @@ extension SetupInitialViewController { if keys.isEmpty { logger.logInfo("No key backups found in inbox") - state = .noKeyBackups + state = .noKeyBackupsInInbox } else { logger.logInfo("\(keys.count) key backups found in inbox") let viewController = SetupBackupsViewController(fetchedEncryptedKeys: keys, user: user) diff --git a/FlowCrypt/Controllers/SideMenu/NavigationController/NavigationChildController.swift b/FlowCrypt/Controllers/SideMenu/NavigationController/NavigationChildController.swift index 3cdbef01c..aff315b70 100644 --- a/FlowCrypt/Controllers/SideMenu/NavigationController/NavigationChildController.swift +++ b/FlowCrypt/Controllers/SideMenu/NavigationController/NavigationChildController.swift @@ -16,5 +16,5 @@ protocol NavigationChildController { extension NavigationChildController where Self: UIViewController { var shouldShowBackButton: Bool { true } - func handleBackButtonTap() { } + func handleBackButtonTap() {} } diff --git a/FlowCrypt/Core/Models/DecryptedPrivateKey.swift b/FlowCrypt/Core/Models/DecryptedPrivateKey.swift new file mode 100644 index 000000000..0eba131f6 --- /dev/null +++ b/FlowCrypt/Core/Models/DecryptedPrivateKey.swift @@ -0,0 +1,21 @@ +// +// DecryptedPrivateKey.swift +// FlowCrypt +// +// Created by Yevhen Kyivskyi on 16.07.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import Foundation + +struct DecryptedPrivateKeysResponse: Decodable { + + let privateKeys: [DecryptedPrivateKey] + + static let empty = DecryptedPrivateKeysResponse(privateKeys: []) +} + +struct DecryptedPrivateKey: Decodable { + + let decryptedPrivateKey: String +} diff --git a/FlowCrypt/Extensions/URLSessionExtension.swift b/FlowCrypt/Extensions/URLSessionExtension.swift index 4ab1fb449..613a60ead 100644 --- a/FlowCrypt/Extensions/URLSessionExtension.swift +++ b/FlowCrypt/Extensions/URLSessionExtension.swift @@ -28,7 +28,8 @@ extension URLSession { let status = res?.statusCode ?? GeneralConstants.Global.generalError let urlMethod = urlRequest.httpMethod ?? "GET" let urlString = urlRequest.url?.absoluteString ?? "??" - let message = "URLSession.call status:\(status) ms:\(trace.finish()) \(urlMethod) \(urlString)" + let headers = urlRequest.allHTTPHeaderFields ?? [:] + let message = "URLSession.call status:\(status) ms:\(trace.finish()) \(urlMethod) \(urlString), headers: \(headers)" Logger.nested("URLSession").logInfo(message) let validStatusCode = 200 ... 299 diff --git a/FlowCrypt/Functionality/Services/AttesterApi.swift b/FlowCrypt/Functionality/Services/AttesterApi.swift index aed3cbac0..3a0ab9e02 100644 --- a/FlowCrypt/Functionality/Services/AttesterApi.swift +++ b/FlowCrypt/Functionality/Services/AttesterApi.swift @@ -70,7 +70,7 @@ extension AttesterApi { if let value = token { httpMethod = .post - headers = [URLHeader(value: "Authorization", httpHeaderField: "Bearer \(value)")] + headers = [URLHeader(value: "Bearer \(value)", httpHeaderField: "Authorization")] } else { httpMethod = .put headers = [] diff --git a/FlowCrypt/Functionality/Services/EmailKeyManagerApi.swift b/FlowCrypt/Functionality/Services/EmailKeyManagerApi.swift new file mode 100644 index 000000000..62ad54b4b --- /dev/null +++ b/FlowCrypt/Functionality/Services/EmailKeyManagerApi.swift @@ -0,0 +1,74 @@ +// +// EmailKeyManagerApi.swift +// FlowCrypt +// +// Created by Yevhen Kyivskyi on 17.07.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import Promises + +protocol EmailKeyManagerApiType { + + func getPrivateKeysUrlString() -> String? + func getPrivateKeys() -> Promise +} + +enum EmailKeyManagerApiError: Error { + case noGoogleIdToken + case noPrivateKeysUrlString +} +extension EmailKeyManagerApiError: LocalizedError { + var errorDescription: String? { + switch self { + case .noGoogleIdToken: return "emai_keymanager_api_no_google_id_token_error_description".localized + case .noPrivateKeysUrlString: return "" + } + } +} + +class EmailKeyManagerApi: EmailKeyManagerApiType { + + private let organisationalRulesService: OrganisationalRulesServiceType + + init(organisationalRulesService: OrganisationalRulesServiceType = OrganisationalRulesService()) { + self.organisationalRulesService = organisationalRulesService + } + + func getPrivateKeysUrlString() -> String? { + guard let keyManagerUrlString = organisationalRulesService.getSavedOrganisationalRulesForCurrentUser().keyManagerUrlString else { + return nil + } + return "\(keyManagerUrlString)v1/keys/private" + } + + func getPrivateKeys() -> Promise { + Promise { [weak self] resolve, reject in + guard let self = self else { throw AppErr.nilSelf } + guard let urlString = self.getPrivateKeysUrlString() else { + reject(EmailKeyManagerApiError.noPrivateKeysUrlString) + return + } + + guard let idToken = GoogleUserService().idToken else { + reject(EmailKeyManagerApiError.noGoogleIdToken) + return + } + + let headers = [ + URLHeader( + value: "Bearer \(idToken)", + httpHeaderField: "Authorization" + )] + let request = URLRequest.urlRequest( + with: urlString, + method: .get, + body: nil, + headers: headers + ) + let response = try awaitPromise(URLSession.shared.call(request)) + let decryptedPrivateKeysResponse = try JSONDecoder().decode(DecryptedPrivateKeysResponse.self, from: response.data) + resolve(decryptedPrivateKeysResponse) + } + } +} diff --git a/FlowCrypt/Functionality/Services/GlobalRouter.swift b/FlowCrypt/Functionality/Services/GlobalRouter.swift index c0d2d3709..f7b4274a3 100644 --- a/FlowCrypt/Functionality/Services/GlobalRouter.swift +++ b/FlowCrypt/Functionality/Services/GlobalRouter.swift @@ -11,7 +11,7 @@ import UIKit protocol GlobalRouterType { func proceed() - func signIn(with rout: GlobalRoutingType) + func signIn(with route: GlobalRoutingType) func switchActive(user: User) func signOut() } @@ -71,10 +71,10 @@ extension GlobalRouter { // MARK: - extension GlobalRouter { - func signIn(with rout: GlobalRoutingType) { - logger.logInfo("Sign in with \(rout)") + func signIn(with route: GlobalRoutingType) { + logger.logInfo("Sign in with \(route)") - switch rout { + switch route { case .gmailLogin(let viewController): googleService.signIn(in: viewController) .then(on: .main) { [weak self] session in diff --git a/FlowCrypt/Functionality/Services/GoogleUserService.swift b/FlowCrypt/Functionality/Services/GoogleUserService.swift index 75acc3da6..e8c5447b9 100644 --- a/FlowCrypt/Functionality/Services/GoogleUserService.swift +++ b/FlowCrypt/Functionality/Services/GoogleUserService.swift @@ -48,6 +48,12 @@ final class GoogleUserService: NSObject, GoogleUserServiceType { .accessToken } + var idToken: String? { + authorization?.authState + .lastTokenResponse? + .idToken + } + var authorization: GTMAppAuthFetcherAuthorization? { getAuthorizationForCurrentUser() } diff --git a/FlowCrypt/Functionality/Services/Organisational Rules Service/ClientConfigurationService.swift b/FlowCrypt/Functionality/Services/Organisational Rules Service/ClientConfigurationService.swift new file mode 100644 index 000000000..68808cef6 --- /dev/null +++ b/FlowCrypt/Functionality/Services/Organisational Rules Service/ClientConfigurationService.swift @@ -0,0 +1,47 @@ +// +// OrgRulesSignInPersmissionsService.swift +// FlowCrypt +// +// Created by Yevhen Kyivskyi on 15.07.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +protocol ClientConfigurationServiceType { + func checkShouldUseEKM() -> ClientConfigurationService.CheckForUsingEKMResult +} + +class ClientConfigurationService: ClientConfigurationServiceType { + + private let organisationalRulesService: OrganisationalRulesServiceType + + init(organisationalRulesService: OrganisationalRulesServiceType = OrganisationalRulesService()) { + self.organisationalRulesService = organisationalRulesService + } + + /** + * This method checks if the user is set up for using EKM, and if other client configuration is consistent with it. + * There are three possible outcomes: + * 1) EKM is in use because organisationalRules.isUsingKeyManager == true and other OrgRules are consistent with it (result: no error, use EKM) + * 2) EKM is in use because organisationalRules.isUsingKeyManager == true and other OrgRules are NOT consistent with it (result: error) + * 3) EKM is not in use because organisationalRules.isUsingKeyManager == false (result: normal login flow) + */ + func checkShouldUseEKM() -> CheckForUsingEKMResult { + let organisationalRules = self.organisationalRulesService.getSavedOrganisationalRulesForCurrentUser() + if !organisationalRules.isUsingKeyManager { + return .doesNotUseEKM + } + if !organisationalRules.mustAutoImportOrAutogenPrvWithKeyManager { + return .inconsistentClientConfiguration(message: "organisational_rules_autoimport_or_autogen_with_private_key_manager_error".localized) + } + if organisationalRules.mustAutogenPassPhraseQuietly { + return .inconsistentClientConfiguration(message: "organisational_rules_autogen_passphrase_quitely_error".localized) + } + if !organisationalRules.forbidStoringPassPhrase { + return .inconsistentClientConfiguration(message: "organisational_rules_forbid_storing_passphrase_error".localized) + } + if organisationalRules.mustSubmitAttester { + return .inconsistentClientConfiguration(message: "organisational_rules_must_submit_attester_error".localized) + } + return .usesEKM + } +} diff --git a/FlowCrypt/Functionality/Services/Organisational Rules Service/ClientConfigurationServiceResults.swift b/FlowCrypt/Functionality/Services/Organisational Rules Service/ClientConfigurationServiceResults.swift new file mode 100644 index 000000000..c6db0461b --- /dev/null +++ b/FlowCrypt/Functionality/Services/Organisational Rules Service/ClientConfigurationServiceResults.swift @@ -0,0 +1,17 @@ +// +// ClientConfigurationServiceResults.swift +// FlowCrypt +// +// Created by Yevhen Kyivskyi on 22.07.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import Foundation + +extension ClientConfigurationService { + enum CheckForUsingEKMResult { + case usesEKM + case inconsistentClientConfiguration(message: String) + case doesNotUseEKM + } +} diff --git a/FlowCrypt/Functionality/Services/Organisational Rules Service/OrganisationalRulesService.swift b/FlowCrypt/Functionality/Services/Organisational Rules Service/OrganisationalRulesService.swift index a51556851..361bf226d 100644 --- a/FlowCrypt/Functionality/Services/Organisational Rules Service/OrganisationalRulesService.swift +++ b/FlowCrypt/Functionality/Services/Organisational Rules Service/OrganisationalRulesService.swift @@ -6,6 +6,7 @@ // Copyright © 2021 FlowCrypt Limited. All rights reserved. // +import FlowCryptCommon import Foundation import Promises diff --git a/FlowCrypt/Models/OrganisationalRule.swift b/FlowCrypt/Models/OrganisationalRule.swift index 7596471ea..f67d01eae 100644 --- a/FlowCrypt/Models/OrganisationalRule.swift +++ b/FlowCrypt/Models/OrganisationalRule.swift @@ -6,6 +6,7 @@ // Copyright © 2021 FlowCrypt Limited. All rights reserved. // +import FlowCryptCommon import Foundation /// Organisational rules, set domain-wide, and delivered from FlowCrypt Backend @@ -120,4 +121,12 @@ class OrganisationalRules { var shouldHideArmorMeta: Bool { (clientConfiguration.flags ?? []).contains(.hideArmorMeta) } + + var forbidStoringPassPhrase: Bool { + (clientConfiguration.flags ?? []).contains(.forbidStoringPassphrase) + } + + var keyManagerUrlString: String? { + clientConfiguration.keyManagerUrl?.addTrailingSlashIfNeeded + } } diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index 81c300f9f..ac45a9037 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -203,3 +203,13 @@ // 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"; + +"organisational_rules_autoimport_or_autogen_with_private_key_manager_error" = "Combination of rules (key_manager_url set but PRV_AUTOIMPORT_OR_AUTOGEN is missing) is not supported on this platform"; +"organisational_rules_autogen_passphrase_quitely_error" = "Combination of rules (PRV_AUTOIMPORT_OR_AUTOGEN + PASS_PHRASE_QUIET_AUTOGEN) is not supported on this platform"; +"organisational_rules_forbid_storing_passphrase_error" = "Combination of rules (PRV_AUTOIMPORT_OR_AUTOGEN + missing FORBID_STORING_PASS_PHRASE) is not supported on this platform"; +"organisational_rules_must_submit_attester_error" = "Combination of rules (PRV_AUTOIMPORT_OR_AUTOGEN + ENFORCE_ATTESTER_SUBMIT) is not supported on this platform"; +"organisational_rules_can_create_keys_error" = "Combination of rules (PRV_AUTOIMPORT_OR_AUTOGEN + missing NO_PRV_CREATE) is not supported on this platform"; +"organisational_rules_ekm_private_keys_message" = "Ignoring %d keys returned by EKM %@ (not implemented)"; + +// Email key manager api error +"emai_keymanager_api_no_google_id_token_error_description" = "There is no Google ID token were found while getting client configuration"; diff --git a/FlowCryptCommon/Extensions/StringExtensions.swift b/FlowCryptCommon/Extensions/StringExtensions.swift index 15ce79b66..52f448087 100644 --- a/FlowCryptCommon/Extensions/StringExtensions.swift +++ b/FlowCryptCommon/Extensions/StringExtensions.swift @@ -15,7 +15,14 @@ public extension String { } return self } - + + var addTrailingSlashIfNeeded: String { + if self.last != "/" { + return "\(self)/" + } + return self + } + func data() -> Data { data(using: .utf8)! }