From 19f34b48644dd26a343cc2dcabf439eac9cb7b70 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Mon, 27 Dec 2021 17:13:08 +0200 Subject: [PATCH 1/6] #1615 API endpoints for uploading messages --- FlowCrypt.xcodeproj/project.pbxproj | 22 +++- .../Extensions/URLSessionExtension.swift | 4 +- .../EnterpriseServerApi.swift | 120 +++++++++++++----- .../Models/EnterpriseServerApiError.swift | 25 ++++ .../Models/MessageUploadRequest.swift | 17 +++ .../Functionality/Services/ApiCall.swift | 2 +- .../Remote Pub Key Services/AttesterApi.swift | 2 +- .../Resources/en.lproj/Localizable.strings | 1 + 8 files changed, 153 insertions(+), 40 deletions(-) create mode 100644 FlowCrypt/Functionality/Services/Account Server Services/Models/EnterpriseServerApiError.swift create mode 100644 FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadRequest.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 8f86959dc..50d363770 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -38,7 +38,6 @@ 2C2A3B4B2719EE6100B7F27B /* KeyServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2A3B4A2719EE6100B7F27B /* KeyServiceTests.swift */; }; 2C2A3B4D2719EF7300B7F27B /* PassPhraseServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2A3B4C2719EF7300B7F27B /* PassPhraseServiceMock.swift */; }; 2C2D0B95275FDF6B0052771D /* Version6SchemaMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2D0B94275FDF6B0052771D /* Version6SchemaMigration.swift */; }; - 2C339B07275CB136005DEA79 /* FatalErrorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C339B06275CB136005DEA79 /* FatalErrorViewController.swift */; }; 2C4E60F72757D91A00DE5770 /* EncryptedStorageMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4E60F62757D91A00DE5770 /* EncryptedStorageMock.swift */; }; 2C60AB0C272564D40040D7F2 /* InvalidStorageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C60AB0B272564D40040D7F2 /* InvalidStorageViewController.swift */; }; 2CAF25322756C37E005C7C7C /* AppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CAF25312756C37E005C7C7C /* AppContext.swift */; }; @@ -69,6 +68,8 @@ 5168FB0B274F94D300131072 /* MessageAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5168FB0A274F94D300131072 /* MessageAttachment.swift */; }; 51775C32270B01C200D7C944 /* PrvKeyInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51775C31270B01C200D7C944 /* PrvKeyInfoTests.swift */; }; 51775C39270C7D2400D7C944 /* StorageMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51775C38270C7D2400D7C944 /* StorageMethod.swift */; }; + 517C2E302779F90700FECF32 /* MessageUploadRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 517C2E2F2779F90700FECF32 /* MessageUploadRequest.swift */; }; + 517C2E33277A0C6300FECF32 /* EnterpriseServerApiError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 517C2E32277A0C6300FECF32 /* EnterpriseServerApiError.swift */; }; 5180CB9127356D48001FC7EF /* MessageSubjectNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5180CB9027356D48001FC7EF /* MessageSubjectNode.swift */; }; 5180CB9327357B67001FC7EF /* RawClientConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5180CB9227357B67001FC7EF /* RawClientConfiguration.swift */; }; 5180CB9527357BB0001FC7EF /* WkdApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5180CB9427357BB0001FC7EF /* WkdApi.swift */; }; @@ -111,8 +112,8 @@ 5ADEDCBC23A4329000EC495E /* PublicKeyDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ADEDCBB23A4329000EC495E /* PublicKeyDetailViewController.swift */; }; 5ADEDCBE23A4363700EC495E /* KeyDetailInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ADEDCBD23A4363700EC495E /* KeyDetailInfoViewController.swift */; }; 5ADEDCC023A43B0800EC495E /* KeyDetailInfoViewDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ADEDCBF23A43B0800EC495E /* KeyDetailInfoViewDecorator.swift */; }; - 606FE33A2745AA2E009DA039 /* AttachmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 606FE3392745AA2E009DA039 /* AttachmentViewController.swift */; }; 601EEE31272B19D200FE445B /* CheckMailAuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 601EEE30272B19D200FE445B /* CheckMailAuthViewController.swift */; }; + 606FE33A2745AA2E009DA039 /* AttachmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 606FE3392745AA2E009DA039 /* AttachmentViewController.swift */; }; 7F72537A0C44D3CE670F0EFD /* Pods_FlowCryptUIApplication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3382C015A576728FA08BA310 /* Pods_FlowCryptUIApplication.framework */; }; 949ED9422303E3B400530579 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 949ED9412303E3B400530579 /* Colors.xcassets */; }; 9F003D6125E1B4ED00EB38C0 /* TrashFolderProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F003D6025E1B4ED00EB38C0 /* TrashFolderProvider.swift */; }; @@ -454,7 +455,6 @@ 2C2A3B4A2719EE6100B7F27B /* KeyServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyServiceTests.swift; sourceTree = ""; }; 2C2A3B4C2719EF7300B7F27B /* PassPhraseServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassPhraseServiceMock.swift; sourceTree = ""; }; 2C2D0B94275FDF6B0052771D /* Version6SchemaMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Version6SchemaMigration.swift; sourceTree = ""; }; - 2C339B06275CB136005DEA79 /* FatalErrorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FatalErrorViewController.swift; sourceTree = ""; }; 2C4E60F62757D91A00DE5770 /* EncryptedStorageMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedStorageMock.swift; sourceTree = ""; }; 2C60AB0B272564D40040D7F2 /* InvalidStorageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvalidStorageViewController.swift; sourceTree = ""; }; 2CAF25312756C37E005C7C7C /* AppContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppContext.swift; sourceTree = ""; }; @@ -495,6 +495,8 @@ 5168FB0A274F94D300131072 /* MessageAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageAttachment.swift; sourceTree = ""; }; 51775C31270B01C200D7C944 /* PrvKeyInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrvKeyInfoTests.swift; sourceTree = ""; }; 51775C38270C7D2400D7C944 /* StorageMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageMethod.swift; sourceTree = ""; }; + 517C2E2F2779F90700FECF32 /* MessageUploadRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageUploadRequest.swift; sourceTree = ""; }; + 517C2E32277A0C6300FECF32 /* EnterpriseServerApiError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterpriseServerApiError.swift; sourceTree = ""; }; 5180CB9027356D48001FC7EF /* MessageSubjectNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSubjectNode.swift; sourceTree = ""; }; 5180CB9227357B67001FC7EF /* RawClientConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawClientConfiguration.swift; sourceTree = ""; }; 5180CB9427357BB0001FC7EF /* WkdApi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WkdApi.swift; sourceTree = ""; }; @@ -532,8 +534,8 @@ 5ADEDCBD23A4363700EC495E /* KeyDetailInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyDetailInfoViewController.swift; sourceTree = ""; }; 5ADEDCBF23A43B0800EC495E /* KeyDetailInfoViewDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyDetailInfoViewDecorator.swift; sourceTree = ""; }; 5ADEDCC123A43C6800EC495E /* KeyTextCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyTextCellNode.swift; sourceTree = ""; }; - 606FE3392745AA2E009DA039 /* AttachmentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentViewController.swift; sourceTree = ""; }; 601EEE30272B19D200FE445B /* CheckMailAuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckMailAuthViewController.swift; sourceTree = ""; }; + 606FE3392745AA2E009DA039 /* AttachmentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentViewController.swift; sourceTree = ""; }; 949ED9412303E3B400530579 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; 9F003D6025E1B4ED00EB38C0 /* TrashFolderProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrashFolderProvider.swift; sourceTree = ""; }; 9F003D6C25EA8F3200EB38C0 /* SessionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionService.swift; sourceTree = ""; }; @@ -1033,6 +1035,15 @@ path = Extensions; sourceTree = ""; }; + 517C2E312779F91300FECF32 /* Models */ = { + isa = PBXGroup; + children = ( + 517C2E32277A0C6300FECF32 /* EnterpriseServerApiError.swift */, + 517C2E2F2779F90700FECF32 /* MessageUploadRequest.swift */, + ); + path = Models; + sourceTree = ""; + }; 51B0C772272AB5FD00124663 /* TestExtensions */ = { isa = PBXGroup; children = ( @@ -1644,6 +1655,7 @@ children = ( 32DCAC9C0512037018F434A1 /* BackendApi.swift */, 21C7DF08266C0D8F00C44800 /* EnterpriseServerApi.swift */, + 517C2E312779F91300FECF32 /* Models */, ); path = "Account Server Services"; sourceTree = ""; @@ -2706,6 +2718,7 @@ 9FF0671025520D7100FCC9E6 /* MessageGateway.swift in Sources */, 9F31AB8C23298B3F00CF87EA /* Imap+retry.swift in Sources */, 51B4AE51271444580001F33B /* PubKeyRealmObject.swift in Sources */, + 517C2E302779F90700FECF32 /* MessageUploadRequest.swift in Sources */, 9F82D352256D74FA0069A702 /* InboxViewContainerController.swift in Sources */, D227C0E3250538100070F805 /* LocalFoldersProvider.swift in Sources */, 9FA405C7265AEBA50084D133 /* SetupGenerateKeyViewController.swift in Sources */, @@ -2714,6 +2727,7 @@ 32DCAF9DA9EC47798DF8BB73 /* SignInViewController.swift in Sources */, 606FE33A2745AA2E009DA039 /* AttachmentViewController.swift in Sources */, 21489B78267CB42400BDE4AC /* LocalClientConfiguration.swift in Sources */, + 517C2E33277A0C6300FECF32 /* EnterpriseServerApiError.swift in Sources */, 9FF0671C25520D9D00FCC9E6 /* MailProvider.swift in Sources */, 9FE743072347AA54005E2DBB /* MainNavigationController.swift in Sources */, 9F5C2A8B257E6C4900DE9B4B /* ImapError.swift in Sources */, diff --git a/FlowCrypt/Extensions/URLSessionExtension.swift b/FlowCrypt/Extensions/URLSessionExtension.swift index 67671b6fb..cc35c7e02 100644 --- a/FlowCrypt/Extensions/URLSessionExtension.swift +++ b/FlowCrypt/Extensions/URLSessionExtension.swift @@ -57,7 +57,7 @@ extension URLSession { } } -enum HTTPMetod: String { +enum HTTPMethod: String { case put = "PUT" case get = "GET" case post = "POST" @@ -71,7 +71,7 @@ struct URLHeader { extension URLRequest { static func urlRequest( with url: URL, - method: HTTPMetod = .get, + method: HTTPMethod = .get, body: Data? = nil, headers: [URLHeader] = [] ) -> URLRequest { diff --git a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift index b5b90d7b4..32e9e9036 100644 --- a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift +++ b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift @@ -11,20 +11,7 @@ import MailCore protocol EnterpriseServerApiType { func getActiveFesUrl(for email: String) async throws -> String? func getClientConfiguration(for email: String) async throws -> RawClientConfiguration -} - -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 - } - } + func uploadMessage(for email: String) async throws -> String } /// server run by individual enterprise customers, serves client configuration @@ -36,7 +23,7 @@ class EnterpriseServerApi: EnterpriseServerApiType { private enum Constants { /// 404 - Not Found static let getToleratedHTTPStatuses = [404] - /// -1001 - request timed out, -1003 - сannot resolve host, -1004 - can't conenct to hosts, + /// -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] @@ -52,6 +39,20 @@ class EnterpriseServerApi: EnterpriseServerApiType { let clientConfiguration: RawClientConfiguration } + private struct MessageReplyTokenResponse: Decodable { + let replyToken: String + } + + private struct MessageUploadResponse: Decodable { + let url: String + } + + private lazy var decoder: JSONDecoder = { + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + return decoder + }() + private func constructUrlBase(emailDomain: String) -> String { guard !CommandLine.isDebugBundleWithArgument("--mock-fes-api") else { return "http://127.0.0.1:8001/fes" // mock @@ -98,27 +99,55 @@ class EnterpriseServerApi: EnterpriseServerApiType { throw EnterpriseServerApiError.emailFormat } - guard let fesUrl = try await getActiveFesUrl(for: email) else { - return .empty - } + let response: ClientConfigurationResponse = try await performRequest( + email: email, + url: "/api/v1/client-configuration?domain=\(userDomain)", + method: .get + ) - let request = ApiCall.Request( - apiName: Constants.apiName, - url: "\(fesUrl)/api/v1/client-configuration?domain=\(userDomain)" + return response.clientConfiguration + } + + func uploadMessage(from sender: String, + to: [String], + cc: [String], + bcc: [String] + ) async throws -> String { + let replyToken = try await getReplyToken(for: sender) + + let request = MessageUploadRequest( + associateReplyToken: replyToken, + from: sender, + to: to, + cc: cc, + bcc: bcc ) - let safeReponse = try await ApiCall.call(request) - let decoder = JSONDecoder() - decoder.keyDecodingStrategy = .convertFromSnakeCase + let response: MessageUploadResponse = try await performRequest( + email: sender, + url: "/api/v1/message" + ) - guard let clientConfiguration = (try? decoder.decode( - ClientConfigurationResponse.self, - from: safeReponse.data - ))?.clientConfiguration - else { - throw EnterpriseServerApiError.parse - } - return clientConfiguration + return response.url + } + + // MARK: - Helpers + private func getIdToken(email: String) async throws -> String { + let googleService = GoogleUserService( + currentUserEmail: email, + appDelegateGoogleSessionContainer: nil + ) + + return try await googleService.getCachedOrRefreshedIdToken() + } + + private func getReplyToken(for email: String) async throws -> String { + let response: MessageReplyTokenResponse = try await performRequest( + email: email, + url: "/api/v1/message/new-reply-token" + ) + + return response.replyToken } private func shouldTolerateWhenCallingOpportunistically(_ error: Error) -> Bool { @@ -131,4 +160,31 @@ class EnterpriseServerApi: EnterpriseServerApiType { } return true } + + private func performRequest( + email: String, + url: String, + method: HTTPMethod = .post + ) async throws -> T { + guard let fesUrl = try await getActiveFesUrl(for: email) else { + throw EnterpriseServerApiError.noActiveFesUrl + } + + let idToken = try await getIdToken(email: email) + let authorizationHeader = URLHeader(value: "Bearer: \(idToken)", httpHeaderField: "Authorization") + + let request = ApiCall.Request( + apiName: Constants.apiName, + url: "\(fesUrl)\(url)", + method: method, + headers: [authorizationHeader] + ) + + let safeResponse = try await ApiCall.call(request) + + guard let data = try? decoder.decode(T.self, from: safeResponse.data) + else { throw EnterpriseServerApiError.parse } + + return data + } } diff --git a/FlowCrypt/Functionality/Services/Account Server Services/Models/EnterpriseServerApiError.swift b/FlowCrypt/Functionality/Services/Account Server Services/Models/EnterpriseServerApiError.swift new file mode 100644 index 000000000..d2be5b7d1 --- /dev/null +++ b/FlowCrypt/Functionality/Services/Account Server Services/Models/EnterpriseServerApiError.swift @@ -0,0 +1,25 @@ +// +// EnterpriseServerApiError.swift +// FlowCrypt +// +// Created by Roma Sosnovsky on 27/12/21 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import Foundation + +enum EnterpriseServerApiError: Error { + case parse + case emailFormat + case noActiveFesUrl +} + +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 + case .noActiveFesUrl: return "organisational_rules_fes_url_error_description".localized + } + } +} diff --git a/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadRequest.swift b/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadRequest.swift new file mode 100644 index 000000000..e1bbd805e --- /dev/null +++ b/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadRequest.swift @@ -0,0 +1,17 @@ +// +// MessageUploadRequest.swift +// FlowCrypt +// +// Created by Roma Sosnovsky on 27/12/21 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import Foundation + +struct MessageUploadRequest { + let associateReplyToken: String + let from: String + let to: [String] + let cc: [String] + let bcc: [String] +} diff --git a/FlowCrypt/Functionality/Services/ApiCall.swift b/FlowCrypt/Functionality/Services/ApiCall.swift index 101f35719..daa5bb316 100644 --- a/FlowCrypt/Functionality/Services/ApiCall.swift +++ b/FlowCrypt/Functionality/Services/ApiCall.swift @@ -14,7 +14,7 @@ extension ApiCall { struct Request { var apiName: String var url: String - var method: HTTPMetod = .get + var method: HTTPMethod = .get var body: Data? var headers: [URLHeader] = [] var timeout: TimeInterval = 60.0 diff --git a/FlowCrypt/Functionality/Services/Remote Pub Key Services/AttesterApi.swift b/FlowCrypt/Functionality/Services/Remote Pub Key Services/AttesterApi.swift index 275c8620e..f43595e3a 100644 --- a/FlowCrypt/Functionality/Services/Remote Pub Key Services/AttesterApi.swift +++ b/FlowCrypt/Functionality/Services/Remote Pub Key Services/AttesterApi.swift @@ -72,7 +72,7 @@ extension AttesterApi { @discardableResult func update(email: String, pubkey: String, token: String?) async throws -> String { - let httpMethod: HTTPMetod + let httpMethod: HTTPMethod let headers: [URLHeader] if let value = token { httpMethod = .post diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index 3949bcd71..a37522550 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -238,6 +238,7 @@ // 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_fes_url_error_description" = "Couldn't get active FES url"; "organisational_rules_url_not_valid" = "Please check if key manager url set correctly"; "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"; From de75b9e091a6b1b84de3fb0c5de26cc8adc6cc79 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Wed, 29 Dec 2021 15:34:36 +0200 Subject: [PATCH 2/6] #1615 add uploadMessage endpoint --- FlowCrypt.xcodeproj/project.pbxproj | 1 - FlowCrypt/Extensions/UIColorExtensions.swift | 2 +- .../EnterpriseServerApi.swift | 40 +++++++++++++----- .../Models/MessageUploadRequest.swift | 41 +++++++++++++++++++ .../ComposeMessageService.swift | 5 ++- .../Mocks/EnterpriseServerApiMock.swift | 5 +++ .../Extensions/Data/DataExtensions.swift | 7 ++++ 7 files changed, 88 insertions(+), 13 deletions(-) diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 5d0070dd7..753552e90 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -1043,7 +1043,6 @@ path = Models; sourceTree = ""; }; - 51B0C772272AB5FD00124663 /* TestExtensions */ = { 51B0C772272AB5FD00124663 /* Extensions */ = { isa = PBXGroup; children = ( diff --git a/FlowCrypt/Extensions/UIColorExtensions.swift b/FlowCrypt/Extensions/UIColorExtensions.swift index 367e0fdc0..7d3f5ffb7 100644 --- a/FlowCrypt/Extensions/UIColorExtensions.swift +++ b/FlowCrypt/Extensions/UIColorExtensions.swift @@ -20,7 +20,7 @@ public extension UIColor { lightStyle: .black ) } - + static var warningColor: UIColor { UIColor(r: 194, g: 126, b: 35) } diff --git a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift index c1c6241b1..d3df8e72d 100644 --- a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift +++ b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift @@ -12,7 +12,7 @@ import FlowCryptCommon protocol EnterpriseServerApiType { func getActiveFesUrl(for email: String) async throws -> String? func getClientConfiguration(for email: String) async throws -> RawClientConfiguration - func uploadMessage(for email: String) async throws -> String + func upload(message: Data, sender: String, to: [String], cc: [String], bcc: [String]) async throws -> String } /// server run by individual enterprise customers, serves client configuration @@ -109,14 +109,15 @@ class EnterpriseServerApi: EnterpriseServerApiType { return response.clientConfiguration } - func uploadMessage(from sender: String, - to: [String], - cc: [String], - bcc: [String] + func upload(message: Data, + sender: String, + to: [String], + cc: [String], + bcc: [String] ) async throws -> String { let replyToken = try await getReplyToken(for: sender) - let request = MessageUploadRequest( + let uploadRequest = MessageUploadDetails( associateReplyToken: replyToken, from: sender, to: to, @@ -124,9 +125,25 @@ class EnterpriseServerApi: EnterpriseServerApiType { bcc: bcc ) + let boundary = UUID().uuidString + + let body = MessageUploadRequest( + boundary: boundary, + details: uploadRequest.jsonString, + content: message + ).httpBody + + let contentTypeHeader = URLHeader( + value: "multipart/form-data; boundary=\(boundary)", + httpHeaderField: "Content-Type" + ) + let response: MessageUploadResponse = try await performRequest( email: sender, - url: "/api/v1/message" + url: "/api/v1/message", + headers: [contentTypeHeader], + method: .post, + body: body as Data ) return response.url @@ -165,20 +182,23 @@ class EnterpriseServerApi: EnterpriseServerApiType { private func performRequest( email: String, url: String, - method: HTTPMethod = .post + headers: [URLHeader] = [], + method: HTTPMethod = .post, + body: Data? = nil ) async throws -> T { guard let fesUrl = try await getActiveFesUrl(for: email) else { throw EnterpriseServerApiError.noActiveFesUrl } let idToken = try await getIdToken(email: email) - let authorizationHeader = URLHeader(value: "Bearer: \(idToken)", httpHeaderField: "Authorization") + let authorizationHeader = URLHeader(value: "Bearer \(idToken)", httpHeaderField: "Authorization") let request = ApiCall.Request( apiName: Constants.apiName, url: "\(fesUrl)\(url)", method: method, - headers: [authorizationHeader] + body: body, + headers: [authorizationHeader] + headers ) let safeResponse = try await ApiCall.call(request) diff --git a/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadRequest.swift b/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadRequest.swift index e1bbd805e..2b6a0d9c2 100644 --- a/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadRequest.swift +++ b/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadRequest.swift @@ -9,9 +9,50 @@ import Foundation struct MessageUploadRequest { + private let boundary: String + private let lineSeparator = "\r\n" + + let httpBody = NSMutableData() + + init(boundary: String, details: String, content: Data) { + self.boundary = boundary + + append(details: details) + append(content: content) + + httpBody.append("--\(boundary)--") + } + + func append(details: String) { + httpBody.append("--\(boundary)\(lineSeparator)") + httpBody.append("Content-Disposition: form-data; name=\"details\"\(lineSeparator)") + httpBody.append("Content-Type: application/json; charset=utf-8\(lineSeparator)") + httpBody.append(details) + httpBody.append(lineSeparator) + } + + func append(content: Data) { + httpBody.append("--\(boundary)\(lineSeparator)") + httpBody.append("Content-Disposition: form-data; name=\"content\"; filename=\"content\"\(lineSeparator)") + httpBody.append("Content-Type: application/octet-stream\(lineSeparator)") + httpBody.append(content) + httpBody.append(lineSeparator) + } +} + +struct MessageUploadDetails: Encodable { let associateReplyToken: String let from: String let to: [String] let cc: [String] let bcc: [String] } + +extension Encodable { + var jsonString: String { + guard let jsonData = try? JSONEncoder().encode(self), + let jsonString = String(data: jsonData, encoding: .utf8) + else { return "" } + return jsonString + } +} diff --git a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift index b324aaff1..39c0228f3 100644 --- a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift +++ b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift @@ -24,6 +24,7 @@ final class ComposeMessageService { private let contactsService: ContactsServiceType private let core: CoreComposeMessageType & KeyParser private let draftGateway: DraftGateway? + private let enterpriseServer: EnterpriseServerApiType private lazy var logger: Logger = Logger.nested(Self.self) init( @@ -32,7 +33,8 @@ final class ComposeMessageService { messageGateway: MessageGateway, draftGateway: DraftGateway? = nil, contactsService: ContactsServiceType? = nil, - core: CoreComposeMessageType & KeyParser = Core.shared + core: CoreComposeMessageType & KeyParser = Core.shared, + enterpriseServer: EnterpriseServerApiType = EnterpriseServerApi() ) { self.messageGateway = messageGateway self.draftGateway = draftGateway @@ -42,6 +44,7 @@ final class ComposeMessageService { clientConfiguration: clientConfiguration ) self.core = core + self.enterpriseServer = enterpriseServer self.logger = Logger.nested(in: Self.self, with: "ComposeMessageService") } diff --git a/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift b/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift index b033b3c59..10179647d 100644 --- a/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift +++ b/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift @@ -53,4 +53,9 @@ final class EnterpriseServerApiMock: EnterpriseServerApiType { getClientConfigurationForCurrentUserCount += 1 return try getClientConfigurationForCurrentUserCall() } + + func upload(message: Data, sender: String, to: [String], cc: [String], bcc: [String]) async throws -> String { + // TODO + return "" + } } diff --git a/FlowCryptCommon/Extensions/Data/DataExtensions.swift b/FlowCryptCommon/Extensions/Data/DataExtensions.swift index bf89f775f..62c761272 100644 --- a/FlowCryptCommon/Extensions/Data/DataExtensions.swift +++ b/FlowCryptCommon/Extensions/Data/DataExtensions.swift @@ -21,6 +21,13 @@ public extension Data { } } +public extension NSMutableData { + func append(_ string: String) { + guard let data = string.data(using: .utf8) else { return } + self.append(data) + } +} + extension String { init(data: Data) { self = String(decoding: data, as: UTF8.self) From 2a0d73f4dbfa223c37792ded3d12c44442c63bca Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Thu, 30 Dec 2021 00:33:29 +0200 Subject: [PATCH 3/6] #1615 update upload message request --- .../Models/MessageUploadRequest.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadRequest.swift b/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadRequest.swift index 2b6a0d9c2..ef3dde348 100644 --- a/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadRequest.swift +++ b/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadRequest.swift @@ -20,13 +20,13 @@ struct MessageUploadRequest { append(details: details) append(content: content) - httpBody.append("--\(boundary)--") + httpBody.append("--\(boundary)--\(lineSeparator)") } func append(details: String) { httpBody.append("--\(boundary)\(lineSeparator)") httpBody.append("Content-Disposition: form-data; name=\"details\"\(lineSeparator)") - httpBody.append("Content-Type: application/json; charset=utf-8\(lineSeparator)") + httpBody.append("Content-Type: application/json; charset=utf-8\(lineSeparator)\(lineSeparator)") httpBody.append(details) httpBody.append(lineSeparator) } @@ -34,7 +34,7 @@ struct MessageUploadRequest { func append(content: Data) { httpBody.append("--\(boundary)\(lineSeparator)") httpBody.append("Content-Disposition: form-data; name=\"content\"; filename=\"content\"\(lineSeparator)") - httpBody.append("Content-Type: application/octet-stream\(lineSeparator)") + httpBody.append("Content-Type: \"application/octet-stream\"\(lineSeparator)\(lineSeparator)") httpBody.append(content) httpBody.append(lineSeparator) } From 5967073d6f069835cba779ca634dcb3eeab76298 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Thu, 30 Dec 2021 13:07:54 +0200 Subject: [PATCH 4/6] #1615 add MultipartDataRequest --- FlowCrypt.xcodeproj/project.pbxproj | 25 ++------ .../EnterpriseServerApi.swift | 33 +++++++---- .../Models/MessageUploadRequest.swift | 58 ------------------- .../Models/MultipartDataRequest.swift | 39 +++++++++++++ .../Mocks/EnterpriseServerApiMock.swift | 1 - 5 files changed, 66 insertions(+), 90 deletions(-) delete mode 100644 FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadRequest.swift create mode 100644 FlowCrypt/Functionality/Services/Account Server Services/Models/MultipartDataRequest.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 753552e90..9451f2d0d 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -72,7 +72,7 @@ 5168FB0B274F94D300131072 /* MessageAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5168FB0A274F94D300131072 /* MessageAttachment.swift */; }; 51775C32270B01C200D7C944 /* PrvKeyInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51775C31270B01C200D7C944 /* PrvKeyInfoTests.swift */; }; 51775C39270C7D2400D7C944 /* StorageMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51775C38270C7D2400D7C944 /* StorageMethod.swift */; }; - 517C2E302779F90700FECF32 /* MessageUploadRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 517C2E2F2779F90700FECF32 /* MessageUploadRequest.swift */; }; + 517C2E302779F90700FECF32 /* MultipartDataRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 517C2E2F2779F90700FECF32 /* MultipartDataRequest.swift */; }; 517C2E33277A0C6300FECF32 /* EnterpriseServerApiError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 517C2E32277A0C6300FECF32 /* EnterpriseServerApiError.swift */; }; 5180CB9127356D48001FC7EF /* MessageSubjectNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5180CB9027356D48001FC7EF /* MessageSubjectNode.swift */; }; 5180CB9327357B67001FC7EF /* RawClientConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5180CB9227357B67001FC7EF /* RawClientConfiguration.swift */; }; @@ -498,7 +498,7 @@ 5168FB0A274F94D300131072 /* MessageAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageAttachment.swift; sourceTree = ""; }; 51775C31270B01C200D7C944 /* PrvKeyInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrvKeyInfoTests.swift; sourceTree = ""; }; 51775C38270C7D2400D7C944 /* StorageMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageMethod.swift; sourceTree = ""; }; - 517C2E2F2779F90700FECF32 /* MessageUploadRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageUploadRequest.swift; sourceTree = ""; }; + 517C2E2F2779F90700FECF32 /* MultipartDataRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipartDataRequest.swift; sourceTree = ""; }; 517C2E32277A0C6300FECF32 /* EnterpriseServerApiError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterpriseServerApiError.swift; sourceTree = ""; }; 5180CB9027356D48001FC7EF /* MessageSubjectNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSubjectNode.swift; sourceTree = ""; }; 5180CB9227357B67001FC7EF /* RawClientConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawClientConfiguration.swift; sourceTree = ""; }; @@ -1017,28 +1017,11 @@ path = SignIn; sourceTree = ""; }; - 32DCAEF9FEEF84D4F0D4A516 /* Extensions */ = { - isa = PBXGroup; - children = ( - D2D27B78248A8694007346FA /* BigIntExtension.swift */, - E26D5E20275AA417007B8802 /* BundleExtensions.swift */, - E26D5E1E275AA295007B8802 /* CommandLineExtensions.swift */, - 51E4F0B427348E310017DABB /* Error+Extension.swift */, - 51B0C7702729861C00124663 /* String+Extension.swift */, - 518389C92726D8F700131B2C /* UIApplicationExtension.swift */, - 9F31AB9D232BF2A600CF87EA /* UIColorExtension.swift */, - 518389C72726D7DD00131B2C /* UIViewController+Spinner.swift */, - 9FEED1B7230C08D700700F8E /* UIViewControllerExtensions.swift */, - 32DCA7E0AFE19FACB0F233ED /* URLSessionExtension.swift */, - ); - path = Extensions; - sourceTree = ""; - }; 517C2E312779F91300FECF32 /* Models */ = { isa = PBXGroup; children = ( 517C2E32277A0C6300FECF32 /* EnterpriseServerApiError.swift */, - 517C2E2F2779F90700FECF32 /* MessageUploadRequest.swift */, + 517C2E2F2779F90700FECF32 /* MultipartDataRequest.swift */, ); path = Models; sourceTree = ""; @@ -2719,7 +2702,7 @@ 9FF0671025520D7100FCC9E6 /* MessageGateway.swift in Sources */, 9F31AB8C23298B3F00CF87EA /* Imap+retry.swift in Sources */, 51B4AE51271444580001F33B /* PubKeyRealmObject.swift in Sources */, - 517C2E302779F90700FECF32 /* MessageUploadRequest.swift in Sources */, + 517C2E302779F90700FECF32 /* MultipartDataRequest.swift in Sources */, 9F82D352256D74FA0069A702 /* InboxViewContainerController.swift in Sources */, D227C0E3250538100070F805 /* LocalFoldersProvider.swift in Sources */, 9FA405C7265AEBA50084D133 /* SetupGenerateKeyViewController.swift in Sources */, diff --git a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift index d3df8e72d..a08969ecf 100644 --- a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift +++ b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift @@ -48,6 +48,14 @@ class EnterpriseServerApi: EnterpriseServerApiType { let url: String } + private struct MessageUploadDetails: Encodable { + let associateReplyToken: String + let from: String + let to: [String] + let cc: [String] + let bcc: [String] + } + private lazy var decoder: JSONDecoder = { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase @@ -117,24 +125,29 @@ class EnterpriseServerApi: EnterpriseServerApiType { ) async throws -> String { let replyToken = try await getReplyToken(for: sender) - let uploadRequest = MessageUploadDetails( + let detailsData = try MessageUploadDetails( associateReplyToken: replyToken, from: sender, to: to, cc: cc, bcc: bcc - ) + ).toJsonData() - let boundary = UUID().uuidString + let detailsDataItem = MultipartDataItem( + data: detailsData, + name: "details", + contentType: "application/json" + ) + let contentDataItem = MultipartDataItem( + data: message, + name: "content", + contentType: "application/octet-stream" + ) - let body = MessageUploadRequest( - boundary: boundary, - details: uploadRequest.jsonString, - content: message - ).httpBody + let request = MultipartDataRequest(items: [detailsDataItem, contentDataItem]) let contentTypeHeader = URLHeader( - value: "multipart/form-data; boundary=\(boundary)", + value: "multipart/form-data; boundary=\(request.boundary)", httpHeaderField: "Content-Type" ) @@ -143,7 +156,7 @@ class EnterpriseServerApi: EnterpriseServerApiType { url: "/api/v1/message", headers: [contentTypeHeader], method: .post, - body: body as Data + body: request.httpBody as Data ) return response.url diff --git a/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadRequest.swift b/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadRequest.swift deleted file mode 100644 index ef3dde348..000000000 --- a/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadRequest.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// MessageUploadRequest.swift -// FlowCrypt -// -// Created by Roma Sosnovsky on 27/12/21 -// Copyright © 2017-present FlowCrypt a. s. All rights reserved. -// - -import Foundation - -struct MessageUploadRequest { - private let boundary: String - private let lineSeparator = "\r\n" - - let httpBody = NSMutableData() - - init(boundary: String, details: String, content: Data) { - self.boundary = boundary - - append(details: details) - append(content: content) - - httpBody.append("--\(boundary)--\(lineSeparator)") - } - - func append(details: String) { - httpBody.append("--\(boundary)\(lineSeparator)") - httpBody.append("Content-Disposition: form-data; name=\"details\"\(lineSeparator)") - httpBody.append("Content-Type: application/json; charset=utf-8\(lineSeparator)\(lineSeparator)") - httpBody.append(details) - httpBody.append(lineSeparator) - } - - func append(content: Data) { - httpBody.append("--\(boundary)\(lineSeparator)") - httpBody.append("Content-Disposition: form-data; name=\"content\"; filename=\"content\"\(lineSeparator)") - httpBody.append("Content-Type: \"application/octet-stream\"\(lineSeparator)\(lineSeparator)") - httpBody.append(content) - httpBody.append(lineSeparator) - } -} - -struct MessageUploadDetails: Encodable { - let associateReplyToken: String - let from: String - let to: [String] - let cc: [String] - let bcc: [String] -} - -extension Encodable { - var jsonString: String { - guard let jsonData = try? JSONEncoder().encode(self), - let jsonString = String(data: jsonData, encoding: .utf8) - else { return "" } - return jsonString - } -} diff --git a/FlowCrypt/Functionality/Services/Account Server Services/Models/MultipartDataRequest.swift b/FlowCrypt/Functionality/Services/Account Server Services/Models/MultipartDataRequest.swift new file mode 100644 index 000000000..030d54daf --- /dev/null +++ b/FlowCrypt/Functionality/Services/Account Server Services/Models/MultipartDataRequest.swift @@ -0,0 +1,39 @@ +// +// MultipartDataRequest.swift +// FlowCrypt +// +// Created by Roma Sosnovsky on 27/12/21 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import Foundation + +struct MultipartDataRequest { + private let lineSeparator = "\r\n" + + let boundary = UUID().uuidString + let httpBody = NSMutableData() + + init(items: [MultipartDataItem]) { + items.forEach(append) + appendBoundary() + } + + private func append(item: MultipartDataItem) { + httpBody.append("--\(boundary)\(lineSeparator)") + httpBody.append("Content-Disposition: form-data; name=\"\(item.name)\"; filename=\"\(item.name)\"\(lineSeparator)") + httpBody.append("Content-Type: \(item.contentType)\(lineSeparator)\(lineSeparator)") + httpBody.append(item.data) + httpBody.append(lineSeparator) + } + + private func appendBoundary() { + httpBody.append("--\(boundary)--\(lineSeparator)") + } +} + +struct MultipartDataItem { + let data: Data + let name: String + let contentType: String +} diff --git a/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift b/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift index 10179647d..ce36fc313 100644 --- a/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift +++ b/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift @@ -55,7 +55,6 @@ final class EnterpriseServerApiMock: EnterpriseServerApiType { } func upload(message: Data, sender: String, to: [String], cc: [String], bcc: [String]) async throws -> String { - // TODO return "" } } From 666b317f8ba3086390850655c6c23285006606b3 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Thu, 30 Dec 2021 13:15:24 +0200 Subject: [PATCH 5/6] #1615 remove test code --- .../Compose Message Service/ComposeMessageService.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift index 39c0228f3..b324aaff1 100644 --- a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift +++ b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift @@ -24,7 +24,6 @@ final class ComposeMessageService { private let contactsService: ContactsServiceType private let core: CoreComposeMessageType & KeyParser private let draftGateway: DraftGateway? - private let enterpriseServer: EnterpriseServerApiType private lazy var logger: Logger = Logger.nested(Self.self) init( @@ -33,8 +32,7 @@ final class ComposeMessageService { messageGateway: MessageGateway, draftGateway: DraftGateway? = nil, contactsService: ContactsServiceType? = nil, - core: CoreComposeMessageType & KeyParser = Core.shared, - enterpriseServer: EnterpriseServerApiType = EnterpriseServerApi() + core: CoreComposeMessageType & KeyParser = Core.shared ) { self.messageGateway = messageGateway self.draftGateway = draftGateway @@ -44,7 +42,6 @@ final class ComposeMessageService { clientConfiguration: clientConfiguration ) self.core = core - self.enterpriseServer = enterpriseServer self.logger = Logger.nested(in: Self.self, with: "ComposeMessageService") } From e7d4d3fa8240377c0d814f20b77d20eb70029f4e Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Thu, 30 Dec 2021 23:37:48 +0200 Subject: [PATCH 6/6] #1615 pr updates --- FlowCrypt.xcodeproj/project.pbxproj | 4 ++ .../EnterpriseServerApi.swift | 64 ++++++++----------- .../Models/MessageUploadDetails.swift | 17 +++++ .../Mocks/EnterpriseServerApiMock.swift | 6 +- 4 files changed, 51 insertions(+), 40 deletions(-) create mode 100644 FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadDetails.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 558475dd2..799a2ca97 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -69,6 +69,7 @@ 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 */; }; + 5152F196277E5AED00BE8A5B /* MessageUploadDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5152F195277E5AED00BE8A5B /* MessageUploadDetails.swift */; }; 5168FB0B274F94D300131072 /* MessageAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5168FB0A274F94D300131072 /* MessageAttachment.swift */; }; 51775C32270B01C200D7C944 /* PrvKeyInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51775C31270B01C200D7C944 /* PrvKeyInfoTests.swift */; }; 51775C39270C7D2400D7C944 /* StorageMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51775C38270C7D2400D7C944 /* StorageMethod.swift */; }; @@ -496,6 +497,7 @@ 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 = ""; }; + 5152F195277E5AED00BE8A5B /* MessageUploadDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageUploadDetails.swift; sourceTree = ""; }; 5168FB0A274F94D300131072 /* MessageAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageAttachment.swift; sourceTree = ""; }; 51775C31270B01C200D7C944 /* PrvKeyInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrvKeyInfoTests.swift; sourceTree = ""; }; 51775C38270C7D2400D7C944 /* StorageMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageMethod.swift; sourceTree = ""; }; @@ -1023,6 +1025,7 @@ isa = PBXGroup; children = ( 517C2E32277A0C6300FECF32 /* EnterpriseServerApiError.swift */, + 5152F195277E5AED00BE8A5B /* MessageUploadDetails.swift */, 517C2E2F2779F90700FECF32 /* MultipartDataRequest.swift */, ); path = Models; @@ -2587,6 +2590,7 @@ D2E26F6C24F25B1F00612AF1 /* KeyAlgo.swift in Sources */, D2891AC424C62446008918E3 /* ErrorHandler.swift in Sources */, 51938DC1274CC291007AD57B /* MessageQuoteType.swift in Sources */, + 5152F196277E5AED00BE8A5B /* MessageUploadDetails.swift in Sources */, 9F41FA2F253B7624003B970D /* BackupSelectKeyDecorator.swift in Sources */, D227C0E8250538A90070F805 /* FoldersService.swift in Sources */, 5ADEDCB623A426E300EC495E /* KeyDetailViewController.swift in Sources */, diff --git a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift index a08969ecf..8a510a62e 100644 --- a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift +++ b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift @@ -12,7 +12,8 @@ import FlowCryptCommon protocol EnterpriseServerApiType { func getActiveFesUrl(for email: String) async throws -> String? func getClientConfiguration(for email: String) async throws -> RawClientConfiguration - func upload(message: Data, sender: String, to: [String], cc: [String], bcc: [String]) async throws -> String + func getReplyToken(for email: String) async throws -> String + func upload(message: Data, details: MessageUploadDetails) async throws -> String } /// server run by individual enterprise customers, serves client configuration @@ -48,14 +49,6 @@ class EnterpriseServerApi: EnterpriseServerApiType { let url: String } - private struct MessageUploadDetails: Encodable { - let associateReplyToken: String - let from: String - let to: [String] - let cc: [String] - let bcc: [String] - } - private lazy var decoder: JSONDecoder = { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase @@ -111,27 +104,24 @@ class EnterpriseServerApi: EnterpriseServerApiType { let response: ClientConfigurationResponse = try await performRequest( email: email, url: "/api/v1/client-configuration?domain=\(userDomain)", - method: .get + method: .get, + withAuthorization: false ) return response.clientConfiguration } - func upload(message: Data, - sender: String, - to: [String], - cc: [String], - bcc: [String] - ) async throws -> String { - let replyToken = try await getReplyToken(for: sender) - - let detailsData = try MessageUploadDetails( - associateReplyToken: replyToken, - from: sender, - to: to, - cc: cc, - bcc: bcc - ).toJsonData() + func getReplyToken(for email: String) async throws -> String { + let response: MessageReplyTokenResponse = try await performRequest( + email: email, + url: "/api/v1/message/new-reply-token" + ) + + return response.replyToken + } + + func upload(message: Data, details: MessageUploadDetails) async throws -> String { + let detailsData = try details.toJsonData() let detailsDataItem = MultipartDataItem( data: detailsData, @@ -152,7 +142,7 @@ class EnterpriseServerApi: EnterpriseServerApiType { ) let response: MessageUploadResponse = try await performRequest( - email: sender, + email: details.from, url: "/api/v1/message", headers: [contentTypeHeader], method: .post, @@ -172,15 +162,6 @@ class EnterpriseServerApi: EnterpriseServerApiType { return try await googleService.getCachedOrRefreshedIdToken() } - private func getReplyToken(for email: String) async throws -> String { - let response: MessageReplyTokenResponse = try await performRequest( - email: email, - url: "/api/v1/message/new-reply-token" - ) - - return response.replyToken - } - private func shouldTolerateWhenCallingOpportunistically(_ error: Error) -> Bool { guard let apiError = error as? ApiError, @@ -197,21 +178,26 @@ class EnterpriseServerApi: EnterpriseServerApiType { url: String, headers: [URLHeader] = [], method: HTTPMethod = .post, - body: Data? = nil + body: Data? = nil, + withAuthorization: Bool = true ) async throws -> T { guard let fesUrl = try await getActiveFesUrl(for: email) else { throw EnterpriseServerApiError.noActiveFesUrl } - let idToken = try await getIdToken(email: email) - let authorizationHeader = URLHeader(value: "Bearer \(idToken)", httpHeaderField: "Authorization") + if withAuthorization { + let idToken = try await getIdToken(email: email) + let authorizationHeader = URLHeader(value: "Bearer \(idToken)", httpHeaderField: "Authorization") + var headers = headers + headers.append(authorizationHeader) + } let request = ApiCall.Request( apiName: Constants.apiName, url: "\(fesUrl)\(url)", method: method, body: body, - headers: [authorizationHeader] + headers + headers: headers ) let safeResponse = try await ApiCall.call(request) diff --git a/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadDetails.swift b/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadDetails.swift new file mode 100644 index 000000000..c2613a638 --- /dev/null +++ b/FlowCrypt/Functionality/Services/Account Server Services/Models/MessageUploadDetails.swift @@ -0,0 +1,17 @@ +// +// MessageUploadDetails.swift +// FlowCrypt +// +// Created by Roma Sosnovsky on 30/12/21 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import Foundation + +struct MessageUploadDetails: Encodable { + let associateReplyToken: String + let from: String + let to: [String] + let cc: [String] + let bcc: [String] +} diff --git a/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift b/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift index ce36fc313..4f90e55d4 100644 --- a/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift +++ b/FlowCryptAppTests/Functionality/Services/Client Configuration Service/Mocks/EnterpriseServerApiMock.swift @@ -54,7 +54,11 @@ final class EnterpriseServerApiMock: EnterpriseServerApiType { return try getClientConfigurationForCurrentUserCall() } - func upload(message: Data, sender: String, to: [String], cc: [String], bcc: [String]) async throws -> String { + func getReplyToken(for email: String) async throws -> String { + return "" + } + + func upload(message: Data, details: MessageUploadDetails) async throws -> String { return "" } }